之前我們詳細介紹了跨站腳本攻擊(XSS)的攻擊原理與基本防禦,這次就更進一步,將XSS攻防融入MVC架構中,從Model、View、Controller三層出發,完整打造安全的網頁應用。
完整防禦XSS必須兼顧這三層。
雖然XSS主要是輸出層的問題,但在模型層可做基礎驗證減少異常資料,例如限制字串長度、剔除絕對危險字元。
一開始,我們宣告一個公開的類別 (class) 叫做 CommentModel。"public" 表示這個類別可以被專案中的任何其他程式碼存取。這個類別通常用來當作一個資料模型 (Data Model),代表一則「評論」的資料結構。
public class CommentModel
{
// 第一個「屬性 (Attribute)」,一種資料註解 (Data Annotation),
// 用來為下方的 Content 屬性 (Property) 加上驗證規則。
// [StringLength(500)] 的意思是限制 Content 字串的最大長度不能超過 500 個字元。
// 這在資料庫設計或前端輸入驗證時非常有用,可以防止使用者輸入過長的內容。
[StringLength(500)]
// 這是第二個「屬性」樣也是一個資料註解,使用*「正規表示式 (Regular Expression)」來進行驗證。
//     用來防止「跨網站指令碼 (Cross-Site Scripting, XSS)」攻擊,避免使用者輸入惡意的 HTML 或 JavaScript 程式碼。
[RegularExpression(@"^[^<>]*$", ErrorMessage = "Invalid characters detected.")]
// ErrorMessage = "Invalid characters detected.":
//   如果驗證失敗 (也就是字串包含了 < 或 >),就會顯示這個錯誤訊息給使用者。
public string Content { get; set; }
}
// 宣告一個公開的字串屬性 (Property) 叫做 Content。
// public string Content: 代表這個屬性是公開的 (public)、型別是字串 (string),名稱是 Content。
// { get; set; }: 這是 C# 的*「自動實作屬性 (Auto-Implemented Property)」。*
//   get: 允許外部程式碼「讀取」這個屬性的值。
//   set: 允許外部程式碼「寫入」這個屬性的值。
補充一下程式碼說明:
// [RegularExpression(@"^[^<>]$", ErrorMessage = "Invalid characters detected.")]
@"^[^<>]$": 這是正規表示式的樣式 (Pattern)。
@: 表示這是一個「逐字字串」,讓 C# 不會將反斜線 \ 視為跳脫字元。
^: 表示字串的「開頭」。
[^<>]: 表示比對「任何不是 < 也不是 > 的字元」。方括號中的 ^ 代表「非」。
*: 表示比對「前面那個規則零次或多次」。
$: 表示字串的「結尾」。
整個樣式合起來的意思是:這個字串從頭到尾都不能包含 '<' 或 '>' 這兩個字元。
使用MVC內建的Model Validation功能驗證輸入資料,同時避免直接操作原始輸入,確保資料合乎規範。
同樣步驟:
這是一個「屬性 (Attribute)」,用來指定這個動作方法只能透過 HTTP POST 請求來呼叫。
HTTP POST 通常用於建立新的資源 (例如:發布一則新的評論)。這可以防止使用者直接在瀏覽器網址列輸入 URL 來觸發這個方法 (因為那會是 GET 請求), 確保只有從表單 (form) 提交時才能執行。
[HttpPost]
// 宣告一個公開的 (public) 動作方法,名稱為 PostComment。
//  public: 表示這個方法可以被 ASP.NET Core 框架從外部呼叫。
//  IActionResult: 這是方法的返回型別。它是一個介面,表示這個方法可以回傳多種不同的執行結果,例如一個 View (HTML 頁面)、一個重新導向 (Redirect) 指令、或是一個 JSON 物件等。
//  PostComment: 方法的名稱。
//  (CommentModel model): 這是方法的參數。
//   ASP.NET Core 的*「模型繫結 (Model Binding)」*機制會自動啟動,
//   它會嘗試從傳入的 HTTP POST 請求中 (例如 from 表單的欄位) 提取資料,
//   並建立一個 CommentModel 物件,然後將對應的資料填入 model 的屬性中 (例如 Content 屬性)。
public IActionResult PostComment(CommentModel model)
{
檢查模型的狀態是否有效。
// "ModelState" 是一個內建的屬性,它會收集所有關於模型繫結和驗證的資訊。
// ".IsValid" 是一個布林值 (true/false)。
// ASP.NET Core 會自動根據你在 CommentModel 類別上設定的資料註解 ([StringLength], [RegularExpression])
// 來驗證傳入的資料。
// 如果所有驗證規則都通過,ModelState.IsValid 就會是 true。
if (ModelState.IsValid)
{
--- 如果驗證成功,就執行這個區塊的程式碼 ---
//  "dbContext" 通常代表你的資料庫上下文 (Database Context),例如在 Entity Framework Core 中。
".Comments" 代表資料庫中的 "Comments" 資料表。
// ".Add(model)" 將剛剛通過驗證的 model 物件加入到資料庫上下文中,準備進行新增操作。
dbContext.Comments.Add(model);
    // 將所有在資料庫上下文中的變更 (如此處的新增操作) 實際寫入到資料庫中。
    dbContext.SaveChanges();
資料成功儲存後,將使用者重新導向 (Redirect) 到名為 "Index" 的動作方法。
// 這是一種常見且推薦的模式,稱為 Post-Redirect-Get (PRG)。它可以防止使用者在提交表單後按「重新整理」按鈕時,不小心重複提交一次相同的資料。
return RedirectToAction("Index");
}
如果 ModelState.IsValid 是 false (驗證失敗),就執行下方的程式碼
回傳原本的 View,並將包含使用者輸入資料和錯誤訊息的 model 物件一起傳回去。
這樣做的好處是:
// 1. 頁面不會清空,使用者不需要重新填寫所有欄位。
// 2. 你可以在 View 中使用輔助標籤 (Tag Helpers) 來顯示對應欄位的錯誤訊息
//    (例如:"Invalid characters detected."),引導使用者修正他們的輸入。
return View(model);
}
Razor語法自帶自動HTML編碼功能,使用@符號輸出資料會自動轉譯,避免惡意腳本執行。
@model CommentModel
若需要在JavaScript或HTML屬性中使用資料,務必採用Contextual Encoding。
最後說明:
Razor 的 HTML 輔助方法 (HTML Helper),用來「顯示」資料。
@Html: 啟用 HTML 輔助方法。
.DisplayFor(...): 指示要產生一個用於顯示的 HTML。
m => m.Content: 這是一個 Lambda 運算式,它指定要顯示的是模型 (m) 中的 Content 屬性。
最終效果:這行程式碼會讀取傳入此 View 的 CommentModel 物件中的 Content 屬性值, 並將其以純文字的形式呈現在 HTML 的 標籤中。它會自動處理 HTML 編碼,防止內容中的特殊字元 (如 < >) 被當成 HTML 標籤執行。
部署並啟動本機MVC網站。
使用瀏覽器或Burp Suite攔截介面,輸入含有XSS腳本的留言:
觀察View層是否顯示為純文字(正常)或執行彈窗(異常)。
修改輸入驗證規則與View層顯示方式,確保安全。
再次使用OWASP ZAP或Burp Suite掃描網站,確認XSS漏洞是否消失。
(但今天主要是程式建立說明,可以自己去玩玩看!!!)
這次的心得非常有感!以前在上web若用 HTTP GET是不是很常將自己的網站架設介面分頁前其他人一覽無疑,靜態網站其實還算還好,但動態的若只是用簡單的程式框架,使用MVC架構卻不知道如何使用驗證規則其實很容易駭客攻破,以前在上課時頂多界跟著老師上課打,不知道變數程式碼原來意義對資安來說這麼大!(而且現在VS系列都有程式補足,完全不擔心....)
這次沒放上畫面圖是因為真的太陽春,平常使用mvc、mvvm等等架構設計會需要非常多防呆、維護的程式碼,這裡只是舉例一下部分!!!