如何通过boost::iostream防范Zip炸弹?代码安全问题咨询
Boost::iostreams Gzip解压与Zip炸弹防护问题
1. 遇到Zip炸弹会导致内存耗尽/进程崩溃吗?
绝对会!你这段代码完全没做解压后的数据量限制——要是输入是个Zip炸弹(比如几KB的压缩包能解压出几十GB甚至更大的数据),back_inserter会持续往decompressed向量里写入数据,直到系统内存被彻底耗尽。最终要么进程被系统的OOM(内存不足)杀手强制终止,要么因为内存分配失败直接崩溃。毕竟vector会无限制自动扩容,完全没有上限约束,这正好给了Zip炸弹可乘之机。
2. 怎么防范这种风险?
这里有几个实用的防护方案,按优先级推荐:
- 设置解压大小上限(最可靠):自定义一个输出流过滤器,在写入数据时实时累计字节数,一旦超过预设的安全上限就抛出异常,中断解压流程。比如写个简单的限流过滤器:
然后把这个过滤器加到你的解压链最前面:class limit_output_filter : public boost::iostreams::output_filter { public: explicit limit_output_filter(std::size_t max_size) : max_size_(max_size), current_size_(0) {} template<typename Sink> bool put(Sink& snk, char c) { if (current_size_ >= max_size_) { throw std::runtime_error("Decompressed data exceeds allowed size limit"); } ++current_size_; return boost::iostreams::put(snk, c); } private: std::size_t max_size_; std::size_t current_size_; };std::vector<char> unzip(std::vector<char> const& compressed, std::size_t max_decompressed_size) { std::vector<char> decompressed; boost::iostreams::filtering_ostream os; // 先加限流过滤器,把住数据量的第一道关 os.push(limit_output_filter(max_decompressed_size)); os.push(boost::iostreams::gzip_decompressor()); os.push(boost::iostreams::back_inserter(decompressed)); try { boost::iostreams::write(os, &compressed[0], compressed.size()); os.reset(); } catch (const std::runtime_error& e) { // 处理超限情况,比如清空向量后抛出异常,或者返回空向量 decompressed.clear(); throw; } return decompressed; } - 结合压缩比校验:在解压过程中实时计算压缩比(解压后大小/压缩前大小),如果超过一个合理的阈值(比如100:1,具体根据你的业务场景调整)就中断解压。不过要注意,有些合法数据(比如全是0的文件)也可能有高压缩比,所以这个方法最好和大小上限配合使用,不要单独依赖。
- 改用流式处理:如果业务允许,不要把整个解压后的数据都加载到内存的vector里,而是边解压边处理(比如分块写入文件、或者逐块处理业务逻辑),这样能避免一次性占用大量内存,从根源上降低OOM风险。
3. 能不能在解压前拿到原始数据的大小?
很遗憾,对于gzip格式来说,没有可靠的方法在完全解压前获取准确的原始数据大小:
- gzip头部并不存储未压缩数据的总大小,只有在压缩包的尾部有4字节的未压缩长度字段,但这个字段仅当未压缩数据小于4GB时有效;如果数据超过4GB,这个值是截断后的,完全不准确。
- 更关键的是,这个尾部的长度字段可以被恶意篡改(比如Zip炸弹会伪造一个很小的数值,实际解压后却超大),所以就算能解析出来,也不能信任这个值来做内存分配或者限流。
所以,别指望提前获取大小来规避风险,最靠谱的还是在解压过程中动态限制数据量。
内容的提问来源于stack exchange,提问作者alpha
相关产品推荐
相关产品推荐

