放一篇存货,文中所有绕过方法已经确认在雷池引擎修复。
前几日我在写谛听溯源 JS 的利用时,发现有个目标网站用了云盾,导致 XSS 没法正常工作。然而就是那样一个简单的绕过我费了挺长时间,痛定思痛之余,我决定研究一下 XSS 的绕过问题,以后再遇到这类问题也能快速解决。研究目标呢,当然就选了我心中最强的 XSSChop。不过 XSS 绕过方法千千万,我这里选了一个针对语义分析的产品比较有效的方向——从 JS 语法角度绕过 XSSChop。所谓从 JS 语法角度绕过,就是说寻找一些偏僻的或是新的 JS 特性,而这些特性尚未被 XSSChop 支持或是支持的不够完善时,就会有漏报。下面是这次测试时用到的的一些环境信息。
- 测试时间: 2020.3.8
- 浏览器版本: MacOS Chrome 79
- XSSChop版本: 2020-03-06. version: 0c25be48.
- 测试目标:urlpath 中执行
javascript:eval(name)
为 normal 或 post body 中<script>eval(name)</script>
为 normal
这里测试目标用的是 eval(name)
是有现实意义的,name 是 window 和 iframe 的一个属性,在 xss 利用时,常常可以搭配 name 使用 eval(name)
完成任意代码执行,eval
本身是一个高危函数,这个能绕过,alert
之类的就不足为提了。
编码的梦魇
JS 中常见的编码有这样几种:
|
|
其中第一种 unicode 的方式是比较特殊的,因为这种方式可以在非字符串中直接使用,例如可以直接作为函数名调用:
|
|
上面这些方式雷池都支持的很完善,不过在 ES6 中定义了一种新的 unicode 编码方式:
|
|
这种编码可以在两个周前完全绕过 XSSChop 的检查,跟吴雷师傅说了下,据说有支持这种方式但打分低了导致 Normal。今天我又看了一下,发现 body 中的确实变为 Low 了
但是 urlpath 中的方式依然是 normal,可能需要再细化一下算法,而且修复的时候要注意下 javascript://
伪协议 中可以搭配其他编码诸如 html 实体编码、url 编码等。
“可变”的常量
为了更方便的阅读大的数字,很多语言在实现时都支持名为 Numeric Separators
的特性,简单来讲就是支持在数字常量中插入一些 _
来手动分割数字,比较常用的是千位分隔符:
|
|
这样就可以一眼知道这是一百万。2020 年 5 月过后,很多浏览器都支持了这一特性,具体可以看 https://caniuse.com/#feat=mdn-javascript_grammar_numeric_separators ,如果解析器不支持这一方式变会产生绕过:
稍加调整便可以执行 eval(name)
命令:
值得注意的是,下面这几种方式都可以表示数字 123
|
|
千面狐 Eval
在 JS 中将字符串视为代码执行的常见方法有如下几种:
|
|
其中最后一种比较有意思,实际上不仅 Map,大部分的内置 Class 都支持 string 参数的 construstor 方法,搭配 ES6 中的 tagged template 语法,可以写出一些比较魔幻的写法:
|
|
然而,只要出现了 constructor 雷池都是 high,我猜分析的时候只要发现 MemberExpression 的右值为 constructor
,就会是高危。但 js 中获取属性还有其他方式,比如:
刚才说的是常见的几种,还有一种不太常见但很好用的方式: Array Iterator。即借助迭代器的callback来执行,大致有下面的几种写法:
|
|
把上面这一堆复制到 chrome console 中执行,你将收获 11 个弹窗(:
不过经过我人肉测试发现,这些 case 雷池都基本考虑到了,但如果 Array 中用的是 name,结果就是 normal 了,猜测也是打分问题。
进击的 ES(X)
ES 每年都有新的标准诞生,到现在已经有 ES10 了,对于语义分析的 XSSChop 来说,这是一个巨大的挑战,因为新标准中往往有一些和之前不一样的语法,如果解析引擎没有与时俱进的支持这些语法,就会直接在产生 AST 的过程中报错而结束整个分析流程从而被绕过。这里我从 ES8, ES9, ES10 中分别找了一个可以绕过的特性:
Async/Await (ES8)
写过 Python asyncio 的同学应该对这个很熟悉,JS 的协程和 Python 的非常像,但细节上稍有差异,比如 Python 中这个写法会报错:
|
|
因为 await 后不是一个 “awaitable” 的对象,相比之下 JS 的写法要宽松很多,下面的写法在 JS 中是完全正常的:
|
|
雷池因为不认识该关键字可以直接绕过:
对象 REST 解构 (ES9)
ES6 中引入了 REST 参数和拓展运算符,也称为变量解构赋值,可以用下面的例子简单回顾下:
|
|
但 ES6 中的拓展运算符(…) 仅能用于数组,ES9 将这一用法进行了拓展,使得 Object 也可以用该运算符了,用法大致是这样:
|
|
类似的,也可以利用该语法使雷池产生错误而绕过:
省略的 catch 绑定 (ES10)
在 ES10 之前,使用 catch 时必须为 catch 子句绑定异常变量,无论是否用到该变量,ES10 允许省略该变量,即:
|
|
雷池的检测结果:
staging.. (Chrome 80 已经支持)
|
|
一点思考
不得不说,XSSChop 整体还是很优秀的,在低误报的同时能覆盖大量的 case,在处理变形和混淆上也有着不错的表现,和其他家只要有 alert 就高危相比要好很多。但我感觉有两方面还需要调整下:
- 打分机制还需要不断的完善,人肉的测试去寻找漏报是一条比较有效的途径。
- xsschop 需要不断的跟进新的 ES 标准,实际上每次新增的特性并不多,xsschop 需要重点关注下新增的语法和新增的函数,别让词法分析成为语义分析的拦路虎。
类比考虑一下,xsschop 有实现标准的问题,那么 sqlchop 会不会也有类似的问题呢?这个有时间再研究吧。