先來看以下的程式,網頁上放了一個<textarea>及<input type="button">,按鈕後以$.post()方式將<textarea>的內容送至ASP.NET Server端程式,在Page_Load中讀取Request["data"]並顯示出來,另外並透過$.ajaxSetup()指定error錯誤事件函數,捕捉並顯示伺服器端的錯誤資訊。

排版顯示純文字
<%@ Page Language="C#" %>
<!DOCTYPE>
 
<script runat="server">
    void Page_Load(object sender, EventArgs e)
    {
        if (Request["mode"] == "ajax")
        {
            Response.ContentType = "text/plain";
            Response.Write("Data=" + Request["data"]);
            Response.End();
        }
    }
</script>
 
<html>
<head runat="server">
    <title>MyLab</title>
    <style>
        body,input { font-size: 9pt; }
    </style>
    <script src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js'></script>   
    <script>
        $(function () {
            $("#txtXml").text("<data>Text</data>");
            //發生錯誤時顯示錯誤訊息
            $.ajaxSetup({
                error: function (xhr, textStatus, err) {
                    alert("ERROR: " + err + "=>" + xhr.responseText);
                }
            });
            //以$.post方式將textarea的內容送至Server端
            $("#btnAjaxPost").click(function () {
                $.post("?mode=ajax", { data: $("#txtXml").text() },
                    function (r) { alert(r); }
                );
            });
        });
    </script>
 
</head>
<body>
    <form id="form1" runat="server">
    <textarea id="txtXml" cols="20" rows="4">
    </textarea><br />
    <input type="button" id="btnAjaxPost" value="Ajax Post" />
    </form>
</body>
</html>

老鳥們應該已嗅出異常: 程式會出錯!!

由於我故意在<textarea>中塞入了XML內容,而ASP.NET 2.0起增加了Request Validation的機制,防範惡意人士在URL或PostBack內容中夾雜HTML標籤進行XSS攻擊。很多人遇到這個狀況,直覺反應就是毫不猶豫將@Page validateRequest設成false(甚至一不做二不休,在web.config設定把整個網站的requestValidate都關掉),程式不再出錯,就開開心心快快樂樂繼續寫下去。

身為一個輕度資安偏執者,我反對這種處理方式!! 多想兩分鐘,你可以不要validateRequest="false"。

廚師帶著鍋杓要進總統府外燴,經過金屬探測門安檢時警報大作,解決之道是把金屬探測器拆掉??

不是吧? 針對鍋杓的特例另外處理才是王道,但在資訊系統中無法通過檢核就把防護關掉眼不見為淨的例子比比皆是... (檔案拒絕存取就一律開Everyone、程式在Windows 7跑不了就關UAC)

依我的看法:

應保留requestValidate="true"擋下大部分未知的可能攻擊,針對確實會包含XML/HTML資料且另有內容安全檢核程序的情境,再設法繞道而行

才是安全的策略!

要怎麼讓HTML/XML內容傳送時避免被Request Validation機制阻攔呢? 其實不難,Client端在送出資料前進行編碼,Server端在收到資料進行解碼還原即可。而借用encodeURIComponent()應是最簡單的寫法了,例如:

在$.post()時改成
$.post("?mode=ajax", { data: encodeURIComponent($("#txtXml").text()) }

在Page_Load()時改成
Response.Write("Data=" + HttpUtility.UrlDecode(Request["data"]));

前後端一搭一唱,就可以在不觸發警告的情況下完成XML或HTML資料傳送。但必須牢記,加上了這段設計,意味著駭客就能在Request["data"]中設法摻入HTML標籤,一定要加上邏輯杜絕Request["data"]夾帶HTML標籤引來XSS攻擊的風險

encodeURIComponent()的做法有個小缺點,就是編碼後內容會膨脹。例如原本的<data>Text</data>會變成%253Cdata%253E%25E5%259C%258B%25E5%25AE%25B6%253C%252Fdata%253E。若要改善,需另覓其他的編碼規則,或在部分情境可考量將XML內容直接當成Post的內容主體(補充參考)。

簡單範例如下,Client端改用$.ajax()方式傳送,指定type="POST", contentType="text/xml"(或是text/plain亦可), 另外還要指定processData=false,即可將data內容當成POST本體,如此亦可避開Request Validation,達成相同目的。

排版顯示純文字
<%@ Page Language="C#" %>
<!DOCTYPE>
 
<script runat="server">
    void Page_Load(object sender, EventArgs e)
    {
        if (Request["mode"] == "ajax")
        {
            Response.ContentType = "text/plain";
            //使用StreamReader讀入Request.InputStream的所有內容
            var sr = new System.IO.StreamReader(Request.InputStream);
            Response.Write("Data=" + sr.ReadToEnd());
            Response.End();
        }
    }
</script>
 
<html>
<head runat="server">
    <style>
        body,input { font-size: 9pt; }
    </style>
    <script src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js'></script>   
    <script>
        $(function () {
            $("#txtXml").text("<data>Text</data>");
            //以$.post方式將textarea的內容送至Server端
            $("#btnAjaxPost").click(function () {
                $.ajax({
                    url: "?mode=ajax",
                    type: "POST",
                    contentType: "text/xml", //指定格式為XML
                    data: $("#txtXml").text(),
                    processData: false, //指定直接傳送內容不做處理
                    success: function (r) { alert(r); }
                });
            });
        });
    </script>
 
</head>
<body>
    <form id="form1" runat="server">
    <textarea id="txtXml" cols="20" rows="4">
    </textarea><br />
    <input type="button" id="btnAjaxPost" value="Ajax Post" />
    </form>
</body>
</html>

Comments

# by lsk

雖然知道設validateRequest="false"是不好的 但微軟官方文件也沒有說正確的作法,它也是說設validateRequest="false"就好了,如果msdn有較正確的說法,也不會造成大家都如此做

# by Fank Pan

MSDN 對於 類別庫 的 介紹從來都只有 說明基本用法 , 進階的設計 本來就是 身為 程式設計師 所需 思考的. 再來 , MSDN 對於 "HttpRequestValidationException" 的說明中有 "我們強列建議您的應用程式應該明確地檢查關於要求中止的所有輸入。" 這句話已經給了 設計思考的方向了...... 沒去好好思考這句法 , 就盲目的使用 validateRequest="false" 的你 , 是不是值得檢討......

# by taiping8899

"驗證輸入" "編碼輸出" 是我的體悟 供大家參考~~

# by ChuLiHeng

重點是資料是別人傳過來的 綠x信用卡與智x小額付費等等 有沒有辦法在.Net 4.0不用或是只針對單頁面進行處理 .Net 4.0 需要加入 <httpRuntime requestValidationMode="2.0" /> 但是好像要加在根web.config ?

# by Jeffrey

to ChuLiHeng, .NET 4.0仍然可以針對ASPX個別設定,一樣是透過<%@Page ValidateRequest="true/false" %>宣告。 差別在於.NET 4.0採較安全嚴謹的預設值,故網頁未指定時預設是開啟的,如果你想恢復2.0未指定時預設關閉的做法(基於安全,建議不要),則可用<httpRuntime requestValidationMode="2.0" />加以調整。

# by Vinix

@Jeffrey: 我這邊目前使用.NET 4.7.2,依照預設requestValidationMode="4.5",如果單獨使用<%@Page ValidateRequest="false" %>是沒有作用的,網路上有很多人反映這個問題。 另外,requestValidationMode="2.0"是只針對網頁啟用而非預設關閉。請參考: https://docs.microsoft.com/zh-tw/dotnet/api/system.web.configuration.httpruntimesection.requestvalidationmode?view=netframework-4.8 第三,文章中使用decode/encodeURIComponent的作法在requestValidationMode="4.5"沒用,只要偵測到用了Query String的值就會問答無用的擋下。 我的範例: function btnSearch_Click() { location.href = window.location.href.split('?')[0] + "?q=" + encodeURIComponent($("#txtKeyword").val()); } $(window).load(function () { var keyword = decodeURIComponent(getUrlVars()["q"]); $("#txtKeyword").val(keyword); });

# by Vinix

對我前一則留言稍加補充: requestValidationMode="4.5"的情況下,跟Server/Client端的程式碼無關,只要檢查到Query String中有所謂危險的值就會丟出例外,即使我已經用了<%@Page ValidateRequest="false" %>以及Javascript來處理該Query String以避免XSS(請見我的前一則留言。另外該頁面的Code-behind並沒有存取Request.QueryString["q"]的值,或者該說我根本沒在Code-behind新增任何程式碼,因為該頁只是一個簡單的Google Custom Search結果頁)。 要重現此問題,簡單的在任何網址後加上q=<html>就可以了。 Ex: https://website/anypage.aspx?q=<html> 當requestValidationMode="4.5",以上步驟就會丟出例外: ===== 具有潛在危險 Request.QueryString 的值已從用戶端 (q="<html>") 偵測到。 描述: ASP.NET 偵測到要求中的資料具有潛在危險,因為它可能包括 HTML 標記或指令碼。此資料可能表示有人嘗試危害應用程式的安全性,例如跨站台的指令碼處理攻擊。如果您的應用程式允許這種輸入,則可以在網頁中包含程式碼,以便明確地允許它。如需詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkID=212874。 例外狀況詳細資訊: System.Web.HttpRequestValidationException: 具有潛在危險 Request.QueryString 的值已從用戶端 (q="<html>") 偵測到。 ===== 而以上錯誤當中的網址是建議在web.config設定「該頁」的requestValidationMode="2.0",再到該頁設定ValidateRequest="false"。但是對於沒有使用的Query String也丟出例外是矯枉過正了吧? Orz

Post a comment