ASP.NET MVC整合RichText編輯器範例與注意事項
4 |
最近的ASP.NET MVC專案用到了RichText編輯器,允許使用者編輯包含不同字型、大小、粗細、顏色的格式化文字,其中有些需注意細節,整理筆記備忘。
網頁版RichText編譯器的選擇不少,本文以KendoEditor為例,結果則以PostBack方式回傳。即使換用其他編輯器或改以AJAX回傳,ASP.NET MVC整合重點大同小異。
範例的MVC網站共有Index及Result兩個View,Index為編輯器頁面,Result則用來顯示結果。Controller除了Index及Result兩個Action,再增加一個Sumbit Action,負責接受前端送回內容,模擬將結果寫入DB(為求簡化,以保存在記憶體替代)供Result View讀取顯示,接著導向Result View顯示編輯結果。
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Mvc.Controllers
{
public class HomeController : Controller
{
static string _content = string.Empty;
void SaveToDb(string content)
{
//模擬寫入DB
_content = content;
}
string ReadFromDb()
{
//模擬由DB讀取
return _content;
}
public ActionResult Index()
{
return View();
}
[HttpPost]
[ValidateInput(false)]
public ActionResult Submit(string content)
{
SaveToDb(content);
return RedirectToAction("Result");
}
public ActionResult Result()
{
ViewBag.Content = ReadFromDb();
return View();
}
}
}
Index.cshtml已盡量簡化,網頁只有一個KendoEditor及一顆送出鈕,送出前透過JavaScript取出編輯結果(HTML)存入<input type="hidden" name="content" />,傳送給Submit Action接收:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Kendo Editor Test</title>
<link rel="stylesheet"
href="//kendo.cdn.telerik.com/2016.2.714/styles/kendo.common.min.css" />
<link rel="stylesheet"
href="//kendo.cdn.telerik.com/2016.2.714/styles/kendo.default.min.css" />
<script src="//kendo.cdn.telerik.com/2016.2.714/js/jquery.min.js"></script>
<script src="//kendo.cdn.telerik.com/2016.2.714/js/kendo.all.min.js"></script>
</head>
<body>
<div>
@using (Html.BeginForm("Submit", "Home"))
{
<textarea id="editor" style="width: 480px; height: 200px;">
黑暗執行緒
</textarea>
<input type="hidden" id="content" name="content" />
<button id="submit" type="submit">Submit</button>
}
</div>
<script>
$("#editor").kendoEditor({
tools: [
"formatting",
"bold",
"italic",
"underline",
"strikethrough",
"foreColor",
"backColor"
]
});
var editor = $("#editor").data("kendoEditor");
$("#submit").click(function () {
$("#content").val(editor.value());
});
</script>
</body>
</html>
Result.cshtml也很單純,在Server端將HTML內容存入ViewBag.Content,View裡以@ViewBag.Content顯示的結果經過HtmlEncode處理(<變成<)可呈現HTML原始碼,@Html.Raw(ViewBag.Content)則將HTML內容變成網頁一部分,可呈現HTML裡<h1>、<span style="color:#444">等樣式效果。注意:Html.Raw()允許使用者輸入內容成為網頁HTML語法的一部分,跟SQL Injection漏洞原理相仿,一旦引用就存在被注入惡意程式碼的風險,若一定要使用需嚴防XSS攻擊!這部分後面會再說明。
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>結果顯示</title>
<style>fieldset { width: 400px; height: 120px; }</style>
</head>
<body>
<fieldset>
<legend>輸入內容</legend>
<div>@ViewBag.Content</div>
</fieldset>
<fieldset>
<legend>HTML顯示結果</legend>
<div>@Html.Raw(ViewBag.Content)</div>
</fieldset>
</body>
</html>
就這樣,一個提供使用者編輯格式化文字內容的網頁介面就完成了。
接下來,來談談幾個需要注意的地方。
第一,Submit Action宣告為[HttpPost],不允許以GET方式執行。原因:永遠不要使用GET方式接收指令進行資料更新!
第二,在ActionResult Submit(string content)上有個[ValidateInput(false)],目的在關閉Request內容檢核。基於安全考量,ASP.NET MVC預設會攔截包含XML標籤的Request內容,避免有心人士透過Action注入XSS攻擊程式。但在RichText編輯情境,content包含HTML是正常的,若不設定[ValidateInput(false)]停用檢核機制,送出資料時會出現錯誤:
具有潛在危險Request.Form的值已從用戶端(content="<h2><span style="col…")偵測到。
關閉ValidateInput代表我們預期並接受content參數包含HTML語法,但於此同時也開始要承擔「content內容可能包藏XSS攻擊」風險。等等,KendoEditor並不容許輸入<script>、<iframe>,使用者應該沒法搞怪吧?錯!只要資料來自前端由使用者提供,處處隱藏殺機,例如以下XSS注入示範:
不需用特殊道具,瀏覽器開啟F12跑一行指令,即可篡改傳送內容加入惡意程式碼,若Result View是公眾瀏覽的頁面,就可能被當成發動攻擊的跳板。
第三點,要防止使用者輸入HTML夾帶惡意程式,最有效的方法是使用Sanitizer工具進行過濾,只保留白名單列舉的HTML標籤,排除可能夾帶惡意內容的管道。至於過濾工具,過去大家蠻常用的AntiXSS Library Sanitizer,處於3.x版不夠安全,4.x版把不該殺的也殺光光的尷尬處境(4.x版被一顆星評價洗版),已不再是好選擇。重新評估,我選擇較活躍的開源專案-HtmlSanitizer。
【2016-08-22更新】感謝Bruce補充,AntiXSS Library在4.3.1版再改回白名單保留邏輯(被罵了兩年,呼~),可再納入考量。
可使用NuGet安裝:
裝妥後在Submit()加上content = new HtmlSanitizer().Sanitize(content),即可過濾content可能有害的內容,前述示範惡意插入的JavaScript會整段被移除。
[HttpPost]
[ValidateInput(false)]
public ActionResult Submit(string content)
{
content = new HtmlSanitizer().Sanitize(content);
SaveToDb(content);
return RedirectToAction("Result");
}
重新整理重點:
- Razor語法插入後端內容時預設會經過HtmlEncode,基本上能有效防止XSS攻擊。但RichText在呈現時必須原始呈現,需使用@Html.Raw()嵌入頁面。使用Html.Raw()代表使用者輸入內容有可能成為網頁HTML一部分,務必從嚴檢核,防範被插入惡意程式。
- 接收資料進行更動作業的Action宜加上[HttpPost]降低被攻擊機率。
- 接收HTML資料的Action需加上[ValidateInput(false)],避免資料傳送被封鎖。
- 關閉ValidateInput後,防範攻擊就變成我們的責任,HTML內容進入系統前應使用Sanitizer濾掉可能有害部分。
注意:所有可能以Html.Raw()內嵌或直接成為網頁HTML一部分的輸入參數都應該處理。
[2016-08-19更新]
關閉ValidateInput後Action的所有參數都允許傳入HTML,如要進一步限定只開放某個參數接受HTML,使用AllowHTML Attribute會更安全,可一次避免其他參數被植入XSS攻擊的風險,感謝demo補充。
Comments
# by demo
建議不要使用[ValidateInput(false)] 因為這樣會關閉整個Action的檢查,使用Viewmodel接受資料,並且針對唯一需要開放 HTML的屬性使用 AllowHTML attribute 可以進一步的把開放的空間再縮小
# by Jeffrey
to demo, 感謝補充,已加入本文。
# by KKbruce
AntiXSS 4.3.0 有改回來哦 參考:http://blog.kkbruce.net/2014/06/microsoft-web-protection-library-430.html
# by Jeffrey
to KKBruce,感謝提供,已補充於本文。