博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring MVC防御CSRF、XSS和SQL注入攻击
阅读量:6273 次
发布时间:2019-06-22

本文共 9721 字,大约阅读时间需要 32 分钟。

hot3.png

说说CSRF

对来说,其实、、Rails、等都已经支持自动在涉及POST的地方添加Token(包括FORM表单和AJAX POST等),似乎是一个tag的事情,但如果了解一些实现原理,手工来处理,也是有好处的。因为其实很多人做web开发,但涉及到web安全方面的都是比较资深的开发人员,很多人安全意识非常薄弱,所以对他们来说,已经是比较高深的东西了。先说说什么是?你这可以这么理解攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。一般都是利用你的已登录已验证的身份来发送恶意请求。比较著名的一个例子就是2009年黑客利用Gmail的一个漏洞成功获取好莱坞明星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" />

更多信息查看的

第二种方法是手动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(、版权购买和破解、注册码自己解决)

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

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

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

转载于:https://my.oschina.net/swchenml/blog/474373

你可能感兴趣的文章
PHP字符编码转换类3
查看>>
【2016阿里安全峰会】解读安全人才缺乏困境破解之法【附PDF下载】
查看>>
50条大牛C++编程开发学习建议
查看>>
rsync同步服务配置手记
查看>>
Android下创建一个sqlite数据库
查看>>
数组<=>xml 相互转换
查看>>
MFC单文档应用程序显示图像
查看>>
DT科技评论:第2期
查看>>
poj 2777(线段树的节点更新策略)
查看>>
Swift-EasingAnimation
查看>>
[翻译] BKZoomView
查看>>
C++类设计的一些心得
查看>>
tableVIew删除时的delete按钮被挡住时重写的方法
查看>>
读cookie中文字符乱码问题
查看>>
招募译者翻译并发数据结构
查看>>
普通表转换为分区表
查看>>
Java 容器 & 泛型:三、HashSet,TreeSet 和 LinkedHashSet比较
查看>>
性能优化总结(六):预加载、聚合SQL应用实例
查看>>
Drill官网文档翻译四 Drill的性能
查看>>
一步一步教你用PHP+MySql搭建网站 No.1 主页&amp;数据库连接
查看>>