看到篇文章,裏面說了個不知道的事兒,很粗糙生硬的翻了一下,記在這裏,回頭研究。
路過的朋友,如感興趣,請直接看原文,以免耽誤了您的時間。
原文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-- 一個數據庫記錄表格
這裏就是缺陷。你不應該用鏈接來刪除一條記錄。使用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 視圖
但是,我不想依賴於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使用圖片
爲使圖片正確對齊,我爲表格單元格增加了垂直對齊的樣式。我使用了下面的樣式:
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">並添加樣式表來改進外觀。