如何保护自己免受gzip或bzip 2炸弹的攻击?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (102)

这个问题有关炸弹压缩包,可想到的有gzip或bzip 2压缩,例如web服务接受.tar.gz档案。

在使用tarfile模块的python代码中,最优雅的检测压缩炸弹的方法是什么,最好不要重复太大的逻辑(例如,对tarfile模块的透明解压缩支持)?

而且,可以使之简单一点:不涉及真正的文件;输入是一个类似文件的对象(由web框架提供,表示用户上传的文件)。

提问于
用户回答回答于

这将确定gzip流的未压缩大小,同时使用有限的内存:

#!/usr/bin/python
import sys
import zlib
f = open(sys.argv[1], "rb")
z = zlib.decompressobj(15+16)
total = 0
while True:
    buf = z.unconsumed_tail
    if buf == "":
        buf = f.read(1024)
        if buf == "":
            break
    got = z.decompress(buf, 4096)
    if got == "":
        break
    total += len(got)
print total
if z.unused_data != "" or f.read(1024) != "":
    print "warning: more input after end of gzip stream"

它将在解压缩时返回tar文件中所有文件所需的空间的轻微高估。长度包括这些文件以及tar目录信息。

除了输入数据的大小之外,gzip.py代码并不控制解压缩的数据量。在gzip.py中,它一次读取1024个压缩字节。因此,可以使用gzip.py(1032),如果对未压缩数据的内存占用量可以达到1056768字节(1032)*1024,其中1032:1是最大的压缩比。这里的解决方案使用zlib.decompress使用第二个参数,它限制未压缩数据的数量。gzip.py没有。

这将通过解码tar格式准确地确定提取的tar条目的总大小:

#!/usr/bin/python

import sys
import zlib

def decompn(f, z, n):
    """Return n uncompressed bytes, or fewer if at the end of the compressed
       stream.  This only decompresses as much as necessary, in order to
       avoid excessive memory usage for highly compressed input.
    """
    blk = ""
    while len(blk) < n:
        buf = z.unconsumed_tail
        if buf == "":
            buf = f.read(1024)
        got = z.decompress(buf, n - len(blk))
        blk += got
        if got == "":
            break
    return blk

f = open(sys.argv[1], "rb")
z = zlib.decompressobj(15+16)
total = 0
left = 0
while True:
    blk = decompn(f, z, 512)
    if len(blk) < 512:
        break
    if left == 0:
        if blk == "\0"*512:
            continue
        if blk[156] in ["1", "2", "3", "4", "5", "6"]:
            continue
        if blk[124] == 0x80:
            size = 0
            for i in range(125, 136):
                size <<= 8
                size += blk[i]
        else:
            size = int(blk[124:136].split()[0].split("\0")[0], 8)
        if blk[156] not in ["x", "g", "X", "L", "K"]:
                total += size
        left = (size + 511) // 512
    else:
        left -= 1
print total
if blk != "":
    print "warning: partial final block"
if left != 0:
    print "warning: tar file ended in the middle of an entry"
if z.unused_data != "" or f.read(1024) != "":
    print "warning: more input after end of gzip stream"

你可以用它的一个变体扫描TAR文件来寻找炸弹。这有一个优点,就是在你不得不解压缩数据之前,在头信息中找到一个很大的大小。

至于.tar.bz 2存档,Pythonbz 2库(至少在3.3)对于占用太多内存的bz2bz2炸弹来说是不可避免的不安全的。大bz2.decompress函数不提供第二个参数,例如zlib.decompress是的。更糟糕的是,bz2格式的最大压缩比比zlib高得多,这是由于运行长度编码造成的。bzip 2压缩1GB的零到722字节。所以你不能测量bz2.decompress通过测量输入量,就像可以用zlib.decompress即使没有第二个论点。对解压缩输出大小缺乏限制是Python接口中的一个基本缺陷。

我看了看_3.3中的bz2module.c,看看是否有一种无文档的方法来使用它来避免这个问题。这是无法避免的。大decompress函数只会不断地增长结果缓冲区,直到它能够解压缩所有提供的输入。_bz2module.c需要修复。

用户回答回答于

若要限制进程及其子进程可用的资源,请执行以下操作。

如果需要在内存中解压缩,则可以设置resource.RLIMIT_AS(或RLIMIT_DATARLIMIT_STACK)例如,使用上下文管理器自动将其还原为以前的值:

import contextlib
import resource

@contextlib.contextmanager
def limit(limit, type=resource.RLIMIT_AS):
    soft_limit, hard_limit = resource.getrlimit(type)
    resource.setrlimit(type, (limit, hard_limit)) # set soft limit
    try:
        yield
    finally:
        resource.setrlimit(type, (soft_limit, hard_limit)) # restore

with limit(1 << 30): # 1GB 
    # do the thing that might try to consume all memory

如果达到限制的;MemoryError是可以上调的。

扫码关注云+社区