Why is Server-side Prototype Pollution More Difficult to Detect?
Vì một số lý do, prototype pollution phía server thường khó phát hiện hơn so với biến thể phía client của nó:
Không có quyền truy cập mã nguồn - Không có cách nào dễ dàng để có cái nhìn tổng quan về các sink có mặt hoặc phát hiện các thuộc tính gadget tiềm năng.
Thiếu các công cụ dành cho nhà phát triển - Vì JavaScript đang chạy trên một hệ thống từ xa, chúng ta không có khả năng kiểm tra các đối tượng trong thời gian chạy như khi sử dụng DevTools của trình duyệt để kiểm tra DOM.
Vấn đề DoS - Việc làm ô nhiễm các đối tượng trong môi trường phía server thành công bằng cách sử dụng các thuộc tính thực thường làm hỏng chức năng ứng dụng hoặc làm sập hoàn toàn máy chủ.
Sự tồn tại của ô nhiễm - Khi chúng ta làm ô nhiễm một prototype phía server, thay đổi này tồn tại trong suốt vòng đời của tiến trình Node và chúng ta không có cách nào để đặt lại nó.
Detecting Server-side Prototype Pollution via Polluted Property Reflection
Một cái bẫy dễ dàng cho các nhà phát triển là quên hoặc bỏ qua thực tế rằng một vòng lặp for...in của JavaScript lặp qua tất cả các thuộc tính có thể liệt kê của một đối tượng, bao gồm cả những thuộc tính mà nó đã kế thừa thông qua chuỗi prototype.
const myObject = { a: 1, b: 2 };// pollute the prototype with an arbitrary propertyObject.prototype.foo = 'bar';// confirm myObject doesn't have its own foo propertymyObject.hasOwnProperty('foo'); // false// list names of properties of myObjectfor(const propertyKey in myObject){ console.log(propertyKey);}// Output: a, b, foo
Nếu ứng dụng sau đó bao gồm các thuộc tính được trả về trong một phản hồi, điều này có thể cung cấp một cách đơn giản để thăm dò prototype pollution phía server. Các POST hoặc PUT request gửi dữ liệu JSON đến một ứng dụng hoặc API là những ứng viên hàng đầu cho loại hành vi này vì các máy chủ thường phản hồi bằng một biểu diễn JSON của đối tượng mới hoặc được cập nhật.
POST /user/update HTTP/1.1Host: vulnerable-website.com...{ "user":"wiener", "firstName":"Peter", "lastName":"Wiener", "__proto__":{ "foo":"bar" }}
Nếu trang web dễ bị tấn công, thuộc tính được chèn của chúng ta sau đó sẽ xuất hiện trong đối tượng được cập nhật trong phản hồi:
Bất kỳ tính năng nào liên quan đến việc cập nhật dữ liệu người dùng đều đáng để điều tra vì chúng thường liên quan đến việc hợp nhất dữ liệu đến vào một đối tượng hiện có đại diện cho người dùng trong ứng dụng. Nếu chúng ta có thể thêm các thuộc tính tùy ý vào người dùng của riêng mình, điều này có thể dẫn đến một số lỗ hổng, bao gồm cả leo thang đặc quyền.
Lab: Privilege Escalation via Server-side Prototype Pollution
Request ban đầu được sử dụng để cập nhật địa chỉ của người dùng:
POST /my-account/change-address HTTP/2Host: 0a92007803c2d47d809e0d7f00e8007c.web-security-academy.netCookie: session=1d9xQCX3IsMagk4vkY9V0lTCJgW67NZ4Content-Length: 168Content-Type: application/json;charset=UTF-8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36Origin: https://0a92007803c2d47d809e0d7f00e8007c.web-security-academy.netReferer: https://0a92007803c2d47d809e0d7f00e8007c.web-security-academy.net/my-account?id=wiener{ "address_line_1": "Wiener HQ", "address_line_2": "One Wiener Way", "city": "Wienerville", "postcode": "BU1 1RP", "country": "UK", "sessionId": "1d9xQCX3IsMagk4vkY9V0lTCJgW67NZ4"}
Bây giờ chúng ta có thể truy cập endpoint /admin và xóa người dùng carlos.
Detecting Server-side Prototype Pollution without Polluted Property Reflection
Một cách tiếp cận là thử chèn các thuộc tính khớp với các tùy chọn cấu hình tiềm năng cho máy chủ. Sau đó, chúng ta có thể so sánh hành vi của máy chủ trước và sau khi chèn để xem liệu thay đổi cấu hình này có hiệu lực hay không. Nếu có, đây là một dấu hiệu mạnh mẽ cho thấy chúng ta đã tìm thấy thành công một lỗ hổng prototype pollution phía server.
Chúng ta sẽ xem xét các kỹ thuật sau:
Status code override
JSON spaces override
Charset override
Info
Chúng ta có thể sử dụng bất kỳ kỹ thuật nào được đề cập trong phần này để giải quyết lab đi kèm.
Status Code Override
Các framework JavaScript phía server như Express cho phép các nhà phát triển đặt các trạng thái phản hồi HTTP tùy chỉnh. Trong trường hợp có lỗi, một máy chủ JavaScript có thể đưa ra một phản hồi HTTP chung, nhưng bao gồm một đối tượng lỗi ở định dạng JSON trong phần thân.
HTTP/1.1 200 OK...{ "error": { "success": false, "status": 401, "message": "You do not have permission to access this resource." }}
Mô-đun http-errors của Node chứa hàm sau để tạo loại phản hồi lỗi này:
function createError () { //... if (type === 'object' && arg instanceof Error) { err = arg status = err.status || err.statusCode || status // first line } else if (type === 'number' && i === 0) { //... if (typeof status !== 'number' || (!statuses.message[status] && (status > 400 || status >= 600))) { // second line status = 500 } //...
Dòng được chú thích đầu tiên cố gắng gán biến status bằng cách đọc thuộc tính status hoặc statusCode từ đối tượng được truyền vào hàm (arg). Nếu các nhà phát triển của trang web không đặt rõ ràng một thuộc tính status cho lỗi, chúng ta có thể sử dụng điều này để thăm dò prototype pollution như sau:
Tìm cách kích hoạt một phản hồi lỗi.
Thử làm ô nhiễm prototype bằng thuộc tính status của riêng chúng ta (mà không kích hoạt phản hồi lỗi).
Kích hoạt lại phản hồi lỗi và kiểm tra xem chúng ta đã ghi đè thành công mã trạng thái hay chưa.
Note
Chúng ta phải chọn một mã trạng thái trong phạm vi 400-599. Nếu không, Node mặc định là một trạng thái 500 bất kể, như chúng ta có thể thấy từ dòng được chú thích thứ hai, vì vậy chúng ta sẽ không biết liệu chúng ta đã làm ô nhiễm prototype hay chưa.
JSON Spaces Override
Framework Express cung cấp một tùy chọn json spaces, cho phép chúng ta cấu hình số lượng khoảng trắng được sử dụng để thụt lề bất kỳ dữ liệu JSON nào trong phản hồi. Trong nhiều trường hợp, các nhà phát triển để thuộc tính này không xác định vì họ hài lòng với giá trị mặc định, làm cho nó dễ bị ô nhiễm thông qua chuỗi prototype.
Info
Mặc dù prototype pollution đã được khắc phục trong Express 4.17.4, các trang web chưa nâng cấp vẫn có thể dễ bị tấn công.
Charset Override
Lưu ý rằng đoạn mã sau truyền một đối tượng tùy chọn vào hàm read(), được sử dụng để đọc phần thân request để phân tích.
Một trong những tùy chọn này, encoding, xác định bộ mã hóa ký tự nào sẽ sử dụng. Điều này được lấy từ chính request thông qua lệnh gọi hàm getCharset(req), hoặc nó mặc định là UTF-8:
Nếu chúng ta nhìn kỹ vào hàm getCharset(), có vẻ như các nhà phát triển đã dự đoán rằng header Content-Type có thể không chứa một thuộc tính charset rõ ràng, vì vậy họ đã triển khai một số logic trả về một chuỗi rỗng trong trường hợp này.
Nếu chúng ta có thể tìm thấy một đối tượng có các thuộc tính hiển thị trong một phản hồi, chúng ta có thể sử dụng điều này để thăm dò các nguồn:
Thêm một chuỗi được mã hóa UTF-7 tùy ý vào một thuộc tính được phản ánh trong một phản hồi. Ví dụ, foo trong UTF-7 là +AGYAbwBv-.
Lý do tại sao chúng ta sử dụng thuộc tính "content-type"
Để tránh ghi đè các thuộc tính khi một request chứa các header trùng lặp, hàm _addHeaderLine() kiểm tra rằng không có thuộc tính nào đã tồn tại với cùng một khóa trước khi chuyển các thuộc tính sang một đối tượng IncomingMessage:
Nếu có, header đang được xử lý sẽ bị loại bỏ. Do cách triển khai này, việc kiểm tra này (có lẽ là vô ý) bao gồm các thuộc tính được kế thừa thông qua chuỗi prototype. Điều này có nghĩa là nếu chúng ta làm ô nhiễm prototype bằng thuộc tính content-type của riêng mình, thuộc tính đại diện cho header Content-Type thực từ request sẽ bị loại bỏ tại thời điểm này, cùng với giá trị dự định được lấy từ header.
Lab: Detecting Server-side Prototype Pollution without Polluted Property Reflection
Status Code Override
Đầu tiên, chúng ta thử cách ghi đè mã trạng thái bằng cách xóa dấu ngoặc nhọn cuối cùng trong phần thân request của endpoint thay đổi địa chỉ:
POST /my-account/change-address HTTP/2{ "address_line_1": "Wiener HQ", "address_line_2": "One Wiener Wayaaaa", "city": "Wienerville", "postcode": "BU1 1RP", "country": "UK", "sessionId": "xy5OPCgzfUd9qXASWVs0CgYDQijCXxcZ"
Lặp lại request đầu tiên và phản hồi của nó có giá trị được giải mã của thuộc tính role, cho thấy chúng ta đã thực hiện prototype pollution thành công:
Scanning for Server-side Prototype Pollution Sources
Khám phá trang web mục tiêu bằng trình duyệt của Burp để lập bản đồ càng nhiều nội dung càng tốt và tích lũy lưu lượng truy cập trong lịch sử proxy.
Trong Burp, đi đến tab Proxy > HTTP history.
Lọc danh sách để chỉ hiển thị các mục trong phạm vi.
Chọn tất cả các mục trong danh sách.
Nhấp chuột phải vào lựa chọn của chúng ta và đi đến Extensions > Server-Side Prototype Pollution Scanner > Server-Side Prototype Pollution, sau đó chọn một trong các kỹ thuật quét từ danh sách.
Khi được nhắc, hãy sửa đổi cấu hình tấn công nếu cần, sau đó nhấp vào OK để khởi động quá trình quét.
Info
Trong Burp Suite Professional, tiện ích mở rộng báo cáo bất kỳ nguồn prototype pollution nào nó tìm thấy thông qua bảng điều khiển Issue activity trên các tab Dashboard và Target. Nếu chúng ta đang sử dụng Burp Suite Community Edition, chúng ta cần đi đến tab Extensions > Installed, chọn tiện ích mở rộng, sau đó theo dõi tab Output của nó để biết bất kỳ vấn đề nào được báo cáo.
Note
Nếu chúng ta không chắc nên sử dụng kỹ thuật quét nào, chúng ta cũng có thể chọn Full scan để chạy một quá trình quét sử dụng tất cả các kỹ thuật có sẵn. Tuy nhiên, điều này sẽ liên quan đến việc gửi nhiều request hơn đáng kể.
Bypassing Input Filters for Server-side Prototype Pollution
Các ứng dụng Node có thể xóa hoặc vô hiệu hóa hoàn toàn __proto__ bằng cách sử dụng các cờ dòng lệnh --disable-proto=delete hoặc --disable-proto=throw tương ứng. Tuy nhiên, điều này cũng có thể bị vượt qua bằng cách sử dụng kỹ thuật constructor.
Lab: Bypassing Flawed Input Filters for Server-side Prototype Pollution