第2467篇:AI驱动的安全扫描——代码漏洞的自动检测和修复建议
2026/4/30大约 6 分钟
第2467篇:AI驱动的安全扫描——代码漏洞的自动检测和修复建议
适读人群:Java工程师、安全工程师、技术负责人 | 阅读时长:约16分钟 | 核心价值:用LLM增强代码安全扫描,发现传统SAST工具遗漏的业务逻辑安全漏洞
我们公司有一次做安全审计,请了一家专业的安全公司来做渗透测试。他们花了两周,最后报告里列了23个漏洞。
其中有8个是Checkmarx这类SAST工具应该能发现的(SQL注入、XSS等)。这8个我们之前确实没重视,因为工具扫出来了但一直没修。
但更让我意外的是剩下15个——这些是业务逻辑层面的安全问题:越权访问、密码重置绕过、金额计算中的精度问题被利用来薅羊毛……这类问题,传统的SAST工具完全发现不了,因为它们没有业务语义理解能力。
这15个漏洞,每一个都要被利用,都需要了解业务流程和代码逻辑的人才能发现。
LLM刚好具备这种能力。
安全扫描的三个层次
传统SAST工具覆盖的是语法层面,LLM覆盖的是语义层面,这两者是互补关系,不是替代关系。
核心实现
1. 安全扫描编排器
@Service
public class SecurityScanOrchestrator {
private final SastScanner sastScanner; // 传统SAST扫描(SpotBugs/Semgrep)
private final LLMSecurityAnalyzer llmAnalyzer; // LLM语义分析
private final DependencyScanner depScanner; // 依赖漏洞扫描
private final SecurityReportGenerator reporter;
public SecurityScanReport scan(Path projectRoot, ScanConfig config) {
log.info("开始安全扫描: {}", projectRoot);
// 并行执行三种扫描
CompletableFuture<List<SecurityIssue>> sastFuture =
CompletableFuture.supplyAsync(() -> sastScanner.scan(projectRoot));
CompletableFuture<List<SecurityIssue>> llmFuture =
CompletableFuture.supplyAsync(() -> llmAnalyzer.analyze(projectRoot, config));
CompletableFuture<List<DependencyVulnerability>> depFuture =
CompletableFuture.supplyAsync(() -> depScanner.scan(projectRoot));
// 等待所有扫描完成
CompletableFuture.allOf(sastFuture, llmFuture, depFuture).join();
List<SecurityIssue> allIssues = new ArrayList<>();
allIssues.addAll(sastFuture.join());
allIssues.addAll(llmFuture.join());
// 去重:相同漏洞被多个扫描器发现的,合并
List<SecurityIssue> deduplicatedIssues = deduplicateIssues(allIssues);
return reporter.generate(deduplicatedIssues, depFuture.join(), projectRoot);
}
}2. LLM安全分析器
@Service
public class LLMSecurityAnalyzer {
private final ChatClient chatClient;
private final SecurityFocusedFileSelector fileSelector;
public List<SecurityIssue> analyze(Path projectRoot, ScanConfig config) {
// 优先分析高风险文件(认证、授权、支付、数据访问等)
List<Path> priorityFiles = fileSelector.selectSecuritySensitiveFiles(projectRoot);
List<SecurityIssue> allIssues = new ArrayList<>();
for (Path file : priorityFiles) {
List<SecurityIssue> fileIssues = analyzeFile(file);
allIssues.addAll(fileIssues);
}
return allIssues;
}
private List<SecurityIssue> analyzeFile(Path filePath) {
String sourceCode = Files.readString(filePath);
String relativePath = projectRoot.relativize(filePath).toString();
// 快速预判:这个文件是否有安全敏感操作
if (!hasSecuritySensitiveOperations(sourceCode)) {
return Collections.emptyList();
}
ChatResponse response = chatClient.call(new Prompt(
List.of(
new SystemMessage(SECURITY_ANALYSIS_SYSTEM_PROMPT),
new UserMessage(buildSecurityAnalysisPrompt(relativePath, sourceCode))
),
OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withTemperature(0.1f)
.withResponseFormat(new ResponseFormat(ResponseFormat.Type.JSON_OBJECT))
.build()
));
return parseSecurityIssues(response.getResult().getOutput().getContent(), relativePath);
}
private boolean hasSecuritySensitiveOperations(String code) {
// 快速过滤,避免对每个文件都调用LLM
return code.contains("HttpServletRequest") ||
code.contains("@RequestMapping") ||
code.contains("@RestController") ||
code.contains("Authentication") ||
code.contains("Principal") ||
code.contains("password") ||
code.contains("Password") ||
code.contains("token") ||
code.contains("Token") ||
code.contains("BigDecimal") || // 可能涉及金额计算
code.contains("jdbcTemplate") ||
code.contains("createQuery") ||
code.contains("executeQuery");
}
private String buildSecurityAnalysisPrompt(String filename, String code) {
return """
请对以下Java代码进行安全审查,重点关注业务逻辑层面的安全问题:
文件: %s
```java
%s
```
重点检查:
1. **越权访问**:用户A是否可以访问/修改用户B的数据?资源所有权验证是否缺失?
2. **认证绕过**:是否有可以绕过认证检查的路径?JWT/session验证是否完整?
3. **业务逻辑漏洞**:金额计算是否有精度问题或溢出风险?状态机转换是否可被强制跳过?
4. **数据泄露**:API响应是否包含不应该暴露的字段(密码hash、内部ID等)?
5. **参数篡改**:价格、数量、用户ID是否从客户端直接读取而未做服务端校验?
6. **IDOR**:通过ID直接访问资源时,是否验证了访问者有权限?
对于发现的每个问题,提供具体的代码行号和修复建议。
""".formatted(filename, code);
}
private static final String SECURITY_ANALYSIS_SYSTEM_PROMPT = """
你是一个资深的应用安全工程师(APPSEC),专注于发现代码中的业务逻辑安全漏洞。
你的分析原则:
1. 只报告真实的安全问题,不报告理论上可能的问题
2. 对于不确定的问题,标注confidence并说明疑虑
3. 优先关注高严重性问题(数据泄露、未授权访问、金融风险)
4. 给出具体的修复代码示例,不要只说"需要加验证"
返回JSON格式:
{
"issues": [
{
"id": "唯一ID",
"cwe": "CWE编号(如果适用)",
"severity": "CRITICAL/HIGH/MEDIUM/LOW",
"category": "BROKEN_ACCESS_CONTROL/AUTHENTICATION/BUSINESS_LOGIC/DATA_EXPOSURE/INJECTION",
"title": "漏洞标题",
"description": "详细描述",
"line": 行号,
"vulnerableCode": "有漏洞的代码片段",
"fixedCode": "修复后的代码",
"attackScenario": "攻击场景描述",
"confidence": 0.0-1.0
}
]
}
""";
}3. 高风险文件识别
@Component
public class SecurityFocusedFileSelector {
private static final List<String> HIGH_RISK_PATTERNS = List.of(
"Controller", "Handler", "Resource", // API入口
"Service", "Manager", // 业务逻辑
"Repository", "Dao", "Mapper", // 数据访问
"Auth", "Security", "Permission", // 认证授权
"Payment", "Order", "Billing", // 支付相关
"User", "Account", "Profile" // 用户数据
);
public List<Path> selectSecuritySensitiveFiles(Path projectRoot) {
List<Path> allJavaFiles = findAllJavaFiles(projectRoot);
// 按风险优先级排序
return allJavaFiles.stream()
.sorted(Comparator.comparingInt(
(Path p) -> calculateRiskPriority(p.getFileName().toString())
).reversed())
.collect(toList());
}
private int calculateRiskPriority(String filename) {
int priority = 0;
for (String pattern : HIGH_RISK_PATTERNS) {
if (filename.contains(pattern)) {
priority++;
}
}
return priority;
}
}4. 自动修复建议生成
对于找到的安全问题,生成具体的修复代码:
@Service
public class SecurityFixGenerator {
private final ChatClient chatClient;
public SecurityFix generateFix(SecurityIssue issue, String originalCode) {
String fixPrompt = """
以下代码存在安全漏洞,请生成修复方案:
**漏洞类型**: %s
**严重程度**: %s
**描述**: %s
**攻击场景**: %s
**有漏洞的代码**:
```java
%s
```
请提供:
1. 完整的修复代码(不只是片段)
2. 修复原理说明
3. 修复后需要补充的单元测试
""".formatted(
issue.getCategory(),
issue.getSeverity(),
issue.getDescription(),
issue.getAttackScenario(),
issue.getVulnerableCode()
);
ChatResponse response = chatClient.call(new Prompt(
new UserMessage(fixPrompt),
OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withTemperature(0.1f)
.build()
));
return parseSecurityFix(response.getResult().getOutput().getContent(), issue);
}
}典型业务逻辑漏洞案例
案例1:越权订单查询
// 有漏洞的代码:直接用参数里的orderId查询,没有验证是当前用户的订单
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
return orderService.findById(orderId); // 任何登录用户都能查任意订单
}
// 修复后:
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId,
@AuthenticationPrincipal UserDetails user) {
Order order = orderService.findById(orderId);
if (!order.getUserId().equals(user.getUserId())) {
throw new AccessDeniedException("无权访问此订单");
}
return order;
}案例2:金额精度问题
// 有漏洞:double精度问题,0.1 + 0.2 != 0.3
double discount = 0.1;
double price = 9.9;
double finalPrice = price * (1 - discount); // 精度问题可能被利用
// 修复后:
BigDecimal discount = new BigDecimal("0.1");
BigDecimal price = new BigDecimal("9.9");
BigDecimal finalPrice = price.multiply(BigDecimal.ONE.subtract(discount))
.setScale(2, RoundingMode.HALF_UP);和传统SAST工具的定位差异
我们的实践中,LLM安全扫描和传统SAST工具各有侧重:
- SonarQube/SpotBugs:代码质量+基础安全规则,覆盖率高,速度快,适合在每次提交时运行
- Semgrep:规则可定制,可以添加公司特定的安全规则
- LLM安全分析:业务逻辑漏洞,特别是越权、绕过等,适合在重要功能发布前深度扫描
三者结合,才能达到相对完整的覆盖。
