你知道爲什麼Facebook的API以一個循環作爲開頭嗎?

如果你有在瀏覽器中查看過發給大公司API的請求,你可能會注意到,JSON前面會有一些奇怪的JavaScript:

爲什麼他們會用這幾個字節來讓JSON失效?

爲了保護你的數據

如果沒有這些字節,那麼有可能任何網站都可以訪問這些數據。

這個漏洞被稱爲JSON劫持,也就是網站可以從這些API中提取JSON數據。

起源

在JavaScript 1.5及更早版本中,可以覆蓋原始類型對象的構造函數,並使用括號調用覆蓋的版本。

你可以這樣:

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

這樣就會彈出alert!

使用以下腳本替換var x,攻擊者就可以閱讀你的電子郵件!

這是通過在加載外部腳本之前覆蓋Array構造函數來實現的。

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

數據提取

即使你重載了構造函數,仍然可以通過this來訪問它。

這是一個代碼片段,它將alert數組的所有數據:

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++;
}

在創建數組後,它們的值將被alert出來!

ECMAScript 4提案中已修復了這個問題,我們現在無法再覆蓋大多數原始類型的原型,例如Object和Array。

儘管ES4從未發佈,但主要瀏覽器在發現後很快就修復了這個漏洞。

在今天的JavaScript中,你仍然可以使用類似的行爲,但它受限於你創建的變量,或者不使用括號創建的對象。

這是之前的一個修訂版本:

// 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]);

這是一個使用Array關鍵字創建數組的版本:

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

Array("secret","values");

如你所見,你添加到數組中的數據被記錄下來,但功能保持不變!

修復方案並沒有阻止使用Array來創建數組,而是在使用括號創建對象時強制使用原生實現,而不是自定義函數。

這意味着我們仍然可以創建一個Array函數,但不能與方括號([1,2,3])一起使用。

如果我們使用x = new Array(1,2,3)或x = Array(1,2,3),它仍將被調用,但不會給JSON劫持留下可趁之機。

新的變體

我們知道舊版本的瀏覽器很容易受到這個漏洞的攻擊,那麼現在呢?

隨着最近EcmaScript 6的發佈,添加了很多新功能,例如Proxies!

來自Portswigger的Gareth Heyes在博客上介紹了這個漏洞的新變體,它仍然允許我們從JSON端點竊取數據!

通過使用Proxies(而不是Accessor),我們可以竊取到任意創建的變量,無論它的名稱是什麼。

它可以像Accessor一樣,但可以訪問任意可訪問或寫入屬性。

使用這個和另外一個技巧,就可以再次竊取數據!

UTF-16BE是一個多字節字符集,一個字符由兩個字節組成。例如,如果你的腳本以[“作爲開頭,它將被視爲字符0x5b22而不是0x5b 0x22。0x5b22恰好是一個有效的JavaScript變量=)。

使用這個腳本:

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

通過使用這個腳本中的一些受控數據和移位腳本,我們就可以再次滲透數據!

這是Gareth最後的POC,摘自他的博文:

<!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"] -->

我不會深入解釋這個方法,而是建議你閱讀他的帖子,以獲取更多信息。

預防

這是OWASP的官方建議

  • 使用CSRF保護,如果不存在安全標頭或csrf令牌,就不返回數據,以防止被利用。
  • 始終將JSON作爲對象返回。

最後的解決方案很有趣。

在Firefox和IE中,這個是有效的:

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

但這樣不行:

{"key":"value"}

它之所以無效是因爲Firefox和IE認爲括號是塊語句的開頭,而不是創建對象。

沒有引號的符號{key:“value”}被視爲標籤,值被視爲一個語句。

結論

雖然這些東西在今天可能是無效的,但我們永遠不會知道明天將會帶來什麼新的錯誤,因此我們仍應盡力阻止API被利用。

如果我們把這個StackOverflow答案視爲理所當然,我們就很容易受到現代變體的影響,因此仍然可能被黑客入侵。

谷歌和Facebook在JSON數據之前添加無效的JavaScript或無限循環,OWASP也列出了其他替代方案。

英文原文

https://dev.to/antogarand/why-facebooks-api-starts-with-a-for-loop-1eob

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章