介绍
  作为一个在X94的航空工程师,你的老板要求你从2号楼的工程图中检索出一个特定的专利。不幸的是,进入大楼需要你出示你具有进入大楼的资格的证明,然后你迅速地以徽章的形式出示给了保安。到了十三楼,进入建筑师工程师图纸库要求通过他们的生物鉴定系统来验证你是你声称的那个人。后在你的目的地,你提供给库管理员一串对你毫无意义的字母数字代码,但是在合适的人手上,它可以转换成哪里可以找的你需要的工程图的真实索引。
  在上面的比喻中,我们可以很容易地确定适当的安全措施来保护敏感数据的访问。除了个人访问所需验证,一个附加的可能不是很明显的安全措施是以字母数字码的形式混淆技术文档身份,并间接映射到真实的文档身份和库中的位置。
  形象地说,这个比喻是行的被称为“非安全的直接对象引用”的Web应用安全漏洞的解答,该漏洞在OWASP关键漏洞Top10中排第四。但如果这是答案的话,你接下来自然会问“关于我Web应用的具体问题是什么且该如何去解决?”
  不安全的直接对象引用
  我们对在我们网站上展示商品的想法都很熟悉。用户通过发起请求来查看商品详情,向他们的购物车里添加商品,或进行类似的活动。你很有可能会利用商品的ID去标识用户正在请求哪件商品的详细信息,标识添加进他们购物车的商品等等。重要的是,这个ID很有可能是存储商品信息的数据库表的主键。如果真是这样,那么我们拥有了一个直接对象引用。在网页上展示的某个商品(对象)被特定的ID标识,而这个ID是对数据库中相同标识的直接引用。
  “说的不错,但那又如何?”是这样,在简单的商家对顾客场景下,上文所讲的情况不是什么问题。但假定这是一个金融类服务应用,比方说是你常用的网上银行,上面有你的各个活期、定期储蓄账户和其他敏感数据,那将会怎样呢?想象一下,你在你的账户页面选择查看ID为1344573490的存款账户的详细信息:


  作为一个经过身份核实的名为Mary Wiggins的用户,网站显示了针对你存款账户的信息:


  我们可以直接看出这个支票户头是我们拥有的账户,同时也能确认这是一个直接引用。但要是你决定把accountNumber参数从1344573490改为1344573491,那将会发生什么呢?


  Erin Maley,谁是Erin Maley?那不是我们。我们作为Mary Wiggins是已经明确被认证过的。我们所有所做的事情是顺序地增加账户号直到下一个可能的值,并且我们可以看到一个不是我们所持有的账户信息。在这个例子中,我们有一个直接关联的账户,它可以被定义为系统内任何地方被标识的账户号。更进一步说,我们演习了一个潜在的问题,曝光一个直接相关的账户是简单的数据工程。
  如果你自己觉得这不是直接引用惹的祸,而是身份验证上出了差错,那么你只对了一半。我们讨论不安全直接对象引用所造成的缺陷时,实际上看到了两个问题。我发现下图能够更清楚的描述这个缺陷究竟是什么:


  如果不安全的直接对象引用涉及以下两方面……
  泄露敏感数据
  缺乏合理的访问控制
  ……那么我们对于弥补这个缺陷的看法是什么,以及我们应该何时采取行动?接下来,我们首先解决影响大范围广的问题——合理的访问控制。
  多层级的访问控制
  像文章开头举的例子,多层级的访问控制是必须的。虽然我们有权进入大楼,但进入楼内某些区域需要特定的权限。当我们考虑在Web应用中保护资源时,可以使用这样的准则来达到目的。
  通过路由进行访问控制
  首先,当前合法用户是否有权请求资源?在我们对该用户一无所知的情况下,该如何确定当前用户可以被允许发起这个请求?因此第一步我们要做的是,在和用户交互时,通过添加访问控制来保护资源。
  在ASP.NET中,用户交互通过控制器动作(controller action)完成。我们可以在ASP.NET MVC控制器上使用[Authorize]特性(attribute)来确保用户只有先经过系统核实身份才能执行控制器上的动作,而匿名用户将被拒绝。
  [Authorize]
  public class AccountsController:Controller
  {
  [HttpGet]
  public ActionResult Details(long accountNumber)
  {
  //...
  这样确保了API无法被公开使用,根据你的ASP.NET配置,用户会被重定向到登录页面(默认行为)。[Authorize]特性通过额外的约束来匹配特定的用户和角色:
  [Authorize(Roles="Admin,Manager")]
  public class AccountsController:Controller
  {
  //..
  [Authorize]特性除了可以被应用到控制器动作上外,还能进行更多粒度的控制。例如在控制器上放置身份验证约束,同时在控制器的不同动作上使用基于角色的访问控制。
  在我们的银行账户例子中,只对用户进行身份验证是不够的,因为我们(只经过身份验证的用户)竟然能访问另一个用户的支票账户信息。对于像银行账户例子中看到的这种滥用行为,通常被称作为横向权限提升,用户可以访问其他相同等级的用户信息。然而,有权发起对某个资源的请求与拥有对实际资源的权限是完全不同的概念。
  数据访问控制
  因此,我们必须采取的第二层也是重要访问控制是,保证用户被授权访问资源。在基于角色的访问控制的情况下,这跟确保用户属于合理的角色一样容易。如果被请求的资源只需要某个提升的权限,你可以利用之前演示的[Authorize]的Role属性来搞定。
  [Authorize(Roles="Admin")]
  public class AccountsController:Controller
  {
  //..
  但是更多的时候,你被要求在数据层面对用户进行权限验证,以保证其有权访问所请求的资源。考虑到受许多不同因素的影响,解决方案多种多样,上文提到的查看银行账户详情的案例,我们可以验证用户是否为其所请求账户的拥有者:
  [Authorize]
  public class AccountsController:Controller
  {
  [HttpGet]
  public ActionResult Details(long accountNumber)
  {
  Account account=_accountRepository.Find(accountNumber);
  if(account.UserId!=User.Identity.GetUserId())
  {
  return new HttpUnauthorizedResult("User is not Authorized.");
  }
  //...
  记得我们已经在控制器级别使用了[Authorize]特性,所以没必要在动作级别画蛇添足。
  需要重点注意的是,在上面的关于在ASP.NET中使用Forms Authentication引发的非授权结果的例子中将会强制一个302跳转到登陆页面,无论用户是否已经的到授权。因此,你或许需要对处理这种行为作出必要的改变,这取决于你的应用,你的需求和你用户的期望。你的选择或者你是否需要处理这种行为很大程度上依赖于框架的风格,使用OWIN模块,和你的应用的需要。
  好处是减少了去确定没有用户提权的次数,保证了合适的访问权限控制。至少,我们可以加强对请求本身和请求对被请求资源的访问的访问控制。但是,如同我前面提到的若干种场合,在我们的应用加强防止数据泄露总是应该评估的一个安全步骤。什么是我所说的“数据泄露”?我们可以通过研究其他包含不安全的直接对象引用(如混淆)来回答这个问题。
  如果你对此感兴趣,请在Twitter上follow我Follow@maxrmccarty
  混淆
  混淆是故意隐藏意图的行为。在我们这儿,我们可以使用混淆手段来推断安全性。一个人们认同的简单例子是URL短链。虽然初衷并不是为了安全性,像这样的URL http://bit.ly/1Gg2Pnn是从真实的URL从混淆过来的。根据这个短链,Bit.ly能够将混淆的URL http://bit.ly/1Gg2Pnn映射到真正的http://lockmedown.com/preventing-xss-in-asp-net-made-easy.
  我使用了关于银行账户交互的金融例子,因为这是一个完美的例子,在其中的元数据是很敏感的。在这种情况下,一个支票帐户是我们要保护的数据。而账户号码是关于支票账号的元数据,我们认为这是敏感数据。
  我们看到在前面我们只是增加了帐号的数值能够严格访问另一个用户的支票帐户,因为没有数据级访问控制。但我们可以通过混淆账号建立另一防御屏障使恶意用户失去直接驾驭系统的能力,这通过改变数值行。