jsonp安全性防范,分為以下幾點:
1、防止callback參數意外截斷js代碼,特殊字符單引號雙引號,換行符均存在風險
2、防止callback參數惡意添加標簽(如script),造成XSS漏洞
3、防止跨域請求濫用,阻止非法站點惡意調用
針對第三點,我們可以通過來源refer白名單匹配,以及 cookieToken 機制來限制
而前兩點,傳統的做法分為以下幾種:
1、純手工過濾特殊字符,引號尖括號等,一旦發現潛在惡意字符則服務端拒絕,返回錯誤。
此種方式較為嚴格,但是隨之而來的問題是失敗率會有所提升,尤其對於對外開發者。
而且JS中惡意字符的變形十分多,此方式需要枚舉所有非法字符,可能存在疏漏。
我們不應該將潛在的惡意字符都一概屏蔽,因為確實有些需求需要傳入並存儲這些字符。
2、對於callback參數作嚴整的格式檢查,或強制約定指定格式。基本可以徹底解決安全問題,
但同樣是對調用端不是完全透明,使用者需要額外去知曉相關限制和約定,可能會造成不必要的溝通成本。
3、返回包體添加header頭部,強制指定MIME類型,避免按HTML方式解析,防止XSS漏洞。
這似乎是個很完美的解決方案。
但是十分詭異的是,在某些版本的火狐瀏覽器下,直接訪問MIME類型為JAVASCRIPT的請求時,瀏覽器仍然會按照HTML解析。
或許是該瀏覽器設計的缺陷,但它忽略了我們設置的header。無法保證所有瀏覽器嚴格按照MIME類型解析。
我們的關注點一直在於如何限制用戶輸入,但是請從另外一個層面去考慮該問題,或許就會豁然開朗。
我們先了解一下JS本身的特性。
JSONP的本質是構造全局回調函數,之后加載script標簽觸發回調函數。
通常我們使用函數可以這么寫
function test(){}test();
而在全局的函數也就默認是window的成員。也可以顯示書寫為
window.test = function(){};window.test();
而JS中對象的成員是可以使用字符串索引的方式訪問的,故進一步改造為
window['test']=function(){};window['test']();
現在有注意到么,如此一來我們已經把函數名已經完全限制在了字符串上下文,
理論上只要做好了防注入工作,callback參數是不可能跳出字符串上下文意外執行代碼的。
以PHP為例,單字符串防止注入甚至可以直接使用json_encode該字符串實現。
window['alert("123");abc']();
上面的callback參數雖然有注入的風險,可以由於callback參數嚴格限制在字符串內部,僅會作為文本,不會意外執行
但仍然存在xss漏洞問題
看下面例子
window['<script>alert(123);</script>']();
我們雖然已經保證了<script>嚴格限制在引號內部,不會造成js注入,但是直接在瀏覽器中輸入該jsonp請求仍會按照HTML解析,產生XSS
漏洞,即便設置了header也很難防范。
在進一步,我們只需要保證瀏覽器內不會明文出現<>標簽,那么問題便可徹底解決。
基本思路是在服務端做一次urlencode。而在output輸出decodeURIComponent(‘#####’)來規避顯示尖括號。
Urlencode過的字符串,只可能包含字母數字%,也順便解決了注入的問題
最終附上一段簡短的代碼。根本解決jsonp的安全問題
<?php header('Content-type: text/javascript'); //加上此句可以消除chrome的警告
$callback = urlencode($_GET['callback']);
echo "window[decodeURIComponent('{$callback}')]({ret:0,msg:’OK’});"
請求1:http://www.test.com/a.php?callback=alert(123);abc
響應1:window[decodeURIComponent('alert(123)%3Babc')]({ret:0,msg:'OK'});
請求2:http://www.test.com/a.php?callback=<script>alert(123);</script>
響應2:window[decodeURIComponent('%3Cscript%3Ealert(123)%3B%3C%2Fscript%3E')]({ret:0,msg:'OK'});
上述幾個例子都可以證明jsonp安全漏洞已被徹底規避,即便存在嘗試注入的惡意參數,仍能最大限度保證程序完全正常工作觸發回調。
附上源碼
<html> <head> <title>GoJSONP</title> </head> <body> <script type="text/javascript"> function jsonhandle(data){ console.table(data); } </script> <script type="text/javascript" src="http://code.jquery.com/jquery-3.3.1.js"> </script> <script type="text/javascript"> $(document).ready(function(){ // var url = "http://www.project.com/project/l.php?callback=alert('123');jsonhandle"; var url = "http://www.project.com/project/l.php?callback=jsonhandle"; var obj = $('<script><\/script>'); obj.attr("src",url); $("body").append(obj); }); </script> </body> </html>
<?php header('Content-type: text/javascript'); //加上此句可以消除chrome的警告 //xss // $callback = $_GET['callback']; // echo "$callback({ret:0,msg:'ok'});" //safe $callback = urlencode($_GET['callback']); echo "window[decodeURIComponent('{$callback}')]({ret:0,msg:'OK'});" ?>