原文地址:http://weblogs.asp.net/stephenwalther/archive/2008/06/26/prevent-url-manipulation-attacks.aspx
\n
摘要:在这个Tip中,Stephen Walther介绍了黑客如何通过操作URL从ASP.NET MVC网站中窃取敏感信息。Stephen Walther还探讨了如何构建单元测试来防止这类攻击。
\n
在一个网站上,黑客可以通过URL操作攻击来简单地访问其他用户的数据。如果你通过记录的ID来获取数据记录,而又没有针对每个数据库请求检查是否是由正确的用户发起的请求,则任何人都可以读取其他用户的数据库记录。
\n
ASP.NET MVC框架的一个优势在于,它可以暴露出直观的URL。不幸的是,这个优势也可能是危险的。黑客可以通过操作URL来从一个ASP.NET MVC网站中窃取数据。
\n
我们来看一个简单的示例程序,它将面临URL操作攻击。假设你正在为医院创建网站。医院的病人可以登录网站来查看他们的病历。这个应用程序有四个视图。
\n
当病人第一次向该应用程序发起请求时,他会看到图1所示的视图。该视图包含一个链接,病人单击这个链接可以看到他的病历。
\n
图1 – Index.aspx
\n
\n
如果病人尚未登录,他将被重定向到如图2所示的Login视图。病人必须输入正确的凭证才能查看他的病历记录(凭证存放在Web.config中)。
\n
图2 – Login.aspx
\n
\n
通过验证后,病人会看到图3所示的Summary视图。该视图显示了一个列表,给出了一组指向详细病历记录的链接。数据记录的获取是根据病人的用户名进行的。
\n
图3 – Summary.aspx
\n
\n
最后,如果病人单击了一个病历记录链接,他就会看到如图4所示的Details视图。该视图显示了一条单独的记录。
\n
图4 – Details.aspx
\n
\n
这里就是黑客能够通过URL操作攻击来窃取病人数据的地方了。注意图4中用于为Phil(病人名)获取详细数据的URL。该URL看上去是这样的:
\n
http://localhost:48583/MedicalHistory/Details/6
\n
该URL非常直观。请求这个URL可以获取数据库中Id是6的数据。由于这个URL是如此的直观,你可以很容易将其修改为另外一个编号——
\n
http://localhost:48583/MedicalHistory/Details/4
\n
修改了URL之后,Phil可以看到Rob的私人病历记录,如图5所示。这恐怕不好吧。
\n
图5 – Phil可以看到Rob的私人记录
\n
\n
清单1列出了用于返回Summary和Details视图的控制器。这样编写控制器导致该医院网站为URL操作攻击敞开了大门。
\n
清单1 – MedicalHistoryCotroller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Tip10.Models;
namespace Tip10.Controllers
{
public class MedicalHistoryController : Controller
{
private readonly MedicalHistoryDataContext _db;
public MedicalHistoryController()
: this(new MedicalHistoryDataContext())
{ }
public MedicalHistoryController(MedicalHistoryDataContext dataContext)
{
_db = dataContext;
}
public ActionResult Summary()
{
// Authenticate Guard Clause
if (!User.Identity.IsAuthenticated)
{
return RedirectToAction(“Login”, “Home”);
}
// Show summary of medical history
var records = from r in _db.MedicalHistories
where r.PatientUserName == User.Identity.Name
orderby r.EntryDate
select new SummaryMedicalHistory {Id=r.Id, EntryDate=r.EntryDate, Subject=r.Subject};
return View(records);
}
public ActionResult Details(int id)
{
// Authenticate Guard Clause
if (!User.Identity.IsAuthenticated)
{
return RedirectToAction(“Login”, “Home”);
}
// Show detailed medical record
var record = _db.MedicalHistories.SingleOrDefault(r => r.Id == id);
return View(record);
}
}
}
MedicalHistoryController暴露了两个操作,名字分别是Summary和Details。两个操作都从MedicalHistory数据表中获取数据。
\n
Summary操作并没有为URL操作攻击敞开大门。在获取数据库记录时,记录是针对当前病人的用户名检查过的。记录是通过下面的LINQ to SQL查询获取的:
\n
var records = from r in _db.MedicalHistories
where r.PatientUserName == User.Identity.Name
orderby r.EntryDate
select new SummaryMedicalHistory {Id=r.Id, EntryDate=r.EntryDate, Subject=r.Subject};
不好的查询出现在Details操作中。当Details操作获取一条特定的数据库记录时,只使用了记录的Id:
\n
var record = _db.MedicalHistories.SingleOrDefault(r => r.Id == id);
由于这样编写了查询,黑客只需简单地修改传给Details操作的Id就能看到其他病人的病历记录。
\n
下面是编写查询的正确方法:
\n
var record = _db.MedicalHistories.SingleOrDefault(r => r.Id == id &&
r.PatientUserName == User.Identity.Name);
在这个修改过的查询中,只有同时匹配指定Id和当前病人用户名的记录会返回。这个数据库查询是安全的。
\n
针对URL操作攻击创建单元测试
\n
在构建ASP.NET MVC网站时很容易出现错误,使得你自己被暴露在URL操作攻击下。如何防止这种错误?编写单元测试是一种办法。
\n
考虑清单2中的单元测试。
\n
清单2 – MedicalHistoryControllerTest.cs
\n
清单2 – MedicalHistoryControllerTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Security.Principal;
using Tip10.Controllers;
using Tip10.Models;
using Moq;
namespace Tip10Tests.Controllers
{
[TestClass]
public class MedicalHistoryControllerTest
{
const string testDBPath = @”C:\\Users\\swalther\\Documents\\Common Content\\Blog\\Tip10 Prevent Querystring Manipulation Attacks\\CS\\Tip10\\Tip10Tests\\App_Data\\MedicalHistoryDB_Test.mdf”;
/**//// <summary>
/// Tests that Phil cannot read Rob’s database records
/// Mocks ControllerContext to mock Phil’s identity
/// and attempts to grab one of Rob’s records. The
/// result had better be null or their is a querystring
/// manipulation violation.
/// </summary>
[TestMethod]
public void DetailsCheckForURLAttack()
{
// Arrange
var testDataContext = new MedicalHistoryDataContext(testDBPath);
var controller = new MedicalHistoryController(testDataContext);
controller.ControllerContext = GetMockUserContext(“Phil”, true);
// Act
var robRecord = testDataContext.MedicalHistories
.FirstOrDefault(h => h.PatientUserName == “Rob”);
var result = controller.Details(robRecord.Id) as ViewResult;
var medicalHistory = (MedicalHistory)result.ViewData.Model;
// Assert
Assert.IsNull(medicalHistory, “Phil can read Rob’s medical records!”);
}
private static ControllerContext GetMockUserContext(string userName, bool isAuthenticated)
{
// Mock Identity
var mockIdentity = new Mock<IIdentity>();
mockIdentity.ExpectGet(i => i.Name).Returns(userName);
mockIdentity.ExpectGet(i => i.IsAuthenticated).Returns(isAuthenticated);
// Mock Principal
var mockPrincipal = new Mock<IPrincipal>();
mockPrincipal.ExpectGet(p => p.Identity).Returns(mockIdentity.Object);
// Mock HttpContext
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.ExpectGet(c => c.User).Returns(mockPrincipal.Object);
return new ControllerContext(mockHttpContext.Object, new RouteData(), new Mock<IController>().Object);
}
}
}
该单元测试允许你检查Details操作是否为URL操作攻击敞开了大门。这里是测试的工作流程。
\n
首先,我创建了一个DataContext来呈现测试数据库。测试数据库中包含了两位假想病人(Phil和Rob)的病历记录。测试数据库是产品数据库的副本,但其中包含的是假数据。
\n
接下来,我mock了ControllerContext。我必须mock一个ControllerContext,因为我想在调用Details操作时假装成是Phil。我希望测试当我被验证为是用户Phil时,是否能访问Rob的病历记录。
\n
我是用了一个名为GetMockUserContext()的方法来mock这个ControllerContext。该法官法使用了名为Moq的Mock Object Framework。有关Moq的更多信息,请阅读下面这篇博客文章:
\n
http://weblogs.asp.net/stephenwalther/archive/2008/06/11/tdd-introduction-to-moq.aspx
\n
接下来,从测试数据库返回了一条Rob的病历记录。Rob的病历记录的Id是由Phil用户传递给Details操作的。
\n
最后,会根据Details操作返回的记录是否为null产生一个断言。如果记录不是null,则测试会失败,Rob的记录可能会被Phil偷走。
\n
小结
\n
要小心URL操作攻击。如果你需要保护敏感数据——如病历记录和信用卡号——你需要特别小心这类攻击。在这个Tip中,我介绍了一种让你的网站更加安全的途径。请利用单元测试来针对URL操作攻击测试你的控制器操作。
\n
如果你想试验本文中的代码,请单击下面的链接下载源代码。你需要修改MedicalHistoryControllTest文件中testDBPath的值,使其对应你机器上的测试用病历数据库。
此处下载源代码:http://weblogs.asp.net/blogs/stephenwalther/Downloads/Tip10/Tip10.zip。\n
来源:http://www.cnblogs.com/AndersLiu
\n