# 延长 XSS 生命期

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-03-03T03:37:00Z
Canonical: https://wepostx.com/topics/728

> 乌云历史资料归档
>
> **原始作者：** EtherDream
> **原始编号：** superkieran-wooyundrops:485
> **原始发布时间：** 2015-03-03 11:37
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

## 0x00 前言

XSS 的本质仍是一段脚本。和其他文档元素一样，页面关了一切都销毁。除非能将脚本蔓延到页面以外的地方，那样才能获得更长的生命力。
庆幸的是，从 DOM 诞生的那一天起，就已为我们准备了这个特殊的功能，让脚本拥有突破当前页面的能力。
下面开始我们的续命黑魔法。

## 0x01 反向注入

一个不合理的标准，往往会埋下各种隐患。
按理来说，只有通过脚本弹出的页面，才能拥有 opener 属性。然而事实上，通过超链接点开的页面居然也有。这为 XSS 打开了一扇大门 —— XSS 不仅可以操控当前页面，甚至还能传染给同源的父页面。

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

XSS 一旦感染到父页面里，战斗力就大幅提升了。
可以想象，只要看了一个带有 XSS 的帖子，即使立即关了，那么帖子列表页也会遭到感染。
更有趣的是，opener 这个属性不受同源策略限制。即使父页面不同源，但父页面的 opener 仍然可以访问。
我们可以顺着 opener.opener.opener... 一直往上试探，只要是和当前页面同源的，仍然能够进行操控 —— 尽管中间隔着其他不同源的页面。

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

网站的主页面显然比详细页更受用户的信任，停留的时间也会更长，因此攻击力可成倍的增加。

## 0x02 正向注入

如果说反向注入是苟且偷生的话，那么正向注入就是当家做主翻身的机会了。
尽管我们能够控制父页面，但从父页面点开的网页仍然不受操控。如果具有控制子页面的能力，那就更完美了。
不幸的是，我们无法控制超链接打开的新页面。唯一能够操控的新页面，那就是 window.open 的弹框页。幸运的是，在绝大多数浏览器上，它们看起来的效果是一样的。
因此，我们可以在用户的点击瞬间，屏蔽掉默认的超链接行为，用弹框页取而代之，即可把 XSS 注入到 window.open 返回的新页面里了。

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

类似的，通过子页面递归打开的新页面，同样也无法逃脱。于是子子孙孙尽在我们的掌控之中。

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

反向注入，让我们占据已有的地盘；正向注入，把我们的势力扩大蔓延出去。两者结合，即可占据半壁江山了。
值得注意的是，正向注入中有个细节问题。并非所有的超链接都是弹出型的（_blank），也有不少是在当前页面跳转的。若是想劫持的狠点，可以忽略这个问题；如果不想被细心的用户发现，那么可以判断下当前超链接以及<base>的 target 属性，决定是否劫持。

## 0x03 页面监督

上面提到，如果是在当前页面里跳转，那么还能继续感染吗？或者说，某个页面刷新之后，是否就丢失了？
答案是肯定的。如果我们不采取一些措施，任凭占据的地盘不断丢失，那么我们的势力范围就会越来越小，直到消亡。
相比进攻，防守则更为困难。我们不知何时会失去，因此必须定时去检查。

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

一旦发现对方已摆脱我们的控制，那么必须立即重新注入，以恢复我们的势力。

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

对于新页面的 XSS 来说，当然是注入的越早越好。越前面拥有越高的优先级，甚至可以拦截页面的正常业务功能。
为了能尽快获知页面刷新、跳转等行为，我们还可跟踪 unload 事件，在页面即将丢失的瞬间，将消息通知出去，让对方尽快来拯救自己。

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

这样，就不必等待定时器了，可以最快的速度恢复。甚至能赶在页面的第一个脚本之前，运行我们的 XSS。
当然，并非任何情况都能收回的。如果跳转到了不同源的页面，那显然是无能为力了 —— 不过，就此而放弃它吗？回答是：决不妥协！
尽管页面已经和我们分道扬镳了，但所在的窗体仍然被我们掌控。我们可以跳转、关闭它，甚至还有可能出现奇迹：只要页面跳转回我们的站点，又可被我们所收复！

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

## 0x04 互相联结

不难发现，只要还有一个页面存在，就有可能收回曾经被占领的地盘。因此，我们要将可控的页面都联结起来，让每个页面都知晓并监督所有成员。
当有新成员加入时，通知给大家，记录在各自的页面里。

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

这样即使其中一个页面意外关闭了，也不会丢失重要的信息 —— 信息已被分布储存在各个页面里了。
因此，页面开的越多，相互联结就越牢固。

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

如果只剩最后一个页面，那么一旦刷新之后就没人来拯救了，于是就会消亡。
所以，把超链接都变成新页面中打开，还是有很大的优势的。

## 0x05 降域尝试

一些网站为了方便通信，将 document.domain 降到根域。例如支付宝网站的绝大部分页面，都是 alipay.com。这样原本不同源的子站，这时也能够相互操控了。
因此，遇到不同源的页面，可以尝试降低自身的域，再次发起操作，或许就能成功注入了。

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

## 0x06 表单劫持

之前说到正向注入，是通过劫持超链接点击实现的。事实上，除了超链接外，还有个进入新页面的方式，那就是表单提交。
相比超链接，表单显得棘手一些。我们不仅得打开一个新页面，还要把表单里的数据也提交上去。如果把整个表单的元素克隆到新页面提交，一些数据又会丢失。
不过，仔细研究一下表单元素，会发现有一个非常简单的方法：原来 window.open 第二个参数可以赋予新窗口一个 name，然后将 name 赋予表单的 target 属性，即可在我们创建的新窗口里提交。这样就可以把 XSS 注入进去了。

## 0x07 框架注入

不同页面之间可以正反注入。同个页面中，可能存在多个框架页，因此还可以尝试框架页之间的上下注入。
也许，XSS 位于页面中某个小框架。如果只局限于自身页面，那么是毫无发展空间的。因此得跳出圈子，向更广阔的 parent 页面注入。

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

类似的，如果主页面仅仅是个外壳，实际内容运行在某个框架里，那么得注入到子框架中，才能获取更有意义的信息。

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

同样，我们还可以将上下注入结合，即可让 XSS 从某个框架页里破壳而出，感染到所有的框架页里。

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

## 0x08 后记

尽管这些特征从 DOM 诞生起就已存在了，不过要写出一个完善的脚本并不容易。直到如今的 IE 11，不同窗体间的操作，仍有各种奇怪问题，更不用说那些非主流 IE 了。
不过好在除 IE 外的其他主流浏览器，都能很好的运行。下面分享一个 Demo，其中实现了上述部分功能：
[http://www.etherdream.com/FunnyScript/XSSGhost/](http://www.etherdream.com/FunnyScript/XSSGhost/)
当我们顺着超链接往前点，一旦进入有 XSS 的页面，先前的父页面都遭到感染。更严重的是，被感染的页面打开的子页面，也都无一幸免。即使刷新，也会被其他页面监控到，从而立即恢复。
正如幽灵鬼魂一般挥之不去。

## Replies
