大模型应用XSS漏洞实战:从攻击到防御的完整解决方案
1. 项目概述当大模型遇上XSS一场意料之中的攻防战最近在安全圈里一个话题的热度持续攀升大模型应用中的XSS漏洞。这听起来可能有点“关公战秦琼”的错位感——一个是前沿的AI技术一个是存在了二十多年的经典Web漏洞。但恰恰是这种结合暴露了当前大模型应用开发中一个普遍被忽视的“阿喀琉斯之踵”。我最近就深度参与了一个企业内部大模型知识库系统的安全审计与加固项目完整地走了一遍从漏洞发现、原理分析到代码修复的全过程。今天我就把这个实战案例掰开揉碎了讲给你听不仅会还原漏洞场景更重要的是我会分享一套可以直接“抄作业”的防护代码和配置策略。简单来说这个项目里的系统前端用Vue/React后端是Spring Boot接入了某款主流的大模型API实现了一个智能问答和文档分析平台。问题就出在当用户提问或系统展示大模型生成的内容时如果这些内容里包含了精心构造的恶意脚本而前端又直接将其作为HTML渲染了那么经典的XSS攻击就借尸还魂了。这绝不是危言耸听很多团队在拥抱大模型时注意力都放在了模型效果、响应速度和成本上却忘了最基本的输入输出安全。攻击者完全可以利用这一点窃取用户的会话Cookie、发起未授权的操作甚至将整个应用变成攻击跳板。接下来我们就从攻击者的视角出发看看这个漏洞是怎么被发现的然后再切换到防御者的角色一步步把它堵上。2. 漏洞发现一次针对大模型输出的定向渗透测试2.1 测试环境与目标分析我们的测试目标是一个部署在测试环境的智能客服系统。其核心流程是用户在前端输入问题后端将问题连同一些上下文发送给大模型API获取回答后再返回给前端渲染展示。前端为了追求“富文本”体验部分区域使用了v-htmlVue或dangerouslySetInnerHTMLReact来直接渲染模型返回的答案以便正确显示换行、加粗等简单格式。测试的第一步是信息收集。我们确认了前端框架、查看了网络请求发现模型回答是以JSON格式返回其中一个字段answer_content包含了完整的HTML片段。这是一个非常明确的危险信号后端没有对模型输出做任何净化和转义直接信任并下发了。2.2 构造与投递恶意载荷经典的XSS测试载荷如scriptalert(1)/script在这里可能不会直接生效因为大模型本身具有“安全意识”可能会拒绝生成或改写明显包含恶意脚本的文本。因此我们需要更隐蔽的测试方法。方法一上下文污染与指令混淆我们尝试在用户提问中以“举例说明”、“请用代码演示”为名诱导模型生成包含脚本的示例。例如提问“请写一段HTML代码展示如何用JavaScript弹出一个对话框。” 模型可能会生成类似下面的“教学代码”p示例代码如下/p precodelt;scriptgt;alert(Hello);lt;/scriptgt;/code/pre关键在于如果后端处理不当模型返回的整个文本块被当成了HTML那么precode标签内的实体字符lt;和gt;可能在某个环节被错误地解码还原成了真正的和从而导致脚本执行。我们通过拦截代理修改响应手动将lt;scriptgt;替换为script成功触发了弹窗。方法二利用Markdown或特定格式解析缺陷许多系统为了美观会将模型返回的Markdown转换为HTML。我们测试了诸如以下载荷这是一段正常文本。 ) // 利用图片链接协议 [链接](javascript:alert(XSS)) // 利用链接href如果使用的Markdown解析器版本老旧或配置不当没有对生成的href或src属性进行协议过滤白名单校验就可能产生javascript:伪协议注入。方法三事件处理器与SVG/MathML向量当直接script标签被过滤时我们转向HTML事件属性或其它可执行代码的向量img srcx onerroralert(XSS) svgscriptalert(XSS)/script/svg在测试中我们通过模拟“用户上传的、被模型引用的文档内容中包含此类向量”的场景发现当模型在回答中提及或“引用”这些内容时系统同样会不加处理地渲染导致漏洞触发。实操心得测试大模型的XSS不能只盯着直接的脚本注入。要思考模型作为“中间处理器”和“内容生成器”的特性测试点应放在模型输出内容的解析与渲染环节以及系统对模型输出内容的信任边界上。使用Burp Suite或类似的拦截工具在模型返回的JSON响应中直接修改answer_content字段进行测试效率最高。2.3 漏洞确认与影响评估通过上述测试我们确认了至少两处存储型XSS漏洞用户恶意提问经模型“转述”后存储其他用户查看时触发和一处反射型XSS漏洞恶意回答即时返回并在当前页面渲染触发。攻击者可以利用此漏洞盗取用户凭证窃取登录用户的Session Cookie或Token。冒充用户操作在用户不知情下以其身份发送消息、修改资料。客户端钓鱼在应用内伪造登录弹窗诱骗用户输入密码。传播恶意软件通过插入恶意脚本引导用户下载或执行恶意程序。漏洞的根本原因在于系统在设计上完全信任了大模型API的输出将其视为“安全数据”而忽略了模型本质上是一个不受控的、可能被恶意输入引导的“内容生成器”。3. 漏洞原理深度拆解为什么大模型应用是XSS的温床3.1 信任链的断裂从用户输入到模型输出在传统Web应用中安全防御的核心是建立清晰的信任边界。通常我们“不信任任何用户输入”所以会对所有来自前端、接口的用户数据进行严格的验证、过滤和转义。这个链条是不可信用户输入 - 严格过滤 - 安全的数据 - 安全地渲染。但在大模型应用中这个链条被拉长并扭曲了用户输入 - 大模型不可控的复杂处理单元- 模型输出新的、潜在的不可信数据- 直接渲染。开发者常常错误地将“经过大模型处理”等同于“被净化了”认为模型具有“理解”和“过滤”能力。然而大模型本质上是基于概率生成文本它没有安全语义理解能力。它可能被精心设计的提示词Prompt诱导生成符合语法但包含恶意代码的内容也可能在“学习”了训练数据中的不安全样本后复现出危险代码。3.2 渲染管道的安全缺失漏洞爆发的最后一个环节是前端渲染。为了灵活展示模型生成的、带有简单格式的内容开发者倾向于使用以下危险操作innerHTML/v-html/dangerouslySetInnerHTML这些API会直接将字符串作为HTML解析并插入DOM如果字符串中含有脚本则必然执行。不安全的动态属性绑定例如:hrefmodelOutputLink如果modelOutputLink是模型返回的javascript:...则构成漏洞。第三方库的滥用如使用未正确配置的Markdown转HTML库如marked旧版本默认不转义HTML、富文本编辑器库等这些库可能默认不安全或需要显式开启安全模式。3.3 与训练数据投毒的结合风险这是一个更深层次的威胁。如果攻击者能够污染大模型的训练数据例如在开源代码库、论坛讨论中植入特定的恶意代码模式那么模型在生成相关代码建议时就可能直接输出带有后门或漏洞的代码。当这类输出被应用不加处理地展示和采用时就构成了供应链攻击。虽然本次实战未涉及此层面但它是大模型安全必须考虑的宏观风险。4. 修复方案设计与核心代码实现修复的核心思路是“永不信任始终验证”将大模型的输出重新纳入不可信数据范畴并在输出到最终用户界面的每一个环节施加防护。我们采用了一种深度防御的策略。4.1 后端修复输出过滤与内容安全策略CSP第一道防线强制输出转义与过滤在后端Spring Boot控制器返回模型结果前对answer_content等字段进行严格的HTML转义。但注意不能简单地转义所有字符否则会破坏原有的合法格式如br。我们需要一个更智能的“白名单”过滤。我们引入了OWASP Java HTML Sanitizer这个强大的库它允许我们定义一个允许的标签和属性白名单。import org.owasp.html.HtmlPolicyBuilder; import org.owasp.html.PolicyFactory; Service public class ContentSecurityService { // 1. 定义针对大模型输出的HTML净化策略 private static final PolicyFactory MODEL_OUTPUT_POLICY new HtmlPolicyBuilder() .allowElements(p, br, strong, em, ul, ol, li, code, pre, blockquote) .allowAttributes(class).onElements(code, pre) .allowUrlProtocols(https, http) // 只允许http/https链接 .allowAttributes(href).onElements(a).requireRelNofollowOnLinks() // 链接自动加nofollow .allowAttributes(src).onElements(img).matching(ContentSecurityService::isValidImageUrl) .toFactory(); private static boolean isValidImageUrl(String url, String elementName) { // 简单示例确保是图片URL且协议合法 return url.startsWith(https://) || url.startsWith(http://); } // 2. 净化方法 public String sanitizeModelOutput(String rawModelOutput) { if (rawModelOutput null) { return ; } // 使用策略进行过滤不允许的标签和属性将被移除或转义 return MODEL_OUTPUT_POLICY.sanitize(rawModelOutput); } }在控制器中调用PostMapping(/chat) public ApiResponseChatResult chat(RequestBody ChatRequest request) { // ... 调用大模型API获取原始回答 rawAnswer ... String safeAnswer contentSecurityService.sanitizeModelOutput(rawAnswer); // ... 将safeAnswer返回给前端 ... }第二道防线部署严格的内容安全策略CSPCSP是一个重要的浏览器安全特性通过HTTP头告诉浏览器哪些资源可以加载和执行是缓解XSS的终极武器之一。即使有恶意脚本被注入严格的CSP也能阻止其执行。我们在Spring Boot的配置或全局过滤器中添加CSP头import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; Configuration public class SecurityConfig implements WebMvcConfigurer { Bean public FilterRegistrationBeanFilter cspFilter() { FilterRegistrationBeanFilter registration new FilterRegistrationBean(); registration.setFilter(new Filter() { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response (HttpServletResponse) res; // 一个非常严格的CSP策略示例禁止任何内联脚本和eval String cspHeader Content-Security-Policy: default-src self; script-src self https://trusted.cdn.com; // 只允许来自自身和特定CDN的JS style-src self unsafe-inline; // 允许内联样式必要时可收紧 img-src self https: data:; // 允许图片来自自身、https协议和dataURL connect-src self https://api.bigmodel.com; // 限制可连接的API端点 frame-ancestors none;; // 禁止被嵌套 response.setHeader(Content-Security-Policy, cspHeader); chain.doFilter(req, res); } }); registration.addUrlPatterns(/*); return registration; } }注意事项CSP策略的制定需要谨慎过于严格可能会破坏网站正常功能。建议先在“报告模式”Content-Security-Policy-Report-Only下运行观察控制台报告逐步调整到合适的策略后再强制执行。4.2 前端修复安全的渲染实践后端做了过滤和CSP前端也不能掉以轻心这是最后一道关卡。原则能不用v-html就不用对于纯文本展示坚决使用文本插值{{ modelAnswer }}或React的{modelAnswer}Vue和React会自动进行HTML转义。必须渲染富文本时使用经过安全审计的库如果确实需要展示模型返回的简单格式如加粗、列表应使用专门的安全HTML渲染库。Vue 3 示例使用v-html配合净化函数: 虽然Vue的v-html有风险但如果我们已经在后端和前端双重净化了数据可以谨慎使用。更好的做法是使用一个安全的渲染组件。template div !-- 危险做法直接渲染 -- !-- div v-htmlmodelAnswer/div -- !-- 安全做法1使用计算属性进行前端二次净化可选增加防御深度 -- div v-htmlsanitizedAnswer/div !-- 安全做法2使用专门的渲染组件如 vue-dompurify-html -- !-- div v-dompurify-htmlmodelAnswer/div -- /div /template script setup import { computed } from vue; import DOMPurify from dompurify; // 前端净化库 const props defineProps([modelAnswer]); const sanitizedAnswer computed(() { // 配置DOMPurify与后端白名单保持一致 const clean DOMPurify.sanitize(props.modelAnswer, { ALLOWED_TAGS: [p, br, strong, em, ul, ol, li, code, pre, blockquote, a, img], ALLOWED_ATTR: [class, href, rel, src], ALLOWED_URI_REGEXP: /^(https?:)?\/\/./i // 限制URL协议 }); return clean; }); /scriptReact 示例使用dangerouslySetInnerHTML配合净化:import React from react; import DOMPurify from dompurify; function SafeRenderComponent({ modelAnswer }) { const sanitizedHtml DOMPurify.sanitize(modelAnswer, { // ... 同样的安全配置 ... }); return div dangerouslySetInnerHTML{{ __html: sanitizedHtml }} /; }安全地处理动态属性对于模型输出中可能包含的链接永远不要直接绑定到href或src。template !-- 危险 -- !-- a :hrefmodelOutputLink点击/a -- !-- 安全使用方法进行校验 -- a :hrefsafeLink(modelOutputLink)点击/a /template script methods: { safeLink(rawLink) { if (!rawLink) return #; // 简单的协议校验 if (rawLink.startsWith(http://) || rawLink.startsWith(https://)) { return rawLink; } // 对于不符合条件的链接返回一个安全的值或进行编码 return #; // 或者更严格return javascript:void(0); 但注意这本身也有极小风险通常用#即可 } } /script4.3 大模型层提示词工程加固除了在应用层防护我们还可以尝试在调用大模型时通过系统提示词System Prompt来约束其输出格式降低生成恶意内容的风险。但这只能作为辅助手段不能替代应用层的安全措施。例如在调用API时可以附加这样的提示你是一个安全的AI助手。请遵守以下规则 1. 你的所有回答都将以纯文本或安全的Markdown格式呈现。 2. 绝对不要在输出中包含任何HTML标签如script, img onerror, svg等。 3. 如果用户要求你生成代码请确保代码示例中的任何HTML或JavaScript片段都以代码块形式呈现并确保其内容不会被解释为可执行代码。 4. 不要生成任何包含“javascript:”伪协议的链接。5. 测试验证与上线前检查清单修复完成后必须进行严格的回归测试和安全验证。5.1 自动化安全测试DAST动态应用安全测试使用ZAP、Burp Suite Professional等工具对聊天接口进行主动扫描重点测试XSS漏洞。SAST静态应用安全测试使用SonarQube、Checkmarx等工具扫描代码查找是否还存在不安全的innerHTML使用或未经验证的输入流。依赖项扫描使用OWASP Dependency-Check或Snyk检查项目中使用的库特别是Markdown解析、HTML净化库是否存在已知漏洞。5.2 手动渗透测试复现重新执行漏洞发现阶段的所有测试用例尝试注入各种XSS载荷。检查网络响应中CSP头是否正确设置。验证前端渲染后的DOM中恶意脚本是否已被转义或移除查看元素源码而不是检查页面效果。使用浏览器开发者工具的控制台查看是否有CSP违规报告。5.3 上线前安全检查清单将以下清单整合到你的CI/CD流程或上线核对表中检查项具体内容验证方法后端输出净化所有从大模型API或其他外部数据源获取的内容在返回前端前是否经过HTML净化白名单过滤代码审查查看对应Service层方法。CSP头配置生产环境HTTP响应头是否包含有效的、非报告模式的Content-Security-Policy使用浏览器开发者工具Network标签查看响应头或使用curl -I命令。前端安全渲染是否完全避免了不必要的v-html/dangerouslySetInnerHTML必须使用时是否配合了前端净化库如DOMPurify代码审查全局搜索危险API。动态属性安全所有动态绑定的href、src等属性其值是否经过协议验证或白名单过滤检查相关工具函数或计算方法。依赖库安全使用的HTML/Markdown处理库是否为最新安全版本其默认配置是否安全运行npm audit或dependency-check。错误处理当净化过程出现异常时是否有降级策略如返回空字符串或转义后的纯文本查看代码的异常处理逻辑。6. 总结与延伸思考这次实战让我深刻体会到在集成任何新技术时尤其是像大模型这种能力强大但边界模糊的技术安全必须作为第一性原理来考虑。大模型并没有改变Web安全的基本规则反而因为其复杂性和不可预测性引入了新的攻击面。修复一个漏洞并不难难的是建立起一套持续的安全意识和防护体系。我个人在实际操作中的体会是不要试图依赖大模型来保证安全。它的核心任务是生成内容而不是做安全审计。安全的责任必须牢牢掌握在应用开发者手中。将模型输出视为“有毒的、需要消毒的用户输入”是构建安全大模型应用的正确心态。最后再分享一个小技巧在团队内部可以建立一个“大模型安全测试用例库”把这次发现的以及能想到的各种诱导生成恶意代码的Prompt收集起来作为每次版本迭代的必测项。这能有效防止同类漏洞在未来的功能开发中复发。安全是一个过程而不是一个状态。随着大模型能力的演进攻击者的手段也会翻新。保持警惕持续学习将安全设计融入每一个开发环节是我们能为自己产品筑起的最坚固的防线。