# 一个可大规模悄无声息窃取淘宝/支付宝账号与密码的漏洞 -（埋雷式攻击附带视频演示）

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2014-04-17T02:59:00Z
Canonical: https://wepostx.com/topics/435

> 乌云历史资料归档
>
> **原始作者：** gainover
> **原始编号：** superkieran-wooyundrops:221
> **原始发布时间：** 2014-04-17 10:59
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

## 0x00 说在前面的话

下面是一段如何利用flash xss rookit漏洞来窃取淘宝/支付宝帐号密码的演示视频，当前漏洞已经修复
由于视频网站上传的均不给通过，只好给出一个URL了：
[http://static.wooyun.org/files/taobaoxss.swf](http://static.wooyun.org/files/taobaoxss.swf)
网速好的可以下载并观看清晰版本：
[http://static.wooyun.org/files/taobaoxss.wmv](http://static.wooyun.org/files/taobaoxss.wmv)
2月底我们PKAV团队在乌云(http://www.wooyun.org/index.php)上向阿里（淘宝/支付宝）报告了该漏洞，漏洞的标题看起来有点恐怖，但它绝对不是“标题党”。 乌云的漏洞审核人员在看了漏洞中的视频演示后，为其附加了一个生动又有一点霸气的名字："埋雷式攻击"。
为何叫埋雷呢？本文中也会给出具体的解释。
当时阿里推出了5W的奖励计划，有人问我这个漏洞为什么不上报asrc（阿里巴巴漏洞应急响应中心），报乌云没奖励的。
我给出的答复是：“ 思路灵感都是乌云来的，自当报还之”。
本人乃至本团队，还是更喜欢并赞同乌云上这种透明的机制，从我们自身角度来讲能学习到不少，从用户角度来讲，至少可以让用户知道发生过什么，怎么保护自己 （试想，如果本文中的漏洞曾被黑客拿来窃取过用户帐号与密码，然后某天厂商修复了，用户并不知道曾经有这个漏洞发生过，也不知道自己密码是如何被盗的）。
从厂商角度来讲：Oh shit! 不要拿漏洞恐吓用户！
下面带大家一起来感受一下，这个来自乌云，又回归乌云的漏洞历程。

## 0x01 第一次漏洞报告

这个有意思的漏洞最初在乌云上由白帽子neobyte报告（ [WooYun: 一个flash的0day导致的淘宝网存储xss(可形成永久后门)](http://www.wooyun.org/bugs/wooyun-2013-039481) ），该漏洞描述了在一些FLASH应用中，因使用Local Shared Object （以下简称LSO）与ExternalInterface.addCallback而导致的一类XSS漏洞。由于此前并没有见到类似的报告，从开发人员角度来说，并不会 十分去注意并防御此类漏洞。我们来看看，在开发人员的角度，通过js与as的交互来实现读取FLASH本地缓存功能的流程图:

![原文图片](/media/2016/06/928ea59dad7d40501c0c6fbec0b0f811)

在这个示意图中，红色的箭头表示函数的调用，蓝色的箭头表示用户数据的流向。普通的开发人员仅仅在意功能是否完整的实现；带有一些安全意识的开发人员会注意到，jsGetData所获得的数据需要经过过滤再进入DOM, 如果没有考虑这点会造成什么安全问题呢？乌云上的p.z大神也给我们举了一个好例子（ [WooYun: 淘宝某处存储型XSS漏洞](http://www.wooyun.org/bugs/wooyun-2013-022080) ）; 但是现在，我们将会遇到一个最糟糕的情况：“不论你是否是一个有安全意识的程序员，你都十分可能掉入这个坑中！”。
这是一个什么样的坑呢？
我们得先更深入了解下javascript调用FLASH所提供的函数接口时，到底做了哪些事情。下图描述了当我们调用jsGetData函数来获取FLASH本地缓存数据的调用过程：

![原文图片](/media/2016/06/e3e87a8829b85f9c12b6c2426673f57b)

上面是一个正常获取数据的流程，假设LSO中存放的数据是AAAAAAAAAAAA，最终，我们的data变量的值也为AAAAAAAAAAAA，当然，这是理想状态下的情况。
如果你对DOM XSS有一定了解，也知道eval是多么evil！(不了解但有兴趣的参见本人马甲贴： [WooYun: [腾讯实例教程] 那些年我们一起学XSS - 10. Dom Xss进阶 [邂逅eval]](http://www.wooyun.org/bugs/wooyun-2012-016197) )，那么你一定不难想到下面的做法：将存储在LSO中的数据做以下改变:
AAAAAAAAAAAA 返回数据过程中会执行： eval('"AAAAAAAAAAAA"');
如果我们将存储的数据更换为:

```text
#!javascript AAAAAA";alert(1);//AAAAAA
```
返回数据将执行：

```text
#!javascript eval('"AAAAAA";alert(1);//AAAAAA"');
```
可以看到，我们在eval的数据中插入了一个alert(1)，当eval执行时，就会执行弹窗操作！（你也可以打开F12的console，然后复制上面的代码运行试一试！）
然而，上面只是我们假想的情况。实际上FLASH会对返回数据进行转义操作，将双引号 (") 转义为反斜线+双引号 (\")，即我们更改存储的数据后，实际执行的是下面的代码：

```text
#!javascript eval('"AAAAAA\";alert(1);//AAAAAA"');
```
正是这个转义，使得我们上面的想法泡汤了。但是，别灰心，FLASH对返回数据的过滤是存在问题的，虽然过滤了双引号 (")，但是FLASH并没有过滤反斜线 (\)，我们只需将上面的代码稍加修改，如下：

```text
#!javascript AAAAAA\";alert(1);//AAAAAA
```
返回数据将会是以下的情况：

```text
#!javascript "AAAAAA\\";alert(1);//AAAAAA"
```
随后返回内容进入eval被当作脚本执行。

```text
eval('返回内容');
```
其中双引号 (")被转义为反斜线+双引号(")，但是该反斜线却被我们添加的反斜线所转义。
//如果你想在控制台测试效果，可以执行以下代码

```text
#!js eval('"AAAAAA\\\\";alert(1);//AAAAAA"');
```
//-- 此处由 @piaca 指出并更正描述，由于实际内容写成字符串时，反斜线需要被写成转义形式。
小总结：到了这里，我们可以知道的是，如果我们可以在LSO中存储恶意数据，当HTML页面中通过javascript调用jsGetData函数，继而通过actionscript中的asGetData函数, 从Flash本地存储中读取该恶意数据时，就会触发我们的恶意代码。
接下来的问题，我们如何往LSO中添加我们的恶意数据呢？最暴力的方法是，抢你电脑，拔你网线，拆你硬盘，而后改之！，&@……！%&！%@ ... 温柔，一定要温柔！
在我们的第一个图中，开发人员同样为我们实现了

```text
jsSetData -> asSetData -> LSO
```
的流程，也就是说，我们可以通过Javascript调用jsSetData来实现修改LSO中的数据。从功能角度上是这么设计的，然而出于安全考虑，FLASH所提供的外部函数接口，并不是什么时候都可以调用的，在默认情况下，只有http://A.com的网页文件才能调用http://A.com下的FLASH文件所提供的函数接口，如下图：

![原文图片](/media/2016/06/b3693b826c686f98d1b03067bcfbebe9)

这样看来，其实在默认的安全设置下，我们并没有直接的办法来修改数据。此外，每个域名下FLASH文件所使用的缓存是独立的，因而正常情况下，我们也无法使用http://B.com的FLASH文件来修改http://A.com下的FLASH缓存（不排除有猥琐的办法哦～）。
然而，这种”js读写FLASH缓存“的功能模块，天生会要求有一种特性，就是写一次，四处皆可用，从开发的角度来讲，这是天经地义的事情，方便，很方便。于是乎，开发人员在代码中加上了下面这么一句：

```text
#!javascript Security.allowDomain("*");
```
这一句代码的具体作用，可参见官方手册（[Flash CS4 Professional ActionScript 2.0](http://help.adobe.com/zh_CN/AS2LCR/Flash_10.0/help.html?content=00001493.html))），简单来说，这一句话，打破了我们上面所述的FLASH默认安全设置，使得任何域名下的网页均可以调用A.com下FLASH文件所提供的addCallback接口。
这样一来，我们整个漏洞的原理图就可以建立起来了：

![原文图片](/media/2016/06/182d9d229363126384f71c541b6e718d)

这个图箭头比较多。当黑客创建了恶意页面后，我们以受害者作为起点，当受害者访问了恶意页面后，会执行一系列操作，从而将恶意数据存入LSO中，这个过程，我们称之为“埋雷”。哪里会被“埋雷”呢？答案是很多地方：
譬如：

```text
低级一点的：黑客A向你发送一个链接，你点开了。 不知不觉一点的：当你到处点网页的时候！ 高级一点的：你看了一篇黑客写的博文/日志，或是有黑客给你的QQ空间日志发表了一条留言或者评论，内容里被植入了XSS代码（需结合QQ的XSS漏洞），你点开了。 再高级再不知不觉一点的：某天你QQ收到了一条礼物消息（QQ空间礼物功能XSS可以攻击任意指定QQ号码用户），或者是某天突然来了一个陌生人的QQ对话框（微博上正在疯传的那个QQ客户端聊天就中的XSS（可登陆和控制他人账号））。
```
总之，如果是有心而为之，你！，被埋雷的可能性非常大。
当受害者被埋下雷之后，受害者再次访问目标站（比如本次漏洞中的淘宝，或者是支付宝），黑客所埋下的恶意代码就会被触发，Bomb! 雷就爆炸了！当然，实际上黑客不大可能去恶作剧式的“Bomb!"的吓你一跳，而是偷偷的，悄无声息的利用这颗无声的”雷“来窃取受害者的帐号与密码。
看我敲了这么多，发了这么多张图，你们觉得这个漏洞的潜在危害大么？反正我觉得挺大的，再来看看厂商对neobyte提交漏洞的响应：

```text
危害等级：中 漏洞Rank：8 确认时间：2013-10-12 16:44
```
是的，厂商似乎没有感觉到这个漏洞的危害。

## 0x02 第二次漏洞报告

到了2013年10月24号，我已经可以查看neobyte所提交的漏洞详情，于是就对他所提交的这个漏洞进行了研究，由于自己对FLASH也推敲过一些，在明白成因之后，也下载了漏洞中所提到的那个存在缺陷的FLASH文件（[http://acjstb.aliyun.com/actionlog/flash/JSocket.swf](http://acjstb.aliyun.com/actionlog/flash/JSocket.swf)）。以下是对方的修复代码：

```text
#!as3 public function getlso():String{ var _local1:SharedObject = SharedObject.getLocal("kj"); var _local2:RegExp = new RegExp("[a-zA-Z]"); if (_local1.data.key == undefined){ return (""); }; if (_local2.test(_local1.data.key)){ this.setlso(""); return (""); }; return (_local1.data.key); }
```
由于业务上，淘宝此处存储在LSO的数据，并不需要字母，但是恶意代码中却需要使用到字母，例如：alert。所以开发人员这里加上了一个正则判断：“如果所获取的数据中存在字母，则返回空字符串”。是的，看起来是被修复了，但是依然存在问题，一来，开发人员并没有过滤掉真正导致潜在危害的反斜线()，二来，Javascript中调用函数完全可以不需要存在任何字母，纳尼？？一起来看看：

```text
#!as3 //我们可以使用以下方式创建一个函数: new Function("alert(1)"); //也可以不要new Function("alert(1)")() //可以将Function转换下 "...".substr.constructor("alert(1)")() //再转换下 "..."["substr"]["constructor"]("alert(1)")() //字符串全部转义 "..."["\163\165\142\163\164\162"]["\143\157\156\163\164\162\165\143\164\157\162"]("\141\154\145\162\164\50\61\51")()
```
这样我们就实现了不需要字母即可执行alert(1)的目的。再利用在上一小节中所说的恶意代码构造技巧，我们只需将AAAAAAAAAAAAAA替换为以下代码：

```text
#!javascript ...\"["\163\165\142\163\164\162"]["\143\157\156\163\164\162\165\143\164\157\162"]("\141\154\145\162\164\50\61\51")()//..
```
漏洞就这样再次出现了，虽然当时已经想到这个漏洞用来钓鱼的危害挺大，但是并没有动力去做一个钓鱼演示，并且漏洞刚被阿里修复不久，只是出于技术上的好意，想提醒开发人员完美的修复。于是，我“不好意思”的登上了我另外一个马甲号，发了一个续集： [WooYun: 一个flash的0day导致的淘宝网存储xss 【续集】](http://www.wooyun.org/bugs/wooyun-2013-040838) 。当时我给漏洞自评了8分，因为漏洞的原作者neobyte也只要了8分，我也没敢多要。但是，对方竟然只给了我5分：

```text
危害等级：中 漏洞Rank：5 确认时间：2013-10-24 14:19
```

## 0x03 第三次漏洞报告

恩，不得不说，为什么还会有第三次呢？主要是因为上面这个5分！个人觉得，如果一个漏洞被修复后，又被绕过，这是更加严重的情况，怎么说也不应该给5分，起码也得6分是不！。这个5分大大刺激了我的“报复欲”，让我想再次绕过他们的修复方案，然后做一个大规模的钓鱼演示，让他们知道这个绝对不能是5分！！！。但是呢，这一次他们的修复方案并没有好的绕过方法！ 虽然如此，隔三差五的，我还是会去研究研究，不怕贼偷，就怕贼惦记.......，直到有一天，我发现FLASH中的修复方案竟然被换掉了，并且，第一眼，我就看出这个换掉的方案又出现问题了。新的修复代码如下：

```text
#!javascript private function getlso():String{ var _local1:SharedObject = SharedObject.getLocal("kj"); var _local2:RegExp = new RegExp("[\\({]"); if (_local1.data.key == undefined){ return (""); }; if (_local2.test(_local1.data.key)){ this.setlso(""); return (""); }; return (_local1.data.key); }
```
可以看到，正则表达式 _local2 仅仅过滤了 ( 和 { ，而前面也已提到，正确的过滤方法应该是过滤 \ 。经过前面2轮的洗礼，我觉得安全人员应该是知道\的危害了，为什么还会出现过滤失误呢？我在漏洞描述中也已经给出了以下猜测，后确认，答案是B。毕竟我也是写了很多年前端的，RegExp这个坑在我曾经也数次掉入。

```text
A. 响应漏洞的同学并没看到我的修复建议并转告开发同学。 B. 开发的同学，认为 [\\({] 是 \ , ( 和 } 的集合， 而实际上，RegExp方式创建正则表达式时，需要写成 [\\\\({] ，原因是：\在字符串里是转义符，在正则里也是转义符。
```
当然，还需要注意的是，这个正则，不允许(和{，那么还可以执行javascript代码吗？答案是可以的，如下：

```text
#!javascript location.href="javascript:alert%28129";
```
使用location.href来执行js，并且将其中的括号与花括号进行URL编码。
嗯，这个漏洞就这样再次出现了，这一次我可不想再得到那可怜的5分！于是花了时间写了一个真实可利用的代码，并录制了本文最开始的视频，撰写了漏洞报告（ [WooYun: 一个可大规模悄无声息窃取淘宝/支付宝账号与密码的漏洞 -（埋雷式攻击附带视频演示）](http://www.wooyun.org/bugs/wooyun-2014-051615) ）。阿里这次给出了15分，还是没有给出我心中的20分。怎么说呢，从用户角度来讲，它的危害肯定是值20分的，但是厂商并不总是站在用户考虑问题的。

```text
危害等级：高 漏洞Rank：15 确认时间：2014-02-26 10:25
```

## 0x04 对于开发人员

注意addCallback: 在Flash中使用addCallback向外部提供函数接口时，如果as中对应函数的返回值是String, Array, Object等类型，并且返回数据中有用户可控的数据时，建议对返回值进行过滤操作，将反斜线()过滤掉。如果没有进行过滤的话，则可能出现与（ [WooYun: QQ空间某功能缺陷导致日志存储型XSS - 14](http://www.wooyun.org/bugs/wooyun-2014-051432) ）相类似的案例。这里我就不给出具体的过滤代码了，相信一般的开发人员的as都比我写的好，以下是伪代码：

```text
#!javascript function asFunction():String{ var data:String="xxx某些用户可控数据xxx"; var obj:Object={"name":"用户可控数据"}; .... data = 过滤(data); obj = 遍历过滤(obj); return data; 或 return obj; }
```
勿滥用*|*Security.allowDomain("*"): **不是非常、极其、十分需要的情况下，建议将allowDomain限定在指定的域名下面，而不要为了方便而使用*。因为使用*，可能还会导致其它安全问题。

## 0x05 对于普通用户

由于这类漏洞十分隐蔽，目前应该没有安全软件能防御此类攻击。鉴于绝大部分厂商并不会“举一反三”的修复同类问题，我们不能完全指望厂商，安全还得靠自己。如果我们尽可能的定期清理Flash player缓存，理论上会大大降低此类漏洞造成的危害。
Flash的本地缓存一般存放在一个叫做 #SharedObjects的目录中，如果有使用Everything这款搜索工具的网友，可以使用Everything查找所有#SharedObjects目录，然后这些目录下的目录（8位的数字/字母组成）下的内容进行删除处理。（当然，删除#SharedObjects目录下数据，可能导致一些FLASH游戏或者FLASH应用的数据需要重新下载，但是相比之下，安全更重要。）
如果你没有使用Everything，下表列出了IE和chrome中Flash的缓存目录所在，找到对应目录进行清理即可，其它浏览器用户，如360***|||||*，搜狗，可自行搜索C盘下SharedObjects目录进行相应删除操作。

```text
IE浏览器 C:\Documents and Settings\用户名 \Application Data\Macromedia\Flash Player\#SharedObjects （Win XP 系统） C:\Users\用户名\AppData\Roaming\Macromedia\Flash Player\#SharedObjects\ （Win 7） Chrome C:\Documents and Settings\用户名 \Local Settings\Application Data\Google\Chrome\User Data\【用户目录】\Pepper Data\Shockwave Flash\WritableRoot\#SharedObjects （Win XP 系统） C:\Users\用户名 \AppData\Local\Google\Chrome\User Data\【用户目录】\Pepper Data\Shockwave Flash\WritableRoot\#SharedObjects （Win 7） *【用户目录】可能是Profile+数字 或者 Default
```
常见的“卫士”软件，自带了垃圾清理功能，但是可能在默认设置中，并未开启清理FLASH缓存功能，所以需要用户手动勾选“清理Flash player缓存”。这里以金山卫士为例：清理垃圾->其它常用软件产生的垃圾文件->Flash player缓存。 但是该方法也许并不会完全清理干净，例如：并不会清理chrome浏览器的flash缓存，因此还是建议使用以上方法手工清理。

## 0x06 说在后面的话

这个漏洞虽然标题是在说“窃取淘宝/支付宝的帐号与密码”，有些人觉得反正不知道支付密码，不要紧！但是这个漏洞所造成的潜在危害，并不是只有淘宝/支付宝帐号与密码那么简单。试想，如果在淘宝的登录页面上再结合一个QQ的xss漏洞，我们可以得到什么信息呢？淘宝/支付宝帐号、密码，QQ帐号及QQ相关信息（关于QQ的XSS可以做什么，参见乌云一哥jannock的  [WooYun: 跨站脚本-可以让战场离得更远（浅谈腾讯架构缺陷）](http://www.wooyun.org/bugs/wooyun-2012-011192) ，这个2012年时候的情况，现在已经有所好转，但是当前状况似乎依然不是太好）。此外，由于淘宝/支付宝这类涉及现金的帐号，密码等级往往十分高，可能会出现支付宝登录密码同样是QQ密码的情况，所以这类密码一旦泄漏，可能会涉及后续更严重的信息泄漏。

## Replies
