呈現腳本塊
目前為止的 js 都是直接插入頁面 .aspx 部分的靜態 <script> 塊。然而,使用公開 ClientScriptManager 對象的 Page.ClientScript 屬性呈現腳本通常更加靈活。
ClientScriptManager 提供了幾個管理腳本塊的有用方法,其中兩個最有用的方法如下:
- RegisterClientScriptBlock() :把腳本塊寫到 Web 表單的開始,在 <form runat=”server”> 標簽后。適合響應 js 事件而被調用的函數使用。這樣的 <script> 塊可以放在任意地方,放在 Web 表單開頭只是出於習慣並使它們容易被找到。
- RegisterStartupScript() :把腳本塊寫到 Web 表單的結尾,在結束標簽 </form> 之前。用於添加那些頁面加載時立刻執行的 js 代碼。這些代碼可能操縱頁面上的其他控件,所有放在 Web 表單的結尾比較安全。
使用這 2 個方法,需要指定腳本塊的鍵名。唯一的鍵名,好處是能保證 ASP.NET 不會把相同的腳本函數添加多次。例如 ASP.NET 驗證控件,每個控件都要使用一些驗證函數,但是讓每個控件都添加重復的 <script> 塊並沒有意義。但當調用 RegisterClientScriptBlock() 時,每個控件都使用相同的鍵名,ASP.NET 知道它們是重復的定義,所以只呈現一份單一副本。
下面的代碼注冊一個 confirmSubmit() 的 js 函數,通過 onsubmit 特性附加到表單上:
protected void Page_Load(object sender, EventArgs e)
{
string script = @"<script type='text/javascript'>
function confirmSubmit(){
var msg = 'Are you sure you want to submit this data?';
return confirm(msg);
}
</script>";
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Confirm", script);
form1.Attributes.Add("onsubmit", "return confirmSubmit();");
}
本示例並沒有從 RegisterClientScriptBlock() 方法中受益。但是,使用 js 創建一個自定義控件時,ClientScriptMananger 方法就非常重要了!
腳本注入攻擊
開發人員並非不能意識到頁面中的安全問題,只是因為很多常見的危險(包括 腳本注入 和 SQL注入)會在不經意間發生。
一個常施加於網頁的攻擊是腳本注入攻擊。用戶提交惡意標簽或腳本代碼(一般通過 TextBox 控件之類的簡單控件),然后呈現到 HTML 頁面,這時就會發生腳本注入攻擊。如果用戶提供的數據被保存到數據庫並插入到其他用戶使用的頁面,攻擊可以影響網站所有用戶的操作。
腳本注入攻擊的基本技術是客戶端提交嵌入了腳本標簽的內容。這些腳本標簽可以包括 <script>、<object>、<applet> 和<embed>。雖然應用程序可以具體檢查這些標簽並用 HTML 編碼來使用無害的 HTML 實體代替這些標簽,但是通常不會執行基本驗證。
請求驗證
腳本注入攻擊是所有 Web 開發人員都關注的,無論他們使用的是 ASP.NET、ASP 還是其他 Web 開發技術。ASP.NET 包含一個設計用於自動防止腳本注入攻擊的功能,叫做請求驗證。它檢查回傳的表單輸入,如果發現潛在的惡意標簽(如 <script>),就報告一個錯誤。實際上,請求驗證禁止所有非數值的標簽,包括 HTML 標簽( 如<b> 或 <img>等)和不正確的標簽(如<abcd>)。
創建一個頁面可測試請求驗證功能:
現在嘗試提交的話,如果是本地運行代碼,可以看到下面的錯誤詳細信息(遠程只會看見一般的錯誤頁面):
禁用請求驗證
當然,有時,請求驗證規則有點太嚴格了。例如,有的程序確實需要用戶輸入 HTML 標簽或者 XML 數據塊。例如用戶要提交一個拍賣列表或者銷售廣告的格式化 HTML 塊的 Web 應用。
這些情況下,你可以 2 步禁用請求驗證:
首先,設置 Page 指令,不過這還不能立刻帶來變化。因為 ASP.NET 4 改變了請求驗證的工作方式。現在它在頁面處理生命周期之前被應用。
<%@ Page ValidateRequest="false" ... %>
其次,配置 web.config 文件,告訴 ASP.NET 4 恢復以前版本的驗證行為:
<system.web>
<httpRuntime requestValidationMode="2.0"/>
...
</system.web>
現在,想象一下如果使用這段代碼在標簽里顯示用戶提供的值會發生什么:
protected void Button1_Click(object sender, EventArgs e)
{
lblInfo.Text = "You entered: " + txtInput.Text;
}
也可以修改 web.config 文件來禁用整個 Web 應用程序的請求驗證。設置或增加 <pages> 元素的 validateRequest 屬性:
<system.web>
<pages validateRequest="false">
<httpRuntime requestValidationMode="2.0"/>
...
</system.web>
腳本注入攻擊中的腳本總是在客戶端執行。但是,這並不意味着它僅僅限於單一用戶!大多情況下,用戶提供的數據被保存到一個數據庫之類的地方,並且能夠被其他用戶瀏覽。例如,一個用戶把腳本塊作為業務名稱加入業務注冊表,其他請求完整業務注冊表的用戶就會受到影響。
在請求驗證被關閉的情況下,為了阻止腳本注入攻擊的發生,你需要在顯示內容前使用 Server 對象顯式地編碼要顯示的內容:
protected void Button1_Click(object sender, EventArgs e)
{
lblInfo.Text = "You entered: " + Server.HtmlEncode(txtInput.Text);
}
擴展請求驗證
對於絕大部分 Web 應用程序,ASP.NET 請求驗證能夠很好的滿足其驗證需要。但是在某些情況下,你可能需要選擇讓一些通常會被拒絕的值通過驗證,或者要增加其他要被禁止的值,此時,需要通過自定義代碼來擴展請求驗證系統。(如果面臨完全禁用請求驗證和通過代碼增加例外的兩難選擇,通常增加例外是更好的選擇!否則,你的程序對各種腳本攻擊或其他損害毫無招架之力)。
為了擴展請求驗證系統,需要派生 System.Web.Util.RequestValidator 類並重寫 IsValidRequestString() 方法:
public class CustomRequestValidator : System.Web.Util.RequestValidator
{
protected override bool IsValidRequestString(HttpContext context,
string value, RequestValidationSource requestValidationSource,
string collectionKey, out int validationFailureIndex)
{
...
}
}
IsValidRequestString() 方法接受 5 個參數:
- context 。請求的 HttpContext,通過它可以訪問內置的 ASP.NET 對象,如 Request、Response、Application、Server、Session 和 Cache。
- value 。它是要進行驗證的字符串文本
- requestValidationSource 。它使用枚舉 RequestValidationSource 來標識要驗證的信息類型。可能的值包括 Cookies、File、Form、Headers、Path 和 QueryString。
- collectionKey 。如果數據源來自某個集合,collectionKey 返回用於索引值的鍵值。例如,對於表單數據,collectionKey 和回發值的輸入控件相對應。
- validationFailureIndex 。如果驗證執行成功,validationFailureIndex 應該設置為 0 並且 IsValidRequestString() 返回 true。如果驗證失敗,IsValidRequestString() 方法返回 false,而 validationFailureIndex 指向字符串中非法數據開始的位置。
通過這些信息,可以很方便的創建改變 ASP.NET 內置行為的應急的驗證方法。
下面的示例里,驗證器檢查是否在進行表單驗證。如果不是,驗證器觸發默認的實現。如果是,則搜索回發輸入中的 <script> 標記。如果包含就拒絕該值,但同時接收其他合法的值,這使它的驗證行為要比 ASP.NET 默認驗證更為寬松:
public class CustomRequestValidator : System.Web.Util.RequestValidator
{
protected override bool IsValidRequestString(HttpContext context,
string value, RequestValidationSource requestValidationSource,
string collectionKey, out int validationFailureIndex)
{
if (requestValidationSource == RequestValidationSource.Form)
{
int errorIndex = value.ToLower().IndexOf("<script>");
if (errorIndex != -1)
{
validationFailureIndex = errorIndex;
return false;
}
else
{
validationFailureIndex = 0;
return true;
}
}
else
{
return base.IsValidRequestString(context, value,
requestValidationSource, collectionKey, out validationFailureIndex);
}
}
}
需要在 web.config 文件中注冊它才能使用:
<system.web>
<httpRuntime requestValidationType="CustomRequestValidator"/>
...
</system.web>
這個示例只是簡單演示了自定義驗證是如何工作的。但是,對於如何學會通過自定義代碼加入或多或少的驗證已經足夠了。
如果要增強標准 ASP.NET 驗證,最好把你的實現添加到默認的實現鏈里。驗證值通過你的檢查后,可以繼續調用基類的 IsValidRequestString() 來執行額外的內置檢查。