代码缩进爆炸攻击
前言
曾经有次被 💩 一样的代码 🤮 到了 —— 几百行代码写在一个函数里,逻辑混乱不堪,更要命的是,代码里居然没有 continue 之类的语句,所有判断都新增一层缩进,以至于这坨代码看上去是这样的:
for (...) {
if (...) {
if (...) {
if (...) {
if (...) {
if (...) {
if (...) {
if (...) {
if (...) {
...
由于我的笔记本屏幕小,光这缩进就占据了大半空间,以至于很多代码都无法完整显示,不得不关闭 IDE 的侧边栏腾出界面。尽管如此,最深处的代码还得左右来回滚动才能勉强浏览。
事后好一阵子才缓过神来。然而对于脑洞大开的 Geeker 来说,即便是 💩 也能激发想象的灵感:既然这种风格这么头疼,那么能不能反过来用于攻防场合,折磨破解的人呢。
极限缩进
由于压缩后的脚本是不带换行、缩进等字符的,而调试时经过格式化会补上这些。因此用少量的字符即可构造超深的层次,从而在调试时产生大量空白字符。
例如,在代码前后加上一堆语块:
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
console.log('Hello World')
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
调试时,我们对代码进行格式化:
格式化后的代码整整有 263,195 个字符,而原代码仅 540 个字符,膨胀了近 500 倍。
由于嵌套层次过深,代码着色功能已无法正常运行,只剩黑色。
将 {
换成 if
嵌套,可见代码从 252 层开始变成黑白:
如果将核心代码放在这里,调试者看到的是一堆没有颜色的字符,并且断点执行时经常需要水平滚动,多少能带来一些困扰。
当然你可能会说,把文件保存到本地,然后删除无用的代码不就可以了。确实可以,不过这就进入了攻防对抗的环节。程序在本地运行有很多特征,例如文件路径,甚至可以获取破解者的隐私信息;即使运行时动态替换脚本,也有很多方案能感知到,例如文件 Hash、函数 Hash 等等。最终陷入混淆和逆向的泥潭。
无缩进
对各种语法进行测试,可发现对象嵌套的效果更好:
var a = {a: {a: {a: {a: ... }}}}
当套娃达到几百层时,整个脚本都无法格式化了:
相比之前没有颜色只能干扰调试,代码不能格式化则几乎无法调试。毕竟一个脚本少则几百行,多则几千几万行,被压缩成一行而无法展开,是完全不可接受的。
演示:https://www.etherdream.com/anti-js-format/indent-bomb.html
不过这个方案只适用于部分浏览器,并不通用,例如 FireFox 仍然可以格式化。(当然 FireFox 也有其他的反格式化方案,这里就不展开讨论了)
需要注意的是,该方案实际应用存在一定风险 —— 有些小众浏览器调低了 JS 引擎的语法层数限制,导致整个脚本无法运行。
其他语言
事实上绝大多数的语言都存在类似问题,甚至包括数据,例如 HTML、XML、JSON 等 —— 只要是树结构,并且支持压缩和美化。
以 JSON 为例,[0]
有 3 个字符,[[0]]
有 5 个字符。压缩状态下,只需前后添加 []
两个字符即可增加一层。
但格式化后长度是非常可观的,我们数一下:
1 层
[
0
]
缩进字符:1
2 层
[
③ [
③ ① 0
③ ]
]
缩进字符:1 + 3 = 4
3 层
[
⑤ [
⑤ ③ [
⑤ ③ ① 0
⑤ ③ ]
⑤ ]
]
缩进字符:1 + 3 + 5 = 9
n 层
规律很明显,n
层有 n²
个缩进字符。可见缩进是呈指数增加的。
对于一个 50,000 层的 JSON,原数据只有 100KB,而格式化后可增加 2,500,000,000 个缩进字符,即 2.5GB!如果缩进使用 4 个空格,甚至可达 10GB,膨胀十万倍!
这其中还不包含新增的换行符。由于换行符的数量只有层数 * 2,相比缩进符可忽略不计。
层数限制
由此可见,如果没有层数限制,再多资源也会被轻易耗尽。
例如有些 JSON 在线美化的网站,由于是在后端处理的,并且整个 JSON 在内存中完整生成才返回,而不是边生成边返回的流模式,这种服务是极易受到攻击的,只需少量数据即可打垮。
其他类型的数据或语言,同样需注意层数限制,否则存在缩进爆炸的风险。