JAVA代码审计-XSS
前言
本篇记录一下java代码审计的XSS漏洞需要关注的点。
0x01 XSS漏洞分析
XSS产生
后台未对用户输入进行检查或过滤,直接把用户输入返回至前端。导致javascript代码在客户端任意执行。
代码举例
public void Message(HttpServletRequest req, HttpServletResponse resp) {
// TODO Auto-generated method stub
String message = req.getParameter("msg");
try {
resp.getWriter().print(message);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
这里获取的msg字段原封不动返回出来,如果是js代码,也会解析执行,造成反射型xss漏洞;
防御XSS
根据漏洞原因,可以采用过滤输入的方法来进行防御:
- 输入转义进行存储
- 过滤特殊字符
- 前段输入限制< `等特殊字符
具体防御采用的方式可以是:
-
全局过滤器,设置filter:
<filter> <filter-name>XssSafe</filter-name> <filter-class>XssFilter</filter-class> </filter> <filter-mapping> <filter-name>XssSafe</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
这里要注意的是,我们的配置是
/*
而不是/
,< url-pattern>/</url-pattern>
会匹配到/login
这样的路径型url,不会匹配到模式为*.jsp
这样的后缀型url,而< url-pattern>/*</url-pattern>
会匹配所有url:路径型的和后缀型的url(包括/login
,*.jsp
,*.js
和*.html
等)。然后编写过滤器的内容就行了,这个网上有写好的,可以直接拿来用,如下:
//XssFilter实现: public class XssFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response); } } //XssHttpServletRequestWrapper实现 public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @SuppressWarnings("rawtypes") public Map<String,String[]> getParameterMap(){ Map<String,String[]> request_map = super.getParameterMap(); Iterator iterator = request_map.entrySet().iterator(); while(iterator.hasNext()){ Map.Entry me = (Map.Entry)iterator.next(); String[] values = (String[])me.getValue(); for(int i = 0 ; i < values.length ; i++){ values[i] = xssClean(values[i]); } } return request_map; } public String[] getParameterValues(String paramString) { String[] arrayOfString1 = super.getParameterValues(paramString); if (arrayOfString1 == null) return null; int i = arrayOfString1.length; String[] arrayOfString2 = new String[i]; for (int j = 0; j < i; j++){ arrayOfString2[j] = xssClean(arrayOfString1[j]); } return arrayOfString2; } public String getParameter(String paramString) { String str = super.getParameter(paramString); if (str == null) return null; return xssClean(str); } public String getHeader(String paramString) { String str = super.getHeader(paramString); if (str == null) return null; str = str.replaceAll("\r|\n", ""); return xssClean(str); } private String xssClean(String value) { //ClassLoaderUtils.getResourceAsStream("classpath:antisamy-slashdot.xml", XssHttpServletRequestWrapper.class) if (value != null) { // NOTE: It's highly recommended to use the ESAPI library and // uncomment the following line to // avoid encoded attacks. // value = encoder.canonicalize(value); value = value.replaceAll("\0", ""); // Avoid anything between script tags Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid anything in a src='...' type of expression scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid anything in a href='...' type of expression scriptPattern = Pattern.compile("href[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Remove any lonesome </script> tag scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Remove any lonesome <script ...> tag scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid eval(...) expressions scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid expression(...) expressions scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid javascript:... expressions scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid vbscript:... expressions scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid onload= expressions scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); } return value; } }
-
使用工具类xssProtect
下载对应jar包放入lib目录导入:https://code.google.com/archive/p/xssprotect/
简单用法:
protectedAgainstXSS(String html){StringReader reader = new StringReader(html); StringWriter writer = new StringWriter(); try { // 从“ html”变量解析传入的字符串 HTMLParser.process( reader, writer, new XSSFilter(), true ); // 返回经过解析和处理的字符串 return writer.toString(); } catch (HandlingException e) { } }
具体的使用方式可以参考:https://www.iteye.com/blog/liuzidong-1744023
-
使用ESAPI可以防御大多数web攻击
pom导入:
<dependency> <groupId>org.owasp.esapi</groupId> <artifactId>esapi</artifactId> <version>2.2.1.1</version> </dependency>
获取输入参数前进行一次实体编码:
public void Message(HttpServletRequest req, HttpServletResponse resp) { // TODO Auto-generated method stub String message = req.getParameter("msg"); String s = ESAPI.encoder().encodeForHTML(message); //进行实体编码 try { resp.getWriter().print(s); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
0x02 审计XSS漏洞
setAttribute
setAttribute方法用来在同一个request周期中保存变量使用,使用时再通过getAttribute方法取出。
@WebServlet("/demo")
public class xssServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");// 设置响应类型
String content = request.getParameter("content"); //获取content传参数据
request.setAttribute("content", content); //content共享到request域
request.getRequestDispatcher("/WEB-INF/pages/xss.jsp").forward(request, response); //转发到xxs.jsp页面中
}
}
jsp中:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
${requestScope.content}
</head>
<body>
</body>
</html>
ModelAndView
ModelAndView可以返回参数到指定页面的request作用域中或者指定返回的页面名称。
@RequestMapping("/test")
public ModelAndView test(){
ModelAndView mav=new ModelAndView("hello");
mav.addObject("time", new Date());
mav.getModel().put("name", "caoyc");
return mav;
}
jsp中:
time:${requestScope.time} <br/>
name:${name }
我们可以通过观察ModelAndView中传入的参数是不是可控的或者有没有什么过滤来进行审计。
可以看到不论是利用哪种方法,最后都需要取出并输出,现在用的比较广泛的除了<%out.println(msg);%>
就是el表达式,也就是说在审计xss中也可以从el表达式入手。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步