SSM 即 SPRING、SPRINGMVC、MYBATIS 三大框架的整合,随着其开发带来的各种便利以及好处,越来越多的公司以及RD使用其开发各种线上产品和后台管理系统,但也带来了许多安全性的问题。
虽然JAVA 本身规范化程度比较高,按照框架的标准进行开发确实能减少许多的SQL注入问题,但在其他方面基本上是没有做任何安全考虑。使用SSM 通过MYBATIS的配置生成SQL语句,基本能屏蔽掉大部分的SQL注入问题。但由于表单提交等大多使用POJO和DTO绑定的方式,虽然SPRINGMVC自带有个编码处理方法,但由于使用有限,String类型参数不会有任何的安全性处理,这样大片的XSS漏洞随之产生 ,CSRF 漏洞上,SRPINGMVC亦没有内置的接口。其他的逻辑产生的安全问题就不再这里面的分析范畴内了。
MYBATIS支持配置XML查询和通用MAPPER查询两种。使用配置XML的方式,XML 文件中,可以使用两种符号接收#和$,#符号类似于参数绑定的方式也就是JAVA的预编译处理,这样是不会带来SQL注入问题的;而$却相当于只把值传进来,不做任何处理,类似于拼接SQL语句。
测试使用相关代码如下:
1. `@RequestMapping("/get1")`
3. `public String username1(HttpServletRequest request, Model model) {`
5. `String name = request.getParameter("name");`
7. `User user = this.userserivce.getUserByUsername1(name);`
9. `model.addAttribute("user", user);`
11. `return "showuser";`
13. `}`
17. `@RequestMapping("/get2")`
19. `public String username2(HttpServletRequest request, Model model) {`
21. `String name = request.getParameter("name");`
23. `User user = this.userserivce.getUserByUsername2(name);`
25. `model.addAttribute("user", user);`
27. `return "showuser";`
29. `}`
1. `User selectByUsername1(String name);`
3. `User selectByUsername2(@Param("name") String name);`
1. `<select id="selectByUsername1" resultMap="BaseResultMap" parameterType="java.lang.String" >`
3. `select`
5. `<include refid="Base_Column_List" />`
7. `from user`
9. `where user_name = #{name,jdbcType=VARCHAR}`
11. `</select>`
15. `<select id="selectByUsername2" resultMap="BaseResultMap" parameterType="java.lang.String" >`
17. `select`
19. `<include refid="Base_Column_List" />`
21. `from user`
23. `where user_name = '${name}'`
25. `</select>`
正常访问,如图:
加入payload访问,如图:
无查询结果,如图:
可以看到数据执行的时候已经做了转义的处理。
正常访问,如图:
可以看到是拼接的SQL语句,加入payload测试访问,如图:
依然正常获取了查询结果, 已有SQL注入问题。
在开发的过程中咱们尽量使用#传参,减少$传参的使用,如有需要,也注意下输入参数的转义处理。
测试使用相关代码如下:
1. `@RequestMapping("/show")`
3. `public String show(HttpServletRequest request){`
5. `return "xssshow";`
7. `}`
11. `@RequestMapping("get")`
13. `public String get(HttpServletRequest request, Model model){`
15. `Integer id = Integer.parseInt(request.getParameter("id"));`
17. `User user = this.userserivce.getUserById(id);`
19. `model.addAttribute("user", user);`
21. `return "getuser";`
23. `}`
27. `@RequestMapping("/add")`
29. `public String add(User user){`
31. `int i = this.userserivce.insert(user);`
33. `return "redirect:/xsstest/show";`
35. `}`
插入payload测试 toor"'><svg/onload=alert(/xss/)>,
数据库里可看到并未做任何处理,如图:
查询访问,存在存储型XSS漏洞,如图:
SPRINGMVC和JSTL进行编码处理
1. `<context-param>`
3. `<param-name>defaultHtmlEscape</param-name>`
5. `<param-value>true</param-value>`
7. `</context-param>`
针对所有FORM,如下:
1. `<spring:htmlEscape defaultHtmlEscape="true" />`
针对单个INPUT等,如下:
1. `<form:input path="name" htmlEscape="true"/>`
直接输出的标签,如下:
`<c:out value="${user.userName}" />`
重写HttpServletRequestWrapper或HttpServletResponseWrapper使用FILTER进行过滤
1. `<filter>`
3. `<filter-name>XSS</filter-name>`
5. `<filter-class>ssm.sec.filter.XSSFilter</filter-class>`
7. `</filter>`
11. `<filter-mapping>`
13. `<filter-name>XSS</filter-name>`
15. `<url-pattern>/*</url-pattern>`
17. `</filter-mapping>`
1. `private static Logger logger = Logger.getLogger(RequestWrapper.class);`
3. `public RequestWrapper(HttpServletRequest servletRequest) {`
5. `super(servletRequest);`
7. `}`
9. `public String[] getParameterValues(String parameter) {`
11. `logger.info("InarameterValues .. parameter .......");`
13. `String[] values = super.getParameterValues(parameter);`
15. `if (values == null) {`
17. `return null;`
19. `}`
21. `int count = values.length;`
23. `String[] encodedValues = new String[count];`
25. `for (int i = 0; i < count; i++) {`
27. `encodedValues[i] = cleanXSS(values[i]);`
29. `}`
31. `return encodedValues;`
33. `}`
35. `public String getParameter(String parameter) {`
37. `logger.info("Inarameter .. parameter .......");`
39. `String value = super.getParameter(parameter);`
41. `if (value == null) {`
43. `return null;`
45. `}`
47. `logger.info("Inarameter RequestWrapper ........ value .......");`
49. `return cleanXSS(value);`
51. `}`
55. `public String getHeader(String name) {`
57. `logger.info("Ineader .. parameter .......");`
59. `String value = super.getHeader(name);`
61. `if (value == null)`
63. `return null;`
65. `logger.info("Ineader RequestWrapper ........... value ....");`
67. `return cleanXSS(value);`
69. `}`
73. `private String cleanXSS(String value) {`
75. `logger.info("InnXSS RequestWrapper ..............." + value);`
79. `value = value.replaceAll("eval\\((.*)\\)", "");`
81. `value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");`
85. `value = value.replaceAll("(?i)<script.*?>.*?<script.*?>", "");`
87. `value = value.replaceAll("(?i)<script.*?>.*?</script.*?>", "");`
89. `value = value.replaceAll("(?i)<.*?javascript:.*?>.*?</.*?>", "");`
91. `value = value.replaceAll("(?i)<.*?\\s+on.*?>.*?</.*?>", "");`
95. `value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");`
97. `value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");`
99. `value = value.replaceAll("'", "& #39;");`
101. `logger.info("OutnXSS RequestWrapper ........ value ......." + value);`
103. `return value;`
105. `}`
107. `}`
1. `package ssm.sec.filter;`
5. `import java.io.IOException;`
9. `import javax.servlet.Filter;`
11. `import javax.servlet.FilterChain;`
13. `import javax.servlet.FilterConfig;`
15. `import javax.servlet.ServletException;`
17. `import javax.servlet.ServletRequest;`
19. `import javax.servlet.ServletResponse;`
21. `import javax.servlet.http.HttpServletRequest;`
25. `import org.apache.log4j.Logger;`
29. `public class XSSFilter implements Filter {`
31. `private static Logger logger = Logger.getLogger(XSSFilter.class);`
33. `private FilterConfig filterConfig;`
37. `public void init(FilterConfig filterConfig) throws ServletException {`
39. `this.filterConfig = filterConfig;`
41. `}`
45. `public void destroy() {`
47. `this.filterConfig = null;`
49. `}`
53. `public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)`
55. `throws IOException, ServletException {`
57. `logger.info("Inlter CrossScriptingFilter ...............");`
59. `chain.doFilter(new RequestWrapper((HttpServletRequest) request), response);`
61. `logger.info("Outlter CrossScriptingFilter ...............");`
63. `}`
67. `}`
重新插入数据, 会发现已经进行了过滤处理,如图:
数据库查看都是过滤和转义的结果,如图:
当然有时候由于业务需要,可能允许用户输入这种危险的字符,这种通用的并不适用,但可以考虑把一些特殊的符号转义成中文符号,也许也恰好能满足业务的需求,如< 转义成 <等,看起来和英文的一样,但由于解释器并不识别, 而又变成安全的了。
SPRINGMVC 视图中传递变量一般使用EL表达式
虽然EL中内置许多默认的函数,但并没有编码处理的函数,因此只能够自定义函数进行处理。
1. `public class XSSEL {`
3. `private static Logger logger = Logger.getLogger(XSSEL.class);`
7. `public static String HTMLEncode(String val){`
9. `logger.info("In EL func values ====>"+ val);`
11. `if(val != null){`
13. `val = val.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """)`
15. `.replace("/", "/").replace("'", "'");`
17. `logger.info("Out EL func values ====>"+ val);`
19. `return val;`
21. `}else{`
23. `return "";`
25. `}`
27. `}`
29. `}`
1. `<?xml version="1.0" encoding="UTF-8"?>`
3. `<taglib xmlns="http://java.sun.com/xml/ns/j2ee"`
5. `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`
7. `xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee`
9. `http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"`
11. `version="2.0">`
15. `<tlib-version>1.0</tlib-version>`
17. `<short-name>sec</short-name>`
23. `<function>`
25. `<name>HTMLEncode</name>`
27. `<function-class>ssm.sec.utils.XSSEL</function-class>`
29. `<function-signature>java.lang.String HTMLEncode(java.lang.String)</function-signature>`
31. `</function>`
35. `</taglib>`
1. `<jsp-config>`
3. `<taglib>`
5. `<!-- 配置标签的引用地址 JSP页面中引用时使用 -->`
7. `<taglib-uri>/eltag</taglib-uri>`
9. `<!-- 配置标签的TLD文件地址 -->`
11. `<taglib-location>/WEB-INF/XSSEL.tld</taglib-location>`
13. `</taglib>`
15. `</jsp-config>`
1. `<%@ taglib uri="/eltag" prefix="sec" %>`
3. `<%@ taglib uri="/WEB-INF/XSSEL.tld" prefix="sec" %>WEB.XML文件中不配置的情况`
5. `${sec:HTMLEncode(user.userName)}`
针对复杂的业务也可以考虑定义一系列安全编码的方法,避免重复造轮子的同时也可以增强程序的安全性。
由于SPRINGMVC 本身不提供像DJANGO那样的CSRF安全接口,因此在这方面就要脆弱了许多。这和其他框架的存在和处理的方式亦差不多,也或者说写出的CSRF代码也更具有通用性。所以呢主要是浅谈对CSRF问题的处理思路以及相应的好与坏。
HTTP协议的REFERER字段会记录请求来源地址,在通常请况下,一般情况下安全请求来自于同一个网站,如果REFERER来源为第三方网站,则很可能是CSRF攻击。即使REFERER是来自于同一个网站,也有很能是站内的CSRF攻击,那么只能通过正则匹配出来源是否为我们指定的允许请求的链接。
CSRF攻击的过程是在用户不知情的情况下构造网络请求,所以验证码机制是最有效最快捷的方法。但很多时候,出于用户体验的情况下,并不能给所有的操作都添加上验证码,除非某个操作的敏感性、重要性都十分的高,那么可以考虑加上验证码的方式来验证。
Token储存在cookie中,必须考虑两个问题:
1、假如同域名下存在XSS,那么cookie则可以被盗取;
2、表单的token值如果和cookie储存的相等,那么当cookie值被盗取的时候,则可以构造出表单cookie。
针对以上两个问题,那么在cookie储存token的实现过程中,必须的有相应的解决方法:
1、对cookie值储存token设置httponly;
2、设置httponly在某些情况下还是可以泄露cookie的,因此表单的token和cookie的token得有一个加密的过程。
Token储存在session中,其实和cookie差不多是一个原理,但有点小差别的是session是储存在服务器的,因此诞生的一个问题便是在分布式环境中session不复制的话,那么此机制便失效了,并且会对正常的业务产生影响。
因此,当使用sesseion储存的时候,必须考虑搭建的web环境为非分布式环境。
One-time token即一次性token值,请求一次一页面即获取不同的token值。One-time token的难点在“并行会话”中,如用户在同一个站点当中打开两个一样的页面,那么必然两个表单内容也一样,CSRF防护措施不能导致只有一个页面才能提交表单,其它的表单都包含的是无效的token值。因此one-time token必须要在保证不影响“并行会话”的基础上实现。
以下是实现思路:
1、 生成token值,以此token值作为memcached的key和value储存;
2、在表单中加入此token值的hidden表单;
3、验证时比较表单和memcached的token值。