name: 漏洞扫描 description: 使用OWASP工具、CVE数据库和安全扫描器自动检测应用程序、依赖项和基础设施中的安全漏洞。在执行安全审计、合规性检查或持续安全监控时使用。
漏洞扫描
概览
系统地使用自动化扫描工具和手动安全评估来识别应用程序、依赖项和基础设施中的安全漏洞。
何时使用
- 部署前安全检查
- 持续安全监控
- 合规性审计(PCI-DSS,SOC 2)
- 依赖项漏洞检测
- 容器安全扫描
- 基础设施安全评估
实施示例
1. Node.js 漏洞扫描器
// scanner.js - 综合漏洞扫描
const { exec } = require('child_process');
const util = require('util');
const fs = require('fs').promises;
const execPromise = util.promisify(exec);
class VulnerabilityScanner {
constructor() {
this.results = {
dependencies: [],
code: [],
docker: [],
secrets: []
};
}
async scanDependencies() {
console.log('使用npm audit扫描依赖项...');
try {
const { stdout } = await execPromise('npm audit --json');
const auditResults = JSON.parse(stdout);
for (const [name, advisory] of Object.entries(auditResults.vulnerabilities || {})) {
this.results.dependencies.push({
package: name,
severity: advisory.severity,
cve: advisory.via.filter(v => typeof v === 'object').map(v => v.cve),
title: advisory.via.filter(v => typeof v === 'object').map(v => v.title),
vulnerableVersions: advisory.range,
recommendation: advisory.fixAvailable ? '更新可用' : '无可用的修复'
});
}
} catch (error) {
console.error('依赖项扫描失败:', error.message);
}
}
async scanCode() {
console.log('使用ESLint安全插件扫描代码...');
try {
const { stdout } = await execPromise('npx eslint . --format json --plugin security');
const eslintResults = JSON.parse(stdout);
for (const file of eslintResults) {
for (const message of file.messages) {
if (message.ruleId && message.ruleId.includes('security')) {
this.results.code.push({
file: file.filePath,
line: message.line,
column: message.column,
rule: message.ruleId,
severity: message.severity === 2 ? '高' : '中',
message: message.message
});
}
}
}
} catch (error) {
console.log('代码扫描完成,发现问题');
}
}
async scanDockerfile() {
console.log('使用hadolint扫描Dockerfile...');
try {
const { stdout } = await execPromise('hadolint Dockerfile --format json');
const hadolintResults = JSON.parse(stdout);
for (const issue of hadolintResults) {
this.results.docker.push({
line: issue.line,
level: issue.level,
code: issue.code,
message: issue.message
});
}
} catch (error) {
console.log('Dockerfile扫描完成');
}
}
async scanSecrets() {
console.log('使用truffleHog扫描秘密...');
try {
const { stdout } = await execPromise('trufflehog filesystem . --json');
const lines = stdout.trim().split('
');
for (const line of lines) {
if (line) {
const finding = JSON.parse(line);
this.results.secrets.push({
file: finding.SourceMetadata?.Data?.Filesystem?.file,
line: finding.SourceMetadata?.Data?.Filesystem?.line,
type: finding.DetectorName,
verified: finding.Verified
});
}
}
} catch (error) {
console.log('秘密扫描完成');
}
}
async runFullScan() {
await this.scanDependencies();
await this.scanCode();
await this.scanDockerfile();
await this.scanSecrets();
return this.generateReport();
}
generateReport() {
const report = {
timestamp: new Date().toISOString(),
summary: {
critical: 0,
high: 0,
medium: 0,
low: 0
},
findings: this.results,
totalIssues: 0
};
// 按严重程度计数
for (const dep of this.results.dependencies) {
report.summary[dep.severity]++;
report.totalIssues++;
}
for (const code of this.results.code) {
report.summary[code.severity]++;
report.totalIssues++;
}
// 高严重程度的秘密
report.summary.high += this.results.secrets.filter(s => s.verified).length;
report.totalIssues += this.results.secrets.length;
return report;
}
}
// 使用方法
async function main() {
const scanner = new VulnerabilityScanner();
const report = await scanner.runFullScan();
await fs.writeFile('security-report.json', JSON.stringify(report, null, 2));
console.log('
=== 安全扫描摘要 ===');
console.log(`总问题数: ${report.totalIssues}`);
console.log(`严重: ${report.summary.critical}`);
console.log(`高: ${report.summary.high}`);
console.log(`中: ${report.summary.medium}`);
console.log(`低: ${report.summary.low}`);
}
main().catch(console.error);
2. Python OWASP 扫描器
# owasp_scanner.py
import subprocess
import json
import os
from typing import Dict, List
from dataclasses import dataclass, asdict
from datetime import datetime
@dataclass
class Vulnerability:
severity: str
cve: str
package: str
version: str
description: str
fix: str
class OWASPScanner:
def __init__(self, project_path: str):
self.project_path = project_path
self.vulnerabilities: List[Vulnerability] = []
def scan_dependencies(self) -> None:
"""使用Safety扫描Python依赖项"""
print("使用Safety扫描依赖项...")
try:
result = subprocess.run(
['safety', 'check', '--json', '--file', 'requirements.txt'],
cwd=self.project_path,
capture_output=True,
text=True
)
if result.stdout:
findings = json.loads(result.stdout)
for vuln in findings:
self.vulnerabilities.append(Vulnerability(
severity=self._map_severity(vuln.get('severity', 'unknown')),
cve=vuln.get('cve', 'N/A'),
package=vuln.get('package_name', ''),
version=vuln.get('analyzed_version', ''),
description=vuln.get('advisory', ''),
fix=f"升级到 {vuln.get('fixed_versions', 'latest')}"
))
except Exception as e:
print(f"依赖项扫描错误: {e}")
def scan_with_bandit(self) -> None:
"""使用Bandit扫描Python代码"""
print("使用Bandit扫描代码...")
try:
result = subprocess.run(
['bandit', '-r', '.', '-f', 'json'],
cwd=self.project_path,
capture_output=True,
text=True
)
if result.stdout:
findings = json.loads(result.stdout)
for issue in findings.get('results', []):
self.vulnerabilities.append(Vulnerability(
severity=issue['issue_severity'].lower(),
cve='BANDIT-' + issue['test_id'],
package=os.path.basename(issue['filename']),
version=str(issue['line_number']),
description=f"{issue['issue_text']} - {issue['test_name']}",
fix=issue.get('more_info', 'Review code')
))
except Exception as e:
print(f"Bandit扫描错误: {e}")
def scan_with_trivy(self) -> None:
"""使用Trivy扫描容器镜像"""
print("使用Trivy扫描容器...")
try:
result = subprocess.run(
['trivy', 'image', '--format', 'json', 'myapp:latest'],
capture_output=True,
text=True
)
if result.stdout:
findings = json.loads(result.stdout)
for result_item in findings.get('Results', []):
for vuln in result_item.get('Vulnerabilities', []):
self.vulnerabilities.append(Vulnerability(
severity=vuln['Severity'].lower(),
cve=vuln.get('VulnerabilityID', 'N/A'),
package=vuln['PkgName'],
version=vuln['InstalledVersion'],
description=vuln.get('Title', vuln.get('Description', '')),
fix=vuln.get('FixedVersion', '无可用的修复')
))
except Exception as e:
print(f"Trivy扫描错误: {e}")
def _map_severity(self, severity: str) -> str:
mapping = {
'critical': 'critical',
'high': 'high',
'medium': 'medium',
'low': 'low'
}
return mapping.get(severity.lower(), 'medium')
def generate_report(self) -> Dict:
summary = {
'critical': 0,
'high': 0,
'medium': 0,
'low': 0
}
for vuln in self.vulnerabilities:
if vuln.severity in summary:
summary[vuln.severity] += 1
return {
'timestamp': datetime.now().isoformat(),
'total_vulnerabilities': len(self.vulnerabilities),
'summary': summary,
'vulnerabilities': [asdict(v) for v in self.vulnerabilities],
'compliance_status': 'FAIL' if summary['critical'] > 0 or summary['high'] > 0 else 'PASS'
}
def run_full_scan(self) -> Dict:
self.scan_dependencies()
self.scan_with_bandit()
self.scan_with_trivy()
report = self.generate_report()
with open('owasp-scan-report.json', 'w') as f:
json.dump(report, f, indent=2)
return report
# 使用方法
if __name__ == '__main__':
scanner = OWASPScanner('.')
report = scanner.run_full_scan()
print(f"
=== OWASP扫描完成 ===")
print(f"总漏洞数: {report['total_vulnerabilities']}")
print(f"严重: {report['summary']['critical']}")
print(f"高: {report['summary']['high']}")
print(f"合规性状态: {report['compliance_status']}")
3. CI/CD集成 - GitHub Actions
# .github/workflows/security-scan.yml
name: 安全漏洞扫描
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * *' # 每天凌晨2点
jobs:
vulnerability-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 运行Trivy漏洞扫描器
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH,MEDIUM'
- name: 将Trivy结果上传到GitHub安全
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: 运行Snyk安全扫描
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: OWASP依赖项检查
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'myapp'
path: '.'
format: 'JSON'
- name: 严重漏洞失败
run: |
if [ -f trivy-results.sarif ]; then
HIGH_COUNT=$(jq '.runs[].results | length' trivy-results.sarif)
if [ $HIGH_COUNT -gt 0 ]; then
echo "发现$HIGH_COUNT个高严重漏洞"
exit 1
fi
fi
最佳实践
✅ 要做
- 在CI/CD中自动化扫描
- 定期扫描依赖项
- 使用多个扫描工具
- 设置严重性阈值
- 跟踪漏洞趋势
- 扫描容器和镜像
- 监控CVE数据库
- 文档化误报
❌ 不要做
- 跳过漏洞扫描
- 忽视低严重性问题
- 信任单一扫描工具
- 绕过安全门
- 提交秘密到仓库
常见漏洞类型
- CVE: 库中已知漏洞
- CWE: 常见弱点模式
- OWASP Top 10: Web应用风险
- 容器漏洞: 基础镜像问题
- 依赖项混淆: 供应链攻击
- 秘密暴露: 硬编码凭证
扫描工具
- Trivy: 容器和文件系统扫描器
- Snyk: 依赖项和代码扫描
- OWASP Dependency Check: Java, .NET, Node.js
- Safety: Python依赖项检查器
- npm audit / yarn audit: Node.js包
- Bandit: Python安全linter
- Semgrep: 静态分析工具