本文目录导读:

实现PHP API安全扫描,通常包含静态代码分析和动态安全测试两个层面,下面我会从工具选择、实现思路到CI/CD集成给出具体方案。
静态代码分析(SAST)
静态分析可以在不运行代码的情况下发现潜在漏洞。
使用 Psalm 或 PHPStan 配合安全规则
// 安装工具 composer require --dev vimeo/psalm composer require --dev phpstan/phpstan // 安装安全扩展规则 composer require --dev psalm/plugin-symfony composer require --dev phpstan/phpstan-symfony
配置示例(psalm.xml):
<?xml version="1.0"?>
<psalm
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
>
<projectFiles>
<directory name="src" />
</projectFiles>
<issueHandlers>
<MissingReturnType errorLevel="error" />
<UnsafeInstantiation errorLevel="error" />
<RawObjectIteration errorLevel="error" />
<!-- SQL注入检测 -->
<SqlInjection>
<errorLevel type="error">
<directory name="src/" />
</errorLevel>
</SqlInjection>
<!-- XSS检测 -->
<XSS>
<errorLevel type="error">
<directory name="src/" />
</errorLevel>
</XSS>
</issueHandlers>
</psalm>
专业安全扫描工具:RIPS
RIPS 是专为PHP设计的安全扫描器:
# Docker方式运行 docker run -it --rm -v $(pwd):/scan rips/rips -f /scan/src -o /scan/report.html # 扫描命令示例 rips scan --target /path/to/php/files --output /path/to/report.html
自定义静态扫描脚本
<?php
// custom-security-scanner.php
class SecurityScanner {
private array $vulnerabilities = [];
// 扫描模式
private array $patterns = [
'sql_injection' => [
'/\$_GET\[.*\]\s*.*(?:query|exec)/i',
'/\$_POST\[.*\]\s*.*(?:query|exec)/i',
'/\$_REQUEST\[.*\]\s*.*(?:query|exec)/i'
],
'xss' => [
'/echo\s+\$_GET\[.*\]/i',
'/echo\s+\$_POST\[.*\]/i',
'/print\s+\$_GET\[.*\]/i'
],
'command_injection' => [
'/exec\s*\(\s*\$_GET/i',
'/system\s*\(\s*\$_GET/i',
'/shell_exec\s*\(\s*\$_GET/i',
'/passthru\s*\(\s*\$_GET/i'
],
'file_inclusion' => [
'/include\s*\(\s*\$_GET/i',
'/require\s*\(\s*\$_GET/i',
'/include_once\s*\(\s*\$_GET/i',
'/require_once\s*\(\s*\$_GET/i'
]
];
public function scanDirectory(string $dir): array {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir)
);
foreach ($iterator as $file) {
if ($file->getExtension() === 'php') {
$this->scanFile($file->getPathname());
}
}
return $this->vulnerabilities;
}
private function scanFile(string $filePath): void {
$content = file_get_contents($filePath);
$lines = explode("\n", $content);
foreach ($this->patterns as $type => $patterns) {
foreach ($patterns as $pattern) {
foreach ($lines as $lineNo => $line) {
if (preg_match($pattern, $line)) {
$this->vulnerabilities[] = [
'file' => $filePath,
'line' => $lineNo + 1,
'type' => $type,
'code' => trim($line),
'severity' => $this->getSeverity($type)
];
}
}
}
}
}
private function getSeverity(string $type): string {
$severities = [
'sql_injection' => 'critical',
'command_injection' => 'critical',
'xss' => 'high',
'file_inclusion' => 'critical'
];
return $severities[$type] ?? 'medium';
}
public function generateReport(): string {
$report = "# PHP Security Scan Report\n\n";
$report .= "## Summary\n";
$report .= "- Total vulnerabilities found: " . count($this->vulnerabilities) . "\n\n";
// 按严重程度分组
$grouped = [];
foreach ($this->vulnerabilities as $vuln) {
$grouped[$vuln['severity']][] = $vuln;
}
foreach ($grouped as $severity => $vulns) {
$report .= "### {$severity} Severity Issues\n";
foreach ($vulns as $vuln) {
$report .= "- **{$vuln['type']}**: {$vuln['file']} (Line {$vuln['line']})\n";
$report .= " ```php\n {$vuln['code']}\n ```\n";
}
$report .= "\n";
}
return $report;
}
}
// 使用示例
$scanner = new SecurityScanner();
$results = $scanner->scanDirectory('/path/to/project');
echo $scanner->generateReport();
动态安全测试(DAST)
动态测试在运行时检查API的安全性。
使用 OWASP ZAP 进行API扫描
# Docker方式运行ZAP docker run -u zap -p 8080:8080 -i owasp/zap2docker-stable \ zap-api-scan.py \ -t http://localhost:8000/api-docs.json \ -f openapi \ -r zap_report.html
Python脚本自动扫描:
#!/usr/bin/env python3
# api-security-scan.py
import requests
import json
from zapv2 import ZAPv2
class APISecurityScanner:
def __init__(self, api_key, target_url, api_docs_url):
self.api_key = api_key
self.target_url = target_url
self.api_docs_url = api_docs_url
self.zap = ZAPv2(apikey=api_key)
def scan_api(self):
# 配置目标
self.zap.urlopen(self.api_docs_url)
# 导入API定义
with requests.Session() as session:
response = session.get(self.api_docs_url)
api_definition = response.json()
# 开始被动扫描
scan_id = self.zap.ascan.scan(self.target_url)
# 等待扫描完成
while int(self.zap.ascan.status(scan_id)) < 100:
print(f"Scan progress: {self.zap.ascan.status(scan_id)}%")
time.sleep(5)
# 获取报告
alerts = self.zap.core.alerts()
return self.generate_report(alerts)
def generate_report(self, alerts):
report = {
'high_risk': [],
'medium_risk': [],
'low_risk': []
}
for alert in alerts:
risk = alert.get('risk', '').lower()
if risk in report:
report[risk].append({
'name': alert['alert'],
'url': alert['url'],
'parameter': alert.get('param', ''),
'description': alert['description'],
'solution': alert.get('solution', '')
})
return report
# 使用示例
scanner = APISecurityScanner(
api_key='your-api-key',
target_url='http://localhost:8000',
api_docs_url='http://localhost:8000/api-docs'
)
results = scanner.scan_api()
自定义API安全测试脚本
<?php
// api-security-validator.php
class APISecurityValidator {
private array $endpoints;
private array $vulnerabilities = [];
public function __construct(array $endpoints) {
$this->endpoints = $endpoints;
}
public function runTests(): void {
foreach ($this->endpoints as $endpoint) {
$this->testSQLInjection($endpoint);
$this->testXSS($endpoint);
$this->testIDOR($endpoint);
$this->testRateLimiting($endpoint);
$this->testAuthentication($endpoint);
}
}
private function testSQLInjection(array $endpoint): void {
// SQL注入测试载荷
$payloads = [
"' OR '1'='1",
"1; DROP TABLE users--",
"' UNION SELECT * FROM users--",
"admin' --",
"1' AND 1=1--"
];
foreach ($payloads as $payload) {
$response = $this->sendRequest(
$endpoint['method'],
$endpoint['url'],
[$endpoint['parameter'] => $payload]
);
if ($this->detectSQLInjection($response)) {
$this->vulnerabilities[] = [
'type' => 'SQL Injection',
'endpoint' => $endpoint['url'],
'payload' => $payload,
'severity' => 'critical'
];
}
}
}
private function testXSS(array $endpoint): void {
$payloads = [
'<script>alert(1)</script>',
'<img src=x onerror=alert(1)>',
'javascript:alert(1)',
'"><script>alert(1)</script>'
];
foreach ($payloads as $payload) {
$response = $this->sendRequest(
$endpoint['method'],
$endpoint['url'],
[$endpoint['parameter'] => $payload]
);
if (strpos($response['body'], $payload) !== false) {
$this->vulnerabilities[] = [
'type' => 'XSS',
'endpoint' => $endpoint['url'],
'payload' => $payload,
'severity' => 'high'
];
}
}
}
private function testIDOR(array $endpoint): void {
// 测试不安全的直接对象引用
$testIds = [999999, -1, 0, 'admin'];
foreach ($testIds as $id) {
$response = $this->sendRequest(
$endpoint['method'],
str_replace('{id}', $id, $endpoint['url'])
);
if ($response['status'] === 200 && !isset($response['headers']['X-Auth-Required'])) {
$this->vulnerabilities[] = [
'type' => 'IDOR',
'endpoint' => $endpoint['url'],
'test_id' => $id,
'severity' => 'high'
];
}
}
}
private function testRateLimiting(array $endpoint): void {
$requests = [];
for ($i = 0; $i < 100; $i++) {
$start = microtime(true);
$response = $this->sendRequest(
$endpoint['method'],
$endpoint['url']
);
$requests[] = microtime(true) - $start;
}
// 检查是否有速率限制
$allSuccess = count(array_filter($requests, function($time) {
return $time < 1; // 1秒内完成
})) === 100;
if ($allSuccess) {
$this->vulnerabilities[] = [
'type' => 'Missing Rate Limiting',
'endpoint' => $endpoint['url'],
'severity' => 'medium'
];
}
}
private function testAuthentication(array $endpoint): void {
// 测试未认证访问
$response = $this->sendRequest(
$endpoint['method'],
$endpoint['url'],
[],
false // 不发送认证头
);
if ($response['status'] === 200) {
$this->vulnerabilities[] = [
'type' => 'Missing Authentication',
'endpoint' => $endpoint['url'],
'severity' => 'critical'
];
}
}
private function sendRequest(
string $method,
string $url,
array $params = [],
bool $authenticated = true
): array {
$ch = curl_init();
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_TIMEOUT => 10
];
if ($method === 'POST') {
$options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = http_build_query($params);
} else {
$options[CURLOPT_URL] .= '?' . http_build_query($params);
}
if ($authenticated) {
$options[CURLOPT_HTTPHEADER] = [
'Authorization: Bearer ' . getenv('API_TOKEN')
];
}
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
return [
'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'headers' => substr($response, 0, $headerSize),
'body' => substr($response, $headerSize)
];
}
private function detectSQLInjection(array $response): bool {
// 检测SQL错误的模式
$patterns = [
'/SQL syntax.*MySQL/i',
'/Warning.*mysql_/i',
'/Uncaught.*PDOException/i',
'/ORA-[0-9]{5}/i',
'/PostgreSQL.*ERROR/i',
'/invalid input syntax for type/i'
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $response['body'])) {
return true;
}
}
return false;
}
public function getReport(): string {
$report = "# API Security Scan Report\n\n";
$report .= "Generated: " . date('Y-m-d H:i:s') . "\n\n";
if (empty($this->vulnerabilities)) {
$report .= "✅ No vulnerabilities found!\n";
return $report;
}
$grouped = [];
foreach ($this->vulnerabilities as $vuln) {
$grouped[$vuln['severity']][] = $vuln;
}
foreach ($grouped as $severity => $vulns) {
$report .= "## {$severity} Severity\n";
foreach ($vulns as $vuln) {
$report .= "- **{$vuln['type']}** on {$vuln['endpoint']}\n";
if (isset($vuln['payload'])) {
$report .= " Payload: `{$vuln['payload']}`\n";
}
}
$report .= "\n";
}
$total = count($this->vulnerabilities);
$report .= "## Summary\nTotal vulnerabilities: {$total}\n";
$report .= "- Critical: " . count($grouped['critical'] ?? []) . "\n";
$report .= "- High: " . count($grouped['high'] ?? []) . "\n";
$report .= "- Medium: " . count($grouped['medium'] ?? []) . "\n";
return $report;
}
}
// 使用示例
$endpoints = [
['method' => 'GET', 'url' => 'http://localhost:8000/api/users', 'parameter' => 'id'],
['method' => 'POST', 'url' => 'http://localhost:8000/api/login', 'parameter' => 'username'],
['method' => 'GET', 'url' => 'http://localhost:8000/api/user/{id}', 'parameter' => 'id']
];
$validator = new APISecurityValidator($endpoints);
$validator->runTests();
echo $validator->getReport();
CI/CD 集成示例
GitHub Actions
# .github/workflows/security-scan.yml
name: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0' # 每周扫描
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
tools: composer
- name: Install Dependencies
run: composer install --prefer-dist --no-progress
# 静态扫描
- name: Static Security Analysis
run: |
composer require --dev vimeo/psalm
vendor/bin/psalm --show-info=true --report=psalm_report.json
# 使用自定义扫描器
- name: Custom Security Scanner
run: |
php custom-security-scanner.php > security_report.md
cat security_report.md
# 动态扫描(需要启动服务)
- name: Start API Server
run: |
php -S localhost:8000 -t public &
sleep 3
- name: Dynamic Security Scan
run: |
php api-security-validator.php > dynamic_scan_report.md
- name: Upload Scan Results
uses: actions/upload-artifact@v2
with:
name: security-reports
path: |
*report*
*.html
*.json
- name: Security Report Summary
run: |
echo "## Security Scan Results" >> $GITHUB_STEP_SUMMARY
echo "### Static Analysis" >> $GITHUB_STEP_SUMMARY
cat security_report.md >> $GITHUB_STEP_SUMMARY
echo "### Dynamic Analysis" >> $GITHUB_STEP_SUMMARY
cat dynamic_scan_report.md >> $GITHUB_STEP_SUMMARY
最佳实践建议
分层扫描策略
# 开发环境:快速静态扫描 php psalm --config=psalm.xml --show-info=false --report=quick_report.json # 预发布环境:完整扫描 php api-security-validator.php --endpoints=api_endpoints.json --verbose # 生产环境:持续监控 docker run --rm -v $(pwd):/app owasp/zap2docker-stable \ zap-api-scan.py -t https://api.example.com/openapi.json -r full_report.html
安全扫描清单
- 输入验证:SQL注入、XSS、命令注入、文件包含
- 认证授权:JWT安全、角色权限、会话管理
- 配置安全:CORS、HTTPS、安全头、错误信息
- 业务逻辑:IDOR、竞态条件、批量分配
- 传输安全:TLS配置、敏感数据传输
- 数据存储:密码存储、加密密钥、日志安全
常用工具对比
| 工具 | 类型 | 优点 | 缺点 |
|---|---|---|---|
| Psalm | 静态 | 深度分析,自定义规则 | 配置复杂 |
| PHPStan | 静态 | 性能好,社区大 | 安全规则有限 |
| RIPS | 静态 | 专为PHP设计 | 商业版收费 |
| OWASP ZAP | 动态 | 全面,开源 | 资源消耗大 |
| Burp Suite | 动态 | 强大,可扩展 | 商业版收费 |
这些工具和脚本可以根据你的具体需求进行定制,关键是要建立持续的安全扫描机制,而不仅仅是一次性检查,建议将安全扫描集成到CI/CD流水线中,并在每个部署前执行完整的扫描流程。