ASP.NET保安系列 - 常被遺忘的伺服器端驗證
4 |
【ASP.NET保安系列前言】
我一直對資安十分敏感,而剛好身為一位網站開發人員,自然對系統架構的安全格外關注,過去已寫過不少文章討論網站設計上的安全議題。例如: ASP.NET防駭指南、你的網站在裸奔嗎?、游擊式的SQL Injection攻擊、瀏覽器XSS防身術比武大會、隱含殺機的GET式AJAX資料更新... 很遺憾地,常有題材可以討論,意味著因程式寫法疏失產生資安風險的問題沒消失過,而隨著技術推陳出新,又會有新陷阱冒出來。結論是,身為網站開發人員,永遠鬆懈不得,只要稍一犯錯,自認鐵打的安全網站當場變成紙糊的。
打算陸續整理一些開發ASP.NET網站時易遺漏的瑣碎安全細節,有些是從別人的個案中學到的,有些則是我自己犯過的錯,文章裡會分享自己處理該議題的經驗與做法,也歡迎大家回饋及指正,這些文章就姑且歸納為ASP.NET保安系列。
前陣子提到ASP.NET表單驗證控制項,順便再提一個重要觀念:
如果資料的嚴謹度很重要,就不應輕易信任客戶端傳來的內容!
過去看過不少案例,開發人員開開心心地在網頁上拖拉驗證控制項,測試欄位A沒輸入會被擋下來,欄位B有非數字就不能過關,接著在Button_OnClick()加入將欄位A, B寫進資料庫的程式碼,大喊一聲搞定,高高興興收拾書包回家。實際上,這樣的做法是有瑕疵的,它犯了一個錯 -- 只依賴客戶端的驗證便信任前端傳回的結果!
來個邪惡的示範:
<%@ Page Language="C#" %>
<script runat="server">
protected void btnSubmit_Click(object sender, EventArgs e)
{
lblUpdteResult.Text=("模擬資料庫更新內容 = " + txtInput.Text);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>數字欄位輸入測試</title>
</head>
<body>
<form id="form1" runat="server">
請輸入純數字: <asp:TextBox ID="txtInput" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="rv1" ControlToValidate="txtInput" runat="server"
ErrorMessage="不可空白" Text="*" />
<asp:RegularExpressionValidator ID="cv1" ControlToValidate="txtInput" runat="server"
ErrorMessage="本欄位只接受純數字" Text="*" ValidationExpression="[0-9]+" />
<br />
<asp:Button ID="btnSubmit" runat="server" Text="送出" OnClick="btnSubmit_Click" />
<asp:ValidationSummary ID="vs1" runat="server" />
<asp:Label ID="lblUpdteResult" runat="server" />
</form>
</body>
</html>
RequiredFieldValidator與RegularExpressionValidator預設都會啟用Javascript寫的檢核函數,當使用者輸入內容不符合驗證規則時,可即時顯示錯誤訊息,並阻止表單內容送回伺服器端。
在一般狀況下,當欄位包含非數字時,按下送出鈕將無法送出表單。換句話說,伺服器端的btnSubmit_Click事件,只有在欄位通過驗證時才會被執行,認定txtInput一定是數字並直接寫入資料庫,應該是安全的才對。
BUT !
寫程式最機車的就是這個BUT! (偷學九把刀...)
如果不是一般狀況下呢? 使用者瀏覽器的Javascript引擎被停用或忽然故障,或是遇到某個很黑暗的人開啟了IE Dev Tools動點手腳...
登楞! 非數字內容一把推倒驗證器,就這麼闖進你的資料庫。
這是RegularExpressionValidator的錯嗎? 當然不是,Script端的保護機制是很脆弱,我們不應期望靠它阻擋惡意人士入侵或破壞,這是撰寫Client-Side機制時的重要原則。與安全性嚴謹性相關的機制,Client-Side可以發揮即時快速的回應,但一定要設想Client-Side機制失效的情境,在Server-Side構築最後的攔截防線!
而ASP.NET驗證器可貴之處也在於此,它同時提供了Javascript端與Server端的檢查,因此我們可省下為同一條驗證規則分別在前端與後端寫程式的麻煩。Button預設會觸發驗證邏輯(CauseValidation),因此表單在PostBack時會自動呼叫Validate(),我們在Click事件中透過Page.IsValid即可取得伺服器端的驗證結果。因此,要改善上述程式,只要多加一行:
protected void btnSubmit_Click(object sender, EventArgs e)
{
if (Page.IsValid)
lblUpdteResult.Text=("模擬資料庫更新內容 = " + txtInput.Text);
}
舉手之勞,但如此程式的嚴謹性才完整,開發時可別忽略了。
【延伸閱讀】
Comments
# by jain
完全中了,我們的驗証常被破,謝謝您的分享,立刻改來用!
# by demo
寫網頁的應該要有認知不要相信從 前端傳來的一切資料,還有少利用 hide 欄位來放暫存資料
# by 喜羊羊
感謝黑大分享這系列主題,有個問題想請教;若沒有使用 ASP.NET 驗證控制項的情況下,我們又該如何撰寫構築 前端與後端的驗證機制,並且網頁依然符合 ajax 的體驗快感。
# by Jeffrey
to 喜羊羊,好問題! 如果不用ASP.NET驗證控制項,如何做到前端及後端的驗證? 我的想法是: 沒有巧門,找其他替代控制項,不然自已寫,要做到完美,就只能用Javascript跟Server端各寫一次。我曾嘗試過Javascript驗證時透過AJAX呼叫Server端程式做驗證(這樣可以只寫一份在Server端),但是在即時性跟回應速度上會打折扣。另一個嘗試是使用Silverlight驗證,驗證完的結果加密後送至後端,後端永遠只接受加密後的格式(驗證可以只寫在前端),不過Silverlight一樣得送到User家裡執行,無法擺脫被破解或假造的可能。最後的心得--逃避不了跟ASP.NET驗證控制項做一樣的事,省不了事。