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

 

          看到篇文章,裏面說了個不知道的事兒,很粗糙生硬的翻了一下,記在這裏,回頭研究。

          路過的朋友,如感興趣,請直接看原文,以免耽誤了您的時間。

           原文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 οnclick="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">並添加樣式表來改進外觀。


     

       

    



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章