ASP.NET MVC Tip #46 – 勿使用Delete链接,会造成安全漏洞

来源:互联网 发布:吉吉写作软件mac 编辑:程序博客网 时间:2024/06/06 22:16

 

          看到篇文章,里面说了个不知道的事儿,很粗糙生硬的翻了一下,记在这里,回头研究。

          路过的朋友,如感兴趣,请直接看原文,以免耽误了您的时间。

           原文ASP.NET MVC Tip #46 – Don’t use Delete Links because they create Security Holes

正文

        我创建了一个 ASP.NET MVC 示例程序, 打算公布在 http://ww.ASP.net/mvc . 当 ASP.NET MVC 专题小组对该程序进行代码审查时 , 一个惊人的缺陷显现出来.

        该程序极其简单。它包含了一个呈现数据库记录列表的视图。每条记录下面是一个Edit链接和一个Delete链接(见图1)。很标准的东西。或者,所以我想...

图1-- 一个数据库记录表格

clip_image002

        这里就是缺陷。你不应该用链接来删除一条记录。使用Delete链接就打开了一个安全漏洞。


      安全缺陷

         某人可以发一封包含一个image的邮件给你。该image通过如下标签被嵌入到邮件中:

         <img src=”http://www.theApp.com/Home/Delete/23” _fcksavedurl=””http://www.theApp.com/Home/Delete/23”” />

        注意,src属性指向了Home控制器类的Delete() 方法。打开邮件(并且允许邮件客户端显示image)将在无警告的情况下删除23号记录。这是不好的。这是安全漏洞。

        我曾经遇到过这种安全问题,但没有多想。REST纯粹主义者会捍卫Get请求不应该改变程序状态的想法。换句话说,执行Get请求应该是安全的,无副作用的。

        比如,您不会希望搜索引擎在抓取您网站的时候删除您程序中的所有记录。执行Get请求不应该对您的程序有持久性影响。

        删除一条记录的时候,适当的Http操作是 HTTP DELETE。HTTP 协议支持如下HTTP操作:

       .OPITIONS - 返回可用的通信可选项的信息(幂等)。

       .GET -  返回请求的任何信息(幂等)。

       .HEAD - 与 GET 执行同样的操作,只是没有消息体(幂等)。

       .POST - 发布新信息或更新已有信息(非幂等)。

       .PUT - 发布新信息或更新已有信息(幂等)。

       .DELETE - 删除信息(幂等)。

       .TRACE - 执行一个消息循环 (幂等)。

       .CONNECT -  用于SSL通道。

        这些操作被定义为HTTP 1.1 标准的一部分,您可以在这里http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html看到.

        注意,对 HTTP POST 和 HTTP PUT 的描述是相同的。为了解 POST 和 PUT 的区别,你需要明白幂等的含义。一个幂等操作无论执行多少次,都有相同的结果。比如,您执行一个POST操作来创建一条新的数据库记录,那么您每次都可以创建一条新的数据库记录。POST操作是非幂等的,因为每次操作的执行都会对您的程序有不同影响。

        另一方面,如果您执行PUT操作,那么操作的执行都必须创建相同的数据库记录。PUT 操作是幂等的,应为执行PUT操作1000次与执行1次是一样的。

        注意,HTTP DELETE 操作也是幂等的。多次执行HTTP DELETE 操作对您程序的影响应该是相同的。比如,请求 /Home/Delete/23 应该删除23号数据库记录,而不是其他记录,无论请求执行多少次。

        HTML 只支持 GET和POST

      所以,删除一条数据库记录时,恰当的做法是执行HTTP DELETE操作。执行一个HTTP DELETE操作不会打开安全漏洞,且不违背REST原则。

       不幸的是,标准的HTML不支持GET、POST外的其他操作。链接总是执行GET操作,表单能执行GET或POST操作。HTML不支持其他的HTTP操作。

      根据HTML 3.1 规范,HTML只支持GET和POST。参见http://www.w3.org/TR/REC-html32.html#form。此外,IE只支持GET和POST。参见http://msdn.microsoft.com/en-us/library/ms534167(VS.85).aspx。

       执行 Ajax Deletes

     如果您想超越标准HTML,您可以利用Ajax来执行HTTP DELETE操作。XmlHttpRequest对象支持任何HTTP操作。因此,如果您愿意您的程序依赖于Javascript,您可以以正确的方式做一切。

      清单1中的Home 控制器包含Index()和Delete()方法。Index()方法从Movies数据库返回所有的movies,Delete()方法通过特定的Id删除特定的movie(此控制器使用Entity Framework)。

清单1 - ControllersHomeController.cs 

using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Mvc;using Tip46.Models; namespace Tip46.Controllers{    [HandleError]    public class HomeController : Controller    {        private MoviesDBEntities _entities = new MoviesDBEntities();         public ActionResult Index()        {            ViewData.Model = _entities.MovieSet.ToList();            return View();        }         [AcceptVerbs(HttpVerbs.Delete)]        public ActionResult Delete(int id)        {            var movieToDelete = (from m in _entities.MovieSet                                 where m.Id == id                                 select m).FirstOrDefault();            _entities.DeleteObject(movieToDelete);            _entities.SaveChanges();             return RedirectToAction("Index");        }     }}

         注意,Delete()方法声明了AcceptVerbs特性。Delete()方法只能被HTTP DELETE操作调用。

        清单2中的Index视图通过HTML table显示来自Movies数据库表中的数据。每个movie记录下呈现一个Delete链接。

清单2 - ViewsHomeIndex.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Tip46.Models.Movie>>" %><asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script> <script type="text/javascript">     function deleteRecord(recordId)    {        // Perform delete        var action = "/Home/Delete/" + recordId;         var request = new Sys.Net.WebRequest();        request.set_httpVerb("DELETE");        request.set_url(action);        request.add_completed(deleteCompleted);        request.invoke();    }     function deleteCompleted()    {        // Reload page        window.location.reload();    } </script> <h2>Index</h2>     <table>     <% foreach (var item in Model) { %>         <tr>            <td>                <%-- Ajax Delete --%>                <a onclick="deleteRecord(<%= item.Id %>)" href="JavaScript:void(0)">Delete</a>                 <%-- GET Delete: Security Hole                <%= Html.ActionLink("Delete", "Delete", new { id=item.Id })%>--%>            </td>            <td>                <%= Html.Encode(item.Id) %>            </td>            <td>                <%= Html.Encode(item.Title) %>            </td>            <td>                <%= Html.Encode(item.Director) %>            </td>            <td>                <%= Html.Encode(item.DateReleased) %>            </td>         </tr>     <% } %>     </table> </asp:Content>


        Delete通过Ajax调用执行。Delete链接调用Javascript deleteRecord()函数。该函数使用 Microsoft ASP.NET AJAX WebRequest 对象来执行Ajax调用。该WebRequest对象执行HTTP DELETE操作。

        Delete操作执行完毕后,Javascript deleteCompleted()方法被调用。该方法重新加载了当前页面(这里将来会有一个更优雅的方式是使用下一个ASP.NET Ajax版本带来的ASP.NET Ajax模板功能,那样的话就只更新表格,而不用重新加载整个页面了)。


图2 - Index 视图

clip_image004

        但是,我不想依赖于Javascript

       很多开发人员不想自己的网站依赖于Javacript。回句话说,他们希望Javascript被关闭时,他们的网站一样可以工作。他们的需求是有些合理性的。不是所有移动设备都支持Javascript(尽管大多数做到了),并且Javascript还有可访问性问题(尽管Aria应该修正这些可访问性问题)。

        如果您想自己的网站在Javascript被禁用时能工作,那么删除记录时,不能执行HTTP DELETE操作。而应该执行HTTP POST操作,HTTP POST不会像HTTP GET那样暴露出安全漏洞。

        您可以使用AcceptVerbs特性来防止控制器动作被HTTP POST之外的操作调用。所以, Delete()动作看起来应该是这样的:

[AcceptVerbs(HttpVerbs.Post)]public ActionResult Delete(int id){    var movieToDelete = (from m in _entities.MovieSet                         where m.Id == id                         select m).FirstOrDefault();    _entities.DeleteObject(movieToDelete);    _entities.SaveChanges();     return RedirectToAction("Index");}

       不幸的是,通过标准HTML来执行HTTP POST操作的唯一途径是使用<form>标签。并且,您必须还用一个<input type="submit">,<input type="image">,或者<input type="button">标签来为删除记录创建一个按钮。

       这里最好的选择是<input type="image">。那样的话,您可以在展示数据库记录表格时,使得Edit和Delete链接看起来一样。因为我不想我的示例程序依赖于Javascript,所以我打算采用此方式。

       清单3中是无Javascript依赖的Index视图。

清单3 - ViewsHomeIndex.aspx (无 JavaScript)

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Tip46.Models.Movie>>" %><asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"><h2>Index</h2>     <table>     <% foreach (var item in Model) { %>         <tr>            <td>                <a href='<%= Url.Action("Edit", "Home", new { id = item.Id })%>'><img src="Content/Edit.png" alt="edit" border="0" /></a>            </td>            <td>                <% using (Html.BeginForm("Delete", "Home", new { id = item.Id }))                   { %>                    <input type="image" src="Content/Delete.png" />                <% } %>            </td>            <td>                <%= Html.Encode(item.Id) %>            </td>            <td>                <%= Html.Encode(item.Title) %>            </td>            <td>                <%= Html.Encode(item.Director) %>            </td>            <td>                <%= Html.Encode(item.DateReleased) %>            </td>         </tr>     <% } %>     </table>     <p>        <%= Html.ActionLink("Create New", "Create") %>    </p> </asp:Content>

        我从Visual Studio 图片库中获取了为Edit和Delete链接使用的图片(见图3)。您可以在您硬盘的这个位置拿到这些图片集:

C:Program FilesMicrosoft Visual Studio 9.0Common7VS2008ImageLibrary

       

图3 - 为Edit和Delete使用图片

clip_image006


        为使图片正确对齐,我为表格单元格增加了垂直对齐的样式。我使用了下面的样式:

table{  border-collapse:collapse;} td{  vertical-align:top;  padding:10px;  border-bottom: solid 1px black;}

        结论

        不要使用Delete链接来删除数据库记录。潜在的,可能有人在您未知情的情况下通过执行GET请求来删除。

        最好的选择是使用Javascript来执行HTTP DELETE 操作。使用Javascript能让您避开安全漏洞。使用Javascript可以让您最终HTTP协议的语义。

        如果您不想您的程序依赖于Javascript,那第二个最好的选择执行HTTP POST来替代HTTP DELETE。执行HTML POST 需要您使用HTML表单。这个是丑陋的,然而,您可以通过使用<input type="image">并添加样式表来改进外观。


     

       

    



原创粉丝点击