980
人物
扯談web安全之JSON
前言
JSON(JavaScript Object Notation),可以說是事實的瀏覽器,服務器交換數據的標準了。目測其它的格式如XML,或者其它自定義的格式會越來越少。
為什麼JSON這麼流行?
和JavaScript無縫對接是一個原因。
還有一個重要原因是可以比較輕鬆的實現跨域。如果是XML,或者其它專有格式,則很難實現跨域,要通過flash之類來實現。
任何一種數據格式,如何解析處理不當,都會存在安全漏洞。下麵扯談下JSON相關的一些安全東東。
在介紹之前,先來提幾個問題:
- 為什麼XMLHttpRequest要遵守同源策略?
- XMLHttpRequest 請求會不會帶cookie?
- <script scr="..."> 的標簽請求會不會帶cookie?
- 向一個其它域名的網站提交一個form,會不會帶cookie?
- CORS請求能不能帶cookie?
JSON注入
有的時候,可能是為了方便,有人會手動拚接下JSON,但是這種隨手代碼,卻可能帶來意想不到的安全隱患。第一種方式,利用字符串拚接:
String user = "test01"; String password = "12345', admin:'true"; String json = "{user:'%s', password:'%s'}"; System.out.println(String.format(json, user, password)); //{user:'test01', password:'12345', admin:'true'}
用戶增加了管理員權限。
第二種,利用Parameter pollution, 類似http parameter pollution
String string = "{user:'test01',password:'hello', password:'world'}"; JSONObject parse = JSON.parseObject(string); String password = parse.getString("password"); System.out.println(password); //world當JSON數據key重複了會怎麼處理?大部分JSON解析庫都是後麵的參數覆蓋了前麵的。
下麵的演示了修改別人密碼的例子:
//user%3Dtest01%26password%3D12345%27%2Cuser%3Dtest02" //user=test01&password=12345',user=test02 HttpServletRequest request = null; String user = request.getParameter("user"); //檢查test01是否登陸 String password = request.getParameter("password"); String content = "{user:'" + user + "', password:'" + password + "'}"; User user = JSON.parseObject(content, User.class); //{"password":"12345","user":"test02"} updateDb(user);
所以說,不要手動拚接JSON字符串
瀏覽器端應該如何處理JSON數據?
像eval這種方式,自然是不能采用的。
現在的瀏覽器都提供了原生的方法 JSON.parse(str) 來轉換為JS對象。
如果是IE8之前的瀏覽器,要使用這個庫來解析:https://github.com/douglascrockford/JSON-js
參考:https://zh.wikipedia.org/wiki/JSON#.E5.AE.89.E5.85.A8.E6.80.A7.E5.95.8F.E9.A1.8C
JQuery裏內置了JSON解析庫
JSONP callback注入
簡單介紹jsonp的工作原理。jsonp工作原理:
用js在document上插入一個<script>標簽,標簽的src指向遠程服務器的API地址。客戶端和服務器約定一個回調函數的名字,然後服務器返回回調函數包裹著的數據,然後瀏覽器執行回調函數,取得數據。比如jquery是這樣子實現的:
var url = 'https://localhost:8080/testJsonp?callback=?'; $.getJSON(url, function(data){ alert(data) });jquery自動把?轉成了一個帶時間戳特別的函數(防止緩存):
https://localhost:8080/testJsonp?callback=jQuery1102045087050669826567_1386230674292&_=1386230674293相當於插入了這麼一個<script>標簽:
<script src="https://localhost:8080/testJsonp?callback=jQuery1102045087050669826567_1386230674292&_=1386230674293"></script>服務器返回的數據是這樣子的:
jQuery1102045087050669826567_1386230674292({'name':'abc', 'age':18})瀏覽器會執行直接執行這個JS函數。
所以,如果在callback函數的名字上做點手腳,可以執行任意的JS代碼。所以說callback名字一定要嚴格過濾。
當然,callback函數的名字通常是程序自己控製的,但是不能排除有其它被利用的可能。
那麼callback函數的名字,如何過濾?應當隻允許合法的JS函數命名,用正則來匹配應該是這樣子的:
^[0-9a-zA-Z_.]+$正則可能比較慢,可以寫一個函數來判斷:
static boolean checkJSONPCallbackName(String name) { try { for (byte c : name.getBytes("US-ASCII")) { if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { continue; } else { return false; } } return true; } catch (Throwable t) { return false; } }
實際上對callback函數名字進行嚴格檢驗還有其它的一個好處,就是防範了很多UTF-7編碼攻擊。
因為UTF-7編碼的頭部都是帶有特殊字符的,如"+/v8","+/v9",這樣就過濾掉非法編碼的請求了。
jsonp的請求的驗證
jsonp在使用的時候,還有容易犯的錯誤是沒有驗證用戶的身份。第一,操作是否是用戶自己提交的,而不是別的網頁用<script>標簽,或者用<form>提交的
所以要檢查request的refer,或者驗證token。這個實際是CSRF防護的範疇,但是很容易被忽略。
第二,要驗證用戶的權限。
很多時候,可能隻是驗證了用戶是否登錄 。卻沒有仔細判斷用戶是否有權限。
比如通過JSONP請求修改了別的用戶的數據。
所以說,一定要難證來源。判斷refer,驗證用戶的身份。
到烏雲上搜索,可以找到不少類似的漏洞,都是因為沒有嚴格驗證用戶的權限。https://www.wooyun.org/searchbug.php?q=jsonp
JSON hijacking
在JS裏可以為對象定義一些setter函數,這樣的話就存在了可以利用的漏洞。比如在瀏覽器的JS Console裏執行:
window.__defineSetter__('x', function() { alert('x is being assigned!'); }); window.x=1;會很神奇地彈出一個alert窗口,說明我們定義的setter函數起作用了。
結合這個,當利用<script>標簽請求外部的一個JSON API時,如果返回的是數組型,就可以利用竊取數據。
比如有這樣的一個API:
https://www.test.com/friends
返回的數據是JSON Array:
[{'user':'test01','age':18},{'user':'test02,'age':19},{'user':'test03','age':20}]在攻擊頁麵上插入以下的代碼,就可以獲取到用戶的所有的朋友的信息。
<script> Object.prototype.__defineSetter__('user',function(obj) {alert(obj); } ); </script> <script src="https://www.test.com/friends"></script>這個漏洞在前幾年很流行,比如qq郵箱的一個漏洞:https://www.wooyun.org/bugs/wooyun-2010-046
現在的瀏覽器都已經修複了,可以下載一個Firefox3.0版本來測試下。目前的瀏覽器在解析JSON Array字符串的時候,不再去觸發setter函數了。但對於object.xxx 這樣的設置,還是會觸發。
IE的utf-7編碼解析問題
這個漏洞也曾經很流行。利用的是老版的IE可以解析utf-7編碼的字符串或者文件,繞過服務器的過濾。舉個烏雲上的例子:https://www.wooyun.org/bugs/wooyun-2011-01293
有這樣的一個jsonp調用接口:
https://jipiao.taobao.com/hotel/remote/livesearch.do?callback=%2B%2Fv8%20%2BADwAaAB0AG0APgA8AGIAbwBkAHkAPgA8AHMAYwByAGkAcAB0AD4AYQBsAGUAcgB0ACgAMQApADsAPAAvAHMAYwByAGkAcAB0AD4APAAvAGIAbwBkAHkAPgA8AC8AaAB0AG0APg
url decoder之後是:
https://jipiao.taobao.com/hotel/remote/livesearch.do?callback=+/v8 +ADwAaAB0AG0APgA8AGIAbwBkAHkAPgA8AHMAYwByAGkAcAB0AD4AYQBsAGUAcgB0ACgAMQApADsAPAAvAHMAYwByAGkAcAB0AD4APAAvAGIAbwBkAHkAPgA8AC8AaAB0AG0APg
因為jsonp調用是直接返回callback包裝的數據,所以實際上,上麵的請求直接返回的是:
+/v8 +ADwAaAB0AG0APgA8AGIAbwBkAHkAPgA8AHMAYwByAGkAcAB0AD4AYQBsAGUAcgB0ACgAMQApADsAPAAvAHMAYwByAGkAcAB0AD4APAAvAGIAbwBkAHkAPgA8AC8AaAB0AG0APg-(調用結果數據)
IE做了UTF-7解碼之後數據是這樣子的:
<htm><body><script>alert(1);</script></body></htm>(調用結果數據)
於是,就執行了XSS。
另外用IFrame也是可以的。但是我在IE8上測試,url的後綴需要是html才會觸發。
IE把沒有聲明返回Content-Type的請求當做了"text/html"類型的,然後解析就有問題了。隻要服務器端顯式設置了Content-Type為"application/json",則IE不會識別編碼,就不會觸發漏洞。所以說服務器端的Content-Type一定要設置對。盡管設置之後調試有點麻煩,但是卻大大提高了安全性。
JSON格式設置為:"application/json"
JavaScript設置為:"application/x-javascript"
JavaScript還有一些設置為:"text/javascript"等,都是不規範的。
其它的一些東東
MongoDB注入
這個實際上就是JSON注入,簡單的字符串拚接,可能會引發各種數據被修改的問題。
JSON解析庫的問題
有些JSON庫解析庫支持循環引用,那麼是否可以構造特別的數據,導致其解析失敗?從而引起CPU使用過高,拒絕服務等問題?
FastJSON的一個StackOverflowError Bug:
https://github.com/alibaba/fastjson/issues/76
有些JSON庫解析有問題:
https://www.freebuf.com/articles/web/10672.html
JSON-P
有人提出一個JSON-P的規範,但是貌似目前都沒有瀏覽器有支持這個的。
原理是對於JSONP請求,瀏覽器可以要求服務器返回的MIME是"application/json-p",這樣可以嚴格校驗是否合法的JSON數據。
CORS(Cross-Origin Resource Sharing)
為了解決跨域調用的安全性問題,目前實際上可用的方案是CORS:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
https://www.w3.org/TR/cors/
原理是通過服務器端設置允許跨域調用,然後瀏覽器就允許XMLHttpRequest跨域調用了。
CORS可以發起GET/POST請求,不像JSONP,隻能發起GET請求。
默認情況下,CORS請求是不帶cookie的。
我個人認為,這個方案也很蛋疼,一是需要服務器配置,二是協議複雜。瀏覽器如果不能確定是否能夠跨域調用,還要先進行一個Preflight Request。。
實際上,即使服務器不允許CORS,XMLHttpRequest請求實際上是發送出去,並且返回數據的了,隻是瀏覽器沒有讓JS環境拿到而已。
另外,我認為有另外一種數據泄露的可能:黑客可能控製了某個路由,他不能隨意抓包,但是他可以在回應頭裏插入一些特別的頭部,比如:
Access-Control-Allow-Credentials: true那麼,這時XMLHttpRequest請求就是帶cookie的了。
最初的問題
回到最初的問題:
- 為什麼XMLHttpRequest要遵守同源策略?
- XMLHttpRequest 請求會不會帶cookie?
- <script scr="..."> 的標簽請求會不會帶cookie?
- 向一個其它域名的網站提交一個form,會不會帶cookie?
總結:
- 禁止手動拚接JSON字符串,一律應當用JSON庫輸出。也不應使用自己實現的ObjectToJson等方法,因為可能有各種沒有考慮到的地方。
- jsonp請求的callback要嚴格過濾,隻允許"_",0到9,a-z, A-Z,即合法的javascript函數的命名。
- jsonp請求也要判斷合法性,比如用戶是否登陸(這點很容易被忽略)。
- 設置好Content-Type(這點對於調試不方便,但是提高了安全性)。
- 以jsonp方式調用第三方的接口,實際相當於引入了第三方的JS代碼,要慎重。
參考:
https://www.json.org/
https://www.slideshare.net/wurbanski/nosql-no-security
https://github.com/douglascrockford/JSON-js
https://toolswebtop.com/ 在線編碼轉換,可以轉換UTF-7
https://www.thespanner.co.uk/2011/05/30/json-hijacking/
https://www.thespanner.co.uk/2009/11/23/bypassing-csp-for-fun-no-profit/
https://stackoverflow.com/questions/1830050/why-same-origin-policy-for-xmlhttprequest
最後更新:2017-04-03 08:26:21