Home » Posts tagged "xss"

利用上傳的檔案跳過 CSP 限制

CSP 可以做到一些簡單的保護機制,但在設計不良的情況下還是有辦法繞過。

這次是上傳合法的 JPEG 檔案,但當作 javascript 檔案繞過去:「Bypassing CSP using polyglot JPEGs」。

開頭的「FF D8 FF E0」可以在「List of file signatures」這邊看到是「JPEG raw or in the JFIF or Exif file format」,而這四個字元在 javascript 不會出問題。接下來的「2F 2A」表示 JPEG header 長度,剛好就是「/*」,把後面的東西給包起來,後面再用類似的方式一直組合就打穿了...

這種攻擊要跳過的是「用 CSP 的 self 限制不能引用外部網站 javascript」的限制,但還是有些前提:

  • 允許使用者傳到同一個 domain 上面。
  • 網站上有 XSS 漏洞。

其中第一個問題常見的解法是另外開一個 domain 來放使用者上傳的檔案 (最好是連 top domain 都不一樣,完全隔開),才可以透過 CSP 降低風險...

XSScrapy:自動化 XSS 攻擊

也是不知道在哪邊看到的,反正容易安裝就裝起來玩玩看:「XSScrapy: fast, thorough XSS vulnerability spider」。XSScrapy 是一套自動化攻擊軟體,是目前這類測試軟體裡用起來最簡單的版本。

直接 clone 下來就可以執行了,如果有遇到 dependency 的問題,可以透過 pip 安裝:

pip install -r requirements.txt

執行的方法就這樣:

./xsscrapy.py -u http://example.com

上面這個方法是在訪客模式下慢慢跑,你也可以給他一組帳號密碼讓他填 form,他會試著登入後不斷打下去:

./xsscrapy.py -u http://example.com/login_page -l loginname -p pa$$word

也可以用 HTTP Basic Authentication:

./xsscrapy.py -u http://example.com/login_page -l loginname -p pa$$word --basic

然後發現的問題會寫到 XSS-vulnerable.txt 裡面:

XSS vulnerabilities are reported in XSS-vulnerable.txt

用法就這樣而已... XD

在 HTML 內嵌 JSON object 時要注意的事情...

有時候我們會因為效能問題,在 HTML 內嵌入 JSON object,而不是再多一個 HTTP request 取得。

但「嵌入」的行為如果沒有處理好,就產生非常多 XSS attack vector 可以玩。

首先最常犯的錯誤是使用錯誤的 escape function:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<?= addslashes($str) ?>";
</script>
</body>
</html>

這樣可以用 </script><script>alert(1);// 攻擊 $str。因為 addslashes() 並不會過濾到這個字串,而產生這樣的 HTML:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "</script><script>alert(1);//";
</script>
</body>
</html>

而這個字串會造成 DOM parser 解讀上產生不是我們預期的行為:

可以看到在字串裡面的 </script> 被拆開了。

這是因為瀏覽器會先拆解產生 DOM tree,再把 <script></script> 內的程式碼交給 JavaScript engine 處理。所以在一開始產生 DOM tree 的時候,是看不懂 JavaScript 程式邏輯的...

正確的方法是用 json_encode() 處理,因為 PHPjson_encode() 預設會把 / (slash) 變成 \/ (這是 JSON spec 裡合法的轉換):

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = <?= json_encode($str) ?>;
</script>
</body>
</html>

這會產生出:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<\/script><script>alert(1);//";
</script>
</body>
</html>

但上面這段 HTML 與 PHP code 仍然有問題,如果 $str<!--<script 時,你會發現 DOM 又爛掉了:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<!--<script>";
</script>
</body>
</html>

escape.alf.nu 的 Level 15 就是利用這個問題,再加上其他的漏洞而完成 XSS 攻擊。

為了這個問題去 StackOverflow 上問:「Why does <!--<script> cause a DOM tree break on the browser?」,才又發現上面這段 code 並不是合法的 HTML5 (先不管 head & title 的部份,補上後仍然不是合法的 HTML5)。

原因在於 DOM parser 對 <script></script> 的特殊處理:「4.3.1.2 Restrictions for contents of script elements」。(話說這段 ABNF 差點讓我翻桌...)

解法是在 <script></script> 的開頭與結尾加上 HTML 註解:(這剛好是 HTML 4.01 建議的方法)

<!DOCTYPE HTML>
<html>
<body>
<script>
<!--
var a = "<!--<script>";
-->
</script>
</body>
</html>

那段 ABNF 的目的是希望可以盡可能往後找到 --></script> 結尾的地方。

當然你也可以用 json_encode()JSON_HEX_TAG<> 硬轉成 \u003c\u003e 避開這個問題,但這使得呼叫 json_encode() 時要多一個參數 (而非預設參數),用起來比較卡...

這個問題會變得這麼討厭,是因為 DOM parser 與 JavaScript 語法之間有各自的處理方式,然後又有些 pattern 是之前的 spec 遺留下來的包袱 (像是 HTML 4.01 在「18.3.2 Hiding script data from user agents」裡有提到用 <!----> 包裝 <script></script>),變成在設計 HTML5 時都要考慮進去相容...

之前會習慣用 <!--//--> 包裝 <script></script> 倒不是這個原因,而是因為不這樣做的話,jQuery 在 IE 使用 html() 時遇到有 <script></script> 的字串會爛掉,所以後來寫的時候變成習慣了...

反而因為這個習慣而避開了這個問題...

超難搞啊...

Filter Input & Escape Output...

維基百科上有一篇「Secure input and output handling」說明要怎麼處理 input 與 output (對資安方面的說明)。標題的 Filter Input 與 Escape Output 是 Gasol 之前提到後才知道的名詞,以前只知道要 validate & escape,沒想過一個比較好記的念法...

這邊都以 PHP 為主,其他程式語言也應該會有對應的方式...

Input 有很多管道,有可能是使用者或是 3rd party 廠商透過 Form 傳進來的資料 (在 PHP 裡可能是 $_GET 或是 $_POST),也有可能是 cookie 的資料 (因為使用者可以修改,所以視為不安全的資料)。從檔案讀資料進來 (可能是普通的文字檔,或是 XML,也可能是圖片) 也算是 input。

Output 也有很多管道,像是 HTML、JSON,或是組 SQL statement 時使用變數。

Filter Input

在處理 input data 時,一般常常忘記的是「先強制轉成 Non-null UTF-8 string」(現在一般都是用 UTF-8,所以這邊就只講 UTF-8)。

這是因為很多 PHP function 對非 Non-null UTF-8 string 有非定義行為 (undefined behavior,不保證效果與輸出結果的正確性),加上一般常見的產品需求可以用 Non-null UTF-8 string 滿足,所以在 PHP 內拿到資料時可以先 filter 過。

Update:下面說的步驟錯了,請參考「關於 Non-null string 的處理...」這篇的說明。

有兩個步驟要做,第一個是確保他是 Non-null,直接把 \0 以及以後的東西幹掉:

$str_out = preg_replace('/\0.*/g', '', $str_in);
$str = preg_replace('/\0.*/g', '', $str); // 直接取代原來字串

第二個是確保他是 UTF-8 string。這點可以直接用 iconv() 轉,而不用自己寫 regex 處理了:

$str_out = iconv('UTF-8', 'UTF-8', $str_in);
$str = iconv('UTF-8', 'UTF-8', $str);

iconv() 從 UTF-8 轉到 UTF-8 是一個特別的用法,我是在 Cal Henderson 的「Building Scalable Web Sites: Building, Scaling, and Optimizing the Next Generation of Web Applications」上看到的 (有中譯版)。

如果傳進來的值本來就假設是整數,那麼就用 intval() 轉一次。如果假設是非負整數 (包含 0),那麼就用 abs()intval()。如果是文字類的 (像是 e-mail),可以再用其他的 regex 檢查。

一般來說,白名單會比黑名單好。不過這也是一般性,很多時候還是很囧的...

Escape Output

Escape 指的是 Escape character (轉義字元)。不同的情況下會有不同的 escape character,所以保護的方式也不一樣。

以 HTML 來說,想要顯示小於符號 <,實際上要用 &lt; 表示。而在 PHP 裡面常用的 htmlspecialchars() 定義了「只 escape 五個符號」,剛好可以拿來用:

<div><?= htmlspecialchars($str) ?></div>

也有人推薦 htmlentities(),不過因為轉的比較多 (所以要考慮的行為比較多),我比較不喜歡...

對於 XML,在 XML 的規範裡規定一定要把 <& 換成 &lt;&amp; (Character Data and Markup),但 > 則可以 (非必要) 換成 &gt;。也允許把雙引號 " 換成 &quot;,以及單引號 ' 換成 &apos;

所以 XML 剛好可以直接用 htmlspecialchars() (確認完全符合 spec 要求),反過來 htmlentities() 就不保證了。

再來是 MySQL 的 escape 與 charset 有關,所以要用 mysql_real_escape_string() 或是 PDO::quote(),但更好的方法應該是使用 prepare & execute (binding variables)...

而 shell 的 escape 則應該用 escapeshellarg() 再帶入 string 裡。

每一種 output 所需要的 escape function 不同,都不能直接拿 addslashes() 來用... (這個 function 的設計就很 PHP...)

純粹 JSON 的話,用 json_encode() 就可以幫你處理好。問題是在 HTML + JSON 時會讓你處理到抱頭痛哭... 這就是另外的故事了 +_+

結論?

Filter Input + Escape Output 只是最基本的一步,不過在大多數的狀況下,這個方法就可以擋下不少問題了,算是很實用的 policy...

Archives