代码缩进爆炸攻击

前言

曾经有次被 💩 一样的代码 🤮 到了 —— 几百行代码写在一个函数里,逻辑混乱不堪,更要命的是,代码里居然没有 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 层有 个缩进字符。可见缩进是呈指数增加的。

对于一个 50,000 层的 JSON,原数据只有 100KB,而格式化后可增加 2,500,000,000 个缩进字符,即 2.5GB!如果缩进使用 4 个空格,甚至可达 10GB,膨胀十万倍!

这其中还不包含新增的换行符。由于换行符的数量只有层数 * 2,相比缩进符可忽略不计。

层数限制

由此可见,如果没有层数限制,再多资源也会被轻易耗尽。

例如有些 JSON 在线美化的网站,由于是在后端处理的,并且整个 JSON 在内存中完整生成才返回,而不是边生成边返回的流模式,这种服务是极易受到攻击的,只需少量数据即可打垮。

其他类型的数据或语言,同样需注意层数限制,否则存在缩进爆炸的风险。

posted @ 2021-08-20 18:53  EtherDream  阅读(2148)  评论(2编辑  收藏  举报