当前位置: 首页 > news >正文

XSS漏洞及常见处理方案

文章背景:
在近期项目安全测试中,安全团队发现了一处潜在的 跨站脚本攻击(XSS)漏洞,该漏洞可能导致用户数据被篡改或会话劫持等安全风险。针对这一问题,项目组迅速响应,通过代码修复、输入过滤、输出编码等多层防护措施,彻底消除了隐患。

为系统性总结此次漏洞的成因、修复方案及防护经验,开发团队特别编写了XSS漏洞及常见处理方案知识文献,涵盖漏洞原理、攻击场景、修复建议及长期防护策略,旨在为后续项目提供参考,避免同类问题重现。在此,特别鸣谢公司开发团队的雷神及参与修复的开发同事的紧密协作,正是大家的专业与高效,才使得漏洞得以及时闭环,并为团队积累了宝贵的安全实践知识。未来,我们将持续强化安全开发意识,筑牢项目安全防线。

什么是 XSS 漏洞? 
        XSS 漏洞,全称为跨站脚本攻击(Cross-Site Scripting)漏洞,攻击者通过在⽬标⽹站中注 
⼊恶意脚本,使得⽤⼾在访问该⽹站时,浏览器会执⾏这些恶意脚本,从⽽达到攻击者获取⽤
⼾信息、控制⽤⼾操作等⽬的的安全漏洞。
⼀般情况下,⽤⼾输⼊信息提交后,后端直接保存到数据库,⽤⼾需要查看信息时,后端直
接从数据库查询信息返回前端展⽰,如下图:
容易出现漏洞的地⽅基本和富⽂本都有关系,如⽹站后台⽂章编辑、前台评论区,如果⽤⼾
输⼊的内容包含脚本,直接保存到数据库,显⽰详情时再直接显⽰,就可能会执⾏脚本,例如
以下 XSS 脚本被浏览器渲染执⾏(即弹窗)。
<script>alert('XSS');</script>
<img src="x" onerror="alert('XSS')">
<input type="text" value="<script>alert('XSS')">
<svg/onload=alert('XSS')>
<img src="x" onerror="alert(String.fromCharCode(88,83,83))">
<script>document.write('<img src="x" onerror="alert(\'XSS\')">')</script>
<svg><animate onrepeat=top['ale'+'rt'](36) attributeName=x dur=1s repeatCount=2 />
<iframe src=‘http://www.baidu.com/123.js’></iframe>
<svg><animate+onrepeat=top['ale'+'rt'](36)+attributeName=x+dur=1s+repeatCount=2+/>
<iframe src=‘http://xxx.com/123.js’></iframe>
<u+onclick-alert(1)>aaa</u>

漏洞危害
1: 窃取⽤⼾信息
攻击者可通过 XSS 漏洞注⼊恶意脚本,当⽤⼾访问被攻击的⽹站时,脚本就会收集⽤⼾输
⼊的账号、密码等登录信息,并发送给攻击者,进⽽导致⽤⼾账号被盗⽤,个⼈隐私、财产安
全等受到威胁。同时获取⽤⼾在⽹站上填写的其他个⼈信息,如⾝份证号、联系⽅式、家庭住
址等,可能被⽤于精准诈骗。
2:篡改⽹站内容
利⽤ XSS 漏洞修改⽹站的⻚⾯内容,如替换⽹站内容、插⼊恶意⼴告等,使⽹站的正常展
⽰受到破坏,给⽤⼾带来误导性信息,给⽹站带来严重的负⾯影响。
其他还有如实施钓⻥攻击、传播恶意代码等⾏为。
常⻅处理⽅案
从前⾯时序图中可以看出,修复切⼊点可以这⼏步中考虑:
第 2 步:前端提交⽤⼾输⼊信息时,对输⼊内容进⾏脚本过滤。
第 3 步:后端对前端传⼊的内容也进⾏脚本过滤。
第 12 步:前端显⽰详情时禁⽤脚本渲染。
⽅案⼀:前端输⼊过滤
html 输⼊转义
使⽤第三⽅库类似 DOMPurify 来清理和转义输⼊的 html。
import DOMPurify from 'dompurify';
2
3 // 假设这是⽤⼾输⼊的 HTML 内容
4 const userInput = `<img src="x" onerror="alert('XSS')">Hello <b>world</
b>!</p>`;
5
6 // 使⽤ DOMPurify 清理⽤⼾输⼊
7 const sanitizedInput = DOMPurify.sanitize(userInput);
8
9 // 将清理后的内容安全地插⼊到⻚⾯中
10 document.getElementById('output').innerHTML = sanitizedInput;
url 转义
处理 url 输⼊的时候,使⽤ encodeURIComponent 来避免⽤⼾输⼊的恶意链接引发的攻
击。
// ⽤⼾输⼊的内容
2 const userInput = "Hello world & welcome to XSS <script>alert('XSS')</s
cript>";
3
4 // 使⽤ encodeURIComponent 对⽤⼾输⼊进⾏编码
5 const encodedInput = encodeURIComponent(userInput);
6
7 // 构建带有查询参数的 URL
8 const url = `https://example.com/search?query=${encodedInput}`;
严格输⼊验证
除了转义外常规的表单类型提交,尽可能对⽤⼾输⼊进⾏严格的验证,确保输⼊内容符合预
期格式。例如对于数字、⽇期等特定类型的输⼊,可以使⽤正则表达式来限制输⼊的格式。
// 验证邮箱格式
2 const validateEmail = (_, value) => {
3 if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(valu
e)) {
4 return Promise.reject(new Error('请输⼊正确的邮箱地址'));
5 }
6 return Promise.resolve();
7 };
使⽤成熟框架
使⽤ React/Vue 等框架,框架⾃带编译器会将模板 /jsx 解析为树,在 redderer ⾥调⽤
DOM API,减少 encode 操作避免较⼤部分的 xss 隐患。
标签属性异常处理
关注 prerender / SSR 的 hydrate 过程会⽣成 html 、dangerouslySetInnerHTML、
οnlοad= 字符串、href= 字符串、onrepeat= 字符串和⼀些常规的标签属性的过滤异常处
理。
1 import React, { useEffect } from 'react';
2 import ReactDOM from 'react-dom';
3
4 // 安全过滤函数
5 const sanitizeHTML = (html) => {
6 // 简单的正则过滤:去除 onload, href 等事件处理程序
7 return html
8 .replace(/on\w+="[^"]*"/g, '') // 去除所有事件属性如 onload, onerror
等
9 .replace(/<script.*?>.*?<\/script>/g, '') // 去除 <script> 标签
10 .replace(/<iframe.*?>.*?<\/iframe>/g, '') // 去除 <iframe> 标签
11 .replace(/javascript:/g, ''); // 去除 javascript: 协议
12 };
13
14 // ⽰例组件
15 const PrerenderedComponent = ({ unsafeHTML }) => {
16 useEffect(() => {
17 // 仅对 SSR 渲染时进⾏ HTML 安全过滤
18 const safeHTML = sanitizeHTML(unsafeHTML);
19 document.getElementById('content').innerHTML = safeHTML;
20 }, [unsafeHTML]);
21
22 return (
23 <div id="content">
24 {/* 使⽤ dangerouslySetInnerHTML 且已清理的 HTML */}
25 <div dangerouslySetInnerHTML={{ __html: unsafeHTML }} />
26 </div>
27 );
28 };
29
30 // SSR 或 Prerender 渲染时传⼊的潜在不安全内容
31 const unsafeHTML = `
32 <h1>Welcome</h1>
33 <p>Click here: <a href="javascript:alert('XSS')">Link</a></p>
34 <img src="image.jpg" onload="alert('XSS')">
35 <script>alert('XSS')</script>
36 `;
37
38 ReactDOM.hydrate(
39 <PrerenderedComponent unsafeHTML={unsafeHTML} />,
40 document.getElementById('root')
41 );
避免使⽤内联事件
使⽤原⽣ html 时候应该避免在 HTML 中直接使⽤内联事件处理程序,改⽤外部脚本⽂件。
富⽂本过滤
使⽤ textContent innerText 来设置⽤⼾输⼊的内容,⽽不是 innerHTML
使⽤富⽂本或者脚本编辑器等⻚⾯⽅案的时候输⼊的过滤采⽤ DOMPurify 进⾏处理。
1import React, { useState } from 'react';
2 import DOMPurify from 'dompurify';
3
4 const RichTextEditor = () => {
5 const [inputValue, setInputValue] = useState('');
6 const [sanitizedContent, setSanitizedContent] = useState('');
7
8 // 处理输⼊框变化
9 const handleInputChange = (e) => {
10 setInputValue(e.target.value);
11 };
12
13 // 提交并清理输⼊的 HTML
14 const handleSubmit = () => {
15 // 使⽤ DOMPurify 清理⽤⼾输⼊的 HTML
16 const cleanContent = DOMPurify.sanitize(inputValue);
17 setSanitizedContent(cleanContent);
18 };
19
20 return (
21 <div>
22 <h2>富⽂本编辑器</h2>
23
24 {/* 富⽂本编辑器 */}
25 <textarea
26 value={inputValue}
27 onChange={handleInputChange}
28 placeholder="请输⼊内容..."
29 rows="5"
30 cols="40"
31 ></textarea>
32 <br />
33
34 <button onClick={handleSubmit}>提交</button>
35
36 <h3>清理后的内容:</h3>
37 <div
38 id="output"
39 dangerouslySetInnerHTML={{ __html: sanitizedContent }}
40 ></div>
41 </div>
42 );
43 };
44
45 export default RichTextEditor;
采取上述步骤,能在多数情况下确保传⼊接⼝的参数是筛选、处理后的安全内容。但现在多
为前后端分离架构,攻击者可绕过前端,直接调⽤后端 API 接⼝存储含 XSS 隐患的内容。因
此,前端输⼊过滤只是预防⼿段,实际还需结合后⾯两个⽅案。
⽅案⼆:后端存储过滤
后端使⽤开源⼯具 worm-toolkit-valid 来对前端传⼊字符串进⾏验证,该⼯具使⽤注解的⽅
式进⾏⾃定义的验证,⽤法简单,使⽤⽅法如下:
引⼊ jar 包:
<dependency>
2 <groupId>com.worm</groupId>
3 <artifactId>worm-toolkit-valid</artifactId>
4 <version>1.0.6-SNAPSHOT</version>
5 </dependency>
依赖底层 jar 包:
1 <dependency>
2 <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
3 <artifactId>owasp-java-html-sanitizer</artifactId>
4 </dependency>
下⾯为具体的实现逻辑,⾃定义注解:
@Documented
2 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_
TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_
USE})
3 @Retention(RetentionPolicy.RUNTIME)
4 @Repeatable(NoScriptValid.List.class)
5 @Constraint(validatedBy = {NoScriptValidValidator.class})
6 public @interface NoScriptValid {
7 /**
8 * 提⽰信息(存在错误时,当前字段使⽤)
9 */
10 String message() default "Input must not contain JavaScript code";
11
12 /**
13 * 分组
14 */
15 Class<?>[] groups() default {};
16
17 /**
18 * 扩展对象
19 */
20 Class<? extends Payload>[] payload() default {};
21
22 @Documented
23 @Target({
24 ElementType.METHOD,
25 ElementType.FIELD,
26 ElementType.ANNOTATION_TYPE,
27 ElementType.CONSTRUCTOR,
28 ElementType.PARAMETER,
29 ElementType.TYPE_USE
30 })
31 @Retention(RetentionPolicy.RUNTIME)
32 @interface List {
33 NoScriptValid[] value();
34 }
35 }
Java
1 public class NoScriptValidValidator implements ConstraintValidator<NoS
criptValid, String> {
2 /**
3 * PolicyFactory 是 OWASP Java HTML Sanitizer 提供的⼀个⼯⼚类,⽤于创建
清理策略。
4 * Sanitizers.FORMATTING.and(Sanitizers.LINKS).and(Sanitizers.BLOCK
S).and(Sanitizers.IMAGES).and(Sanitizers.STYLES).and(Sanitizers.TABLE
S) 组合了多种清理策略,例如格式化、链接、块元素、图像、样式和表格等。
5 */
6 private final PolicyFactory policy = Sanitizers.FORMATTING.and(San
itizers.LINKS).and(Sanitizers.BLOCKS).and(Sanitizers.IMAGES).and(Saniti
zers.STYLES).and(Sanitizers.TABLES);
7
8 @Override
9 public boolean isValid(String value, ConstraintValidatorContext con
text) {
10 if (CharSequenceUtil.isBlank(value)) {
11 return true;
12 }
13 // 对输⼊值进⾏清理,然后将清理后的值与原始值进⾏⽐较。如果两者相等,说明输
⼊中不包含需要清理的 HTML 或脚本,验证通过;否则,验证失败。
14 String sanitize = policy.sanitize(value);
15 return CharSequenceUtil.equalsAnyIgnoreCase(sanitize, value);
16 }
17 }
NoScriptValidValidator 为⼀个内置的验证器,⾥⾯集合了⼀些清理策略,如果不满⾜项⽬
需求,可以参照此类重新写⼀个验证器,并设置 policy。
验证代码:
1 public class ExpressSaveDto {
2
3 /**
4 * 计价数量
5 */
6 @NoScriptValid(message = "表达式脚本不能输⼊javascript脚本")
7 private String valuationQuantity;
8
9 /**
10 * 计价单价
11 */
12 @NoScriptValid(message = "表达式脚本不能输⼊javascript脚本")
13 private String valuationUnitPrice;
14 }
如果使⽤嵌套验证的⽅式,需要明确在内部需要校验的对象上加上@Valid 注解
@Data
2 public class ExpFormulaSaveDto {
3
4 /**
5 * 公式名称
6 */
7 @NotBlank(message = "公式名称不能为空")
8 @Size(max = 32, message = "公式名称由⻓度不超过32的⽂本组成")
9 private String name;
10
11 @Valid
12 private List<ExpressSaveDto> expressSaveDtos;
13 }
Java
1 @PostMapping("/saveExpress")
2 public BaseResult<Boolean> saveExpress(@RequestBody @Valid ExpFormulaSa
veDto expFormulaSaveDto) {
3 // TODO
4 return BaseResult.ok();
5 }
当前端传给后端的 valuationQuantity 和 valuationUnitPrice 字段中包含脚本时,后端将提
⽰“表达式脚本不能输⼊ javascript 脚本”。
⽅案三:前端渲染过滤
直接渲染类型的内容前转义
防⽌通过 innerHTML document.write 等⽅法,使⽤ textContent
innerText 来进⾏替代。不可避免使⽤ innerHTML 的时候使⽤第三⽅库类似 DOMPurify
清理和转义接⼝获取的 html 内容。
1 import React, { useState } from 'react';
2 import DOMPurify from 'dompurify';
3
4 const SafeContentRenderer = () => {
5 const [userInput, setUserInput] = useState('');
6 const [outputHTML, setOutputHTML] = useState('');
7
8 // 处理⽤⼾输⼊
9 const handleInputChange = (e) => {
10 setUserInput(e.target.value);
11 };
12
13 // 渲染⽂本内容,避免使⽤ innerHTML
14 const renderText = () => {
15 const textContainer = document.getElementById('textContainer');
16 textContainer.textContent = userInput; // 使⽤ textContent 安全渲染
⽂本
17 };
18
19 // 渲染 HTML 内容,使⽤ DOMPurify 进⾏清理
20 const renderHTML = () => {
21 const sanitizedHTML = DOMPurify.sanitize(userInput);
22 setOutputHTML(sanitizedHTML); // 渲染经过清理的 HTML
23 };
24
25 return (
26 <div>
27 <h2>直接渲染内容⽰例</h2>
28
29 <textarea
30 value={userInput}
31 onChange={handleInputChange}
32 placeholder="请输⼊内容..."
33 rows="5"
34 cols="40"
35 ></textarea>
36
37 <div>
38 <button onClick={renderText}>渲染为⽂本</button>
39 <button onClick={renderHTML}>渲染为HTML</button>
40 </div>
41
42 {/* 渲染纯⽂本内容,避免使⽤ innerHTML */}
43 <div id="textContainer" style={{ marginTop: '20px', fontSize: '16
px' }}></div>
44
45 {/* 渲染 HTML 内容,使⽤ DOMPurify 清理过的内容 */}
46 <div id="htmlOutput" style={{ marginTop: '20px' }} dangerouslySet
InnerHTML={{ __html: outputHTML }}></div>
47 </div>
48 );
49 };
50
51 export default SafeContentRenderer;
设置 CSP
设置 Content Security Policy CSP 头,限制⻚⾯可以加载和执⾏的资源规则,防⽌
恶意脚本的注⼊和执⾏。
1 setHeader('Content-Security-Policy', "default-src 'self'; script-src 's
elf' 'sha256-abc123'; style-src 'self' 'unsafe-inline'; img-src 'self'
data:;");
模板引擎
需要使⽤动态脚本插⼊的时候,使⽤ Handlebars Mustache 等模板引擎执⾏。
<script id="entry-template" type="text/x-handlebars-template">
2 <div>{{message}}</div>
3 </script>
总结
XSS 防护的核⼼是通过严格控制⽤⼾输⼊和输出的内容,避免恶意脚本被注⼊并执⾏。前端
通过转义输⼊、使⽤安全的渲染⽅法、实施 CSP 等措施来防⽌脚本执⾏。后端则应确保输⼊验
证、输出转义,并结合框架⾃带的防护机制,确保数据在渲染时是安全的。前后端共同合作,
形成多层防护,才能有效防⽌ XSS 攻击。

相关文章:

  • 3月报|DolphinScheduler项目进展一览
  • Android 14音频系统之音频框架分析
  • 网络安全-Http\Https协议和Bp抓包
  • 洛谷普及P2239 [NOIP 2014 普及组] 螺旋矩阵 和 B3751 [信息与未来 2019] 粉刷矩形
  • MySQL函数运算
  • 深入解析C++引用:安全高效的别名机制及其与指针的对比
  • 常用的 ​​SQL 语句分类整理​​
  • DeepSpeed ZeRO++:降低4倍网络通信,显著提高大模型及类ChatGPT模型训练效率
  • matlab想比较两个变量的内容差异用的函数
  • 如何配置HADOOP_HOME环境变量
  • jvm问题总结
  • 深入探究MapStruct:高效Java Bean映射工具的全方位解析
  • 【linux】--- 进程概念
  • UniRig ,清华联合 VAST 开源的通用自动骨骼绑定框架
  • zigbee和wifi都是无线通信,最大区别是低功耗,远距离!
  • 15.家庭影院,我选Jellyfin
  • 聚氯乙烯(PVC)生产工艺全流程解析与技术发展
  • Pytorch 第十五回:神经网络编码器——GAN生成对抗网络
  • JAVA 主流微服务常用框架及简介
  • CloudWeGo 技术沙龙·深圳站回顾:云原生 × AI 时代的微服务架构与技术实践
  • 乐聚创始人:人形机器人当前要考虑泡沫问题,年底或将进入冷静期
  • “网红”谭媛去世三年:未停更的账号和困境中的家庭
  • 我国首次发布铁线礁、牛轭礁珊瑚礁“体检”报告,菲炮制言论毫无科学和事实依据
  • 中国驻英国大使郑泽光:中国反制美国关税是为了维护国际公平正义和多边贸易体制
  • 与包乐史驾帆航行|航海、钓鱼和写书:一个记者的再就业之路
  • 接棒路颖,国泰海通证券副总裁谢乐斌履新海富通基金董事长