問題所處環境: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<T>.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代碼),這里 < 變成了 < , > 變成了 >:
var title = document.getElementsByTagName('title')[0].innerHTML;
(注意:前面已經提過,document.title會對<title></title>中的內容自動進行HTML解碼,所以不要用它。)
2. 然后通過Javscript的encodeURIComponent進一步編碼,這樣可以躲過ASP.NET的XSS跨站腳本攻擊檢查(檢查時,ASP.NET得到的是 < 與 > )。
3. 在ASP.NET程序中獲取這個查詢字符串時,需要進行額外的HtmlDecode操作,C#代碼如下:
HttpUtility.HtmlDecode(Request.QueryString["t"]);
感言
解決一個問題,最好的慶祝方式就是寫一篇博客!不僅可以分享給別人,自己還會有額外的收獲!而且一定會有!
本來寫這篇博客時,我用的標題是“[Javascript]document.title 引起的Html Decode 問題”,當時以為是Javascript的問題,寫博客過程中才發現是ASP.NET的問題。因為在寫博客過程中,你要清楚地表達出來,來不得半點馬虎,你會對問題進行更深入的研究。
一個東西,你想明白了,並不代表你真正理解。只有你清楚地表達出來,讓別人能明白,才說明你真正理解。