Skip to content

Commit 01a082c

Browse files
committed
docs: improve xss module guidance
1 parent e066f3f commit 01a082c

11 files changed

Lines changed: 273 additions & 81 deletions

File tree

src/main/java/top/whgojp/modules/xss/controller/DomController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* @Date: 2024/5/23 17:25
1515
*/
1616
@Slf4j
17-
@Api(value = "ReflectController", tags = "跨站脚本-Dom型XSS")
17+
@Api(value = "DomController", tags = "跨站脚本-DOM型XSS")
1818
@Controller
1919
@CrossOrigin(origins = "*")
2020
@RequestMapping("/xss/dom")

src/main/java/top/whgojp/modules/xss/controller/OtherController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public R hackCookie(@RequestParam String cookie, HttpServletRequest request) {
8484
private UploadUtil uploadUtil;
8585

8686
// 文件上传接口
87-
@ApiOperation(value = "漏洞场景:文件上传导致存储XSS", notes = "原生漏洞场景,未加任何过滤,Controller接口返回Json类型结果")
87+
@ApiOperation(value = "漏洞场景:文件上传导致存储XSS", notes = "上传可被浏览器或预览服务解析的文件,后续访问文件时可能触发XSS")
8888
@RequestMapping("/vul1Upload")
8989
@ResponseBody
9090
@SneakyThrows
@@ -110,6 +110,7 @@ public R vul1Upload(@RequestParam("file") MultipartFile file,
110110
} catch (Exception e) {
111111
return R.error("上传错误,请检查后重新上传:" + e.getMessage());
112112
}
113+
// XML解析成功后继续落盘,便于演示“解析 + 可访问文件”组合场景。
113114
case "html":
114115
case "svg":
115116
case "pdf":
@@ -122,7 +123,7 @@ public R vul1Upload(@RequestParam("file") MultipartFile file,
122123
return R.error(res);
123124
}
124125
}
125-
@ApiOperation(value = "漏洞场景:模版引擎解析导致存储XSS", notes = "")
126+
@ApiOperation(value = "漏洞场景:模板引擎不安全渲染导致XSS", notes = "th:utext会把内容作为HTML渲染,th:text会进行转义")
126127
@GetMapping("/vul2OtherTemplate")
127128
public String vul2OtherTemplate(@RequestParam("payload") String payload,
128129
@RequestParam("type") String type, Model model) {

src/main/java/top/whgojp/modules/xss/controller/ReflectController.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public String reflect(@PathVariable String view) {
4141
return isValidView(view) ? "vul/xss/reflect/" + view : "error/404";
4242
}
4343

44-
@ApiOperation(value = "漏洞场景:GET型与POST型", notes = "原生漏洞场景,未加任何过滤,Controller接口返回Json类型结果")
44+
@ApiOperation(value = "漏洞场景:GET型与POST型", notes = "原生漏洞场景未加任何过滤,Controller接口返回JSON类型结果。JSON本身通常不会直接触发XSS,但前端不安全渲染JSON字段时可能触发")
4545
@RequestMapping("/vul1")
4646
@ResponseBody
4747
@ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
@@ -59,7 +59,7 @@ public String vul2(@ApiParam(name = "payload", value = "请求参数", required
5959
}
6060

6161
@SneakyThrows
62-
@ApiOperation(value = "漏洞场景:Content-Type问题", notes = "Tomcat内置HttpServletResponse,Content-Type导致反射XSS")
62+
@ApiOperation(value = "漏洞场景:Content-Type问题", notes = "响应Content-Type决定浏览器解析方式,不可信内容以text/html返回时可能导致反射XSS")
6363
@GetMapping("/vul3")
6464
@ResponseBody
6565
@ApiImplicitParams({
@@ -77,7 +77,7 @@ public void vul3(@ApiParam(name = "type", value = "类型", required = true) @Re
7777
case "plain":
7878
log.info("[+]XSS-反射性-Content-Type:text/plain;charset=utf-8:" + payload);
7979
response.getWriter().print(payload);
80-
response.setContentType("text/plain;charset=utf-8"); // response默认返回Content-Type类型是text/plain
80+
response.setContentType("text/plain;charset=utf-8");
8181
response.getWriter().flush();
8282
break;
8383
default:
@@ -91,7 +91,7 @@ public void vul3(@ApiParam(name = "type", value = "类型", required = true) @Re
9191
private static final String WHITELIST_REGEX = "^[a-zA-Z0-9_\\s]+$";
9292
private static final Pattern pattern = Pattern.compile(WHITELIST_REGEX);
9393

94-
@ApiOperation(value = "安全代码:用户输入验证和过滤", notes = "对用户输入的数据进行验证和过滤,确保不包含恶意代码。使用白名单过滤,只允许特定类型的输入,如纯文本或指定格式的数据")
94+
@ApiOperation(value = "安全代码:用户输入验证和过滤", notes = "使用白名单限制输入格式,适合约束字段类型;最终仍需根据输出位置进行上下文编码")
9595
@GetMapping("/safe1")
9696
@ResponseBody
9797
@ApiImplicitParams({
@@ -116,13 +116,13 @@ public R safe1(@ApiParam(name = "type", value = "类型", required = true) @Requ
116116
return R.ok(filterContented);
117117
}
118118

119-
@ApiOperation(value = "安全代码:内容安全策略-CSP防护", notes = "内容安全策略(Content Security Policy)是一种由浏览器实施的安全机制,旨在减少和防范跨站脚本攻击(XSS)等安全威胁。它通过允许网站管理员定义哪些内容来源是可信任的,从而防止恶意内容的加载和执行")
119+
@ApiOperation(value = "安全代码:内容安全策略-CSP防护", notes = "内容安全策略(Content Security Policy)是由浏览器实施的额外防护层,可降低恶意脚本加载和执行风险,但不能替代输出编码与安全模板/DOM用法")
120120
@GetMapping("/safe2")
121121
@ResponseBody
122122
@ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
123123
public String safe2(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload, HttpServletResponse response) {
124-
response.setHeader("Content-Security-Policy", "default-src self");
125-
response.setHeader("Content-Security-Policy-Report-Only", "default-src 'self'; other-uri /xss/reflect/csp-other-endpoint");
124+
response.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self'");
125+
response.setHeader("Content-Security-Policy-Report-Only", "default-src 'self'; report-uri /xss/reflect/csp-report-endpoint");
126126
log.info("[-]XSS-反射性-内容安全策略-CSP防护:" + payload);
127127
return payload;
128128
}
@@ -152,7 +152,7 @@ public void receiveCSPReport(@RequestBody String reportData) {
152152
// }
153153
}
154154

155-
@ApiOperation(value = "安全代码:特殊字符实体转义", notes = "特殊字符实体转义是一种将 HTML 中的特殊字符转换为预定义实体表示的过程。这种转义是为了确保在 HTML 页面中正确显示特定字符,同时避免它们被浏览器误解为 HTML 标签或JavaScript代码的一部分,从而导致页面结构混乱或安全漏洞。")
155+
@ApiOperation(value = "安全代码:HTML正文输出编码", notes = "将HTML正文文本中的特殊字符编码为实体,避免浏览器把不可信数据解析为HTML标签或JavaScript。不同输出上下文需要使用不同编码策略")
156156
@GetMapping("/safe3")
157157
@ResponseBody
158158
@ApiImplicitParams({
@@ -170,19 +170,19 @@ public R safe3(@ApiParam(name = "type", value = "类型", required = true) @Requ
170170
payload = StringUtils.replace(payload, "'", "'");
171171
payload = StringUtils.replace(payload, "/", "/");
172172
filterContented = payload;
173-
log.info("[-]XSS-反射性-内容安全策略-CSP防护-原生实体转移:" + payload);
173+
log.info("[-]XSS-反射型-HTML正文输出编码-手动编码:" + payload);
174174
break;
175175
case "spring":
176176
filterContented = HtmlUtils.htmlEscape(payload);
177-
log.info("[-]XSS-反射性-内容安全策略-CSP防护-Spring框架:" + payload);
177+
log.info("[-]XSS-反射型-HTML正文输出编码-Spring框架:" + payload);
178178
break;
179179
default:
180180
return R.error("参数输入有误!");
181181
}
182182
return R.ok(filterContented);
183183
}
184184

185-
@ApiOperation(value = "安全代码:HttpOnly配置", notes = "HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险。")
185+
@ApiOperation(value = "安全代码:HttpOnly配置", notes = "HttpOnly可以阻止客户端脚本直接读取带有该属性的Cookie,降低XSS窃取Cookie的影响,但不能修复XSS本身")
186186
@RequestMapping(value = "/safe4", method = RequestMethod.GET)
187187
@ResponseBody
188188
@ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)

src/main/java/top/whgojp/modules/xss/controller/StoreController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public String xssStore() {
4545
return "vul/xss/store";
4646
}
4747

48-
@ApiOperation(value = "漏洞场景:原生无过滤", notes = "原生漏洞场景,未加任何过滤,将用户输入存储到数据库中")
48+
@ApiOperation(value = "漏洞场景:原生无过滤", notes = "原生漏洞场景未加任何过滤,将用户输入和User-Agent持久化;后续页面不安全渲染时触发存储型XSS")
4949
@PostMapping("/vul")
5050
@ResponseBody
5151
@ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)

src/main/resources/static/js/staticcode.js

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @email: whgojp@foxmail.com
55
* @Date: 2024/5/19 19:03
66
*/
7-
const vul1ReflectRaw = "// 原生漏洞场景,未加任何过滤,Controller接口返回Json类型结果\n" +
7+
const vul1ReflectRaw = "// 原生漏洞场景未加任何过滤,Controller接口返回JSON类型结果\n" +
88
"public R vul1(String payload) {\n" +
99
" return R.ok(payload);\n" +
1010
"}\n" +
@@ -14,7 +14,7 @@ const vul1ReflectRaw = "// 原生漏洞场景,未加任何过滤,Controller接
1414
"// \"msg\": \"<script>alert(document.cookie)</script>\",\n" +
1515
"// \"code\": 0\n" +
1616
"// }\n" +
17-
"// payload在json中是不会触发xss的 需要解析到页面中\n" +
17+
"// JSON响应本身通常不会直接执行脚本;前端若把字段用innerHTML等方式写入页面,才会触发XSS\n" +
1818
"\n" +
1919
"// 原生漏洞场景,未加任何过滤,Controller接口返回String类型结果\n" +
2020
"public String vul2(String payload) {\n" +
@@ -35,7 +35,7 @@ const vul2ReflectContentType = "// Tomcat内置HttpServletResponse,Content-Typ
3535
" ...\n" +
3636
" }\n" +
3737
"}"
38-
const safe1CheckUserInput = "// 对用户输入的数据进行验证和过滤,确保不包含恶意代码。使用白名单过滤,只允许特定类型的输入,如纯文本或指定格式的数据\n" +
38+
const safe1CheckUserInput = "// 使用白名单限制输入格式,适合约束字段类型;最终仍需根据输出位置进行上下文编码\n" +
3939
"// 前端校验代码\n" +
4040
"var whitelistRegex = /^[a-zA-Z0-9_\\s]+$/;\n" +
4141
"\n" +
@@ -55,19 +55,20 @@ const safe1CheckUserInput = "// 对用户输入的数据进行验证和过滤,
5555
"if (matcher.matches()){\n" +
5656
" return R.ok(payload);\n" +
5757
"}else return R.error(\"输入内容包含非法字符,请检查输入\");"
58-
const safe2CSP = "// 内容安全策略(Content Security Policy)是一种由浏览器实施的安全机制,旨在减少和防范跨站脚本攻击(XSS)等安全威胁。它通过允许网站管理员定义哪些内容来源是可信任的,从而防止恶意内容的加载和执行\n" +
58+
const safe2CSP = "// 内容安全策略(Content Security Policy)是浏览器实施的额外防护层,可降低恶意脚本加载和执行风险,但不能替代输出编码与安全DOM/模板用法\n" +
5959
"// 前端Meta配置\n" +
6060
"<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self'; script-src 'self' https://apis.example.com; style-src 'self' https://fonts.googleapis.com; img-src 'self' data: https://*.example.com;\">\n" +
6161
"\n" +
6262
"\n" +
6363
"// 后端Header配置\n" +
6464
"public String safe2(String payload,HttpServletResponse response) {\n" +
65-
" response.setHeader(\"Content-Security-Policy\",\"default-src self\");\n" +
65+
" response.setHeader(\"Content-Security-Policy\", \"default-src 'self'; script-src 'self'\");\n" +
66+
" response.setHeader(\"Content-Security-Policy-Report-Only\", \"default-src 'self'; report-uri /xss/reflect/csp-report-endpoint\");\n" +
6667
" return payload;\n" +
6768
"}"
6869

69-
const safe3EntityEscape = '// 特殊字符实体转义是一种将HTML中的特殊字符转换为预定义实体表示的过程\n' +
70-
'// 这种转义是为了确保在HTML页面中正确显示特定字符,同时避免它们被浏览器误解为HTML标签或JavaScript代码的一部分,从而导致页面结构混乱或安全漏洞\n' +
70+
const safe3EntityEscape = '// HTML正文输出编码会将特殊字符转换为HTML实体,避免浏览器把不可信数据解析为标签或脚本\n' +
71+
'// 注意:HTML属性、URL、JavaScript字符串、CSS等不同上下文需要使用不同的编码或白名单校验策略\n' +
7172
'public R safe3(@ApiParam(String type, String payload) {\n' +
7273
' String filterContented = "";\n' +
7374
' switch (type){\n' +
@@ -87,7 +88,7 @@ const safe3EntityEscape = '// 特殊字符实体转义是一种将HTML中的特
8788
' }\n' +
8889
'}'
8990

90-
const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险\n" +
91+
const safe4HttpOnly = "// HttpOnly可以阻止客户端脚本直接读取带有该属性的Cookie,降低XSS窃取Cookie的影响,但不能修复XSS本身\n" +
9192
"// 单个接口配置\n" +
9293
"public R safe4(String payload, HttpServletRequest request,HttpServletResponse response) {\n" +
9394
" Cookie cookie = request.getCookies()[ueditor];\n" +
@@ -118,7 +119,7 @@ const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用
118119
" ...\n" +
119120
"}"
120121

121-
const vul1StoreRaw = "// 原生漏洞场景,未加任何过滤,将用户输入存储到数据库中\n" +
122+
const vul1StoreRaw = "// 原生漏洞场景未加任何过滤,将用户输入和User-Agent持久化;后续页面不安全渲染时触发存储型XSS\n" +
122123
"// Controller层\n" +
123124
"public R vul(String payload,HttpServletRequest request) {\n" +
124125
" String ua = request.getHeader(\"User-Agent\");\n" +
@@ -139,7 +140,7 @@ const vul1StoreRaw = "// 原生漏洞场景,未加任何过滤,将用户输入
139140
" values (#{content,jdbcType=VARCHAR},#{ua,jdbcType=VARCHAR}, #{date,jdbcType=VARCHAR})\n" +
140141
"</insert>"
141142

142-
const safe1StoreEntityEscape = "// 表格数据渲染\n" +
143+
const safe1StoreEntityEscape = "// 表格数据渲染:数据库仍保存原始值,输出到HTML页面前按HTML正文文本编码\n" +
143144
"table.render({\n" +
144145
"\t...\n" +
145146
" cols: [\n" +
@@ -151,10 +152,13 @@ const safe1StoreEntityEscape = "// 表格数据渲染\n" +
151152
" return escapeHtml(d.ua); \n" +
152153
" }},\n" +
153154
" \t...\n" +
154-
"// 方法一、HTML 实体转义函数\n" +
155+
"// 方法一、HTML正文输出编码函数\n" +
155156
"function escapeHtml(html) {\n" +
157+
" if (html === null || html === undefined) {\n" +
158+
" return '';\n" +
159+
" }\n" +
156160
" var text = document.createElement(\"textarea\");\n" +
157-
" text.textContent = html;\n" +
161+
" text.textContent = String(html);\n" +
158162
" return text.innerHTML;\n" +
159163
"}\n" +
160164
"// 方法二、JavaScript的文本节点\n" +
@@ -210,11 +214,38 @@ const vul1DomRaw = "// 1. innerHTML XSS\n" +
210214
"});\n" +
211215
"form.on('submit(document-domain)', function(data) {\n" +
212216
" var payload = data.field.documentPayload;\n" +
213-
" document.domain = payload; // 漏洞点:直接修改document.domain\n" +
217+
" document.domain = payload; // 风险点:直接修改document.domain会放宽同源边界或造成异常行为\n" +
214218
" return false;\n" +
215219
"});"
216220

217-
const vul1OtherUpload = "public String uploadFile(MultipartFile file, String suffix,String path) throws IOException {\n" +
221+
const safeDomCode = "// 1. 普通文本输出:使用textContent,不解析HTML\n" +
222+
"document.getElementById('safe-dom-result').textContent = userInput;\n" +
223+
"\n" +
224+
"// 2. URL跳转:校验协议白名单,拒绝javascript:、data:等危险协议\n" +
225+
"var url = new URL(userInput, window.location.origin);\n" +
226+
"var allowedProtocols = ['http:', 'https:'];\n" +
227+
"if (allowedProtocols.indexOf(url.protocol) === -1) {\n" +
228+
" throw new Error('dangerous protocol');\n" +
229+
"}\n" +
230+
"\n" +
231+
"// 3. 替代eval:使用命令白名单映射,而不是执行用户输入\n" +
232+
"var actions = {\n" +
233+
" showTime: function () { return new Date().toLocaleString(); },\n" +
234+
" showLocation: function () { return window.location.pathname; }\n" +
235+
"};\n" +
236+
"var action = actions[userInput];\n" +
237+
"if (action) {\n" +
238+
" action();\n" +
239+
"}\n" +
240+
"\n" +
241+
"// 4. DOM API:创建文本节点,不拼接HTML字符串\n" +
242+
"var node = document.createTextNode(userInput);\n" +
243+
"element.appendChild(node);\n" +
244+
"\n" +
245+
"// 如果业务必须展示富文本,应先使用白名单HTML净化库处理后再渲染\n"
246+
247+
const vul1OtherUpload = "// 上传可被浏览器或预览服务解析的HTML/SVG/XML/PDF等文件,后续访问文件时可能触发XSS或内容安全问题\n" +
248+
"public String uploadFile(MultipartFile file, String suffix,String path) throws IOException {\n" +
218249
" String uploadFolderPath = sysConstant.getUploadFolder();\n" +
219250
" try {\n" +
220251
" String fileName = +DateUtil.current() + \".\"+suffix;\n" +
@@ -230,7 +261,8 @@ const vul1OtherUpload = "public String uploadFile(MultipartFile file, String suf
230261
" }\n" +
231262
"}"
232263

233-
const vul2OtherTemplate = "public String handleTemplateInjection(String payload,String type, Model model) {\n" +
264+
const vul2OtherTemplate = "// th:utext会把内容作为HTML渲染;th:text会进行HTML转义\n" +
265+
"public String handleTemplateInjection(String payload,String type, Model model) {\n" +
234266
" if (\"html\".equals(type)) {\n" +
235267
" model.addAttribute(\"html\", payload);\n" +
236268
" } else if (\"text\".equals(type)) {\n" +

src/main/resources/templates/vul/xss/dom-href.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ <h1>Dom型XSS-href跳转demo</h1>
1414
</div>
1515
<div class="layui-card-body">
1616
<p>
17-
hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)。当其作为可控参数传入eval()时则会存在DOM型XSS漏洞
17+
hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)。当其作为可控参数进入 location.href、innerHTML、eval() 等危险 Sink 时,可能产生DOM型XSS漏洞
1818
通过 <code>location.hash</code> 的方式,将参数写在 <code>#</code> 号后,既能让JS读取到该参数,
19-
又不让该参数传入到服务器,从而避免了WAF的检测
19+
又不让该参数传入到服务器,因此服务端日志或部分网关/WAF可能看不到这段载荷
2020
</p>
2121
<p>
2222
变量<code>hash</code>作为可控部分,并带入url中,变量<code>hash</code>控制的是<code>#</code>之后的部分,
@@ -38,4 +38,4 @@ <h1>Dom型XSS-href跳转demo</h1>
3838
}
3939
</script>
4040
</body>
41-
</html>
41+
</html>

0 commit comments

Comments
 (0)