首页 > Asp.net MVC 3 防止 Cross-Site Request Forgery (CSRF)原理及扩展 安全 注入

Asp.net MVC 3 防止 Cross-Site Request Forgery (CSRF)原理及扩展 安全 注入

原理:http://blog.csdn.net/cpytiger/article/details/8781457

原文地址:http://www.cnblogs.com/wintersun/archive/2011/12/09/2282675.html

   Cross-Site Request Forgery (CSRF) 是我们Web站点中常见的安全隐患。 下面我们在Asp.net MVC3 来演示一下。 例如我们有一个HomeContoller中一个Submit Action,我们标记了Http Post

[HttpPost]
public ActionResult Submit(FormCollection fc)
{ 
    if (!string.IsNullOrEmpty(fc["Title"]))
    { 
        ViewBag.Message = "Submit success!";
        return View("Index");
    }
    return View("Error");
}

在View 使用Razor 简单提交是这样:

@using (Html.BeginForm("Submit", "Home"))
  { 
      @Html.TextBox("Title","text");               
      
  }

点击这个Button我们就提交表单了,接下来我们轻易使用Fiddler来伪造这个Http Post请求:

image 



然后提交,成功了,返回 OK.

POST http://localhost:55181/Home/Submit  HTTP/1.1 

User-Agent: Fiddler 

Host: localhost:55181 

Content-Length: 10

Title=text



那在Asp.net MVC 3 Web Application中如何防止呢?在View中使用

@Html.AntiForgeryToken()

这时当Web应用程序运行时,查看生成HTML,你会看到form标签后有一个hidden input标签

 
 value="WiB+H5TNp6V27ALYB3z/1nkD9BLaZIBbWQOBEllj2R/+MkGZqOjLbIof2MJeEoyUJV2ljujNR4etYV6idzji
G4+JL77P9qmeewc4Erh8LnMBHX6zLas2L67GDhvCom0dpiDZl0cH+PykIC/R+HYzEIUTK/thXuF8OUtLwIfKdly0650U
3I7MD6/cIc5aersJBMZ/p6gv76gc6nvKJDt2w0eMy3tkEfAcnNPTdeWr59Ns+48gsGpZ2GSh6G+Uh7rb" />
        

看源代码是GetHtml方法序列化相应值生成的,

public HtmlString GetHtml(HttpContextBase httpContext, string salt, string domain, string path)
{ 
    Debug.Assert(httpContext != null);
 
    string formValue = GetAntiForgeryTokenAndSetCookie(httpContext, salt, domain, path);
    string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
 
    TagBuilder builder = new TagBuilder("input");
    builder.Attributes["type"] = "hidden";
    builder.Attributes["name"] = fieldName;
    builder.Attributes["value"] = formValue;
    return new HtmlString(builder.ToString(TagRenderMode.SelfClosing));
}

同时还写Cookies

__RequestVerificationToken_Lw__=T37bfAdCkz0o1iXbAvH4v0bdpGQxfZP2PI5aTJgLL/Yhr3128FUY+fvUPApBqz7CGd2uxPiW+lsZ5tvRbeLSetARbHGxPRqiw4LZiPpWrpU9XY8NO4aZzNAdMe+l3q5EMw2iIFB/6UfriWxD7X7n/8P43LJ4tkGgv6BbrGWmKFo= 



更多细节,请查询源代码。然后在Action上增加 [ValidateAntiForgeryToken] 就可以了,它是这样工作的: 



   1: public void Validate(HttpContextBase context, string salt) { 
   2:     Debug.Assert(context != null);
   3:  
   4:     string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
   5:     string cookieName = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);
   6:  
   7:     HttpCookie cookie = context.Request.Cookies[cookieName];
   8:     if (cookie == null || String.IsNullOrEmpty(cookie.Value)) { 
   9:         // error: cookie token is missing
  10:         throw CreateValidationException();
  11:     }
  12:     AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);
  13:  
  14:     string formValue = context.Request.Form[fieldName];
  15:     if (String.IsNullOrEmpty(formValue)) { 
  16:         // error: form token is missing
  17:         throw CreateValidationException();
  18:     }
  19:     AntiForgeryData formToken = Serializer.Deserialize(formValue);
  20:  
  21:     if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal)) { 
  22:         // error: form token does not match cookie token
  23:         throw CreateValidationException();
  24:     }
  25:  
  26:     string currentUsername = AntiForgeryData.GetUsername(context.User);
  27:     if (!String.Equals(formToken.Username, currentUsername, StringComparison.OrdinalIgnoreCase)) { 
  28:         // error: form token is not valid for this user
  29:         // (don't care about cookie token)
  30:         throw CreateValidationException();
  31:     }
  32:  
  33:     if (!String.Equals(salt ?? String.Empty, formToken.Salt, StringComparison.Ordinal)) { 
  34:         // error: custom validation failed
  35:         throw CreateValidationException();
  36:     }
  37: }


从Cookie中获得之前序列化存入的Token,然后反序列化与表单提交的Token进行对比。 接着,又对当前请求的用户认证进行确认。 最后看有没有设置Salt,有的话再进行比较。其中有一步验证没有通过,则throw异常。 

有时的需求是这样的,我们需要使用Session验证用户,那么我们可在上面方法修改增加下面的代码块,意图是对比之前Session值是否与当前认证后Session值相等: 

//verify session 
if (!String.Equals(formToken.SessionId, AntiForgeryData.GetGUIDString(), StringComparison.Ordinal))
{ 
    throw CreateValidationException();
}


在修改AntiForgeryDataSerializer类,它负责序列化,这里我们增加了SessionId属性: 

   1: internal class AntiForgeryDataSerializer
   2:   { 
   3:       [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")]
   4:       public virtual AntiForgeryData Deserialize(string serializedToken)
   5:       { 
   6:           if (String.IsNullOrEmpty(serializedToken))
   7:           { 
   8:               throw new ArgumentException("Argument_Cannot_Be_Null_Or_Empty", "serializedToken");
   9:           }
  10:  
  11:           try
  12:           { 
  13:               using (MemoryStream stream = new MemoryStream(Decoder(serializedToken)))
  14:               using (BinaryReader reader = new BinaryReader(stream))
  15:               { 
  16:                   return new AntiForgeryData
  17:                   { 
  18:                       Salt = reader.ReadString(),
  19:                       Value = reader.ReadString(),
  20:                       CreationDate = new DateTime(reader.ReadInt64()),
  21:                       Username = reader.ReadString(),
  22:                       SessionId=reader.ReadString()
  23:                   };
  24:               }
  25:           }
  26:           catch (Exception ex)
  27:           { 
  28:               throw new System.Web.Mvc.HttpAntiForgeryException("AntiForgeryToken_ValidationFailed", ex);
  29:           }
  30:       }
  31:  
  32:       [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")]
  33:       public virtual string Serialize(AntiForgeryData token)
  34:       { 
  35:           if (token == null)
  36:           { 
  37:               throw new ArgumentNullException("token");
  38:           }
  39:  
  40:           using (MemoryStream stream = new MemoryStream())
  41:           using (BinaryWriter writer = new BinaryWriter(stream))
  42:           { 
  43:               writer.Write(token.Salt);
  44:               writer.Write(token.Value);
  45:               writer.Write(token.CreationDate.Ticks);
  46:               writer.Write(token.Username);
  47:               writer.Write(token.SessionId);
  48:  
  49:               return Encoder(stream.ToArray());
  50:           }
  51:       }
  52: }

在View这样使用,并引入Salt,这使得我们安全机制又提升了一点儿。 

@using (Html.BeginForm("Submit2", "Home"))
  { 
      @Html.AntiForgeryToken(DebugMvc.Controllers.Config.SALT);                          
      @Html.TextBox("Title","text");                  
      
      
        
  }

Action的特性上,我们也配置对应的Salt字符串:

[HttpPost]
[ValidateAntiForgeryToken(Salt = Config.SALT)]
public ActionResult Submit2(FormCollection fc)
{ 
    if (!string.IsNullOrEmpty(fc["Title"]))
    { 
        ViewBag.Message = "Submit success!";
        return View("Index");
    }
    return View("Error");
}
配置类:

public class Config
{ 
    public const string SALT = "Why you are here";
}

这个实现一个简单的Session在HttpModule中,

public class MySessionModule:IHttpModule
{ 
    #region IHttpModule Members
 
    public void Dispose(){}
 
    public void Init(HttpApplication context)
    { 
        context.AcquireRequestState += new EventHandler(this.AcquireRequestState);
    }
 
    #endregion
 
    protected void AcquireRequestState(object sender, EventArgs e)
    { 
        HttpApplication httpApp = (HttpApplication)sender;
        if (httpApp.Context.CurrentHandler is IRequiresSessionState)
        { 
            if (httpApp.Session.IsNewSession)
            { 
                httpApp.Session["GUID"] = Guid.NewGuid();
            }
 
        }
    }
}

这时我们再使用Fiddler模拟请求POST到这个Action,后得到下面的结果,这个异常信息也是可以修改的:

AntiForgeryToken_ValidationFailed

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.Web.Mvc.HttpAntiForgeryException: AntiForgeryToken_ValidationFailed



最后让我们来看单元测试的代码:

   1: namespace DebugMvc.Ut
   2: { 
   3:     using System;
   4:     using System.Collections.Generic;
   5:     using System.Linq;
   6:     using System.Web;
   7:     using Microsoft.VisualStudio.TestTools.UnitTesting;
   8:     using DebugMvc.Controllers;
   9:     using System.Web.Mvc;
  10:     using Moq;
  11:     using System.Collections.Specialized;
  12:     using Match = System.Text.RegularExpressions.Match;
  13:     using System.Text.RegularExpressions;
  14:     using System.Globalization;
  15:  
  16:     [TestClass]
  17:     public class UnitTestForAll
  18:     { 
  19:         private static string _antiForgeryTokenCookieName = AntiForgeryData.GetAntiForgeryTokenName("/SomeAppPath");
  20:         private const string _serializedValuePrefix = @" 
  21:         private const string _someValueSuffix = @", Value: some value, Salt: some other salt, Username: username"" />";
  22:         private readonly Regex _randomFormValueSuffixRegex = new Regex(@", Value: (?[A-Za-z0-9/+=]{24}), Salt: some other salt, Username: username"" />$");
  23:         private readonly Regex _randomCookieValueSuffixRegex = new Regex(@", Value: (?[A-Za-z0-9/+=]{24}), Salt: ");
  24:  
  25:         [TestMethod]
  26:         public void TestValidateAntiForgeryToken2Attribute()
  27:         { 
  28:             //arrange
  29:             var mockHttpContext = new Mock();
  30:  
  31:             var context = mockHttpContext.Object;
  32:             var authorizationContextMock = new Mock();
  33:             authorizationContextMock.SetupGet(ac => ac.HttpContext).Returns(context);
  34:  
  35:             bool validateCalled = false;
  36:             Action validateMethod = (c, s) =>
  37:             { 
  38:                 Assert.AreSame(context, c);
  39:                 Assert.AreEqual("some salt", s);
  40:                 validateCalled = true;
  41:             };
  42:             var attribute = new ValidateAntiForgeryToken2Attribute(validateMethod)
  43:             { 
  44:                 Salt = "some salt"
  45:             };
  46:  
  47:             // Act
  48:             attribute.OnAuthorization(authorizationContextMock.Object);
  49:  
  50:             // Assert
  51:             Assert.IsTrue(validateCalled);
  52:         }
  53:  
  54:         [TestMethod]
  55:         public void GetHtml_ReturnsFormFieldAndSetsCookieValueIfDoesNotExist()
  56:         { 
  57:             // Arrange
  58:             AntiForgeryWorker worker = new AntiForgeryWorker()
  59:             { 
  60:                 Serializer = new DummyAntiForgeryTokenSerializer()
  61:             };
  62:             var context = CreateContext();
  63:  
  64:             // Act
  65:             string formValue = worker.GetHtml(context, "some other salt", null, null).ToHtmlString();
  66:  
  67:             // Assert
  68:             Assert.IsTrue(formValue.StartsWith(_serializedValuePrefix), "Form value prefix did not match.");
  69:  
  70:             Match formMatch = _randomFormValueSuffixRegex.Match(formValue);
  71:             string formTokenValue = formMatch.Groups["value"].Value;
  72:  
  73:             HttpCookie cookie = context.Response.Cookies[_antiForgeryTokenCookieName];
  74:             Assert.IsNotNull(cookie, "Cookie was not set correctly.");
  75:             Assert.IsTrue(cookie.HttpOnly, "Cookie should have HTTP-only flag set.");
  76:             Assert.IsTrue(String.IsNullOrEmpty(cookie.Domain), "Domain should not have been set.");
  77:             Assert.AreEqual("/", cookie.Path, "Path should have remained at '/' by default.");
  78:  
  79:             Match cookieMatch = _randomCookieValueSuffixRegex.Match(cookie.Value);
  80:             string cookieTokenValue = cookieMatch.Groups["value"].Value;
  81:  
  82:             Assert.AreEqual(formTokenValue, cookieTokenValue, "Form and cookie token values did not match.");
  83:         }
  84:  
  85:         private static HttpContextBase CreateContext(string cookieValue = null, string formValue = null, string username = "username")
  86:         { 
  87:             HttpCookieCollection requestCookies = new HttpCookieCollection();
  88:             if (!String.IsNullOrEmpty(cookieValue))
  89:             { 
  90:                 requestCookies.Set(new HttpCookie(_antiForgeryTokenCookieName, cookieValue));
  91:             }
  92:             NameValueCollection formCollection = new NameValueCollection();
  93:             if (!String.IsNullOrEmpty(formValue))
  94:             { 
  95:                 formCollection.Set(AntiForgeryData.GetAntiForgeryTokenName(null), formValue);
  96:             }
  97:  
  98:             Mock mockContext = new Mock();
  99:             mockContext.Setup(c => c.Request.ApplicationPath).Returns("/SomeAppPath");
 100:             mockContext.Setup(c => c.Request.Cookies).Returns(requestCookies);
 101:             mockContext.Setup(c => c.Request.Form).Returns(formCollection);
 102:             mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
 103:             mockContext.Setup(c => c.User.Identity.IsAuthenticated).Returns(true);
 104:             mockContext.Setup(c => c.User.Identity.Name).Returns(username);
 105:  
 106:             var sessionmock = new Mock();
 107:             sessionmock.Setup(s => s["GUID"]).Returns(Guid.NewGuid().ToString());
 108:  
 109:             mockContext.Setup(c => c.Session).Returns(sessionmock.Object);
 110:  
 111:             return mockContext.Object;
 112:         }
 113:     }
 114:  
 115:     internal class DummyAntiForgeryTokenSerializer : AntiForgeryDataSerializer
 116:     { 
 117:         public override string Serialize(AntiForgeryData token)
 118:         { 
 119:             return String.Format(CultureInfo.InvariantCulture, "Creation: {0}, Value: {1}, Salt: {2}, Username: {3}",
 120:                     token.CreationDate, token.Value, token.Salt, token.Username);
 121:         }
 122:         public override AntiForgeryData Deserialize(string serializedToken)
 123:         { 
 124:             if (serializedToken == "invalid")
 125:             { 
 126:                 throw new HttpAntiForgeryException();
 127:             }
 128:             string[] parts = serializedToken.Split(':');
 129:             return new AntiForgeryData()
 130:             { 
 131:                 CreationDate = DateTime.Parse(parts[0], CultureInfo.InvariantCulture),
 132:                 Value = parts[1],
 133:                 Salt = parts[2],
 134:                 Username = parts[3]
 135:             };
 136:         }
 137:     }
 138: }

这里只是UnitTest的一部分,使用Moq来实现Mock HttpContext,从而实现对HttpContext的单元测试。 



小结: Web站点的安全问题,不可轻视。特别现在Ajax大量应用,做好安全检测很重要。

希望对您Web开发有帮助。

转载于:https://www.cnblogs.com/niaowo/p/3782578.html

更多相关:

  • 菜鸟一枚,正在学习C++ Gui Qt4,整理很零碎,欢迎批评指正   1.窗口标题: QWidget *window = new QWidget; window->setWindowTitle("Enter Your Age"); **************************************** 关于标题...

  • 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例: 输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4 总体思路是: 比较两个链表头节点,较小的插入新链表指针之后,同时较小链表指针向后移动一位 实现如下: ListNode* mergeTwo...

  • 1.直接调用微软socket对象处理 static void Main(string[] args){try{IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });//在3721端口新建一个TcpListener对象TcpListener listener = new...

  •   现在很多地方都会用到zookeeper, 用到它的地方就是为了实现分布式。用到的场景就是服务注册,比如一个集群服务器,需要知道哪些服务器在线,哪些服务器不在线。   ZK有一个功能,就是创建临时节点,当机器启动应用的时候就会连接到一个ZK节点,然后创建一个临时节点,那么通过获取监听该路径,并且获取该路径下的节点数量就知道有哪些服务...

  • 前台到后台java时data日期类型的转化 在实体类中用@DataTimeFormat,这样设置即使传过来是空的字符串也是可以转的,要和前面传过来的格式一致,如 @XmlElement(name="BeginDate") @DateTimeFormat(pattern="yyyy-MM-dd") private Date begin...

  • importjava.security.SecureRandom;importjavax.crypto.Cipher;importjavax.crypto.SecretKey;importjavax.crypto.SecretKeyFactory;importjavax.crypto.spec.DESKeySpec;//结果与DES算...

  • 题目:替换空格 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 输入:s = "We are happy." 输出:"We%20are%20happy." 限制: 0 <= s 的长度 <= 10000 解题: 时间复杂度:O(n) 空间复杂度:O(n) class Solution { public:s...

  • 在C++11标准库中,string.h已经添加了to_string方法,方便从其他类型(如整形)快速转换成字面值。 例如: for (size_t i = 0; i < texArrSize; i++)RTX_Shader.SetInt(string("TexArr[") + to_string(i) + "]", 7 + i);...

  • Ubuntu 14.04安装并升级之后,变成楷体字体非常难看,我昨天搞了一晚上,终于理了个头绪,这里整理一下。 经过网上调研,大家的一致看法是,使用开源字体库文泉驿的微黑字体效果比较理想,甚至效果不输windows平台的雅黑字体。下面我打算微黑来美化Ubuntu 14.04. 1.安装文泉驿微黑字体库 sudo aptitude...

  • 使用string时发现了一些坑。 我们知道stl 容器并不是线程安全的,所以在使用它们的过程中往往需要一些同步机制来保证并发场景下的同步更新。 应该踩的坑还是一个不拉的踩了进去,所以还是记录一下吧。 string作为一个容器,随着我们的append 或者 针对string的+ 操作都会让string内部的数据域动态增加,而动态增加的...

  • IHostingEnviroment 获取环境相关洗洗 IsDevelopment()、IsStaging()、IsProduction() 分别为:开发、准生产、生产环境 IsEnviroment("Uat") 自定义环境,比如自定义Uat环境 新建: appsettings.Uat.json文件 {"Enviroment":...

  • 七. DockPanel DockPanel定义一个区域,在此区域中,您可以使子元素通过描点的形式排列,这些对象位于 Children 属性中。停靠面板其实就是在WinForm类似于Dock属性的元 素。DockPanel会对每个子元素进行排序,并停靠在面板的一侧,多个停靠在同侧的元素则按顺序排序。     如果将 LastChild...

  • 该链接有导入,导出源码,我的代码有下链接改写,完善而成的, http://www.cnblogs.com/colder/p/3611906.html using System;using System.Collections.Generic;using System.Linq;using System.Web;using System...

  • 转载请注明出自天外归云的博客园:http://www.cnblogs.com/LanTianYou/ 对于SharePoint中已经是Record的Item,我们想要修改他的属性,这在UI界面是无法完成的: 这时需要通过Records.BypassLocks API来完成。设计一个tool,利用Records.BypassLocks...

  • C# async await 学习笔记1(http://www.cnblogs.com/siso/p/3691059.html)  提到了ThreadId是一样的,突然想到在WinForm中,非UI线程是无法直接更新UI线程上的控件的问题。 于是做了如下测试: using System; using System.Collectio...