IV. Phân tích và khai thác các kỹ thuật phát hiện, tấn công Server-side - Prototype Pollution
1. Điểm khác biệt với lỗ hổng phía client
Khác với lỗ hổng xảy ra ở phía client, Server-side - Prototype Pollution chỉ các lỗ hổng xuất hiện với các dữ liệu hoạt động tại máy chủ. Mặc dù JavaScript ban đầu là một ngôn ngữ chuyên sinh ra với mục đích lập trình cho hệ thống front end, nhưng theo sự thay đổi của thời gian, ngôn ngữ JavaScript nói chung được các nhà phát triển ứng dụng sử dụng rộng rãi để xây dựng máy chủ, API cũng như các hệ thống back end khác. Bạn đọc có thể đọc thêm cuốn sách JavaScript from Frontend to Backend.
2. Khó khăn
Khi kiểm tra và khai thác lỗ hổng Prototype Pollution xảy ra ở phía server, chúng ta sẽ phải đối đầu với một số khó khăn, vấn đề phát sinh cần giải quyết.
- Không có mã nguồn: Tất nhiên, ở phía server thì các lỗ hổng sẽ không hề liên quan tới các tệp
.js
mà chúng ta có thể đọc tùy ý ở trang web. Có thể coi việc kiểm tra dạng lỗ hổng Prototype Pollution dạng server-side giống như một công cuộc kiểm thử "blackbox". - Không có dấu hiệu rõ ràng: Với client-side pollution, chúng ta có thể dễ dàng kiểm tra các thuộc tính đã bị lây nhiễm chưa thông qua
Object.prototype
trong console tool. Với server-side, các giá trị thuộc tính thường không hiển thị trong giao diện (một số trường hợp reflect trong response có thể dễ dàng kiểm tra), chúng ta chỉ có thể cố gắng tìm kiếm các dấu hiệu để thực hiện nhận biết. - Ảnh hưởng tới hệ thống: Một số thuộc tính bị thay đổi trong môi trường server-side có thể ảnh hưởng tới luồng hoạt động của chương trình, dẫn đến hệ thống gặp lỗi không mong muốn.
- Khó khôi phục dữ liệu: Việc không thể biết giá trị ban đầu của thuộc tính sau khi thực hiện lây nhiễm thành công dẫn đến khó khôi phục lại thuộc tính đó trở về giá trị lúc trước, gây ảnh hưởng tới toàn bộ hệ thống.
Bên cạnh việc tìm kiếm và xác nhận lỗ hổng, chúng ta còn phải chú ý tránh các tác động xấu tới server. Hãy cùng xem xét một số phương pháp độc đáo giúp chúng ta phát hiện dạng lỗ hổng này mà vẫn đảm bảo hoạt động bình thường của hệ thống trong các phần tiếp theo.
3. Phát hiện lỗ hổng trong trường hợp dữ liệu được hiển thị trong response
Chắc hẳn với các bạn trong ngành Công nghệ thông tin nói chung đều đã rất quen thuộc với vòng lặp for
. Xem xét cấu trúc for...in
trong ngôn ngữ JavaScript để liệt kê tất cả thuộc tính trong một đối tượng.
const testObject = {'a': 1, 'b': 2};
for (propertyKey in testObject) { console.log(propertyKey);
}
// Output: a b
Điều đặc biệt là vòng lặp này sẽ liệt kê cả các thuộc tính mà đối tượng kế thừa từ nguyên mẫu (đối tượng không chứa thuộc tính đó). Thật vậy, bổ xung thuộc tính test
vào Object.prototype
:
const testObject = {'a': 1, 'b': 2};
Object.prototype.test = '3';
for (propertyKey in testObject) { console.log(propertyKey);
}
Kết quả:
Như vậy, khi các lập trình viên sử dụng vòng lặp for...in
trong quá trình xây dựng chức năng hiển thị các thuộc tính của đối tượng, sẽ "vô tình" giúp kẻ tấn công có thể kiểm tra lỗ hổng một cách dễ dàng hơn. Ví dụ một chương trình như sau:
function getUserData(data) { const userData = {}; for (const key in data) { console.log(key) userData[key] = data[key]; } return userData;
} // ... // Reflect the data in the response
res.json({ Message: "Profile updated successfully", UserData: getUserData(dataProfile),
});
Bài lab Privilege escalation via server-side prototype pollution minh họa cho sự sai sót này.
Đăng nhập ứng dụng, sử dụng chức năng update thông tin:
Ứng dụng gửi dữ liệu tới server bằng JSON và response hiển thị các thông tin của người dùng wiener. Chúng ta có thể thêm tùy ý cặp key:value
Tải lại trang và submit các lần sau nhận thấy cặp test:viblo
vẫn tồn tại, thuộc tính mới dường như được thêm và lưu trữ cố định vào object trong server.
Đồng thời trong response có thuộc tính đặc biết isAdmin
(trong giao diện sẽ không hiển thị):
Như vậy, ý tưởng rất tự nhiên là sử dụng kỹ thuật tấn công prototype pollution nhằm thay đổi thuộc tính isAdmin
thành true
:
Tải lại trang web, thành công trở thành vai trò administrator.
Như vậy, khi sử dụng cấu trúc for...in
trong các chức năng hiển thị thuộc tính đối tượng, chúng ta nên thêm một bước kiểm tra đối tượng có thuộc tính đang xét không data.hasOwnProperty(key)
nhằm tránh việc ứng dụng hiển thị các thuộc tính kế thừa từ prototype. Ví dụ:
function getUserData(data) { const userData = {}; for (const key in data) { if (data.hasOwnProperty(key)) { userData[key] = data[key]; } } return userData;
}
Các phần tiếp theo chúng ta sẽ cùng tìm hiểu về một số phương pháp phát hiện lỗ hổng trong trường hợp trang web không trả về các dữ liệu input từ người dùng.
4. Sử dụng một số thuộc tính trong thư viện qs
thực hiện kiểm tra
4.1. Thuộc tính parameterLimit
Trong Express, body-parser
là một middleware được sử dụng để phân tích (parse) dữ liệu trong request được gửi từ client và đưa vào thuộc tính req.body để sử dụng trong quá trình xử lý. Chú ý tùy chọn parameterLimit
, theo https://github.com/expressjs/body-parser#parameterLimit:
The parameterLimit option controls the maximum number of parameters that are allowed in the URL-encoded data. If a request contains more parameters than this value, a 413 will be returned to the client. Defaults to 1000.
Có thể hiểu thuộc tính parameterLimit
được sử dụng để giới hạn số lượng tham số (parameters) mà Express sẽ phân tích trong request. Đặc biệt, khi ứng dụng sử dụng thư viện qs, ví dụ trong phiên bản v6.4.0:
options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit;
Chương trình kiểm tra thuộc tính parameterLimit
nếu có kiểu number
sẽ sử dụng luôn giá trị đó làm số lượng tham số giới hạn, ngược lại để giá trị mặc định . Bởi vậy, nếu ứng dụng tồn tại lỗ hổng Prototype pollution phía server, chúng ta có thể kiểm tra bằng cách thay đổi thuộc tính parameterLimit
, và quan sát response với số lượng tham số trong request vượt giới hạn đó.
Ví dụ một chương trình luôn trả về giá trị tham số test
do người dùng nhập từ URL, nếu không tìm thấy sẽ trả về undefined
:
Cố gắng thay đổi giá trị thuộc tính parameterLimit
với payload:
"__proto__": { "parameterLimit": 1
}
Nếu lỗ hổng tồn tại, chúng ta chỉ có thể sử dụng một tham số duy nhất:
4.2. Thuộc tính ignoreQueryPrefix
Thông thường, khi chúng ta truyền tham số dạng ??test=
hệ thống sẽ không thể phân tích cú pháp hợp lệ, dẫn đến giá trị test
không được tiếp nhận.
Tuy nhiên, trong thư viện qs chứa tùy chọn ignoreQueryPrefix
nếu có giá trị true sẽ cho phép ứng dụng chấp nhận kiểu truy vấn như trên. Tham khảo thêm tại https://github.com/ljharb/qs/blob/main/dist/qs.js#L270
Nếu thay đổi được giá trị thuộc tính ignoreQueryPrefix
:
"__proto__": { "ignoreQueryPrefix": true
}
Kiểm tra lại trang web, kết quả cho thấy tham số đã được chấp nhận:
4.3. Thuộc tính allowDots
Tương tự với ignoreQueryPrefix
, tùy chọn allowDots
cho phép tham số sử dụng dấu chấm .
trở thành vai trò như một object:
Nguồn: https://portswigger.net/
Bạn đọc tham khảo thêm mã nguồn tại: https://github.com/ljharb/qs/blob/main/dist/qs.js#L259