跨站请求伪造

1. 什么是跨站请求伪造(CSRF)javascript

  CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,一般缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS很是不一样,而且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则经过假装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击每每不大流行(所以对其进行防范的资源也至关稀少)和难以防范,因此被认为比XSS更具危险性。html

  以上是来自百度百科的概念。下面各自举个简单的例子说明:java

  XSS:假设没有预防XSS,我在文章评论区域输入:<script>while(1)alert("呵呵")</script> 而且成功提交。那么下次进来这个页面进来,就会不断弹出对话框,致使该页面没法被正常浏览;对此咱们在输出内容时,若是该内容是用户输入的,那么就应该进行Html Encode,如上面的脚本就会以普通文本的形式显示出来。不只如此,攻击脚本还能够读取用户cookie信息,作任何脚本能作的事情。ajax

  CSRF:与XSS不一样,CSRF利用的当前信任用户,让用户不知不觉的“本身提交”数据。例如用户 B 在用户 A 的文章评论区域上传一张图片,如:浏览器

[img]http://images2015.cnblogs.com/blog/798800/201512/798800-20151230205214839-1087717627.jpg[/img]安全

而后把它改为:服务器

[img]http://Another/Forgery.html[/img]cookie

这是一张无效的图片,而且来自于另外一个站点。在 Forgery.html 中,伪造者建立一个表单,action指向要攻击的页面,当window.onload 执行时自动提交表单。mvc

那么用户 A 在访问该页面时,就会发起请求到Another/Forgery.html。因为Web的身份验证信息一般会保存在浏览器cookie中,而cookie每次都会随请求提交到服务器,因此这个请求会把cookie 和 Forgery.html 的表单信息一块儿提交到服务器。服务器对Cookie进行相关验证,而且认为这是一个正常的请求,执行相关操做。app

2. 模拟一次攻击

    按照上面的思路,接下来咱们模拟一次攻击。新建一个mvc项目,主要有两个页面,一个页面用于显示用户姓名和评论,另外一个页面用于用户本身修改姓名。只是为了演示,这里咱们固定一个用户:张三。如:  

    public class CurrentUser
    {
        private static CurrentUser currentUser = new CurrentUser(){Name="张三"};
        public string Name{get;set;}
        public static CurrentUser Current
        {
            get
            {
                return currentUser;
            }
        }
    }

  显示页面为:

  

   本站点的另外一个页面用于修改用户姓名:  

    <div>
        修改用户信息:
    </div>
    <form action="/home/update" method="post">
        <p>
            <label>用户名:</label>
            <input name="name" />
        </p>
        <p>
            <input type="submit" />
        </p>
    </form>

  

  对应Action为Update,为了提升安全性,咱们给它标记一个[HttpPost]特性(实际状况这里会进行身份验证,而后根据用户id去修改信息)。如:

        [HttpPost]
        public ActionResult Update(string name)
        {
            CurrentUser.Current.Name = name;
            return RedirectToAction("Index");
        }

  能够看到上面的评论区域有一个连接,来自另外一个站点,它的代码很简单,与咱们的修改页面相似,以下:

<body>
    <div>
        <form id="form" action="http://localhost:50025/home/update" method="post">
            <input type="hidden" name="name" value="2b" />
        </form>
    </div>
</body>
<script type="text/javascript">
    window.onload = function () {
        document.getElementById("form").submit();
    }
</script>

  能够看到,该页面表达的action指向了前面的站点的修改页面,而且在页面load完后,就会自动提交。当张三点击这个连接后,会发生什么呢?以下:

 

3. 如何防止

    3.1 尽早防范

  永远不要相信用户提交的数据。一般咱们会在前台和后台对用户的输入进行验证,确保数据的正确性和安全性。以上面的例子,若是用户上传一张图片,而后修改为.html的格式,那么应该不让它保存。咱们能够看博客园的例子,若是这样作,能够保存,但显示出来就是普通文本的格式,这样页面加载时就不会对这个url发起请求。再看csdn,则会弹出提示非法输入。

  cnblogs:  

  csdn:   

  固然,这样只是第一道屏障。不少网站也可能像上面能够输入外部连接,若是用户去点击,依然可能被攻击。因此咱们还须要进一步防范。

    3.2 MVC 的作法

  前面咱们模拟了CSRF的过程,接下看MVC里如何应对这种状况。

  ValidateAntiForgeryAttribute特性

  这是一个继承了FilterAttribute 和 实现了 IActionFilter 的标记特性,它能够应用在Controller或者Action上面。咱们知道实现IActionFilter的 Filter会在 Action执行前进行相关处理,具体逻辑在 IActionFilter接口的 OnAuthorization 方法中。MVC 就是在这个方法中进行验证的。具体是如何验证的呢?

  AntiForgeryToken方法

  ValidateAntiForgery特性表示操做须要验证,咱们还须要使用HtmlHelper的 AntiForgeryToken方法,这是一个实例方法。具体是在View的表单里调用该方法,该方法会生成一个name为__RequestVerificationToken的 input hidden标签,值就是防伪令牌。除此以外,还会生成一个一样名称而且标记为HttpOnly的cookie,值也是经过加密生成的防伪令牌。ValidateAntiForgery特性的OnAuthorization方法就是根据这两个进行验证的。具体是:

  1. 用户请求该页面,AntiForgeryToken方法会生成一个input hidden 和 cookie,值都是通过加密处理的Token。

  2. 用户提交请求,若是Action(Controller)标记了 ValidateAntiForgery特性,则进行验证。

      2.1 若是表单没有一个name为 __RequestVerificationToken的元素,则抛出HttpAntiForgeryException。

    2.2 若是没有一个name为__RequestVerificationToken的cookie,则抛出HttpAntiForgeryException。

    2.3 解析input 和 cookie 的值,判断是否匹配(包括用户名、时间等的比较),不匹配则抛出HttpAntiForgeryException。

  3. 接收到异常,显示错误页或抛出黄页。

  至于 input 的值 和 cookie 的生成,mvc内部会根据当前用户名,时间以及集合 MachineKey 等去加密生成,确保不会轻易被猜出。有兴趣的朋友能够经过源码了解详细过程。

  按照上面的作法,咱们给Update加上一个[ValidateAntiForgery]特性,而且在表单调用HtmlHelper的AntiForgeryToken方法。此时若是用户点击连接,同样访问了Forgery.html,而且自动提交表单,cookie仍是同样会提交,但伪造页面没法知道input hidden 的值,因此没法经过验证。

    3.3 WebForm 的作法

  WebForm 没有 AntiForgeryToken方法 能够直接使用,不过知道MVC的实现过程后,咱们也能够本身实现一套。

  在页面表单,像mvc同样,咱们也输出一个名称为:_RequestVerificationToken 的 input hidden 标签,值为序列化后的Token,具体是调用 HttpRespose 的扩展方法AntiForgeryToken。AntiForgeryToken方法不只会输入input hidden,还会将Guid存储在Context.Item,这是一个在一次请求内各个时期可使用的集合,在页面周期完成后,咱们判断是否有这个标记,若是有,还须要将它写入到Cookie当中。

  表单:

    <form id="Form1" action="UpdateAntiCsrf.aspx" method="post" runat="server">
        <div>
            <%=Response.AntiForgeryToken() %>
            <input type="text" name="name"/>
            <input type="submit"/>
        </div>
    </form>

  AntiForgeryToken扩展方法:

    public static class HttpResposeExtentions
    {
        public static string AntiForgeryToken(this HttpResponse response)
        {
            HttpContext context = HttpContext.Current;
            if (context == null)
            {
                throw new InvalidOperationException("无效请求!");
            }
            Guid guid = Guid.NewGuid();
            context.Items["_RequestVerificationToken"] = guid;
            ObjectStateFormatter formatter = new ObjectStateFormatter();
            return string.Format("<input type='hidden' name='_RequestVerificationToken' value={0} />", formatter.Serialize(guid));                       
        }
    }

  对于验证Token,和将GUID写入到Cookie是经过一个AntiCsrfModule完成的,它主要拦截页面执行前和执行后两个事件。页面执行后完成上面是否须要将GUID写入Cookie的判断,而页面执行前则判断是否须要验证,以及验证结果,一旦不匹配,就抛出异常。代码以下:

    public class AntiCsrfModule : IHttpModule
    {
        public void Dispose ()
        {
        }

        public void Init(HttpApplication app) 
        {
            app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);
            app.PostRequestHandlerExecute += new EventHandler(app_PostRequestHandlerExecute);
        }        

        void app_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            HttpContext context = ((HttpApplication)sender).Context;
            HttpRequest request = context.Request;
            IHttpHandler handler = context.Handler;
            if (handler.GetType().IsDefined(typeof(ValidationAntiForgeryAttribute), true))
            {
                if (request.HttpMethod.Equals("POST", StringComparison.CurrentCultureIgnoreCase))
                {
                    HttpCookie cookie = request.Cookies["_RequestVerificationToken"];
                    if (cookie == null)
                    {
                        throw new InvalidOperationException("无效请求!");
                    }
                    string value = request.Form["_RequestVerificationToken"];
                    if (string.IsNullOrEmpty(value))
                    {
                        throw new InvalidOperationException("无效请求!");
                    }
                    ObjectStateFormatter formatter = new ObjectStateFormatter();
                    Guid? guid = formatter.Deserialize(value) as Guid?;
                    if(guid.HasValue && guid.Value.ToString() == cookie.Value)
                    {
                        return;
                    }
                    throw new InvalidOperationException("无效请求!");
                }
            }
        }

        void app_PostRequestHandlerExecute(object sender, EventArgs e)
        {
            HttpContext context = ((HttpApplication)sender).Context;
            Guid? guid = context.Items["_RequestVerificationToken"] as Guid?;
            if (guid.HasValue)
            {
                HttpCookie cookie = new HttpCookie("_RequestVerificationToken", guid.Value.ToString());
                cookie.HttpOnly = false;                
                context.Response.Cookies.Add(cookie);
            }
        }
    }  

  对于须要验证的页面,经过一个ValidateAntiForgeryAttribute特性标记,以下:

    public class ValidateAntiForgeryAttribute : Attribute
    {
    }

  一样,咱们像前面同样模拟一次攻击。结果如咱们所想,会抛出黄页。

  

    3.4 Ajax 方式

  上面咱们都是经过Post 表单的形式提交数据,若是是以ajax提交的呢?咱们能够在后台判断请求是不是Ajax请求,若是不是则不容许操做。由于js受同源策略限制,另外一个域在没有被受权的状况下,脚本是没法和本域进行通讯的。也就是Another/Forgery.html能够以post的形式提交数据到咱们后台,但没办法以ajax的形式提交,也没办法调用咱们页面的方法或者访问dom元素。

4. 博客园的实现

  例子就在身边。咱们看到博客园【设置基本资料】模块,查看源码就会发现这里用用到了这个技术。

  表单:

  

  Cookie:

相关文章
相关标签/搜索