Spring MVC防御CSRF、XSS和SQL注入攻击

    xiaoxiao2021-04-17  36

    本文说一下SpringMVC如何防御CSRF(Cross-site request forgery跨站请求伪造)和XSS(Cross site script跨站脚本攻击)。

    说说CSRF

    对CSRF来说,其实Spring3.1、ASP.NET MVC3、Rails、Django等都已经支持自动在涉及POST的地方添加Token(包括FORM表单和AJAX POST等),似乎是一个tag的事情,但如果了解一些实现原理,手工来处理,也是有好处的。因为其实很多人做web开发,但涉及到web安全方面的都是比较资深的开发人员,很多人安全意识非常薄弱,CSRF是什么根本没有听说过。所以对他们来说,CSRF已经是比较高深的东西了。先说说什么是CSRF?你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。CSRF一般都是利用你的已登录已验证的身份来发送恶意请求。比较著名的一个例子就是2009年黑客利用Gmail的一个CSRF漏洞成功获取好莱坞明星Vanessa Hudgens的独家艳照。其攻击过程非常简单,给该明星的gmail账户发了一封email,标题是某大导演邀请你来看看这个电影,里面有个图片:<img src="https://mail.google.com/mail?ui=2&fw=true&fwe=hacker@email.com">,结果她登录Gmail,打开邮件就默默无闻的中招了,所有邮件被转发到黑客的账号。因为当时Gmail设置转发的设置页面有漏洞,其设置方法是打开一个窗口,点击确定后实际URL是https://mail.google.com/mail?ui=2&fw=true&fwe=newMail@email.com:

     

    其实即使不是在同一个页面打开,在不同的tab打开也是一样可以通过网站登录验证的,因为受害者首先已经登录了网站,在浏览网站的过程中,若网站设置了Session cookie,那么在浏览器进程的生命周期内,即使浏览器同一个窗口打开了新的tab页面,Session cookie也都是有效的,他们在浏览器同一个窗口的多个tab页面里面是共享的(注:现在Gmail支持多个tab同时持有多个SessionID)。所以攻击步骤是,第一,受害者必须在同一浏览器窗口(即使不是同一tab)内访问并登陆目标站点;第二,这使得Session cookie有效,从而利用受害者的身份进行恶意操作。 

    再举个实际的例子,假设我们界面上有删除某一项的链接,例如:<a href="javascript:void(0)" οnclick="region_del.do?name=0000001">Delete</a>;

    其Java Spring MVC后台有个函数是删除某个item,注意是GET不是POST:

    @RequestMapping(value = "region_del.do", method = RequestMethod.GET) public String regionDel(@RequestParam String name, Locale locale) {      // Delete region name=@name....               return "redirect:/region.html"; }

    点击界面上那个<a href="javascript:void(0)" οnclick="region_del.do?name=0000001">Delete</a>链接,就后台删除某项,看起来非常正常啊。 

    好,现在你登录你的网站,然后在另外一个tab打开这个html文件:

    <! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > < html  xmlns ="http://www.w3.org/1999/xhtml"  xml:lang ="en"  lang ="en" > < head >      < meta  http-equiv ="Content-Type"  content ="text/html; charset=utf-8"   />      < title >hack </ title > </ head > < body >    < img  src ="http://localhost/testsite/region_del.do?name=0000001" />   </ body > </ html >

    发现同样被删除了某项。试想,如果是网银,你的钱已经被转账......(除了referer不一样,session cookie被利用)

     

    好了,现在 后台改成POST(写操作尽量用POST),前台界面那个删除的链接改成Form提交:

    < form  action ="region_del.do"  method ="POST" >   < input  type ="hidden"  name ="name"  value ="0000001" >          < input  type ="submit"  value ="Delete"   /> </ form >

    看起来安全多了。OK,现在你登录你的网站,然后在另外一个tab打开这个html文件:

    <! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > < html  xmlns ="http://www.w3.org/1999/xhtml"  xml:lang ="en"  lang ="en" > < head >      < title >Hack </ title >      < script >        function  steal(){          var  mySubmit  =  document.getElementById( ' steal_form ' );         mySubmit.submit();       }      </ script >    </ head >    < body  onload ='steal()' > < form  id  = "steal_form"  method ="POST"  action ="http://localhost/testsite/region_del.do" >     < input  type ="hidden"  name ="func"  value ="post" > < input  type ="hidden"  name ="name"  value ="0000001" > </ form >    </ body > </ html >

     

    发现同样被删除了某项。试想,如果是网银,你的钱已经被转账......

    当然,你如果前台还是用链接,但改成js,用AJAX POST提交,也是一样的效果:

     

    $.ajax({  type: "POST",  url:.... });

    解决办法就是在Form表单加一个hidden field,里面是服务端生成的足够随机数的一个Token,使得黑客猜不到也无法仿照Token。 

    先写一个类,生成足够随机数的Token(注:Java的Random UUID已经足够随机了,参考这个和这个) 

    package com.ibm.cn.web.beans; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * A manager for the CSRF token for a given session. The { @link  #getTokenForSession(HttpSession)} should used to * obtain the token value for the current session (and this should be the only way to obtain the token value). * ** */ public  final  class CSRFTokenManager {      /**      * The token parameter name       */      static  final String CSRF_PARAM_NAME = "CSRFToken";      /**      * The location on the session which stores the token       */      public  static  final  String CSRF_TOKEN_FOR_SESSION_ATTR_NAME = CSRFTokenManager. class             .getName() + ".tokenval";      public  static String getTokenForSession(HttpSession session) {         String token =  null;                   //  I cannot allow more than one token on a session - in the case of two          //  requests trying to          //  init the token concurrently          synchronized (session) {             token = (String) session                     .getAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME);              if ( null == token) {                 token = UUID.randomUUID().toString();                 session.setAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME, token);             }         }          return token;     }      /**      * Extracts the token value from the session      *       *  @param  request      *  @return       */      public  static String getTokenFromRequest(HttpServletRequest request) {          return request.getParameter(CSRF_PARAM_NAME);     }      private CSRFTokenManager() {     }; }

    打开Form页面的时候在服务端生成Token并保存到Session中,例如:model.addAttribute("csrf", CSRFTokenManager.getTokenForSession(this.session));

    然后在Form中添加Hidden field: 

    <input type="hidden" name="CSRFToken" value="${csrf}" />

    然后在后台提交的时候验证token :

     

    @RequestMapping(value = "region_del.do", method = RequestMethod.GET) public String regionDel(@RequestParam String name, @RequestParam String CSRFToken, Locale locale)     {          if(CSRFToken ==  null || !CSRFToken.equals(session.getAttribute(CSRFTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME).toString())){                 logger.debug("CSRF attack detected. URL: region_edit.do");                  return "redirect:/login.form";         }                        // Delete region name=@name....               return "redirect:/region.html"; }

     

    你还可以把上面的步骤写到BaseController里面,或者写到拦截器里面,拦截所有POST请求,验证CSRF Token。这里掠过....

    如果你用AJAX POST的方法,那么后台一样,前台也要有Hidden field保存Token,然后在提交AJAX POST的时候加上该csrf参数即可。(更多csrf参考这个和这个。)

     

    AJAX POST的CSRF防御

     

    首先在页面进入的时候从后台生成一个Token(每个session),放到一个Hidden input(用Spring tag或freemarker可以写) 。然后在ajax post提交的时候放到http请求的header里面:

         var headers = {};     headers['__RequestVerificationToken'] = $("#CSRFToken").val();          $.ajax({         type: "POST",         headers: headers,         cache:  false,         url: base + "ajax/domain/delete.do",         data: "id=123",         dataType:"json",         async:  true,         error:  function(data, error) {},         success:  function(data)         {                      }     });

    然后在后台controller里面校验header里面这个token,也可以把这个函数放到baseController里面:

    protected  boolean isValidCsrfHeaderToken() {          if (getRequest().getHeader("__RequestVerificationToken") ==  null                 || session                         .getAttribute(CSRFTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME) ==  null                 || ! this.getRequest()                         .getHeader("__RequestVerificationToken")                         .equals(session                                 .getAttribute(                                         CSRFTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME)                                 .toString())) {              return  false;         }          return  true;     }

     

    xss

    关于xss的介绍可以看这个和这个网页,具体我就讲讲Spring MVC里面的预防:

    web.xml加上:

    < context-param >     < param-name >defaultHtmlEscape </ param-name >     < param-value >true </ param-value > </ context-param >

    Forms加上:

     

    <spring:htmlEscape defaultHtmlEscape="true" />

     

    更多信息查看OWASP的页面

     

    第二种方法是手动escape,例如用户可以输入:<script>alert()</script> 或者输入<h2>abc<h2>,如果有异常,显然有xss漏洞。

    首先添加一个jar包:commons-lang-2.5.jar ,然后在后台调用这些函数:StringEscapeUtils.escapeHtml(string); StringEscapeUtils.escapeJavaScript(string); StringEscapeUtils.escapeSql(string);

    前台js调用escape函数即可。

     

    第三种方法是后台加Filter,对每个post请求的参数过滤一些关键字,替换成安全的,例如:< > ' " \ /  # & 

    方法是实现一个自定义的HttpServletRequestWrapper,然后在Filter里面调用它,替换掉getParameter函数即可。

    首先添加一个XssHttpServletRequestWrapper:

    package com.ibm.web.beans; import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; public  class XssHttpServletRequestWrapper  extends HttpServletRequestWrapper {        public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {          super(servletRequest);     }      public String[] getParameterValues(String parameter) {       String[] values =  super.getParameterValues(parameter);        if (values== null)  {                    return  null;           }        int count = values.length;       String[] encodedValues =  new String[count];        for ( int i = 0; i < count; i++) {                  encodedValues[i] = cleanXSS(values[i]);        }        return encodedValues;     }      public String getParameter(String parameter) {           String value =  super.getParameter(parameter);            if (value ==  null) {                   return  null;                   }            return cleanXSS(value);     }      public String getHeader(String name) {         String value =  super.getHeader(name);          if (value ==  null)              return  null;          return cleanXSS(value);     }      private String cleanXSS(String value) {                  // You'll need to remove the spaces from the html entities below         value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");         value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");         value = value.replaceAll("'", "& #39;");         value = value.replaceAll("eval\\((.*)\\)", "");         value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");         value = value.replaceAll("script", "");          return value;     } } 

    然后添加一个过滤器XssFilter :

    package com.ibm.web.beans; import java.io.IOException;   import javax.servlet.Filter;   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;   import javax.servlet.http.HttpServletResponse; public  class XssFilter  implements Filter {     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 XssHttpServletRequestWrapper(                 (HttpServletRequest) request), response);     } }

    最后在web.xml里面配置一下,所有的请求的getParameter会被替换,如果参数里面 含有敏感词会被替换掉:

       < filter >       < filter-name >XssSqlFilter </ filter-name >       < filter-class >com.ibm.web.beans.XssFilter </ filter-class >    </ filter >    < filter-mapping >       < filter-name >XssSqlFilter </ filter-name >       < url-pattern >/* </ url-pattern >       < dispatcher >REQUEST </ dispatcher >    </ filter-mapping >

     (这个Filter也可以防止SQL注入攻击) 

     

    登录页面的攻击例子

    假设登录页面有个输入用户名和密码的输入框,可以有很多Xss/csrf/注入钓鱼网站/SQL等的攻击手段,例如:

     

     输入用户名 :    >"'><script>alert(1779)</script>  输入用户名:     usera>"'><img src="javascript:alert(23664)">  输入用户名:     "'><IMG SRC="/WF_XSRF.html--end_hig--begin_highlight_tag--hlight_tag--">  输入用户名:     usera'"><iframe src=http://demo.testfire.net--en--begin_highlight_tag--d_highlight_tag-->

     

     

    Web安全漏洞检测工具

    推荐使用IBM Rational AppScan(IBM Rational AppScan下载、版权购买和破解、注册码自己解决)

     

    可以录制脚本,设置URL,如果网站需要登录,可以设置自动登录:

     

    检测结果还可以保持为专业的pdf检测报告 

     

     

    业界安全编程标准最佳实践

    OWASP GuideSANS CWE Top 25CERT Secure CodingThe CERT Oracle Secure Coding Standard for JavaThe CERT Oracle Secure Coding Standard for C++The CERT Oracle Secure Coding Standard for CThe CERT Oracle Secure Coding Standard for Perl2011 CWE/SANS Top 25 Most Dangerous Software ErrorsTop 10 Secure Coding Practices
    转载请注明原文地址: https://ju.6miu.com/read-673250.html

    最新回复(0)