本文目录导读:

我来详细说明 PHP 安全扫描流水线的搭建方法。
完整的 PHP 安全扫描流水线架构
# .gitlab-ci.yml 示例
stages:
- dependencies
- static_analysis
- security_scan
- dynamic_scan
# 依赖安装
install_deps:
stage: dependencies
script:
- composer install --no-dev --optimize-autoloader
- npm ci
artifacts:
paths:
- vendor/
- node_modules/
# 静态代码分析
static_analysis:
stage: static_analysis
parallel:
matrix:
- TOOL: [phpcs, phpmd, phpstan, psalm]
script:
- |
case $TOOL in
phpcs)
vendor/bin/phpcs --standard=PSR12 src/
;;
phpmd)
vendor/bin/phpmd src/ text cleancode,codesize,controversial,design,naming,unusedcode
;;
phpstan)
vendor/bin/phpstan analyse src/ --level=9
;;
psalm)
vendor/bin/psalm --show-info=true
;;
esac
# 安全扫描
security_scan:
stage: security_scan
parallel:
matrix:
- TOOL:
- composer_audit
- security_checker
- snuffleupagus
- php_sec_info
- owasp_dependency_check
script:
- |
case $TOOL in
composer_audit)
composer audit --format=json > reports/composer-audit.json
;;
security_checker)
vendor/bin/security-checker security:check composer.lock
;;
snuffleupagus)
# 运行 Snuffleupagus 规则
php -d "snuffleupagus.rules"="default" run_tests.php
;;
php_sec_info)
vendor/bin/php-security-info
;;
owasp_dependency_check)
dependency-check --scan . --format JSON > reports/owasp.json
;;
esac
artifacts:
paths:
- reports/
expire_in: 30 days
# DAST 动态扫描
dynamic_scan:
stage: dynamic_scan
services:
- name: owasp/zap2docker-stable
alias: zap
script:
- |
# 启动目标应用
php -S 0.0.0.0:8000 -t public/ &
# 等待应用启动
sleep 5
# 运行 ZAP 扫描
zap-cli quick-scan --self-contained \
--start-options '-config api.disablekey=true' \
http://app:8000/
# 生成报告
zap-cli report -o reports/zap-report.html -f html
artifacts:
paths:
- reports/zap-report.html
# 代码安全审计
code_audit:
stage: security_scan
script:
- |
# 运行 Progpilot
vendor/bin/progpilot --input src/ --output reports/progpilot.json
# 运行 RIPS 扫描
php -r "
\$scanner = new RipsScanner('src/');
\$scanner->scan();
file_put_contents('reports/rips.html', \$scanner->getHtmlReport());
"
# 运行 Phan Security Plugin
vendor/bin/phan --plugin SecurityPlugin \
--output-mode json \
--output reports/phan-security.json
artifacts:
paths:
- reports/
# 依赖漏洞扫描
dependency_scan:
stage: security_scan
script:
- |
# Syft 扫描
syft packages dir:. -o json > reports/syft.json
# Grype 漏洞扫描
grype dir:. -o json > reports/grype.json
# Trivy 扫描
trivy filesystem --severity HIGH,CRITICAL . > reports/trivy.txt
# Snyk 扫描
snyk test --json > reports/snyk.json
artifacts:
paths:
- reports/
# 配置安全检查
config_scan:
stage: security_scan
script:
- |
# 检查 PHP 配置安全性
php -r "
\$secure = [
'expose_php' => 'Off',
'display_errors' => 'Off',
'allow_url_fopen' => 'Off',
'allow_url_include' => 'Off',
'session.cookie_httponly' => 'On',
'session.cookie_secure' => 'On',
'session.use_only_cookies' => 'On',
'session.use_strict_mode' => 'On',
'session.cookie_samesite' => 'Lax'
];
foreach (\$secure as \$key => \$expected) {
\$actual = ini_get(\$key);
if (\$actual != \$expected) {
echo \"WARNING: \$key should be \$expected, current is \$actual\n\";
}
}
" > reports/config-check.txt
artifacts:
paths:
- reports/config-check.txt
Docker 化流水线配置
# Dockerfile.security
FROM php:8.2-cli
# 安装依赖
RUN apt-get update && apt-get install -y \
git \
curl \
wget \
unzip \
libzip-dev \
libcurl4-openssl-dev \
&& docker-php-ext-install zip curl
# 安装 Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 安装安全工具
RUN wget https://github.com/OWASP/dependency-check/releases/download/v8.3.1/dependency-check-8.3.1-release.zip \
&& unzip dependency-check-8.3.1-release.zip -d /opt/dependency-check \
&& ln -s /opt/dependency-check/bin/dependency-check.sh /usr/local/bin/dependency-check
# 安装安全扫描器
RUN composer global require \
sensiolabs/security-checker \
phpstan/phpstan \
vimeo/psalm \
phan/phan \
rips/rips-scanner
# 安装开源工具
RUN wget https://github.com/anchore/syft/releases/download/v0.87.1/syft_0.87.1_linux_amd64.deb \
&& dpkg -i syft_0.87.1_linux_amd64.deb
RUN wget https://github.com/anchore/grype/releases/download/v0.65.1/grype_0.65.1_linux_amd64.deb \
&& dpkg -i grype_0.65.1_linux_amd64.deb
# 设置工作目录
WORKDIR /workspace
COPY composer.json composer.lock ./
COPY src/ src/
COPY config/ config/
CMD ["composer", "install", "--no-dev", "--optimize-autoloader"]
GitHub Actions 工作流配置
# .github/workflows/security-scan.yml
name: PHP Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * *' # 每天凌晨2点运行
jobs:
security-checks:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.1', '8.2']
tool: ['phpstan', 'psalm', 'security-checker']
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml, json, curl, gd
coverage: xdebug
tools: composer:v2
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Run ${{ matrix.tool }}
run: |
case ${{ matrix.tool }} in
phpstan)
vendor/bin/phpstan analyse src/ --level=9
;;
psalm)
vendor/bin/psalm --show-info=true --stats
;;
security-checker)
vendor/bin/security-checker security:check composer.lock
;;
esac
- name: Upload report
uses: actions/upload-artifact@v3
if: always()
with:
name: security-report-${{ matrix.tool }}-php${{ matrix.php-version }}
path: |
reports/
composer-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Composer audit
run: composer audit --format=json
continue-on-error: true
- name: Check for vulnerabilities
run: |
AUDIT_RESULT=$(composer audit --format=json)
echo "$AUDIT_RESULT" | jq '.advisories'
if echo "$AUDIT_RESULT" | jq -e '.advisories | length > 0' > /dev/null; then
echo "::warning::Vulnerabilities found in dependencies"
exit 1
fi
dependency-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'PHP Application'
path: '.'
format: 'HTML'
args: >
--failOnCVSS 7
--enableExperimental
--suppression suppression.xml
- name: Upload Dependency Check report
uses: actions/upload-artifact@v3
with:
name: dependency-check-report
path: reports/dependency-check-report.html
sast-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Phan Security Scan
run: |
vendor/bin/phan --plugin SecurityPlugin \
--output-mode pylint \
--output reports/phan.log
- name: Progpilot Scan
run: |
vendor/bin/progpilot --input src/ \
--output reports/progpilot.json \
--config progpilot_config.yml
- name: Upload SAST results
uses: actions/upload-artifact@v3
with:
name: sast-reports
path: reports/
自定义扫描规则配置
// progpilot_config.yml
scanner:
inputs:
- src/
outputs:
- reports/progpilot.json
rules:
# SQL注入检查
sql_injection:
enabled: true
sinks:
- mysqli_query
- PDO::query
- db_query
# XSS检查
xss:
enabled: true
sinks:
- echo
- print
- print_r
# 命令注入
command_injection:
enabled: true
sinks:
- exec
- system
- shell_exec
- passthru
# 文件包含
file_inclusion:
enabled: true
sinks:
- include
- include_once
- require
- require_once
# 文件操作
file_operations:
enabled: true
sinks:
- fopen
- file_get_contents
- file_put_contents
- unlink
# 序列化
deserialization:
enabled: true
sinks:
- unserialize
- json_decode
# 自定义漏洞规则
custom_vulnerabilities:
- name: "Weak Password Hash"
pattern: "md5|sha1"
severity: "high"
- name: "Hardcoded Credentials"
pattern: "(password|passwd|pwd)\s*=\s*['\"](?!.*(env|getenv|\$_SERVER))"
severity: "critical"
- name: "Insecure Random"
pattern: "rand\(|mt_rand\("
severity: "medium"
报告生成与聚合
// ReportGenerator.php
<?php
class SecurityReportGenerator
{
private array $reports = [];
private array $vulnerabilities = [];
public function __construct(private string $outputDir)
{
if (!is_dir($outputDir)) {
mkdir($outputDir, 0755, true);
}
}
public function generateSummary(array $scanResults): array
{
$summary = [
'timestamp' => date('Y-m-d H:i:s'),
'total_vulnerabilities' => 0,
'critical' => 0,
'high' => 0,
'medium' => 0,
'low' => 0,
'scan_duration' => 0,
'scans_performed' => count($scanResults)
];
foreach ($scanResults as $tool => $results) {
$counts = $this->parseToolResults($tool, $results);
$summary['critical'] += $counts['critical'];
$summary['high'] += $counts['high'];
$summary['medium'] += $counts['medium'];
$summary['low'] += $counts['low'];
$summary['total_vulnerabilities'] += $counts['total'];
$this->vulnerabilities[$tool] = $results;
}
return $summary;
}
public function generateHTMLReport(array $scanResults): string
{
$summary = $this->generateSummary($scanResults);
$html = <<<HTML
<!DOCTYPE html>
<html>
<head>PHP Security Scan Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
.vulnerability { margin: 10px 0; padding: 10px; border-radius: 5px; }
.critical { background-color: #ffebee; border-left: 4px solid #f44336; }
.high { background-color: #fff3e0; border-left: 4px solid #ff9800; }
.medium { background-color: #fff8e1; border-left: 4px solid #ffc107; }
.low { background-color: #e8f5e9; border-left: 4px solid #4caf50; }
.severity { font-weight: bold; padding: 2px 6px; border-radius: 3px; }
.severity-critical { background-color: #f44336; color: white; }
.severity-high { background-color: #ff9800; color: white; }
.severity-medium { background-color: #ffc107; color: black; }
.severity-low { background-color: #4caf50; color: white; }
</style>
</head>
<body>
<h1>PHP Security Scan Report</h1>
<h2>Summary</h2>
<p>Total Vulnerabilities: {$summary['total_vulnerabilities']}</p>
<p>Critical: {$summary['critical']}</p>
<p>High: {$summary['high']}</p>
<p>Medium: {$summary['medium']}</p>
<p>Low: {$summary['low']}</p>
<p>Scan Date: {$summary['timestamp']}</p>
HTML;
// 添加具体漏洞信息
foreach ($this->vulnerabilities as $tool => $results) {
$html .= "<h3>Tool: {$tool}</h3>";
foreach ($results as $vuln) {
$severity = strtolower($vuln['severity'] ?? 'medium');
$html .= <<<VULN
<div class="vulnerability {$severity}">
<span class="severity severity-{$severity}">{$vuln['severity']}</span>
<strong>{$vuln['title']}</strong>
<p>{$vuln['description']}</p>
<p>File: {$vuln['file']}:{$vuln['line']}</p>
<pre>{$vuln['code']}</pre>
<p>Recommendation: {$vuln['recommendation']}</p>
</div>
VULN;
}
}
$html .= '</body></html>';
file_put_contents(
$this->outputDir . '/security-report-' . date('Y-m-d') . '.html',
$html
);
return $html;
}
private function parseToolResults(string $tool, array $results): array
{
// 根据不同工具格式解析结果
switch ($tool) {
case 'phpstan':
return $this->parsePhpStanResults($results);
case 'psalm':
return $this->parsePsalmResults($results);
case 'progpilot':
return $this->parseProgpilotResults($results);
default:
return $this->parseGenericResults($results);
}
}
}
最佳实践建议
流水线集成建议
# 安全检查点配置
security_pipeline:
# 1. 代码提交前检查(pre-commit hooks)
pre_commit:
- php -l {files} # 语法检查
- composer audit --format=json # 依赖检查
# 2. CI/CD 流水线检查
ci_cd:
- stage: dependencies
check: composer validate && composer audit
- stage: static_analysis
check: phpstan analyse --level=max
- stage: security_scan
check: |
progpilot --input src/ \
security-checker security:check
- stage: dependency_scan
check: grype dir:.
# 3. 部署前检查(门禁)
deployment_gates:
- severity: critical
action: block_deployment
tools: [phpstan, psalm, security-checker]
- severity: high
action: require_approval
tools: [progpilot, dependency-check]
- severity: medium
action: notify
tools: [all]
# 4. 定期扫描
scheduled_scans:
- frequency: daily
time: "02:00"
tools: [all]
- frequency: weekly
time: "03:00"
tools: [full-scan, penetration-test]
- frequency: monthly
time: "04:00"
tools: [owasp-zap, nuclei]
这样的流水线配置可以:
- 在代码提交时实时检查
- CI/CD 过程中全面扫描
- 部署前自动门禁
- 定期执行深度扫描
- 生成详细报告并告警