zoukankan      html  css  js  c++  java
  • 跨站请求伪造(CSRF)

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

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

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

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

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

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

    然后把它改成:

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

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

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

    2. 模拟一次攻击

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class CurrentUser
    {
        private static CurrentUser currentUser = new CurrentUser(){Name="张三"};
        public string Name{get;set;}
        public static CurrentUser Current
        {
            get
            {
                return currentUser;
            }
        }
    }

      显示页面为:

      

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <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去修改信息)。如:

    1
    2
    3
    4
    5
    6
    [HttpPost]
    public ActionResult Update(stringname)
    {
        CurrentUser.Current.Name = name;
        returnRedirectToAction("Index");
    }

      可以看到上面的评论区域有一个链接,来自另一个站点,它的代码很简单,与我们的修改页面类似,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <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当中。

      表单:

    1
    2
    3
    4
    5
    6
    7
    <form id="Form1" action="UpdateAntiCsrf.aspx" method="post" runat="server">
        <div>
            <%=Response.AntiForgeryToken() %>
            <input type="text" name="name"/>
            <input type="submit"/>
        </div>
    </form>

      AntiForgeryToken扩展方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static classHttpResposeExtentions
    {
        public static stringAntiForgeryToken(this HttpResponse response)
        {
            HttpContext context = HttpContext.Current;
            if (context == null)
            {
                throw newInvalidOperationException("无效请求!");
            }
            Guid guid = Guid.NewGuid();
            context.Items["_RequestVerificationToken"] = guid;
            ObjectStateFormatter formatter = newObjectStateFormatter();
            return string.Format("<input type='hidden' name='_RequestVerificationToken' value={0} />", formatter.Serialize(guid));                      
        }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    public class AntiCsrfModule : IHttpModule
    {
        public void Dispose ()
        {
        }
     
        public void Init(HttpApplication app)
        {
            app.PreRequestHandlerExecute += newEventHandler(app_PreRequestHandlerExecute);
            app.PostRequestHandlerExecute += newEventHandler(app_PostRequestHandlerExecute);
        }       
     
        voidapp_PreRequestHandlerExecute(objectsender, 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 newInvalidOperationException("无效请求!");
                    }
                    string value = request.Form["_RequestVerificationToken"];
                    if(string.IsNullOrEmpty(value))
                    {
                        throw newInvalidOperationException("无效请求!");
                    }
                    ObjectStateFormatter formatter = newObjectStateFormatter();
                    Guid? guid = formatter.Deserialize(value) asGuid?;
                    if(guid.HasValue && guid.Value.ToString() == cookie.Value)
                    {
                        return;
                    }
                    throw newInvalidOperationException("无效请求!");
                }
            }
        }
     
        voidapp_PostRequestHandlerExecute(objectsender, EventArgs e)
        {
            HttpContext context = ((HttpApplication)sender).Context;
            Guid? guid = context.Items["_RequestVerificationToken"as Guid?;
            if (guid.HasValue)
            {
                HttpCookie cookie = newHttpCookie("_RequestVerificationToken", guid.Value.ToString());
                cookie.HttpOnly =false;               
                context.Response.Cookies.Add(cookie);
            }
        }
    }  

      对于需要验证的页面,通过一个ValidateAntiForgeryAttribute特性标记,如下:

    1
    2
    3
    public classValidateAntiForgeryAttribute : Attribute
    {
    }

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

      

        3.4 Ajax 方式

      上面我们都是通过Post 表单的形式提交数据,如果是以ajax提交的呢?我们可以在后台判断请求是否是Ajax请求,如果不是则不允许操作。因为js受同源策略限制,另一个域在没有被授权的情况下,脚本是无法和本域进行通信的。也就是Another/Forgery.html可以以post的形式提交数据到我们后台,但没办法以ajax的形式提交,也没办法调用我们页面的方法或者访问dom元素。

    4. 博客园的实现

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

      表单:

      

      Cookie:

  • 相关阅读:
    使用 asp.net mvc和 jQuery UI 控件包
    ServiceStack.Redis 使用教程
    HTC T8878刷机手册
    Entity Framework CodeFirst 文章汇集
    2011年Mono发展历程
    日志管理实用程序LogExpert
    使用 NuGet 管理项目库
    WCF 4.0路由服务Routing Service
    精进不休 .NET 4.0 (1) asp.net 4.0 新特性之web.config的改进, ViewStateMode, ClientIDMode, EnablePersistedSelection, 控件的其它一些改进
    精进不休 .NET 4.0 (7) ADO.NET Entity Framework 4.0 新特性
  • 原文地址:https://www.cnblogs.com/tester-l/p/6045437.html
Copyright © 2011-2022 走看看