本文目录导读:

我来系统地介绍PHP项目代码审计的方法和流程。
审计前的准备工作
环境搭建
# 搭建本地测试环境 - PHP版本:与目标环境一致 - 数据库:MySQL/MariaDB - Web服务器:Nginx/Apache - Xdebug:用于调试和分析 - 动态调试工具:RIPS、phpstan等
工具准备
# 静态分析工具 - RIPS(商业) - PHPStan(免费) - Psalm(免费) - SonarQube(社区版) # 动态调试工具 - Xdebug - Burp Suite - OWASP ZAP - sqlmap - commix # 辅助工具 - Seay源代码审计系统 - Fortify SCA - Checkmarx
审计流程
信息收集阶段
// 1. 入口文件审计
// 检查 index.php、admin.php 等入口
<?php
// 常见的入口文件问题
session_start();
include 'config.php';
include 'functions.php';
// 查看是否有全局过滤
if (!isset($_SESSION['user'])) {
// 缺少安全认证
}
?>
核心配置文件审计
// config.php 审计重点
<?php
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '123456'); // 弱密码问题
define('DB_NAME', 'test');
// 调试模式是否开启
error_reporting(E_ALL);
ini_set('display_errors', 1); // 生产环境不应开启
// 全局配置检查
session_start();
header('Content-Type: text/html; charset=utf-8');
?>
数据库操作审计
// 识别SQL注入漏洞
// 危险写法1:直接拼接(SQL注入)
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = mysql_query($sql);
// 危险写法2:缺少参数化(SQL注入)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = '{$_POST['username']}'");
$stmt->execute();
// 安全写法
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$_POST['username']]);
文件操作审计
// 文件包含漏洞
// 动态包含(文件包含漏洞)
$page = $_GET['page'];
include($page . '.php'); // 可被路径穿越
// 危险函数
include();
include_once();
require();
require_once();
fopen();
file_get_contents();
// 文件上传
if ($_FILES['file']['error'] == 0) {
$path = "uploads/" . $_FILES['file']['name']; // 直接使用用户文件名
move_uploaded_file($_FILES['file']['tmp_name'], $path); // 未验证文件类型
}
XSS漏洞审计
// XSS漏洞场景
// 直接输出未过滤参数
echo $_GET['name']; // 反射型XSS
echo $user_input; // 存储型XSS
// 危险函数
echo();
print();
print_r();
var_dump();
// 不安全的输出
echo "<script>var name = '{$_GET['name']}';</script>";
echo "<div>" . $_GET['content'] . "</div>";
认证与授权审计
// 认证绕过
if ($_SESSION['user_id'] == $_GET['uid']) {
// 逻辑缺陷:用户ID直接来自前端
}
// 未授权访问
if (isset($_SESSION['logged_in'])) {
// 缺少角色验证
}
// CSRF漏洞
<form action="change_password.php" method="POST">
<input type="password" name="new_password">
<!-- 缺少CSRF token验证 -->
</form>
反序列化漏洞
// 不安全反序列化
$data = unserialize($_COOKIE['user_data']); // 用户可以控制输入
class User {
public $username;
public $role;
function __wakeup() {
// 魔法函数可能被利用
}
}
// 危险函数
serialize();
unserialize();
__wakeup();
__destruct();
自动化审计示例
PHPStan静态分析
# 安装PHPStan
composer require --dev phpstan/phpstan
# 运行分析
vendor/bin/phpstan analyse src --level-max
# PHPStan配置示例 (phpstan.neon)
parameters:
level: 5
paths:
- src
excludePaths:
- src/vendor/*
自定义审计脚本
<?php
// automated_audit.php - 简单审计脚本
class CodeAuditor {
private $dangerous_functions = [
'eval', 'exec', 'system', 'passthru', 'shell_exec',
'popen', 'proc_open', 'pcntl_exec', 'assert', 'create_function',
'preg_replace', 'ob_start', 'include', 'require',
'file_get_contents', 'fopen', 'readfile', 'unlink', 'rmdir'
];
private $sql_keywords = [
'select', 'insert', 'update', 'delete', 'where', 'union',
'order by', 'group by', 'having'
];
public function auditFile($filePath) {
$content = file_get_contents($filePath);
$lines = explode("\n", $content);
$vulnerabilities = [];
foreach ($lines as $lineNum => $line) {
// 检查危险函数
foreach ($this->dangerous_functions as $func) {
if (preg_match("/\b{$func}\s*\(/i", $line)) {
$vulnerabilities[] = [
'type' => 'dangerous_function',
'line' => $lineNum + 1,
'function' => $func,
'code' => trim($line)
];
}
}
// 检查SQL注入迹象
if (preg_match('/\$_GET|\$_POST|\$_REQUEST|\$_COOKIE/', $line)) {
if (preg_match('/(select|insert|update|delete|where)\b/i', $line)) {
$vulnerabilities[] = [
'type' => 'sql_injection',
'line' => $lineNum + 1,
'code' => trim($line)
];
}
}
}
return $vulnerabilities;
}
public function scanDirectory($dir) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir)
);
$allVulnerabilities = [];
foreach ($iterator as $file) {
if ($file->getExtension() === 'php') {
$vulns = $this->auditFile($file->getPathname());
if (!empty($vulns)) {
$allVulnerabilities[$file->getPathname()] = $vulns;
}
}
}
return $allVulnerabilities;
}
}
// 使用示例
$auditor = new CodeAuditor();
$results = $auditor->scanDirectory('/path/to/project');
print_r($results);
?>
实战案例分析
典型的SQL注入案例
// 漏洞代码
$id = $_GET['id'];
$query = "SELECT * FROM products WHERE id = '$id'";
$result = mysql_query($query);
// 利用方式
?id=1' OR '1'='1
?id=1 UNION SELECT username,password FROM users--
// 修复方案
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$_GET['id']]);
命令注入案例
// 漏洞代码
$cmd = "ping " . $_GET['ip'];
system($cmd);
// 利用方式
?ip=127.0.0.1; cat /etc/passwd
?ip=127.0.0.1|id
?ip=127.0.0.1&&whoami
// 修复方案
$ip = filter_var($_GET['ip'], FILTER_VALIDATE_IP);
if ($ip) {
system("ping " . escapeshellarg($ip));
}
文件上传漏洞
// 漏洞代码
if ($_FILES['file']['size'] < 100000) {
$target = "uploads/" . $_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'], $target);
}
// 利用方式
// 上传.php文件,配合路径信息获取web shell
// 修复方案
if ($_FILES['file']['size'] < 100000) {
$extension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
if (in_array($extension, $allowed)) {
$filename = uniqid() . '.' . $extension;
$target = "uploads/" . $filename;
move_uploaded_file($_FILES['file']['tmp_name'], $target);
}
}
审计报告生成
<?php
// audit_report.php - 生成审计报告
class AuditReport {
private $findings = [];
public function addFinding($severity, $title, $description, $file, $line) {
$this->findings[] = [
'severity' => $severity,
'title' => $title,
'description' => $description,
'file' => $file,
'line' => $line,
'timestamp' => date('Y-m-d H:i:s')
];
}
public function generateReport() {
$output = "# PHP代码审计报告\n\n";
$output .= "审计时间: " . date('Y-m-d H:i:s') . "\n\n";
// 按严重级别排序
usort($this->findings, function($a, $b) {
$severity = ['critical' => 3, 'high' => 2, 'medium' => 1, 'low' => 0];
return $severity[$b['severity']] - $severity[$a['severity']];
});
// 统计
$stats = [
'critical' => 0,
'high' => 0,
'medium' => 0,
'low' => 0
];
foreach ($this->findings as $finding) {
$stats[$finding['severity']]++;
$output .= "## [{$finding['severity']}] {$finding['title']}\n";
$output .= "- **文件**: {$finding['file']}:{$finding['line']}\n";
$output .= "- **描述**: {$finding['description']}\n\n";
}
//
$output .= "\n## 统计摘要\n";
foreach ($stats as $severity => $count) {
$output .= "- {$severity}: {$count}个\n";
}
return $output;
}
}
// 使用示例
$report = new AuditReport();
$report->addFinding('critical', 'SQL注入漏洞', '用户输入未过滤直接拼接SQL', 'user.php', 45);
$report->addFinding('high', '文件上传漏洞', '未验证上传文件类型', 'upload.php', 23);
echo $report->generateReport();
?>
最佳实践建议
审计优先级
- 高危漏洞:SQL注入、命令执行、文件上传、反序列化
- 中危漏洞:XSS、CSRF、路径穿越
- 低危漏洞:信息泄露、弱口令
修复建议
// 统一安全处理
class Security {
// 输入过滤
public static function sanitize($input) {
return htmlspecialchars(strip_tags(trim($input)));
}
// SQL防注入
public static function bindParams($pdo, $sql, $params) {
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
// 生成CSRF Token
public static function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// 文件上传检查
public static function checkUpload($file) {
$allowed = ['jpg', 'png', 'gif', 'pdf'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
return in_array($ext, $allowed);
}
}
持续审计
- 建立自动化审计流程
- 定期进行代码审查
- 使用版本控制管理变更
- 部署安全监控系统
PHP代码审计需要结合静态分析和动态调试,重点关注:
- 输入验证:所有用户输入都是不可信的
- 输出处理:避免XSS攻击
- 数据库操作:使用参数化查询
- 文件操作:严格限制文件类型和路径
- 安全机制:认证、授权、CSRF防护
建议采用自动化工具辅助人工审计,结合多种工具和技巧,提高审计效率和准确性。