encodeURLComponent編碼問題

躲不過也要躲:讓encodeURIComponent過的尖括號躲過ASP.NET的誤攔截

問題所處環境:IIS 7.5, ASP.NET 4.0, 應用程序池(Application Pool)運行於集成模式(Integrated)。

今天一位園友向我們反饋用網摘收藏博客文章LINQ那些事(9)-解析Table<T>.Attach引發的異常和解決方法時出錯(注意:文章標題中有尖括號)。

我們查了一下,具體的錯誤信息是:

A potentially dangerous Request.QueryString value was detected from the client (t="...9)-解析Table<T>.Attach引發的異常和解決方法...").

錯誤信息分析:爲了防止XSS跨站腳本攻擊,IIS的默認安全設置不允許查詢字符串中包含尖括號,而這次網摘收藏操作卻違反了這個規定,於是引發了這個錯誤。

對於這個問題,你也許會說這麼簡單的問題也好意思寫篇博客,肯定是通過url參數傳遞標題時沒有編碼。如果真是這樣,也會寫篇博客,但不是技術分享,而是檢討書。

剛開始看到這個錯誤時,的確閃過這樣的念頭 —— 難道真的忘了編碼?。。。不會的,記得編了。打開代碼一看,鬆了一口氣,檢討書不用寫了,但技術分享必須的,當然前提是解決了問題。

上網摘中用到的js代碼:

var url = 'http://home.cnblogs.com/wz/create?t=' + encodeURIComponent(document.title);

看!剛剛的!encodeURIComponent,經過無數次實踐證明過的有效的Javascript Url Encode方式。

可是,現在竟然出問題了。。。

首先懷疑是不是頁面標題中沒有對標題內容進行HTML編碼。檢查確認,編碼了:

<title>LINQ那些事(9)-解析Table&lt;T&gt;.Attach引發的異常和解決方法 - 海南K.K - 博客園</title>

接着,看一下document.title的值:

LINQ那些事(9)-解析Table<T>.Attach引發的異常和解決方法  - 海南K.K - 博客園

竟然自動進行HTML解碼了!這還是第一次發現!解碼也沒關係啊,encodeURIComponent對尖括號也會編碼。

繼續前進,看一下encodeURIComponent(document.title)的值:

LINQ%E9%82%A3%E4%BA%9B%E4%BA%8B(9)-%E8%A7%A3%E6%9E%90Table%3CT%3E.Attach%E5%BC%95%E5%8F%91%E7%9A%84%E5%BC%82%E5%B8%B8%E5%92%8C%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95%20-%20%E6%B5%B7%E5%8D%97K.K%20-%20%E5%8D%9A%E5%AE%A2%E5%9B%AD

<被編碼爲%3C,>被編碼爲%3E,正常啊。明明是編了碼的尖括號,IIS怎麼會報錯呢?難道瀏覽器偷偷解了碼,再發送給IIS。

特此檢查了一下瀏覽器請求的URL,瀏覽器對編了碼的URL絲毫未動。

難道是IIS惹的禍?看一下IIS日誌中記錄的URL:

日誌記錄說明了IIS收到的也是編過碼的尖括號。難道罪魁禍首是IIS!

也就是說IIS在判斷時,先將查詢字符串進行解碼,將%3C解碼爲<,將%3E解碼>,然後發現有尖括號,然後出錯!

是不是這樣呢?不入虎穴,焉得真相。根據錯誤信息,用ILSPY查看HttpRequest的源代碼。

錯誤信息如下:

用ILSPY一路追蹤:

1. HttpRequest.QueryString

複製代碼
public NameValueCollection QueryString
{
    get
    {
        if (this._queryString == null)
        {
            this._queryString = new HttpValueCollection();
            if (this._wr != null)
            {
                this.FillInQueryStringCollection();
            }
            this._queryString.MakeReadOnly();
        }
        if (this._flags[1])
        {
            this._flags.Clear(1);
            HttpRequest.ValidateNameValueCollection(this._queryString, "Request.QueryString");
        }
        return this._queryString;
    }
}
複製代碼

2. FillInQueryStringCollection()

複製代碼
// System.Web.HttpRequest
private void FillInQueryStringCollection()
{
    byte[] queryStringBytes = this.QueryStringBytes;
    if (queryStringBytes != null)
    {
        if (queryStringBytes.Length != 0)
        {
            this._queryString.FillFromEncodedBytes(queryStringBytes, this.QueryStringEncoding);
            return;
        }
    }
    else
    {
        if (!string.IsNullOrEmpty(this.QueryStringText))
        {
            this._queryString.FillFromString(this.QueryStringText, true, this.QueryStringEncoding);
        }
    }
}
複製代碼

3. FillFromEncodedBytes

複製代碼
// System.Web.HttpValueCollection
internal void FillFromEncodedBytes(byte[] bytes, Encoding encoding)
{
    int num = (bytes != null) ? bytes.Length : 0;
    for (int i = 0; i < num; i++)
    {
        this.ThrowIfMaxHttpCollectionKeysExceeded();
        int num2 = i;
        int num3 = -1;
        while (i < num)
        {
            byte b = bytes[i];
            if (b == 61)
            {
                if (num3 < 0)
                {
                    num3 = i;
                }
            }
            else
            {
                if (b == 38)
                {
                    break;
                }
            }
            i++;
        }
        string name;
        string value;
        if (num3 >= 0)
        {
            name = HttpUtility.UrlDecode(bytes, num2, num3 - num2, encoding);
            value = HttpUtility.UrlDecode(bytes, num3 + 1, i - num3 - 1, encoding);
        }
        else
        {
            name = null;
            value = HttpUtility.UrlDecode(bytes, num2, i - num2, encoding);
        }
        base.Add(name, value);
        if (i == num - 1 && bytes[i] == 38)
        {
            base.Add(null, string.Empty);
        }
    }
}
複製代碼

就是在這裏進行解碼的,調用的就是HttpUtility.UrlDecode。 

原來罪魁禍不是IIS,是ASP.NET!

問題原因小結: 

ASP.NET在檢測XSS跨站腳本攻擊時,會將查詢字符串解碼,然後調用System.Web.CrossSiteScriptingValidation.IsDangerousString()進行檢查。所以任何對查詢字符串中的尖括號進行直接的UrlEncode編碼操作(比如Javascript的encodeURIComponent, escape, encodeURI)都無法逃過ASP.NET的檢查。

那沒有解決方法呢?有!我們找到了,並且已經在實際中使用,不信的話,可以用網摘收藏一下LINQ那些事(9)-解析Table<T>.Attach引發的異常和解決方法

解決方法:

1. 通過下面的代碼獲取原裝的未進行過HTML解碼的頁面標題(Javascript代碼),這裏 < 變成了 &lt; , > 變成了 &gt;:

var title = document.getElementsByTagName('title')[0].innerHTML;

(注意:前面已經提過,document.title會對<title></title>中的內容自動進行HTML解碼,所以不要用它。)

2. 然後通過Javscript的encodeURIComponent進一步編碼,這樣可以躲過ASP.NET的XSS跨站腳本攻擊檢查(檢查時,ASP.NET得到的是 &lt; 與 &gt; )。

3. 在ASP.NET程序中獲取這個查詢字符串時,需要進行額外的HtmlDecode操作,C#代碼如下:

HttpUtility.HtmlDecode(Request.QueryString["t"]);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章