上個月有個客戶提到他們從後台上傳的檔案不知為何在前台就是看不到,我查看了一下發現檔名中有個加號 ( + ),但奇怪的是原本網站明明就是好的。後來我才想起來客戶最近主機升級了,從 Windows Server 2003 升級到 Windows Server 2008 R2,可能是因為這樣才導致這個問題發生,我研究了一會兒終於明白問題發生的原因,並不是 IIS7 有問題,而是變的更安全了,也因為這個問題讓我更加意識到在實做檔案上傳功能時應該注意到的事情!
我們都知道在 Windows 裡還是有一些特殊字元是可以當成檔名的,像是百分比符號 ( % )、井號 ( # )、加號 ( + ) 以及一些其他字元,但是有些特殊符號由於在 Web 世界裡有其他的特殊用途,並不適合用在 Web 存取的環境中當成 URL 路徑的一部分,而這幾個字元就是我這邊文章想強調的 % , # 與 + 字元 。
我們在 RFC2396 - Uniform Resource Identifiers (URI): Generic Syntax 的 2.2. Reserved Characters 這一小節有提到幾個字元是保留字元,這些字元在網址列出現時都應該被 URLEncode 過才行:
另外也有在 2.4.3. Excluded US-ASCII Characters 小節列出耶些分隔字元不應該直接使用在 URI 上,以及一些特殊字元很有可能會被 Proxy Server 解析為特定用途的的括弧符號也不建議使用:
control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
space = <US-ASCII coded character 20 hexadecimal>
delims = "<" | ">" | "#" | "%" | <">
unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
在 RFC2396 所定義的保留字與需被排除的字元中,有一些本來在 Windows 作業系統中就不允許出現,如果你在檔案總管變更檔名時企圖輸入一些特殊字元在檔名裡,基本上會直接被拒絕設定:
這些保留字與需被排除的字元中,有三個字元在 URI 規格裡有著特殊的目的與用途,分別描述如下:
百分比符號 ( % )
- % 符號用來作為 URLEncode 時的跳脫字元,例如:@ 這個字元在 URLEncode 之後就會變成以 %40 來代表,其中 40 是十六進制的 ASCII 碼。
- 如果要用來表達 % 字元,就必須編碼成 %25 才行!
井號 ( # )
加號 ( + )
我在【詳細解說幾個建置網站時常用的編碼方法】文章裡有提到了幾個建置網站時常用的編碼方法,其中有一個 HttpUtility.UrlPathEncode 編碼方式就是一個非常特殊的方法(Method),這個 Method 只會用來編碼 URL 中 path 的部分 (請參考本文稍早出現過的網址結構示意圖),所以當我們要透過 ASP.NET 輸出一個 <A> 連結時,在 href 屬性的內容就必須利用這個 HttpUtility.UrlPathEncode 方法來編碼檔名與路徑。
這個 Method 在 MSDN 的文件裡有提到:
URL 編碼方式確保所有瀏覽器將會正確傳輸 URL 字串中的文字。?、&、/ 和空格之類的字元可能會在某些瀏覽器中被截斷或毀損,因此這些字元必須在 <A> 標記或查詢字串中編碼,而查詢字串中的字串可能是瀏覽器以要求字串所重新傳送的。
但是這段說明其實描述的不夠清楚明瞭,事實上 HttpUtility.UrlPathEncode 方法 (Method) 對大部分的可見的 ASCII 字元都不會編碼,其中當然包括 百分比符號 ( % )、井號 ( # )、加號 ( + ) 這三個字元,但是這三個在 Web 世界如此特殊的字元卻是可以正常設定為「檔案名稱」的!
舉個例子來說,假設我們有個檔案名稱為 網站建置#數位行銷.doc,並且將該檔案放置在 /docs/ 網站目錄下,那麼我們的超連結可能會寫成這樣:
- <a href="/docs/網站建置#數位行銷.doc">網站建置+數位行銷.doc</a>
然而這樣的寫法基本上是錯誤的,因為在 href 屬性內的資料都沒有經過 UrlPathEncode 編碼,如果套上程式碼,就會像是如下的寫法:
- <a href="<%= HttpUtility.UrlPathEncode("網站建置#數位行銷.doc") %>">
在編碼之後 href 屬性的內容會變成如下的表達式,所有中文字的部分都被編碼成 UTF-8 Encoding 的表示法,其中最值得一提的就是檔名的部分 # 符號竟然沒有編碼,因為 HttpUtility.UrlPathEncode 方法 (Method) 根本不會幫你將這個字元編碼,所以該檔案在下載時就一定會遇到 404 找不到網頁 的情況!
- %e7%b6%b2%e7%ab%99%e5%bb%ba%e7%bd%ae#%e6%95%b8%e4%bd%8d%e8%a1%8c%e9%8a%b7.doc
不僅僅是這個 # 號會出問題,另外一個 百分比符號 ( % ) 也不會加以 URLEncode 過,因此在下載檔案時一樣都會出現問題,都會無法成功下載檔案。
最後一個 加號 ( + ) 其實不會受影響,因為在 RFC 1630 - Universal Resource Identifiers in WWW 規格裡所定義的加號字元其實是被規範在 query 這個部分,在 path 這部分還是可以當成合法的字元使用!
你可能會想說,我可以不用 HttpUtility.UrlPathEncode 方法,直接使用 HttpUtility.UrlEncode 方法 不就得了?是的,你可以這樣做,也應該這樣做,但是不一定會成功!請繼續看下去…
早在幾年前微軟推出了 UrlScan 工具,該工具支援 IIS 5.1 , IIS 6.0 與 IIS 7.0,我在之前也有寫過一篇【介紹好用工具:UrlScan Security Tool ( 入門級的 WAF 工具 )】文章介紹過,他可以保護你的網站以更安全的方式提供服務,並阻擋一些網路上不安全的要求 (HTTP Requests) 到你的網站伺服器。
安裝好之後其 UrlScan.ini 設定檔有個 VerifyNormalization 參數,預設值為 1 (啟用):
在 RFC2396 - Uniform Resource Identifiers (URI): Generic Syntax 的 2.4.2 When to Escape and Unescape 有提到一段話,提醒網頁開發人員要注意在做 URLEncode / URLDecode 的時候,要小心不要連續編碼/解碼兩次以上,否則很容易會導致資料被解碼錯誤,以致於得到不正確的資料。
但是,就是會有一些開發人員槁錯,像之前 XSS / SQL Injection 攻擊非常盛行的時候,有些網站的開發人員並不瞭解怎麼防禦這些攻擊事件,有些人就會針對一些特定的樣式進行字串過濾,像是檔掉 < 或是其 URLEncode 編碼過的 %3c 等等,但是聰明的駭客就會將 %3c 再次 URLEncode 編碼過變成 %253c 以跳過程式的檢查,讓該資料成功寫入到目標網站的資料庫中,有了這些資料就可以再找出網站其他弱點,讓這些資料經過 URLDecode 兩次就能讓駭客入侵成功,以達成 XSS 攻擊的目的。
而 UrlScan 的 VerifyNormalization 參數就是用來保護網站不會受到駭客 2 次編碼的攻擊,只要 UrlScan 偵測到 HTTP Requests 的 URL 包含 % 符號被編碼兩次的狀況,就會直接將本次 HTTP 要求阻擋下來!
到了 IIS 7.5 已經內建了 要求篩選 (Request Filtering) 功能,這個功能其實就是 UrlScan 重新實做後的版本,所以熟悉 UrlScan 的人應該會對他的設定參數十分熟悉,只是多了 GUI 設定介面而已。
點開要求篩選之後,我們可以點選 IIS 管理介面右上角的「編輯功能設定」:
相較於 UrlScan 的 VerifyNormalization 參數,在 IIS 7.5 的要求篩選裡就是「允許雙重逸出」這項設定,取消勾選的狀態就如同 VerifyNormalization=1 是一樣的設定,是預設的設定,這樣的設定會拒絕 HTTP 要求的 URL 有 URLEncode 兩次的情況。
本文稍早有講過透過 HttpUtility.UrlPathEncode 方法 (Method) 來編碼「檔名路徑」(path) 的部分只有 加號 ( + ) 能夠倖免於難,而且 % 與 # 只要做過 URLEncode 編碼就能成功讓檔案下載。
但是 IIS 7.5 的要求篩選與 UrlScan 卻會讓檔名中出現 % 與 + 的狀況變的更加棘手,就算你對 % 與 + 進行 URLEncode 編碼,這樣的 HTTP 要求還是會被阻擋下來,以下是被阻擋的主要原因:
百分比符號 ( % )
- 由於 % 符號是用來作為 URLEncode 時的跳脫字元,假設你的檔名包括 %25 這 3 個字元,那麼透過 URLEncode 過的檔名就會變成 %2525 才行。
- 然而這樣的 URL 會被要求篩選或 UrlScan 偵測為不安全的 URL,因為 %2525 在 URLDecode 之後會變成 %25,而 %25 又可以被 URLDecode 為 %,這樣「雙重逸出」的情況是不被允許的!
加號 ( + )
- 由於加號 ( + ) 只有在 URL 的 query 部分才代表著「空白字元」,所以在 path 部分根本不需要進行 URLEncode 編碼,但是你的程式可能已經透過 HttpUtility.UrlEncode 方法 將 + 編碼成 %2b 了,所以 URL 上會出現 %2b 這個字樣。
- 然而這樣的 URL 也會被要求篩選或 UrlScan 偵測為不安全的 URL,因為 %2b 在 URLDecode 之後會變成 + 號,而 + 號又可以被 URLDecode 為 空白字元,這樣「雙重逸出」的情況也是不被允許的!
- 有趣的是,就算你刻意跳過 + 號不做 URLEncode 編碼,很抱歉,IIS 7.5 還是無法讓你通過,依然把你阻擋在外 (註: 我不確定 IIS 為何要這樣設計,可能就是不希望讓你使用 + 號吧,因為檔名中的 + 號在 path 與 query 有不同的意義,開發人員真的很容易搞不清楚)
有了這樣安全的設定,是真的在保護你的網站不被駭客入侵,我建議各位不要手動調整這個設定,而是修改你的網頁上傳檔案的程式,直接拒絕這三個字元的使用最好,或是上傳檔案時直接過濾掉這些容易出問提的字元也行。
但是像我們的客戶網站上已經有上千個檔案已經上傳在 IIS 站台裡供使用者下載,要不就是寫程式批次修改檔案名稱然後修改上傳檔案的那幾頁程式;要不就是跟資安妥協,直接建議他開啟「允許雙重逸出」這項設定。
對客戶來說,改程式要花錢通常都很困難,所以我提供另一個更棒的解決方案給客戶,不用改程式花錢,又能夠讓「允許雙重逸出」被勾選時 IIS 站台也一樣的安全,這樣的設定有一個前提與兩個設定步驟:
前提
- 檔案上傳的目錄都是靜態檔案或文件,不會有任何可執行的程式,如: *.php, *.aspx, *.asp, …
兩個設定步驟
1. 停用該目錄的的「指令碼」與「執行」權限
請參考我之前 IIS7 如何關閉特定目錄的執行權限(與 IIS6 比較)文章的設定技巧,先選取檔案上傳的目錄,然後在根據文章教學進行設定以關閉「指令碼」功能權限
2. 啟用「允許雙重逸出」設定
由於該目錄已經被停用執行權限,所以就算該目錄被植入指令碼 (網頁木馬程式) 也不能執行 (因為已經取消了執行權限),這時我們就可以放心的啟用「允許雙重逸出」的設定。
透過上述兩個步驟的設定,會讓你在該目錄新增一個 web.config 檔案
其內容如下,你也可以不透過 IIS 管理工具,手動新增 web.config 到該目錄也可以:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers accessPolicy="Read" />
<security>
<requestFiltering allowDoubleEscaping="true" />
</security>
</system.webServer>
</configuration>
呼~ 我知道你看的累了,其實我寫的也累了,但是釐清這些觀念可以避免未來魔鬼的入侵,這些研究算是值得的投資,而且這些觀念與細節還真的牽扯到太多部分,有 RFC Spec, W3C Spec, UrlScan, IIS 7.5, Request Filtering, URLEncode/URLDecode, …,能研究出這些東西還算蠻有感覺的,與您共享! ^^
相關連結