# OBJECT TRONG JAVASCRIPT? Có một sự thật là hầu như mọi thứ trong javascript đều là object ![image](https://hackmd.io/_uploads/H1siq2AgA.png) Trong JavaScript, một object là một thực thể có các properties (thuộc tính) và methods (phương thức). Nó là một cấu trúc dữ liệu linh hoạt và mạnh mẽ, cho phép bạn lưu trữ và truy cập dữ liệu theo cặp **key:value** . Ví dụ với một đối tượng user có thể khai báo như sau: ```javascript const user = { username: "nightcore", userId: 0127, isAdmin: false } ``` Bạn cũng có thể truy cập tới các thuộc tính của đối tượng bằng cách sử dụng dấu . hoặc là cặp dấu [] ```javascript user.username user['userId'] // "nightcore" // 0127 ``` Tương tự với data, các thuộc tính cũng có thể là một hàm thực thi, các hàm này được gọi là các method (phương thức) ```javascript const user = { username: "nightcore", userId: 0127, exampleMethod: function(){ // do something } } ``` Để khởi tạo một object ta có nhiều cách khác nhau ví dụ: ![image](https://hackmd.io/_uploads/rkxuhhCgR.png) **1. Sử dụng Object Literal:** ```javascript const person = { firstName: "John", lastName: "Doe", age: 50, eyeColor: "blue" }; ``` hoặc ```javascript const person = {}; person.firstName = "John"; person.lastName = "Doe"; person.age = 50; person.eyeColor = "blue"; ``` **2. Sử dụng Keyword new** ```javascript const person = new Object(); person.firstName = "John"; person.lastName = "Doe"; person.age = 50; person.eyeColor = "blue"; ``` **3. Thông qua kế thừa từ một object khác bằng cách sử dụng Object.create()** ```javascript var user = Object.create(person); ``` ## PROTOTYPE TRONG JAVASCRIPT? 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. ```javascript 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 key được xác định trước) ![image](https://hackmd.io/_uploads/H1VoJaRg0.png) Dùng console của trình duyệt: ![image](https://hackmd.io/_uploads/Syj7laRlR.png) Ta 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 >**Object tự động kế thừa các thuộc tính của nguyên mẫu.** Ví dụ: Nguyên mẫu `String.prototype` có một method là `removeWhitespace` : ```javascript String.prototype.removeWhitespace = function(){ // remove leading and trailing whitespace } ``` Nó sẽ được kế thừa và sử dụng như sau : ```javascript let searchTerm = " example "; searchTerm.removeWhitespace(); // "example" ``` ## TRUY CẬP PROTOTYPE CỦA MỘT ĐỐI TƯỢNG Để truy cập tới prototype của một đối tượng thì ta có thể sử dụng nhiều cách như ```javascript var person = new Person("nightcore"); person.__proto__; person["__proto__"]; person.constructor.prototype; Object.getPrototypeOf(person); ``` **Xét ví hình ảnh sau:** ![image](https://hackmd.io/_uploads/rJVI53Hk0.png) Từ ảnh trên, ta có thể truy cập nguyên mẫu của Object bằng `__proto__` như sau : ```javascript username.__proto__ // String.prototype username.__proto__.__proto__ // Object.prototype username.__proto__.__proto__.__proto__ // null ``` Ví dụ ta có thể sửa, thêm, các thuộc tính thông qua prototype object ![image](https://hackmd.io/_uploads/HJ4KCp0l0.png) # PROTOTYPE POLLUTION LÀ GÌ? Giống như cái tên đã thể hiện ra Prototype Pollution là Ô nhiễm nguyên mẫu. Đây là một lỗ hổng bảo mật xảy ra khi tấn công vào việc thay đổi đối tượng prototype trong JavaScript một cách không an toàn. Điều này có thể xảy ra khi các đối tượng JavaScript cho phép việc mở rộng và chỉnh sửa prototype của chúng. Cụ thể nguyên mẫu mà mình cần làm ô nhiễm đó chính là `Object.prototype` , bởi vì khi prototype của một đối tượng được sửa đổi một cách không an toàn, các thuộc tính hoặc phương thức có thể được thêm, thay đổi hoặc xóa một cách không mong muốn. Điều này có thể dẫn đến những hậu quả nghiêm trọng, bao gồm việc thay đổi hoạt động của các đối tượng đã tồn tại, gây ra lỗi trong mã nguồn và thậm chí tiềm ẩn các lỗ hổng bảo mật. Ví dụ một ứng dụng web có phân quyền người dùng là user và admin , giả ứng dụng này có sử dụng một đoạn code để check admin: ```javascript if (user.isAdmin == true) { // Admin Dashboard } ``` Nếu như mình có thể thao túng `Object.prototype` và gán thêm một thuộc tính `isAdmin = true` thì lúc này `user.isAdmin` sẽ luôn có giá trị là true → mọi user đều có thể access admin và thực hiện các thao tác không mong muốn ## PROTOTYPE POLLUTION XUẤT HIỆN KHI NÀO? Lỗ hổng prototype pollution thường xảy ra khi một hàm JavaScript kết hợp một đối tượng chứa các thuộc tính có thể điều khiển bởi người dùng vào một đối tượng hiện có mà không trước tiên làm sạch các khóa(key). Điều này cho phép kẻ tấn công inject một thuộc tính với khóa như `__proto__` , cùng với các thuộc tính lồng nhau tùy ý. Do ý nghĩa đặc biệt của `__proto__` trong ngữ cảnh JavaScript, quá trình kết hợp có thể gán các thuộc tính lồng nhau vào prototype của đối tượng thay vì đối tượng mục tiêu. Kết quả, kẻ tấn công có thể gây ra prototype pollution với các thuộc tính chứa giá trị nguy hiểm, sau đó có thể được ứng dụng sử dụng một cách nguy hiểm. Có thể ô nhiễm bất kỳ nguyên mẫu nào, nhưng điều này thường xảy ra với đối tượng nguyên mẫu toàn cục được tích hợp sẵn `Object.prototype` ## PROTOTYPE POLLUTION SOURCES Prototype pollution sources thường là các input mà người dùng có thể kiểm soát được như là: **Prototype pollution via the URL** Khi xử lý URL, các trình phân tích thường chuyển đổi chuỗi truy vấn thành các đối tượng JavaScript. Tuy nhiên, nếu không được xử lý cẩn thận, điều này có thể dẫn đến prototype pollution và thay đổi hành vi của ứng dụng. Ví dụ, kẻ tấn công có thể chèn các thuộc tính độc hại vào URL như sau ```javascript https://vulnerable-website.com/?__proto__[evilProperty]=payload ``` Trong ví dụ này, chuỗi truy vấn `__proto__[evilProperty]=payload` được truyền vào ứng dụng thông qua URL. Khi ứng dụng xử lý URL và chuyển đổi chuỗi truy vấn thành đối tượng JavaScript, thuộc tính `__proto__` sẽ được coi là một thuộc tính của `prototype` của đối tượng, và thuộc tính `evilProperty` sẽ được thêm vào prototype đó ```javascript { existingProperty1: 'foo', existingProperty2: 'bar', __proto__: { evilProperty: 'payload' } } ``` **Prototype pollution via JSON input**: Thông thường, để chuyển đổi chuỗi JSON thành đối tượng JavaScript, chúng ta sử dụng phương thức `JSON.parse()` . Tuy nhiên, phương thức này xử lý các khóa trong đối tượng JSON như là các chuỗi tùy ý, bao gồm cả những khóa như `__proto__` . Điều này cung cấp một cơ chế tiềm năng khác để thực hiện prototype pollution. Giả sử kẻ tấn công tiêm vào một chuỗi JSON độc hại như sau, ví dụ như thông qua một tin nhắn web: ```javascript { "__proto__": { "evilProperty": "payload" } } ``` Nếu chuỗi JSON này được chuyển đổi thành một đối tượng JavaScript bằng phương thức `JSON.parse()`, đối tượng kết quả sẽ thực sự có một thuộc tính với khóa là `__proto__` : ![image](https://hackmd.io/_uploads/SkqGC_k-0.png) Khi gọi `__proto__` ở trong JSON thì nó giống như trỏ đến một property bình thường. Còn khi khi gọi `__proto__` ở trong Object literal thì nó sẽ trỏ đến `Object.prototype Nếu đối tượng được tạo ra thông qua `JSON.parse()` được kết hợp vào một đối tượng hiện có mà không có việc làm sạch khóa đúng cách, điều này sẽ dẫn đến prototype pollution trong quá trình gán, giống như ví dụ dựa trên URL đã được đề cập trước đó. ## PROTOTYPE POLLUTION SINKS Prototype pollution sink thực chất chỉ là một hàm JavaScript hoặc phần tử DOM mà bạn có thể truy cập thông qua prototype pollution, cho phép bạn thực thi mã JavaScript hoặc các lệnh hệ thống tùy ý. ## PROTOTYPE POLLUTION XẢY RA KHI NÀO ? Prototype pollution xảy ra khi lập trình viên không biết về mối nguy hiểm khi vô tình cho phép attacker thêm/sửa/xóa `Object.prototype`. Ta sẽ phân tích một số case tiêu biểu: ### CASE 1: HÀM MERGE() Giả sử ta muốn hợp nhất 2 object a và b như sau: ```javascript let a = { a: 1, b: 2} let b = { b: 3, c: 4} ``` Điều ta mong muốn là tạo ra 1 object c có dạng: ```javascript c = { a: 1, b: 3, c: 4} ``` Để giải quyết việc này, trong 1 vài thư viện thì các developer đã xây dựng ra hàm `mere()` ```javascript let c = merge(a,b); //Lúc này c sẽ có dạng { a: 1, b: 3, c: 4} như ta mong muốn ``` Hàm merge() này có cách hoạt động như sau: ```javascript function merge(a, b) { for (var att in b) { if (typeof a[att] === "object" && typeof b[att] === "object") { merge(a[att], b[att]); } else { a[att] = b[att]; } } return a; } ``` Hàm `merge()` sẽ duyệt qua các thuộc tính của b, nếu như thuộc tính này ở cả object a và b đều là `object` thì `merge()` sẽ đệ quy tiếp, nếu không thỏa mãn điều kiện này thì `merge()` sẽ gán thuộc tính của b vào a. Lý do tại sao thuộc tính b = 3 chứ không phải b = 2 thì chính là do khối code ở else thực hiện. Vậy nếu như b không phải một object đơn thuần như trên thì sao. ```javascript let b = JSON.parse('{"__proto__":{"polluted": 1}}'); ``` Khi gọi `__proto__` ở trong JSON thì nó giống như trỏ đến một property bình thường. Nếu như bạn gán cho một object được khai báo bằng Object literal như này ```javascript var b = { __proto__: { " polluted": 1 } }; ``` Lúc này thì `__proto__` sẽ trỏ đến `Object.prototype` và sửa đổi `Object.prototype` thành `{ " polluted": 1 }` . Hàm merge() sẽ không chạy như ta mong muốn. Để hiểu rõ tại sao thì ta cùng di phân tích đoạn code dưới đây: ```javascript function merge(a, b) { for (var att in b) { if (typeof a[att] === "object" && typeof b[att] === "object") { merge(a[att], b[att]); } else { a[att] = b[att]; } } return a; } var a = { a: 1, b: 1 }; var b = JSON.parse('{"__proto__":{"polluted": 1}}'); var c = merge(a, b); //hàm merge được gọi var d = {}; console.log(d.polluted); //trả về 1 ``` **Bước 1:** Vòng for sẽ duyệt qua tất cả thuộc tính của b , b chính là object được tạo bởi JSON.parse và có thuộc tính `__proto__`. >Vì sao khi dùng var `b = { __proto__: { " polluted": 1 } };` thì không được ? Vì vòng for này sẽ không thấy được thuộc tính `__proto__` , bởi `for (var att in b)` không cho phép tìm các `magic property`. **Bước 2:** Đi đến khối if , lúc này khối if sẽ kiểm tra `a["__proto__"]` và `b["__proto__"]` có phải là dạng object không? `a[__proto__]` chính là `Object.prototype` và `Object.prototype` như mình đã phân tích thì nó là một object, còn `b[__proto__]` là `{"polluted": 1 }` thì đương nhiên `{ "polluted": 1 }` cũng là `object`. **Bước 3:** Sau khi thấy cả `a[__proto__]` và `b[__proto__]` cùng là `object` thì sẽ tiến hành đệ quy. >Nhớ rằng ở đây `a[__proto__] === Object.prototype` , `b[__proto__] === {"polluted": 1}` **Bước 4:** Hàm `merge()` nhận 2 tham số là `Object.prototype` và `{"polluted": 1}` (đại diện cho lần lượt a và b) . Ở đây mình quy ước `a2 === Object.prototype` và `b2 === {"polluted": 1}` **Bước 5:** Vòng for sẽ duyệt qua thuộc tính của b2 , và b2 có thuộc tính `polluted` . Tiếp đến khối if sẽ kiểm tra `a2["polluted"]` tương ứng với `Object.prototype.polluted` và `b2["polluted"]` có phải là dạng `object` không? Ở đây `a2["polluted"] = undefined` , còn `b2["polluted"] = 1` . Vậy ở đây cả 2 điều kiện đều không thỏa mãn, dừng đệ quy và đi đến khối else. **Bước 6:** Khối else sẽ thực hiện `a2["polluted"] = b2["polluted"]` . Viết trực quan ra thì nó sẽ là `Object.prototype.polluted = 1` . Vậy ta đã có thể làm cho `Object.prototype` có thêm thuộc tính là `polluted` . Khi đó toàn bộ các object nào kế thừa từ `Object.prototype` sẽ đều có thuộc tính `polluted` với giá trị `1` . ### CASE 2: HÀM CLONE() Bản chất hàm này cũng được tạo nên từ hàm merge() . Code triển khai của hàm clone() ```javascript function clone(a) { return merge({}, a); } ``` ### CASE 3: PATH ASSIGNMENT OPERATION Code minh hoạ: ```javascript var obj = { b : {"test":321}}; setValue(obj, "b.test", 123); obj.b.test //trả về 123 ``` Các thư viện sử dụng đoạn code trên cho phép gán giá trị theo đường `b.test` và đặt giá trị là `123` . Ở đây ta có thể khai thác bằng cách sử dụng giống như việc thay đổi `Object.prototype` bằng magic property `__proto__` . Đoạn code với exploit payload: ```javascript var obj = {}; setValue(obj, "__proto__.polluted", 1); var d = {}; d.polluted; //trả về 1 ``` ## KẾT LUẬN Bản chất cách khai thác của lỗi này đến từ việc cho phép `access` vào `Object.prototype` . Vậy nên các `pattern` cho phép `access` vào `Object.prototype` đều có thể dẫn đến lỗi. Nếu như dùng Object Literal thì chỉ cần thông qua một lần trỏ đến `__proto__` là có thể `access` vào `Object.prototype` . Nếu thông qua một object được kế thừa qua `n` prototype khác thì tương ứng với `n` lần trỏ đến `__proto__` . ![image](https://hackmd.io/_uploads/rJVI53Hk0.png) ```javascript username.__proto__ // String.prototype username.__proto__.__proto__ // Object.prototype username.__proto__.__proto__.__proto__ // null ``` ## PHÒNG CHỐNG Để phòng chống lỗ hổng prototype pollution, bạn có thể áp dụng các biện pháp sau: 1. Kiểm tra và làm sạch các khóa thuộc tính: Trước khi hợp nhất các đối tượng, hãy kiểm tra và làm sạch các khóa thuộc tính để ngăn chặn việc chèn khóa nguy hiểm như `__proto__` trỏ tới `prototype` của đối tượng. Sử dụng danh sách cho phép (whitelist) là phương pháp hiệu quả nhất. Nếu không thể sử dụng danh sách cho phép, bạn có thể sử dụng danh sách cấm (blacklist) để loại bỏ các chuỗi tiềm năng gây nguy hiểm từ đầu vào người dùng. 2. Ngăn chặn thay đổi các đối tượng `prototype`: Sử dụng phương pháp `Object.freeze()` để đảm bảo rằng các thuộc tính và giá trị của đối tượng không thể bị thay đổi và không thể thêm thuộc tính mới. Áp dụng `Object.freeze()` cho `Object.prototype` để ngăn chặn thay đổi các đối tượng `prototype` : ```javascript Object.freeze(Object.prototype); ``` 3. Ngăn chặn việc kế thừa các thuộc tính: Sử dụng phương pháp `Object.create(null)` để tạo đối tượng với `prototype` là `null`. Điều này đảm bảo đối tượng không kế thừa bất kỳ thuộc tính nào từ `Object.prototype` . ```javascript let myObject = Object.create(null); Object.getPrototypeOf(myObject); // null ``` 4. Sử dụng các đối tượng có bảo vệ tích hợp: Sử dụng các đối tượng có sẵn trong JavaScript như Map và Set có thể cung cấp bảo vệ tích hợp chống lại lỗ hổng prototype pollution. Ví dụ, khi định nghĩa một đối tượng options, bạn có thể sử dụng Map thay vì object thông thường. Map có phương thức `get()` tích hợp sẵn chỉ trả về các thuộc tính được định nghĩa trực tiếp trên Map. ```javascript let options = new Map(); options.set('transport_url', 'https://normal-website.com'); options.transport_url; // undefined options.get('transport_url'); // 'https://normal-website.com' ``` Tương tự, Set cũng là một lựa chọn nếu bạn chỉ lưu trữ các giá trị mà không cần cặp `key:value` . Set cung cấp phương thức `has()` tích hợp sẵn để kiểm tra xem một giá trị có tồn tại trong Set hay không. ```javascript let options = new Set(); options.add('safe'); options.safe; // undefined options.has('safe'); // true ``` >Lưu ý rằng việc áp dụng các biện pháp trên chỉ là một phần trong việc bảo vệ chống lại lỗ hổng prototype pollution. Để đảm bảo an toàn tối đa, hãy luôn theo dõi và cập nhật mã nguồn, bao gồm cả các thư viện bên thứ ba, để đảm bảo rằng không có lỗ hổng mới được tạo ra và khai thác được # LABS ## COWSAY AS A SERVICE - ACSC CTF 2021 Mục tiêu là lấy được giá trị tron biến môi trường `$FLAG` Chall cho ta trang web như sau ![image](https://hackmd.io/_uploads/HkpNpWlb0.png) <details> <summary>index.js</summary> ```javascript import Koa from 'koa'; import Router from '@koa/router'; import auth from 'koa-basic-auth'; import bodyParser from 'koa-bodyparser'; import child_process from 'child_process'; const settings = {}; const style = `<style> body { padding: 2rem; } .form input[type=text] { padding: .5rem 1rem; font-size: 1rem; display: block; margin-bottom: 1rem; } .form input[type=submit] { display: block; margin-bottom: 1rem; color: #fff; background-color: #000; padding: .5rem 1rem; font-size: 1rem; border: none; } .color-setting { margin-bottom: 1rem; } .cowsay { font-size: 2rem; background: #beead6; padding: 0.5rem 1rem; } </style>`; const app = new Koa(); const router = new Router(); // basic auth if (process.env.CS_USERNAME && process.env.CS_PASSWORD) { app.use(auth({ name: process.env.CS_USERNAME, pass: process.env.CS_PASSWORD })) } app.use(async (ctx, next) => { ctx.state.user = ctx.cookies.get('username'); await next(); }); router.get('/', (ctx, next) => { ctx.body = ` ${style} <h1>Welcome to Cowsay as a Service</h1> <p>Before start the service, please enter your name.</p> <form action="/cowsay" method="GET" class="form"> <input type="text" name="user" placeholder="Username"> <input type="submit" value="Login"> </form> <script> document.querySelector('form').addEventListener('submit', () => { const username = document.querySelector('input[name="user"]').value; document.cookie = 'username=' + username; }); </script> `; next(); }); router.get('/cowsay', (ctx, next) => { const setting = settings[ctx.state.user]; const color = setting?.color || '#000000'; let cowsay = ''; if (ctx.request.query.say) { const result = child_process.spawnSync('/usr/games/cowsay', [ctx.request.query.say], { timeout: 500 }); cowsay = result.stdout.toString(); } ctx.body = ` ${style} <h1>Cowsay as a Service</h1> <details class="color-setting"> <summary>Color Preferences</summary> <form action="/setting/color" method="POST"> <input type="color" name="value" value="${color}"> <input type="submit" value="Change Color"> </form> </details> <form action="/cowsay" method="GET" class="form"> <input type="text" name="say" placeholder="hello"> <input type="submit" value="Say"> </form> <pre style="color: ${color}" class="cowsay"> ${cowsay} </pre> `; }); router.post('/setting/:name', (ctx, next) => { if (!settings[ctx.state.user]) { settings[ctx.state.user] = {}; } const setting = settings[ctx.state.user]; setting[ctx.params.name] = ctx.request.body.value; ctx.redirect('/cowsay'); }); app.use(bodyParser()); app.use(router.routes()); app.use(router.allowedMethods()); app.listen(3000); ``` </details> Đọc source thì ta thấy rằng trang web cho ta 2 API - GET /cowsay?say= - POST /setting/:name Với mục tiêu là đọc được biến $FLAG thì endpoint chắc chắn là ở `GET /cowsay?say=` rồi ![image](https://hackmd.io/_uploads/H14TJGgWA.png) Tất nhiên nếu ta thử như vầy sẽ không được rồi, không thể dễ như thế được ![image](https://hackmd.io/_uploads/H1wDgflb0.png) Mò vào [docs](https://nodejs.org/api/child_process.html#child_processspawnsynccommand-args-options) của `child_process.spawnSync()` thì ta biết được lí do tại sao payload trên lại không được ![image](https://hackmd.io/_uploads/ry_n-Ml-0.png) ![image](https://hackmd.io/_uploads/BksmHXlbC.png) Cụ thể thì tại options `shell` có ghi rằng ``` shell <boolean> | <string> If true, runs command inside of a shell. Uses '/bin/sh' on Unix, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell requirements and Default Windows shell. Default: false (no shell). ``` Có nghĩa là theo mặc định thì chúng ta không thể thực hiện lệnh shell trong hàm này được. -> Giả thuyết chúng ta sẽ Protype Pollution để thiết lập lại options shell này thành true Xét API còn lại thử xem có gì khai thác được không ```javascript router.post('/setting/:name', (ctx, next) => { if (!settings[ctx.state.user]) { settings[ctx.state.user] = {}; } const setting = settings[ctx.state.user]; setting[ctx.params.name] = ctx.request.body.value; ctx.redirect('/cowsay'); }); ``` Biết rằng biến `settings` đã được khởi tạo là một object với giá trị rỗng ![image](https://hackmd.io/_uploads/SkKRXMgZA.png) Với `ctx.state.user` được lấy từ cookie ![image](https://hackmd.io/_uploads/BkAxmGebA.png) Ở đây không có filter gì nên chúng ta có thể thực hiện được Prototype Pollution như sau: Ta sẽ truyền `__proto__` vào cookie, lúc này: - `const setting = settings[ctx.state.user];` Sẽ trở thành: - `const setting = settings[__proto__]` Có nghĩa là `setting = Object.prototype` Lúc này ta chỉ cần truyền `:name` với giá trị là `shell` và `ctx.request.body.value` với giá trị true Câu lệnh `setting[ctx.params.name] = ctx.request.body.value;` Sẽ trở thành : `Object.prototype[shell] = true` Đã thực hiện ô nhiễm nguyên mẫu thành công, lúc này ta chỉ cần lụm flag thôi Thực hành ![image](https://hackmd.io/_uploads/B1xSaGeb0.png) Lưu ý do ta phải truyền value là một giá trị boolean (true) nên ta phải đổi Content-Type thành `Content-Type: application/json`. Còn nếu mà ta truyền `Content-Type: application/x-www-form-urlencoded` thì "true" nó chỉ là ở dạng chuỗi (string) thôi Kết quả: ![image](https://hackmd.io/_uploads/SJlC6zx-R.png) `Flag: ACSC{(oo)<Moooooooo_B09DRWWCSX!}` ## no sandbox - ISITDTU CTF 2022 Update... ## social page - ISITDTU CTF 2023 Update... ## KCSC_train_prototype_labs Update... ## animals - TetCTF 2021 Update... # References https://clbuezzz.wordpress.com/2021/11/01/prototype-pollution-attack/ https://viblo.asia/p/tan-cong-prototype-pollution-tren-cac-ung-dung-nodejs-oOVlYwLvK8W https://sheon.hashnode.dev/web-hacking-prototype-pollution-attack https://nhattruong.blog/2023/10/01/lo-hong-prototype-pollution-o-nhiem-nguyen-mau-toan-tap/ https://book.hacktricks.xyz/pentesting-web/deserialization/nodejs-proto-prototype-pollution https://portswigger.net/web-security/prototype-pollution