长亭百川云 - 文章详情

JAVA SSM框架常见安全风险分析

毕方安全实验室

57

2024-07-13

0x00 简介

SSM 即 SPRING、SPRINGMVC、MYBATIS 三大框架的整合,随着其开发带来的各种便利以及好处,越来越多的公司以及RD使用其开发各种线上产品和后台管理系统,但也带来了许多安全性的问题。

虽然JAVA 本身规范化程度比较高,按照框架的标准进行开发确实能减少许多的SQL注入问题,但在其他方面基本上是没有做任何安全考虑。使用SSM 通过MYBATIS的配置生成SQL语句,基本能屏蔽掉大部分的SQL注入问题。但由于表单提交等大多使用POJO和DTO绑定的方式,虽然SPRINGMVC自带有个编码处理方法,但由于使用有限,String类型参数不会有任何的安全性处理,这样大片的XSS漏洞随之产生 ,CSRF 漏洞上,SRPINGMVC亦没有内置的接口。其他的逻辑产生的安全问题就不再这里面的分析范畴内了。

0x01 SQL注入问题

MYBATIS支持配置XML查询和通用MAPPER查询两种。使用配置XML的方式,XML 文件中,可以使用两种符号接收#和$,#符号类似于参数绑定的方式也就是JAVA的预编译处理,这样是不会带来SQL注入问题的;而$却相当于只把值传进来,不做任何处理,类似于拼接SQL语句。

测试使用相关代码如下:

  • CONTROLLER代码


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.      `}`
    


  • DAO代码


1.  `User selectByUsername1(String name);`
    

3.  `User selectByUsername2(@Param("name") String name);`
    


  • MYBATIS XML 配置


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>`
    


1、测试使用#传参

正常访问,如图:

加入payload访问,如图:

无查询结果,如图:

可以看到数据执行的时候已经做了转义的处理。

2、测试使用$传参

正常访问,如图:

可以看到是拼接的SQL语句,加入payload测试访问,如图:

依然正常获取了查询结果, 已有SQL注入问题。

在开发的过程中咱们尽量使用#传参,减少$传参的使用,如有需要,也注意下输入参数的转义处理。

0x02 XSS问题

测试使用相关代码如下:

  • CONTROLLER


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漏洞,如图:

1.一些安全的措施

SPRINGMVC和JSTL进行编码处理

  • WEB.XML中添加


1.      `<context-param>`
    

3.          `<param-name>defaultHtmlEscape</param-name>`
    

5.          `<param-value>true</param-value>`
    

7.      `</context-param>`
    


  • JSP模板中添加,分为以下三种情况:

针对所有FORM,如下:



1.   `<spring:htmlEscape defaultHtmlEscape="true" />`
    


针对单个INPUT等,如下:



1.   `<form:input path="name" htmlEscape="true"/>`
    


直接输出的标签,如下:



    `<c:out value="${user.userName}" />`


重写HttpServletRequestWrapper或HttpServletResponseWrapper使用FILTER进行过滤

  • WEB.XML中添加


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>`
    


  • RequestWrapper.java


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.  `}`
    


  • XSSFilter.java


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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;")`
    

15.                      `.replace("/", "&#x2F;").replace("'", "&#x27;");`
    

17.              `logger.info("Out EL func values ====>"+ val);`
    

19.              `return val;`
    

21.          `}else{`
    

23.              `return "";`
    

25.          `}`
    

27.      `}`
    

29.  `}`
    


  • 建立TLD文件,定义表达式函数


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>`
    


  • 在WEB.XML文件中配置(可省略)


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>`
    


  • 在JSP页面内导入并且使用


1.  `<%@ taglib uri="/eltag" prefix="sec" %>`
    

3.  `<%@ taglib uri="/WEB-INF/XSSEL.tld" prefix="sec" %>WEB.XML文件中不配置的情况`
    

5.  `${sec:HTMLEncode(user.userName)}`
    


针对复杂的业务也可以考虑定义一系列安全编码的方法,避免重复造轮子的同时也可以增强程序的安全性。

0x03 CSRF问题

由于SPRINGMVC 本身不提供像DJANGO那样的CSRF安全接口,因此在这方面就要脆弱了许多。这和其他框架的存在和处理的方式亦差不多,也或者说写出的CSRF代码也更具有通用性。所以呢主要是浅谈对CSRF问题的处理思路以及相应的好与坏。

1.验证HTTP REFERER字段

HTTP协议的REFERER字段会记录请求来源地址,在通常请况下,一般情况下安全请求来自于同一个网站,如果REFERER来源为第三方网站,则很可能是CSRF攻击。即使REFERER是来自于同一个网站,也有很能是站内的CSRF攻击,那么只能通过正则匹配出来源是否为我们指定的允许请求的链接。

2.添加验证码

CSRF攻击的过程是在用户不知情的情况下构造网络请求,所以验证码机制是最有效最快捷的方法。但很多时候,出于用户体验的情况下,并不能给所有的操作都添加上验证码,除非某个操作的敏感性、重要性都十分的高,那么可以考虑加上验证码的方式来验证。

3.添加Token值

token储存在cookie中

Token储存在cookie中,必须考虑两个问题:

1、假如同域名下存在XSS,那么cookie则可以被盗取;

2、表单的token值如果和cookie储存的相等,那么当cookie值被盗取的时候,则可以构造出表单cookie。

针对以上两个问题,那么在cookie储存token的实现过程中,必须的有相应的解决方法:

1、对cookie值储存token设置httponly;

2、设置httponly在某些情况下还是可以泄露cookie的,因此表单的token和cookie的token得有一个加密的过程。

token储存在session中

Token储存在session中,其实和cookie差不多是一个原理,但有点小差别的是session是储存在服务器的,因此诞生的一个问题便是在分布式环境中session不复制的话,那么此机制便失效了,并且会对正常的业务产生影响。

因此,当使用sesseion储存的时候,必须考虑搭建的web环境为非分布式环境。

one-time token(token储存在memcache等中)

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值。

源代码 https://github.com/jige003/ssm-sec

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2