PHP项目如何做代码审计

wen PHP项目 1

本文目录导读:

PHP项目如何做代码审计

  1. 审计前的准备工作
  2. 审计流程
  3. 自动化审计示例
  4. 实战案例分析
  5. 审计报告生成
  6. 最佳实践建议

我来系统地介绍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代码审计需要结合静态分析和动态调试,重点关注:

  1. 输入验证:所有用户输入都是不可信的
  2. 输出处理:避免XSS攻击
  3. 数据库操作:使用参数化查询
  4. 文件操作:严格限制文件类型和路径
  5. 安全机制:认证、授权、CSRF防护

建议采用自动化工具辅助人工审计,结合多种工具和技巧,提高审计效率和准确性。

抱歉,评论功能暂时关闭!