XSS, 跨站脚本攻击, 简单来说, 就是非本站点的脚本被执行了。 关于XSS 的详细介绍和防御参考: ![](https://img.kancloud.cn/43/58/4358b07e7d7def4a15bd89d4cb156ffe_600x460.png) [XSS(跨站脚本)攻击与预防](https://blog.csdn.net/oscar999/article/details/127397397) 和 [跨站脚本攻击(XSS)及防范措施](https://blog.csdn.net/oscar999/article/details/105369085)。 本篇介绍在Java 项目中如何快速修复XSS 漏洞。本篇使用的是黑名单的方式, 对于非法字符进行转义。 黑名单的方式虽然不能完全的解决XSS的漏洞, 但是能有效的减轻攻击, 对于使用类似Coverity等代码静态扫描攻击扫描的漏洞, 修复之后就不会再报相关的警报了。 ## 个别代码处理 如果整个项目中仅有几处被扫描出来存在XSS 攻击漏洞, 以基于Spring 项目的请求方法为例,下面的代码是存在XSS 漏洞的。 ``` @RequestMapping("/unsafe") public String unsafe(@RequestParam(required = false) String input) { String s = "Hello"; s += input; return s; } ``` 上面的代码中, 如果 input 包含了 `<script>`的脚本语句的话, 则这个响应返回给请求方的浏览器端, 相关的脚本就有可能被执行。 解决方法就是对返回的字符串进行黑名单过滤, 该怎么处理字符串呢? 内置tomcat 的jar 档- tomcat-embed-core-9.0.14.jar 提供了一个类: org.apache.tomcat.util.security.Escape , 该类中的htmlElementContent() 方法实现了对特殊字符转义,仿造这个方法写一个类似的htmlElementContent() , 完整代码如下: ``` public class SecurityUtil { /** * XSS 防御, 黑名单, 将非法字符进行替换 * @param content * @return */ public static String htmlElementContent(String content) { if (content == null) { return null; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < content.length(); i++) { char c = content.charAt(i); if (c == '<') { sb.append("&lt;"); } else if (c == '>') { sb.append("&gt;"); } else if (c == '\'') { sb.append("&#39;"); } else if (c == '&') { sb.append("&amp;"); } else if (c == '"') { sb.append("&quot;"); } else if (c == '/') { sb.append("&#47;"); } else { sb.append(c); } } return (sb.length() > content.length()) ? sb.toString() : content; } } ``` 对接口方法进行改写,响应的字符串通过htmlElementContent() 进行处理后则基本是安全的, 如下代码: ``` @RequestMapping("/safe") public String safe(@RequestParam(required = false) String input) { String s = "Hello"; s += input; return SecurityUtil.htmlElementContent(s); } ``` ## 过滤器统一处理 如果是Java Web项目中, 如果每一个请求处理方法都使用上面方式进行处理的话, 则需要处理的地方太多了, 比较好的方式是使用过滤器进行统一处理。 过滤器可以对请求和响应进行XSS 防御的处理, 比如定义一个XssFilter 如下: ``` /**   * @Title: XssFilter.java * @Package com.osxm.websecurity.xss * @Description: TODO * @author XM * @date 2023年1月8日 下午8:09:35 * @Copyright: 2023 * @version V1.0   */ package com.osxm.websecurity.xss; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; /**  * @ClassName XssFilter  * @Description TODO  * @author XM  * @date 2023年1月8日  *   */ public class XssFilter { FilterConfig filterConfig = null; public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } public void destroy() { this.filterConfig = null; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new XssWrapper((HttpServletRequest) request), response); } } ``` 在过滤器处理方法中, 使用XssWrapper对请求进行Xss 过滤, XssWrapper的内容如下: ``` /**   * @Title: XssWrapper.java * @Package com.osxm.websecurity.xss * @Description: TODO * @author XM * @date 2023年1月8日 下午8:10:49 * @Copyright: 2023 * @version V1.0   */ package com.osxm.websecurity.xss; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; /**  * @ClassName XssWrapper  * @Description TODO  * @author XM  * @date 2023年1月8日  *   */ public class XssWrapper extends HttpServletRequestWrapper { public static final String JSON_TYPE = "application/json"; public static final String CONTENT_TYPE = "Content-Type"; public static final String CHARSET = "UTF-8"; private String mBody; HttpServletRequest originalRequest = null; public XssWrapper(HttpServletRequest request) throws IOException { super(request); originalRequest = request; setRequestBody(request.getInputStream()); } /** * 获取最原始的request。已经被getInputStream()了。 * * @return */ public HttpServletRequest getOrgRequest() { return originalRequest; } /** * 获取最原始的request的静态方法。已经被getInputStream()了。 * * @return */ public static HttpServletRequest getOriginalRequest(HttpServletRequest req) { if (req instanceof XssWrapper) { return ((XssWrapper) req).getOrgRequest(); } return req; } @Override public String getHeader(String name) { String value = super.getHeader(name); if (StringUtils.isBlank(value)) { return value; } return StringEscapeUtils.escapeHtml4(value); } @Override public String getQueryString() { return StringUtils.isBlank(super.getQueryString()) ? "" : StringEscapeUtils.escapeHtml4(super.getQueryString()); } @Override public String getParameter(String name) { String value = super.getParameter(name); if (StringUtils.isBlank(value)) { return value; } return StringEscapeUtils.escapeHtml4(value); } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values == null) { return values; } for (int i = 0; i < values.length; i++) { values[i] = StringEscapeUtils.escapeHtml4(values[i]); } return values; } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> map = new LinkedHashMap<String, String[]>(); Map<String, String[]> parameterMap = super.getParameterMap(); if (parameterMap == null) { return super.getParameterMap(); } for (String key : parameterMap.keySet()) { String[] values = parameterMap.get(key); if (values != null && values.length > 0) { for (int i = 0; i < values.length; i++) { values[i] = StringEscapeUtils.escapeHtml4(values[i]); } } map.put(key, values); } return map; } private void setRequestBody(InputStream stream) throws JsonMappingException, JsonProcessingException { String line = ""; StringBuilder body = new StringBuilder(); // 读取POST提交的数据内容 BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.forName(CHARSET))); try { while ((line = reader.readLine()) != null) { body.append(line); } } catch (IOException e) { e.printStackTrace(); } mBody = body.toString(); if (StringUtils.isBlank(mBody)) {// 为空时,直接返回 return; } ObjectMapper objectMapper = new ObjectMapper(); Map<String, Object> map = objectMapper.readValue(mBody, Map.class); Map<String, Object> resultMap = new HashMap<>(map.size()); for (String key : map.keySet()) { Object val = map.get(key); if (map.get(key) instanceof String) { resultMap.put(key, StringEscapeUtils.escapeHtml4(val.toString())); } else { resultMap.put(key, val); } } mBody = objectMapper.writeValueAsString(resultMap); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { if (!JSON_TYPE.equalsIgnoreCase(super.getHeader(CONTENT_TYPE))) {// 非json类型,直接返回 return super.getInputStream(); } if (StringUtils.isBlank(mBody)) {// 为空时,直接返回 return super.getInputStream(); } final ByteArrayInputStream bais = new ByteArrayInputStream(mBody.getBytes(CHARSET)); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener listener) { } }; } } ``` ## 示例代码项目 * [https://github.com/osxm/websecurity_ency/tree/main/src/main/java/com/osxm/websecurity/xss](https://github.com/osxm/websecurity_ency/tree/main/src/main/java/com/osxm/websecurity/xss) ***** ***** https://www.cnblogs.com/wangsongbai/p/15065001.html