[翻译] ASP.NET MVC Tip #10 - 防止URL操作攻击

原文地址:http://weblogs.asp.net/stephenwalther/archive/2008/06/26/prevent-url-manipulation-attacks.aspx

摘要:在这个Tip中,Stephen Walther介绍了黑客如何通过操作URL从ASP.NET MVC网站中窃取敏感信息。Stephen Walther还探讨了如何构建单元测试来防止这类攻击。

在一个网站上,黑客可以通过URL操作攻击来简单地访问其他用户的数据。如果你通过记录的ID来获取数据记录,而又没有针对每个数据库请求检查是否是由正确的用户发起的请求,则任何人都可以读取其他用户的数据库记录。

ASP.NET MVC框架的一个优势在于,它可以暴露出直观的URL。不幸的是,这个优势也可能是危险的。黑客可以通过操作URL来从一个ASP.NET MVC网站中窃取数据。

我们来看一个简单的示例程序,它将面临URL操作攻击。假设你正在为医院创建网站。医院的病人可以登录网站来查看他们的病历。这个应用程序有四个视图。

当病人第一次向该应用程序发起请求时,他会看到图1所示的视图。该视图包含一个链接,病人单击这个链接可以看到他的病历。

图1 - Index.aspx

如果病人尚未登录,他将被重定向到如图2所示的Login视图。病人必须输入正确的凭证才能查看他的病历记录(凭证存放在Web.config中)。

图2 - Login.aspx

通过验证后,病人会看到图3所示的Summary视图。该视图显示了一个列表,给出了一组指向详细病历记录的链接。数据记录的获取是根据病人的用户名进行的。

图3 - Summary.aspx

最后,如果病人单击了一个病历记录链接,他就会看到如图4所示的Details视图。该视图显示了一条单独的记录。

图4 - Details.aspx

这里就是黑客能够通过URL操作攻击来窃取病人数据的地方了。注意图4中用于为Phil(病人名)获取详细数据的URL。该URL看上去是这样的:

http://localhost:48583/MedicalHistory/Details/6

该URL非常直观。请求这个URL可以获取数据库中Id是6的数据。由于这个URL是如此的直观,你可以很容易将其修改为另外一个编号——

http://localhost:48583/MedicalHistory/Details/4

修改了URL之后,Phil可以看到Rob的私人病历记录,如图5所示。这恐怕不好吧。

图5 - Phil可以看到Rob的私人记录

清单1列出了用于返回Summary和Details视图的控制器。这样编写控制器导致该医院网站为URL操作攻击敞开了大门。

清单1 - MedicalHistoryCotroller.cs

清单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数据表中获取数据。

Summary操作并没有为URL操作攻击敞开大门。在获取数据库记录时,记录是针对当前病人的用户名检查过的。记录是通过下面的LINQ to SQL查询获取的:

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:

var record  =  _db.MedicalHistories.SingleOrDefault(r  =>  r.Id  ==  id);

由于这样编写了查询,黑客只需简单地修改传给Details操作的Id就能看到其他病人的病历记录。

下面是编写查询的正确方法:

var record  =  _db.MedicalHistories.SingleOrDefault(r  =>  r.Id  ==  id  &&  
    r.PatientUserName 
==  User.Identity.Name);

在这个修改过的查询中,只有同时匹配指定Id和当前病人用户名的记录会返回。这个数据库查询是安全的。

针对URL操作攻击创建单元测试

在构建ASP.NET MVC网站时很容易出现错误,使得你自己被暴露在URL操作攻击下。如何防止这种错误?编写单元测试是一种办法。

考虑清单2中的单元测试。

清单2 - MedicalHistoryControllerTest.cs

清单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操作攻击敞开了大门。这里是测试的工作流程。

首先,我创建了一个DataContext来呈现测试数据库。测试数据库中包含了两位假想病人(Phil和Rob)的病历记录。测试数据库是产品数据库的副本,但其中包含的是假数据。

接下来,我mock了ControllerContext。我必须mock一个ControllerContext,因为我想在调用Details操作时假装成是Phil。我希望测试当我被验证为是用户Phil时,是否能访问Rob的病历记录。

我是用了一个名为GetMockUserContext()的方法来mock这个ControllerContext。该法官法使用了名为Moq的Mock Object Framework。有关Moq的更多信息,请阅读下面这篇博客文章:

http://weblogs.asp.net/stephenwalther/archive/2008/06/11/tdd-introduction-to-moq.aspx

接下来,从测试数据库返回了一条Rob的病历记录。Rob的病历记录的Id是由Phil用户传递给Details操作的。

最后,会根据Details操作返回的记录是否为null产生一个断言。如果记录不是null,则测试会失败,Rob的记录可能会被Phil偷走。

小结

要小心URL操作攻击。如果你需要保护敏感数据——如病历记录和信用卡号——你需要特别小心这类攻击。在这个Tip中,我介绍了一种让你的网站更加安全的途径。请利用单元测试来针对URL操作攻击测试你的控制器操作。

如果你想试验本文中的代码,请单击下面的链接下载源代码。你需要修改MedicalHistoryControllTest文件中testDBPath的值,使其对应你机器上的测试用病历数据库。

此处下载源代码:http://weblogs.asp.net/blogs/stephenwalther/Downloads/Tip10/Tip10.zip

-----

广告:.NET正则表达式库,http://regex-lib.net

转载于:https://www.cnblogs.com/AndersLiu/archive/2008/07/17/prevent-url-manipulation-attacks.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值