閱讀980 返回首頁    go 人物


扯談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的,也是有可能造成數據泄露的。比如內部網站是根據IP限製訪問的,如果XMLHttpRequest不遵守同源策略,那麼攻擊者可以在用戶瀏覽網頁的時候,發起請求,取得內部網站數據。
  • XMLHttpRequest 請求會不會帶cookie?
同域情況下會,不同域情況下不會。如果服務器設置Access-Control-Allow-Credentials: true ,也是可以跨域帶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

  上一篇:go 還原快播&quot;地下&quot;利益鏈
  下一篇:go 【端午小練】HDU4548-美素數