本文目录导读:

- 📖 目录导读
- 什么是PHP Zip炸弹?
- Zip炸弹的工作原理
- PHP环境下Zip炸弹的威胁分析
- 如何用PHP生成Zip炸弹?
- 实际攻击场景模拟
- PHP防御Zip炸弹的终极方案
- 常见问题问答(FAQ)
- 总结与最佳实践
PHP Zip炸弹:原理、防御与代码实现全解析
📖 目录导读
- 什么是PHP Zip炸弹?
- Zip炸弹的工作原理
- PHP环境下Zip炸弹的威胁分析
- 如何用PHP生成Zip炸弹?
- 实际攻击场景模拟
- PHP防御Zip炸弹的终极方案
- 常见问题问答(FAQ)
- 总结与最佳实践
什么是PHP Zip炸弹?
Zip炸弹(又称解压炸弹)是一种恶意压缩文件,它利用压缩算法的高效性,将大量重复数据压缩成极小的文件,当服务器试图解压时,会瞬间占用大量磁盘空间、内存和CPU资源,导致服务崩溃或拒绝服务(DoS)。
在PHP中,Zip炸弹常通过ZipArchive类、gzinflate或用户上传的ZIP文件发起攻击,典型的例子如著名的zip(压缩后仅42KB,解压后达4.5PB)。
核心问题:PHP环境下的Zip炸弹攻击更隐蔽,因为很多开发者直接使用
extract()或file_get_contents处理上传文件,未做任何限制。
Zip炸弹的工作原理
1 压缩比的关键
Zip炸弹利用极高压缩比实现小体积大破坏。
- 一个包含10GB重复字符“0”的文本文件,使用Zip压缩后可能仅有10MB左右。
- 再嵌套多层压缩(如ZIP内套ZIP),最终体积可缩减至几KB。
2 递归解压陷阱
现代Zip炸弹常采用递归结构:
- 压缩包内包含多个子压缩包,每个子包解压后再触发解压。
- 使用
ZipArchive::extractTo()时,若未限制层级,内存和磁盘会迅速耗尽。
3 PHP的漏洞点
常见的PHP代码漏洞示例:
$zip = new ZipArchive();
if ($zip->open($_FILES['file']['tmp_name']) === TRUE) {
$zip->extractTo('/var/www/uploads/'); // 危险!无限制
$zip->close();
}
这段代码直接解压用户上传的ZIP文件,攻击者只需上传一个恶意构造的ZIP即可瘫痪服务器。
PHP环境下Zip炸弹的威胁分析
1 资源消耗类型
| 资源类型 | 影响描述 |
|---|---|
| 磁盘空间 | 解压后文件体积数百GB甚至TB级,填满分区 |
| 内存 | 递归解压时内存爆炸,PHP进程被OOM Killer终止 |
| CPU | 大量解压运算导致CPU 100%,响应延迟 |
| 硬盘I/O | 连续写入操作导致磁盘IOWAIT飙升 |
2 真实攻击场景
攻击者通过文件上传功能(如头像上传、文档导入)提交恶意ZIP,一旦服务器自动解压:
- 服务器磁盘立即写满,数据库无法写入新记录。
- PHP-FPM进程卡死,网站完全不可访问。
- 日志文件被撑爆,排查困难。
如何用PHP生成Zip炸弹?
注意:以下代码仅供安全研究测试,请勿用于非法用途。
1 基础Zip炸弹生成
// 生成一个包含大量重复数据的压缩包
$zip = new ZipArchive();
$filename = 'bomb.zip';
if ($zip->open($filename, ZipArchive::CREATE) === TRUE) {
// 生成100MB的重复字符串,压缩后约几MB
$content = str_repeat("A", 1024 * 1024 * 100); // 100MB
$zip->addFromString('huge.txt', $content);
$zip->close();
echo "Zip炸弹生成成功: $filename";
}
2 递归嵌套Zip炸弹(高级)
// 递归生成3层嵌套压缩包
function createNestedZip($depth, $sizeMB) {
if ($depth == 0) {
return str_repeat("0", 1024 * 1024 * $sizeMB);
}
$zip = new ZipArchive();
$filename = "layer_$depth.zip";
$zip->open($filename, ZipArchive::CREATE);
$innerContent = createNestedZip($depth - 1, $sizeMB);
$zip->addFromString("inner.zip", $innerContent);
$zip->close();
return file_get_contents($filename);
}
// 生成3层嵌套,每层100MB -> 解压后约800GB+
$finalZip = createNestedZip(3, 100);
file_put_contents('recursive_bomb.zip', $finalZip);
实际攻击场景模拟
假设某网站允许用户上传ZIP简历文件,并自动解压到/uploads/resumes/:
// 漏洞代码
$uploadedFile = $_FILES['resume']['tmp_name'];
$zip = new ZipArchive();
if ($zip->open($uploadedFile) === true) {
$zip->extractTo('/var/www/uploads/resumes/');
$zip->close();
echo "简历上传成功!";
}
攻击者上传一个压缩后仅1KB、解压后达10TB的Zip炸弹,结果:
/var/www/分区迅速被写满,网站所有静态资源无法访问。- PHP进程因内存不足被杀,服务器响应502。
- 恢复需要手动删除海量文件,耗时数小时。
PHP防御Zip炸弹的终极方案
1 基础防御:限制解压大小
function safeExtractZip($zipFile, $destDir, $maxSize = 104857600) { // 100MB
$zip = new ZipArchive();
if ($zip->open($zipFile) !== TRUE) return false;
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
$fileSize = $stat['size'];
if ($fileSize > $maxSize) {
$zip->close();
throw new Exception("文件 {$stat['name']} 超过大小限制");
}
}
// 统计总大小
$totalSize = 0;
for ($i = 0; $i < $zip->numFiles; $i++) {
$totalSize += $zip->statIndex($i)['size'];
if ($totalSize > $maxSize * 5) { // 总大小限制500MB
$zip->close();
throw new Exception("压缩包总大小超过限制");
}
}
$zip->extractTo($destDir);
$zip->close();
return true;
}
2 高级防御:实时监控解压过程
class ZipBombDetector {
private $maxDiskUsage = 524288000; // 500MB
private $maxFileCount = 10000;
private $fileCount = 0;
private $diskUsage = 0;
public function extractWithGuard($zipFile, $destDir) {
$zip = new ZipArchive();
$zip->open($zipFile);
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
$this->fileCount++;
if ($this->fileCount > $this->maxFileCount) {
throw new Exception("文件数量过多,疑似zip炸弹");
}
$this->diskUsage += $stat['size'];
if ($this->diskUsage > $this->maxDiskUsage) {
throw new Exception("累计解压大小超限");
}
// 递归检测嵌套压缩包
$ext = pathinfo($stat['name'], PATHINFO_EXTENSION);
if (in_array($ext, ['zip', 'gz', 'rar'])) {
$this->extractWithGuard(
"zip://$zipFile#" . $stat['name'],
$destDir . '/' . dirname($stat['name'])
);
}
}
$zip->extractTo($destDir);
$zip->close();
}
}
3 终极武器:沙盒解压
使用操作系统级的限制:
# Linux下使用ulimit限制 ulimit -f 102400 # 限制写入文件大小100MB unzip -o bomb.zip -d /safe/dir/
在PHP中搭配exec()执行:
exec("ulimit -f 102400 && unzip -o " . escapeshellarg($zipFile) . " -d " . escapeshellarg($destDir), $output, $retCode);
常见问题问答(FAQ)
Q1: Zip炸弹对PHP 7/8是否有影响?
答:有影响,Zip炸弹攻击的是资源层面,与PHP版本无关,无论PHP 5.x还是8.x,只要代码执行了解压操作,服务器就会中招。
Q2: 如何检测上传文件是否为Zip炸弹?
答:无法100%检测,但可以通过以下手段降低风险:
- 限制上传文件总大小(如10MB)。
- 检查ZIP内文件数量(超过1000个可疑)。
- 检查ZIP内单个文件大小(超过100MB拒绝)。
- 使用
ZipArchive::statIndex()预扫描所有文件。
Q3: 是否可以用纯PHP正则匹配判断?
答:不行,Zip炸弹的恶意行为在解压时才会显现,不依赖文件名或文件内容,攻击者可以轻松绕过基于文件名的黑名单。
Q4: 如果服务器已经被Zip炸弹攻击,如何快速恢复?
答:
- 立即停止PHP-FPM/Nginx服务。
- 使用
find /var/www -type f -size +100M -delete删除大文件。 - 检查磁盘空间:
df -h,必要时扩容。 - 审查上传目录,删除可疑临时文件。
- 修复代码,加入防御逻辑后重启服务。
Q5: 有哪些PHP库可以安全处理Zip?
答:
PhpZip:支持流式解压,可限制解压大小。Flysystem:配合League\Flysystem\ZipArchive\ZipArchiveAdapter使用。- 原生
ZipArchive配合本文的防御函数。
总结与最佳实践
1 必须遵守的规则
- 永远不要直接解压用户上传的ZIP文件。
- 先扫描后解压:使用
statIndex()检查所有文件大小和数量。 - 限制总解压大小:建议不超过500MB(根据服务器配置调整)。
- 限制文件数量:单个ZIP内文件数不超过5000个。
- 使用临时目录隔离:在独立的临时目录解压,完成后移动必要文件。
- 设置运行时限:使用
set_time_limit(30)防止解压耗时过长。
2 终极代码模板
function uploadSafeZip($uploadedFile, $destDir) {
$maxTotalSize = 104857600; // 100MB
$maxFileCount = 5000;
$zip = new ZipArchive();
if ($zip->open($uploadedFile) !== TRUE) return false;
$totalSize = 0;
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
if ($i > $maxFileCount) {
$zip->close();
throw new Exception("文件过多");
}
$totalSize += $stat['size'];
if ($totalSize > $maxTotalSize) {
$zip->close();
throw new Exception("总大小超限");
}
// 禁止压缩包嵌套
$ext = strtolower(pathinfo($stat['name'], PATHINFO_EXTENSION));
if (in_array($ext, ['zip', 'rar', '7z', 'gz'])) {
$zip->close();
throw new Exception("禁止嵌套压缩包");
}
}
$zip->extractTo($destDir);
$zip->close();
return true;
}
3 长期策略
- 使用第三方安全扫描服务(如ClamAV)检查上传文件。
- 在服务器层面部署磁盘配额(Linux的quota工具)。
- 日志监控:解压操作写入日志,异常时自动封禁IP。
记住:防御Zip炸弹的核心不是检测恶意内容,而是限制资源使用,只要在解压前预判并限制大小和数量,就能轻松抵御大多数Zip炸弹攻击,建议将此安全实践融入日常CI/CD流程,并在代码审查中重点检查文件处理逻辑。