Tấn công Prototype Pollution trên các ứng dụng NodeJS

Tấn công Prototype Pollution giống như cái tên đã gợi ý phần nào, là hình thức tấn công (thêm/sửa/xoá thuộc tính) vào prototype của Object trong trong Javascript dẫn đến sai khác logic,...

Tấn công Prototype Pollution trên các ứng dụng NodeJS

Tấn công Prototype Pollution giống như cái tên đã gợi ý phần nào, là hình thức tấn công (thêm/sửa/xoá thuộc tính) vào prototype của Object trong trong Javascript dẫn đến sai khác logic,...

Tấn công Prototype Pollution giống như cái tên đã gợi ý phần nào, là hình thức tấn công (thêm/sửa/xoá thuộc tính) vào prototype của Object trong trong Javascript dẫn đến sai khác logic, nhiều khi dẫn đến việc thực thi những đoạn code tuỳ ý trên hệ thống (Remote Code Excution – RCE). Lỗ hổng này đã được phát hiện ra từ năm 2018 trên một số thư viện Javascript hay dùng là JQuery và đến gần đây lại tiếp tục phát hiện ra lỗ hổng này trên thư viện Lodash, một trong những thư viện phổ biến. Chúng ta cùng thử tìm hiểu xem hình thức tấn công này như thế nào nhé.

Đối tượng trong Javascript

Trước hết, ta cần hiểu qua về Object trong javascript. Object đơn giản là tập hợp của các cặp khoá và giá trị, thường được gọi là các property (thuộc tính) của đối tượng đó. Ví dụ:

Sử dụng {} giúp chúng ta khai báo một đối tượng mới và gán cho nó các thuộc tính name và user_id tương ứng. Sau khi khai báo xong, ta có thể truy cập đến các thuộc tính này như bình thường. Tuy nhiên, bên trong của object user không chỉ có những thuộc tính ta đã gán mà còn có rất nhiều thông tin khác như ở câu lệnh cuối cùng chỉ ra. Vậy những thuộc tính này đến từ đâu?

Trong Javascript, Object là đối tượng cơ bản, là khuôn mẫu cho tất các đối tượng được tạo mới. Ta hoàn toàn có thể tạo một đối tượng rỗng bằng cách truyền null vào Object.create tuy nhiên, đối tượng mới được tạo ra cũng sẽ có kiểu tương ứng với tham số truyền và kế thừa tất cả các thuộc tính cơ bản.

Hàm/Lớp trong Javascript

Trong Javascript, khái niệm của class (lớp) và function (hàm) khá liên quan đến nhau (function bản thân nó đóng vai trò như là constructor (hàm khởi tạo) cho class và bản chất thực tế thì ko có khái niệm “class” trong javascript). Chúng ta cùng xem VD sau:

Trong ví dụ ở trên, chúng ta đã định nghĩa một hàm tên là person và khởi tại 2 đối tượng tên person1 và person2. Nếu chúng ta nhìn vào thuộc tính của những đối tượng mới được tạo, chúng ta có thể để ý thấy 2 điều:

  • Khi hàm được tạo, javascript engine đã bao gồm thuộc tính prototype vào trong hàm. Thuộc tính prototype này là một đối tượng (được gọi là prototype object)) có một thuộc tính constructor mặc định chỉ về hàm mà chứa thuộc tính prototype này.
  • Khi đối tượng được tạo, javascript engine sẽ thêm một thuộc tính __proto__ vào trong đội tượng mới được tạo ra, thuộc tính này chỉ tới prototype object của hàm constructor. Nói một cách ngắn gọn, object.__proto__ chỉ đến function.prototype.

Constructor là gì?

  • Constructor là một thuộc tính đặc biệt trả về hàm đã được dùng để tạo ra đối tượng đó. Prototype object có constructor chỉ đến hàm đó và constructor của constructor sẽ là hàm constructor toàn cục (global). Ví dụ:

Prototypes in javaScript

Một điều cần lưu ý đó là thuộc tính prototype có thể bị thay đổi thêm/sửa/xoá khi thực thi code. Ví dụ:

Ở trên, ta đã thay đổi prototype của hàm và thêm vào đó thuộc tính mới là details. Ta có thể làm điều tương tự bằng các sử dụng đống tượng:

Bạn có thấy điều gì lạ không? Chúng ta thay đổi đối tượng person1 nhưng kéo theo ảnh hưởng lên cả person2. Lý do cho điều này là ở ví dụ trước, chúng ta thay đổi trực tiếp person.prototype để thêm vào thuộc tính mới nhưng ở ví dụ thứ 2, chúng ta làm điều này nhưng ở trên đối tượng. Vì constructor sẽ trả ra hàm được dùng để tạo ra đối tượng nên person1.constructor sẽ chỉ đến hàm person và person1.constructor.prototype lúc này chính là person.prototype.

Prototype Pollution

Lấy ví dụ, obj[a][b] = value. Nếu kẻ tấn công có thể điều khiển được giá trịa và value, khi đó hắn chỉ cần chỉnh giá trị của a thành __proto__ (chú ý trong javascript, obj["__proto__"] và obj.__proto__ là hoàn toàn tương đương) thì khi đó thuộc tính b của tất cả những đối tượng đang tồn tại trong ứng dụng sẽ bị bị gán thành value.

Tuy nhiên, tấn công không đơn giản như ở trên, theo paper thì ta chỉ có thể tấn công được khi có 1 trong 3 điều kiện sau:

  • Thực hiện việc ghép đệ quy các đối tượng (Object recursive merge)
  • Định nghĩa thuộc tính qua đường dẫn (Property definition by path)
  • Thực hiện nhân bản đối tượng (clone object)

Chúng ta cùng xem xét thông qua các lỗi sau:

CVE-2019-11358: Tấn công prototype pollution thông qua jQuery $.extend

$.extend nếu xử lý không đúng có thể dẫn tới chỉnh những thuộc tính của Objectprototype (khuôn mẫu của các object trong app). Khi đó thuộc tính này sẽ xuất hiện trên toàn bộ các đối tượng. Chú ý là chỉ có phiên bản “deep” (tức là g) của $.extened bị ảnh hưởng.

Các lập trình viên thường sử dụng hàm này để nhân bản một đối tượng hoặc điền vào các thuộc tính mới từ một đối tượng mặc định. Ví dụ:

(Ta có thể tưởng tượng myObject là một trường nhập từ người dùng và được serialized vào trong DB)

Ở đoạn code này, thường ta sẽ nghĩ, khi chạy sẽ gán thuộc tính isAdmin và trong đối tượng mới được tạo ra. Nhưng về bản chất, nó đang gán thẳng vào {} và khi đó {}.isAdmin sẽ có giá trị true. Nếu sau đoạn code này, ta thực hiện kiểm tra như sau:

nếu user chưa tồn tại (undefined) thì thuộc tính isAdmin sẽ được tìm kiếm trong đối tượng cha của nó, chính là Object mà lúc này đã bị thêm vào thuộc tính isAdmin với giá trị true ở trên.

Ví dụ khác khi thực hiện trên JQuery 3.3.1:

Những lỗi này có thể ảnh hướng đến rất nhiều dự án Javascript, đặc biệt là các dự án NodeJS, ví dụ thực tế nhất chính là lỗi ở Mongoose, thư viện JS giúp thao tác với MongoDB, vào hồi tháng 12 năm 2018.

CVE-2018-3721, CVE-2019-10744: Tấn công prototype pollution thông qua lodash

Lodash cũng là một thư viện nổi tiếng cung cấp rất nhiều hàm khác nhau, giúp chúng ta viết code thuận tiện vào gọn gàng hơn với hơn 19 triệu lượt tải hàng tuần. Cách thức khai thác tương tự như với JQuery.

CVE-2018-3721

CVE-2019-10744

Lỗi này ảnh hưởng đến tất cả các phiên bản của Lodash, kể cả phiên bản mới nhất 4.17.11. Bản vá sẽ được cập nhật trong thời gian sớm nhất.

Tôi có thể làm gì để phòng tránh?

  1. Đóng băng các thuộc tính bằng Object.freeze(Object.prototype)
  2. Thực hiện validation trên các input JSON theo đúng schema của ứng dụng
  3. Tránh sử dụng các hàm có ghép đệ quy (recursive merge) các đối tượng một cách không an toàn
  4. Sử dụng đối tượng không có thuộc tính prototype, ví dụ Object.create(null)để tránh ảnh hưởng đến prototype chain
  5. Sử dụng Map thay vì Object
  6. Thường xuyên cập nhật bản vá mới cho các thư viện