阅读:2633回复:0
延长 XSS 生命期
◆0 前言
XSS 的本质仍是一段脚本。和其他文档元素一样,页面关了一切都销毁。除非能将脚本蔓延到页面以外的地方,那样才能获得更长的生命力。 庆幸的是,从 DOM 诞生的那一天起,就已为我们准备了这个特殊的功能,让脚本拥有突破当前页面的能力。 下面开始我们的续命黑魔法。 ◆1 反向注入 一个不合理的标准,往往会埋下各种隐患。 按理来说,只有通过脚本弹出的页面,才能拥有 opener 属性。然而事实上,通过超链接点开的页面居然也有。这为 XSS 打开了一扇大门 —— XSS 不仅可以操控当前页面,甚至还能传染给同源的父页面。 图片:2015030303380094938.png XSS 一旦感染到父页面里,战斗力就大幅提升了。 可以想象,只要看了一个带有 XSS 的帖子,即使立即关了,那么帖子列表页也会遭到感染。 更有趣的是,opener 这个属性不受同源策略限制。即使父页面不同源,但父页面的 opener 仍然可以访问。 我们可以顺着 opener.opener.opener... 一直往上试探,只要是和当前页面同源的,仍然能够进行操控 —— 尽管中间隔着其他不同源的页面。 网站的主页面显然比详细页更受用户的信任,停留的时间也会更长,因此攻击力可成倍的增加。 ◆2 正向注入 如果说反向注入是苟且偷生的话,那么正向注入就是当家做主翻身的机会了。 尽管我们能够控制父页面,但从父页面点开的网页仍然不受操控。如果具有控制子页面的能力,那就更完美了。 不幸的是,我们无法控制超链接打开的新页面。唯一能够操控的新页面,那就是 window.open 的弹框页。幸运的是,在绝大多数浏览器上,它们看起来的效果是一样的。 因此,我们可以在用户的点击瞬间,屏蔽掉默认的超链接行为,用弹框页取而代之,即可把 XSS 注入到 window.open 返回的新页面里了。 类似的,通过子页面递归打开的新页面,同样也无法逃脱。于是子子孙孙尽在我们的掌控之中。 图片:2015030303380094938.png 反向注入,让我们占据已有的地盘;正向注入,把我们的势力扩大蔓延出去。两者结合,即可占据半壁江山了。 值得注意的是,正向注入中有个细节问题。并非所有的超链接都是弹出型的(_blank),也有不少是在当前页面跳转的。若是想劫持的狠点,可以忽略这个问题;如果不想被细心的用户发现,那么可以判断下当前超链接以及的 target 属性,决定是否劫持。◆3 页面监督 上面提到,如果是在当前页面里跳转,那么还能继续感染吗?或者说,某个页面刷新之后,是否就丢失了? 答案是肯定的。如果我们不采取一些措施,任凭占据的地盘不断丢失,那么我们的势力范围就会越来越小,直到消亡。 相比进攻,防守则更为困难。我们不知何时会失去,因此必须定时去检查。 图片:2015030303380094938.png 一旦发现对方已摆脱我们的控制,那么必须立即重新注入,以恢复我们的势力。 对于新页面的 XSS 来说,当然是注入的越早越好。越前面拥有越高的优先级,甚至可以拦截页面的正常业务功能。 为了能尽快获知页面刷新、跳转等行为,我们还可跟踪 unload 事件,在页面即将丢失的瞬间,将消息通知出去,让对方尽快来拯救自己。 这样,就不必等待定时器了,可以最快的速度恢复。甚至能赶在页面的第一个脚本之前,运行我们的 XSS。 当然,并非任何情况都能收回的。如果跳转到了不同源的页面,那显然是无能为力了 —— 不过,就此而放弃它吗?回答是:决不妥协! 尽管页面已经和我们分道扬镳了,但所在的窗体仍然被我们掌控。我们可以跳转、关闭它,甚至还有可能出现奇迹:只要页面跳转回我们的站点,又可被我们所收复! ◆4 互相联结 不难发现,只要还有一个页面存在,就有可能收回曾经被占领的地盘。因此,我们要将可控的页面都联结起来,让每个页面都知晓并监督所有成员。 当有新成员加入时,通知给大家,记录在各自的页面里。 图片:2015030303380094938.png 这样即使其中一个页面意外关闭了,也不会丢失重要的信息 —— 信息已被分布储存在各个页面里了。 因此,页面开的越多,相互联结就越牢固。 如果只剩最后一个页面,那么一旦刷新之后就没人来拯救了,于是就会消亡。 所以,把超链接都变成新页面中打开,还是有很大的优势的。 ◆5 降域尝试 一些网站为了方便通信,将 document.domain 降到根域。例如支付宝网站的绝大部分页面,都是 alipay.com。这样原本不同源的子站,这时也能够相互操控了。 因此,遇到不同源的页面,可以尝试降低自身的域,再次发起操作,或许就能成功注入了。 ◆6 表单劫持 之前说到正向注入,是通过劫持超链接点击实现的。事实上,除了超链接外,还有个进入新页面的方式,那就是表单提交。 相比超链接,表单显得棘手一些。我们不仅得打开一个新页面,还要把表单里的数据也提交上去。如果把整个表单的元素克隆到新页面提交,一些数据又会丢失。 不过,仔细研究一下表单元素,会发现有一个非常简单的方法:原来 window.open 第二个参数可以赋予新窗口一个 name,然后将 name 赋予表单的 target 属性,即可在我们创建的新窗口里提交。这样就可以把 XSS 注入进去了。 ◆7 框架注入 不同页面之间可以正反注入。同个页面中,可能存在多个框架页,因此还可以尝试框架页之间的上下注入。 也许,XSS 位于页面中某个小框架。如果只局限于自身页面,那么是毫无发展空间的。因此得跳出圈子,向更广阔的 parent 页面注入。 类似的,如果主页面仅仅是个外壳,实际内容运行在某个框架里,那么得注入到子框架中,才能获取更有意义的信息。 图片:2015030303380094938.png 同样,我们还可以将上下注入结合,即可让 XSS 从某个框架页里破壳而出,感染到所有的框架页里。 ◆8 后记 尽管这些特征从 DOM 诞生起就已存在了,不过要写出一个完善的脚本并不容易。直到如今的 IE 11,不同窗体间的操作,仍有各种奇怪问题,更不用说那些非主流 IE 了。 不过好在除 IE 外的其他主流浏览器,都能很好的运行。下面分享一个 Demo,其中实现了上述部分功能: http://www.etherdream.com/FunnyScript/XSSGhost/ 当我们顺着超链接往前点,一旦进入有 XSS 的页面,先前的父页面都遭到感染。更严重的是,被感染的页面打开的子页面,也都无一幸免。即使刷新,也会被其他页面监控到,从而立即恢复。 正如幽灵鬼魂一般挥之不去。 |
|