Yesterday anyone in IT will have followed the news about the two PHP bugs, CVE-2026-109618 and CVE-2026-109619, better known as PHP2Shell. I wrote a post on what they mean for Citrix NetScaler on the day the PHP fixes came out, before Citrix had released a statement.

Illustration of a NetScaler appliance with a terminal above it showing php -v reporting PHP 8.1.34
Illustration (AI generated)

Since then more information has come out. One thing I read is that triggering the bugs is the easy part. Turning them into code execution requires memory addresses, and those are normally hidden by ASLR (address space layout randomization). Most of the proof of concept exploits on GitHub don’t work at all. The ones that do either brute force the addresses or need a separate memory leak.

VulnCheck says the same, more on that below.

That made me look at what else NetScaler is built on. PHP 8.1 is not the only end of life component: NetScaler runs on FreeBSD 11.4, stripped down and customised by Citrix, and that has been end of life since 2021. Every FreeBSD version that is still supported has ASLR switched on by default. So I looked at two things: does ASLR really matter for these bugs, and does NetScaler have it?

Disclaimer: this is based on the PHP advisories and fix, public analysis and my own static analysis of the firmware (build 14.1-73.46). It is not confirmed by Citrix. I did not build an exploit and I won’t publish one.

The short version

  • NetScaler has not one but at least two end of life components here: PHP 8.1.34 (end of life since 31 December 2025) and FreeBSD 11.4 (end of life since 30 September 2021). Not having ASLR makes weaponising PHP2Shell a lot easier.
  • ASLR matters for these bugs. They leave pointers to freed memory behind, and to get from there to code execution an attacker has to know where things are in memory. The public exploit work confirms it: code execution works without ASLR, and with ASLR it needs a memory leak.
  • NetScaler doesn’t have ASLR. The FreeBSD 11.4 kernel doesn’t randomise where libraries, the heap or the stack go, and httpd and php are not position independent, so they always load at the same address. A supported FreeBSD would have had ASLR on by default, which it is since FreeBSD 13.2.
  • So the step that stops most of the public exploits isn’t there on NetScaler. The addresses are the same on every appliance with the same firmware, and anyone can get them from the firmware image.
  • What doesn’t change: PHP only runs on the management interface. Keep it off the internet and only reachable from admin hosts.

Why addresses matter for these bugs

The fix shows what goes wrong. In the shutdown of a request, PHP now explicitly resets a number of globals it used to leave alone:

c Download
EG(user_error_handler) = UNDEF;
EG(user_exception_handler) = UNDEF;
EG(exception) = NULL;
EG(objects_store).object_buckets = NULL;
EG(objects_store).top = 1;

When a request ends with an out of memory error or a fatal error, these can still point to memory that was already freed. In a worker process that handles many requests, like Apache with mod_php, the next request can use them before they are set up again.

The object store and the error handlers lead to PHP objects, and PHP objects lead to tables of function pointers. So the bug gives an attacker a way to influence a function pointer. To turn that into code execution they need to point it somewhere useful, and for that they need to know addresses. That is exactly what ASLR hides: without a leak, the attacker has to guess, and a wrong guess crashes the worker.

That is my reading of the fix. The evidence that ASLR matters in practice comes from people who actually built exploits:

  • VulnCheck found that code execution works on ARM64 and on x86 systems without ASLR. Of the hundreds of proof of concepts on GitHub, none bypass ASLR on x86-64 by themselves. Their own exploit does work with ASLR enabled, but only by chaining a memory leak into the code execution. Their conclusion is that ASLR should “somewhat reduce the available attack surface area”.
  • Tanium reports the same: code execution works on ARM64 and on x86 with ASLR turned off, and very few of the public exploits work at all.

So ASLR doesn’t make these bugs safe, a leak gets around it. But it is the step that most attempts fail on. I didn’t test this myself, I rely on their results for it.

Does NetScaler have ASLR?

NetScaler 14.1 is built on FreeBSD 11.4. You can see that in the firmware: the binaries carry a FreeBSD 11.4 ABI note, the startup code comes from releng/11.4, and the rc.conf header says src/release/netscaler. FreeBSD 11.4 has been end of life since 30 September 2021, so for over five years. FreeBSD only got ASLR in 13.0, and it was never backported to 11. Since 13.2 it is on by default for 64-bit programs, so every FreeBSD version that is still supported today has it.

But NetScaler is not stock FreeBSD. Jan Tytgat asked a fair question: the kernel is heavily stripped down and customised by Citrix, so how much of standard FreeBSD can you assume? Backporting security fixes is not uncommon, but backporting ASLR would be a huge effort. But we can check, after all we want solid evidence, not assumptions, so I looked at the binaries and the kernel itself.

The binaries load at fixed addresses

Both /bin/httpd (the Apache that hosts mod_php) and /bin/php are of type ET_EXEC, not position independent. Their code and data are at the address the linker chose, every time, on every appliance with this build. That is a property of the binary. No kernel setting can change it.

httpd also has no RELRO, so its GOT (the table with addresses of library functions) is writable and also at a fixed address. The stack is not executable, which forces an attacker to reuse existing code instead of injecting their own. With fixed addresses that is not much of a hurdle.

The kernel doesn’t randomise anything

I extracted the kernel from the firmware. It has a complete symbol table (more than 46,000 symbols), so I could check both the names and the code:

  • In FreeBSD 13 and later, ASLR is controlled by the sysctls kern.elf64.aslr.enable, pie_enable, honor_sbrk and stack_gap. None of these exist in the NetScaler kernel. Not as a symbol, not as a string, not in the section where all sysctl names are registered. There isn’t even a kern.elf64 tree.
  • exec_map_stack, the function FreeBSD added for ASLR to place the stack, doesn’t exist.
  • In IDA, exec_new_vmspace places the stack of a new process at a fixed offset below the fixed top of the stack. There is no random gap.
  • vm_map_find is the function that decides where every memory mapping goes, including where libc and libphp.so are loaded. It does a deterministic first fit from a starting address. Citrix’s own changes are visible in the memory code, but they are about memory accounting for the packet engine, not about randomisation.
  • The kernel’s random number generator is used where you’d expect it: process IDs, TCP sequence numbers, ephemeral ports, UUIDs, crypto. None of the functions that decide where memory goes call it.

To be fair, there is one thing that is randomised: the stack canary, the value that detects a classic stack buffer overflow. That is useful, but it doesn’t hide where anything is in memory.

So to answer Jan: stripping a kernel removes features, it doesn’t add any. The conclusion doesn’t rest on the FreeBSD version, it rests on the code that runs on the appliance and on how the binaries are built.

Prefork makes guessing cheap anyway

NetScaler runs Apache with mod_php, which the advisories call the worst case, and with the prefork MPM: up to 30 worker processes, each handling up to 10,000 requests. Even with ASLR, prefork would weaken it: every worker is a copy of the same parent, so they all have the same memory layout, and a worker that crashes is replaced by a new copy with that same layout. A wrong guess costs one worker, not the attempt.

What this means

Each of these end of life components is a problem on its own. Together they are worse. The end of life PHP has the bug, and it will never get an upstream fix. The end of life FreeBSD has no ASLR, so the step that stops most exploits on a modern system isn’t there.

With a supported FreeBSD the bar would have been higher. libc, libphp.so (where the PHP code itself lives), the heap and the stack would be at random addresses, and an attacker would first need a memory leak, like VulnCheck’s exploit does. It wouldn’t have made the bug safe, and prefork would still make guessing cheaper, but it is exactly the step where most of the public exploits stop. httpd itself would still load at a fixed address, because it isn’t built position independent. That is a build choice, not something a newer kernel fixes.

On NetScaler as it is, the addresses are the same for every appliance with the same firmware, and the firmware can be downloaded. That is the same image I used for this analysis. So NetScaler isn’t a harder target than a typical Linux server with this bug. It’s an easier one. That doesn’t mean there is a ready to use exploit for NetScaler, I didn’t build one.

What doesn’t change is who can reach it. PHP on NetScaler only runs behind the management interface, not on the Gateway or AAA side (see the first post). Everything above is about how reliable an attack is once someone can talk HTTP to the management GUI. If your management interface is not on the internet, the realistic attacker is someone who is already on your network.

What to do

The advice from the first post still stands, only with more urgency:

  1. Keep the management interface off the internet and only allow named admin hosts. Citrix has an ACL example for this. This is the control that decides your risk.
  2. Don’t count on “it’s only the management GUI”. With no ASLR, an attacker who gets onto the management network has an easier job than this bug would normally give them.
  3. Install the fixed build as soon as Citrix releases it. Watch the security bulletin CTX697263 for that.
  4. Look beyond this patch. FreeBSD 11.4 has been end of life since 30 September 2021, and PHP 8.1 since 31 December 2025. A fixed build solves these two CVEs, not the fact that the base of the appliance doesn’t get upstream security fixes. This bug shows why that matters: an end of life kernel makes a bug in a different component easier to exploit. It is a fair question to ask Citrix when NetScaler moves to a supported FreeBSD and PHP.

References

A note on how this post was written: I used AI assistance. Claude helped with the analysis and made the first draft from my notes. It was not written automatically: I steered the investigation, reviewed the draft, corrected what was wrong and rewrote parts in my own words.