天天看點

扯談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

參考:http://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 = 'http://localhost:8080/testJsonp?callback=?';
$.getJSON(url, function(data){
    alert(data)
});           

jquery自動把?轉成了一個帶時間戳特别的函數(防止緩存):

http://localhost:8080/testJsonp?callback=jQuery1102045087050669826567_1386230674292&_=1386230674293           

相當于插入了這麼一個<script>标簽:

<script src="http://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",這樣就過濾掉非法編碼的請求了。

update: 2016-3-22 

spring 4.1裡有一個AbstractJsonpResponseBodyAdvice ,裡面是用正則比對來判斷是否合法的jsonp函數名。

public abstract class AbstractJsonpResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice {

	/**
	 * Pattern for validating jsonp callback parameter values.
	 */
	private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");           

jsonp的請求的驗證

jsonp在使用的時候,還有容易犯的錯誤是沒有驗證使用者的身份。

第一,操作是否是使用者自己送出的,而不是别的網頁用<script>标簽,或者用<form>送出的

是以要檢查request的refer,或者驗證token。這個實際是CSRF防護的範疇,但是很容易被忽略。

第二,要驗證使用者的權限。

很多時候,可能隻是驗證了使用者是否登入 。卻沒有仔細判斷使用者是否有權限。

比如通過JSONP請求修改了别的使用者的資料。

是以說,一定要難證來源。判斷refer,驗證使用者的身份。

到烏雲上搜尋,可以找到不少類似的漏洞,都是因為沒有嚴格驗證使用者的權限。

http://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:

http://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="http://www.test.com/friends"></script>           

這個漏洞在前幾年很流行,比如qq郵箱的一個漏洞:http://www.wooyun.org/bugs/wooyun-2010-046

現在的浏覽器都已經修複了,可以下載下傳一個Firefox3.0版本來測試下。目前的浏覽器在解析JSON Array字元串的時候,不再去觸發setter函數了。但對于object.xxx 這樣的設定,還是會觸發。

IE的utf-7編碼解析問題

這個漏洞也曾經很流行。利用的是老版的IE可以解析utf-7編碼的字元串或者檔案,繞過伺服器的過濾。舉個烏雲上的例子:http://www.wooyun.org/bugs/wooyun-2011-01293

有這樣的一個jsonp調用接口:

http://jipiao.taobao.com/hotel/remote/livesearch.do?callback=%2B%2Fv8%20%2BADwAaAB0AG0APgA8AGIAbwBkAHkAPgA8AHMAYwByAGkAcAB0AD4AYQBsAGUAcgB0ACgAMQApADsAPAAvAHMAYwByAGkAcAB0AD4APAAvAGIAbwBkAHkAPgA8AC8AaAB0AG0APg

url decoder之後是:

http://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庫解析有問題:

http://www.freebuf.com/articles/web/10672.html

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

http://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是不帶Cookie的,也是有可能造成資料洩露的。比如内部網站是根據IP限制通路的,如果XMLHttpRequest不遵守同源政策,那麼攻擊者可以在使用者浏覽網頁的時候,發起請求,取得内部網站資料。

同域情況下會,不同域情況下不會。如果伺服器設定Access-Control-Allow-Credentials: true ,也是可以跨域帶Cookie的。

會。

總結:

  • 禁止手動拼接JSON字元串,一律應當用JSON庫輸出。也不應使用自己實作的ObjectToJson等方法,因為可能有各種沒有考慮到的地方。
  • jsonp請求的callback要嚴格過濾,隻允許"_",0到9,a-z, A-Z,即合法的javascript函數的命名。
  • jsonp請求也要判斷合法性,比如使用者是否登陸(這點很容易被忽略)。
  • 設定好Content-Type(這點對于調試不友善,但是提高了安全性)。
  • 以jsonp方式調用第三方的接口,實際相當于引入了第三方的JS代碼,要慎重。

參考:

http://www.json.org/

http://www.slideshare.net/wurbanski/nosql-no-security

https://github.com/douglascrockford/JSON-js

http://toolswebtop.com/      線上編碼轉換,可以轉換UTF-7    

http://www.thespanner.co.uk/2011/05/30/json-hijacking/

http://www.thespanner.co.uk/2009/11/23/bypassing-csp-for-fun-no-profit/

http://stackoverflow.com/questions/1830050/why-same-origin-policy-for-xmlhttprequest

繼續閱讀