Dark Embargo — Technical Analysis of a PHP 8.4 Vulnerability
UncategorizedIntroduction to Dark Embargo:

This write-up explores the Dark Embargo machine from Hack The Box, focusing on PHP2Shell, a PHP 8.4 memory-corruption vulnerability associated with session upload progress handling. It covers the vulnerability analysis, exploitation concepts, and their security implications.
PHP2Shell – Vulnerability Overview
PHP2Shell refers to two security vulnerabilities found in PHP, the software responsible for processing PHP code on web servers.
These flaws affect PHP itself, not individual websites or plugins. As a result, websites running vulnerable PHP versions may be at risk even if their applications are fully updated.
The vulnerabilities are:
- CVE-2026-109618: A memory-handling flaw that can cause PHP to process damaged or incomplete data.
- CVE-2026-109619: A flaw that can cause PHP to reuse invalid data left behind by an earlier request.
Both vulnerabilities received a CVSS score of 9.2. Researchers demonstrated that attackers could exploit them without logging in to execute commands on affected servers or access sensitive information stored in memory.
Who Is Affected?
Web servers running vulnerable PHP versions may be affected, regardless of whether they host WordPress, Drupal, Joomla, or custom PHP applications.
The fixed PHP releases are:
- PHP 8.2.35 and later
- PHP 8.3.36 and later
- PHP 8.4.27 and later
- PHP 8.5.12 and later
Older PHP branches may require an upgrade or a security update from the operating system vendor.
Objective:
The objective is to investigate the PHP2Shell vulnerability in PHP 8.4, understand how session upload progress handling can lead to memory corruption, and assess its security impact within the Dark Embargo Challenges.

Dark Embargo is accessible at 154.57.164.75 on port 30315.
Challenge File Analysis

The challenge provides a ZIP archive named a319620a-2d18-46a3-ade6-6c2d5a76aa40.zip, with a file size of 6 KB.

After cloning the challenge files, we list the directory contents. We find a Dockerfile, build_docker.sh, flag.txt, and the main application file index.php.

Examining the Dockerfile shows the image is based on php:8.4.26-apache. It copies index.php into the web root and places flag.txt at the root of the container with world-writable permissions.
Web Application Enumeration

Browsing to the target reveals a professional-looking freight forwarding company site called “Embargo” with navigation links and a “Get a Rate Quote” button.

A Nikto scan against the target identifies Apache/2.4.69 and PHP/8.4.26. It also notes missing security headers such as X-Frame-Options and X-Content-Type-Options.

Checking index.php for PHP opening tags returns zero matches, indicating the file is currently pure static HTML/JS with no server-side PHP code present.

We build a local Docker image from the provided Dockerfile so we can analyze and test the application offline.

The newly built image runs locally on port 8080, providing a controlled environment for further exploration of the challenge.

We spin up the local Docker container again, mapping port 8080 so the application is accessible at http://127.0.0.1:8080 for offline testing .Inside the running container we query key PHP settings. memory_limit is 128M, session.save_path is unset, and session.upload_progress.enabled is On — useful details for potential session or upload-based attacks.
Local Environment Setup

The local instance serves the Embargo freight-forwarding landing page correctly from the Docker container, confirming that the application runs as expected on the local environment.

We run a PHP-to-shell probe against the local instance to assess potential exposure. The tool identifies PHP 8.4.26 and tests several file-upload variants.

A minimal Apache prefork configuration limits the server to a single worker process, making race conditions and request-handling behavior easier to debug.

We start a dedicated debugging container named darknite, binding it to port 8081 and mounting a single-worker Apache prefork configuration while adding SYS_PTRACE and an unconfined seccomp profile for easier analysis.

We drop into an interactive root shell inside the darknite container, ready to inspect the application and any potential privilege-escalation paths.

Launching GDB initially fails because the container lacks the debugger, preventing direct debugging until installation.

Installing GDB through apt-get succeeds despite a non-interactive frontend warning. Running the debugger displays the GNU GDB 16.3 banner, confirming successful installation.
Apache Debugging Setup

A process count confirms that Apache runs only two processes under the single-worker configuration. Inspecting PID 1’s effective capabilities reveals the hexadecimal value 00000000a80c25fb, providing insight into the container’s privilege level.

GDB runs in batch mode to identify the memory addresses of zif_system, zval_ptr_dtor, and ps_globals within libphp.so.
Debugging PHP Internals

Python 3, pip, and venv support further scripting and analysis tools inside the container.

A helper script extracts symbol offsets from libphp.so, identifying ZVAL_PTR_DTOR and PS_GLOBALS. However, zif_system is stripped from the library, preventing direct symbol resolution.
Memory Corruption Analysis

GDB attaches to the Apache worker process and loads several shared libraries. However, most libraries lack debugging symbols, limiting symbol-level inspection.

A breakpoint on _zend_bailout allows GDB to intercept execution when the target condition occurs during HTTP request processing. The debugger then resumes execution to monitor the worker’s behaviour.

A TCP connectivity test verifies that the local Apache instance accepts connections on port 80, confirming network accessibility.

Running the helper script against port 8080 reports that the required symbols (zif_system, zval_ptr_dtor, ps_globals) are not present in the loaded binary.

GDB successfully hits the _zend_bailout breakpoint, confirming that request processing reaches the target function.

A short backtrace from the breakpoint shows the call chain through PHP error handling and memory-allocation routines inside libphp.so.

The exploit targets an alternative port and prepares a 284,129-byte payload containing 880 nested fields for further testing.

GDB displays the addresses of ps_globals, http_session_vars, and the session ID, along with related memory chunk offsets for further analysis.

Adding the offset 0x465370 to the base address produces the calculated address 0x7f0c82ff4370.

GDB lists the loaded shared libraries and shows that symbols have already been read for most system and Apache modules.

The zif_system function resides at 0x7fdf9f865370 within the stripped libphp.so library.

Memory mappings reveal the heap region, Apache binary segments, and addresses assigned to various shared libraries.

GDB resolves the function address to 0x7fdf9f865370, while a separate lookup identifies a nearby function within the same library.

Further examination of the process mappings shows the readable and executable segments of libphp.so.

GDB maps libphp.so into separate executable, read-only, and writable regions.
Exploitation Investigation

A breakpoint is placed on popen before resuming execution to monitor subsequent calls.

Calculated addresses and a command payload build the function table, confirming the location of system in memory.

Continuing after the popen breakpoint results in a segmentation fault inside zend_reference_destroy.

The RDI register provides the address of structure S as 0x7f0c810f5300.

Masking the lower bits of the structure address yields the chunk base 0x7f0c81000000.

Subtracting the chunk base from the structure address gives an offset of 0xf5300.

A backtrace from the crash shows the call path through PHP session tracking and RFC1867 post handling.
Exploitation Results

An advanced exploit first checks Apache’s MaxRequestWorkers setting to determine the available worker capacity. Next, it attempts to exhaust the available workers by sending large multipart requests. As a result, the technique aims to trigger a race condition or request-rebinding behaviour that could lead to remote code execution.

Meanwhile, a Netcat listener waits on port 9007 for an incoming connection. Once the exploit succeeds, the reverse connection is established, providing a shell as www-data inside the container.

Reading /flag.txt confirms successful command execution under the www-data account.
Remediation and Mitigation
1. Upgrade PHP
The recommended fix for CVE-2026-109618 and CVE-2026-109619 is to upgrade PHP to a patched release. Make sure every PHP runtime handling web requests receives the update.
The fixed versions are:
- PHP 8.2.35 or later
- PHP 8.3.36 or later
- PHP 8.4.27 or later
- PHP 8.5.12 or later
PHP 8.1 and older branches no longer receive official fixes from the PHP project. Systems running these versions should move to a supported branch or use an available security backport from their operating system vendor.
After upgrading, restart the relevant services, including Apache when using mod_php or PHP-FPM.
2. Check the PHP Version
Run the following command to check the installed PHP version:
php -vKeep in mind that the command reports the CLI version, which may differ from the version used by Apache or PHP-FPM. Check the web server’s PHP runtime as well to make sure it is running a patched release.
3. Apply Temporary Mitigations
If upgrading PHP is not immediately possible, a few configuration changes can help reduce exposure while preparing the update.
- Set
session.upload_progress.enabled = Offin a custom PHP configuration file if upload progress tracking is not required. - Lower
post_max_sizeto match the application’s upload requirements. - Reduce
max_input_nesting_levelif the application does not need deeply nested input. - Set
display_errors = 0andfatal_error_backtraces = 0in production. - Review PHP memory limits and use operating system controls such as
cgroup memory.maxto restrict resource usage. - Adjust PHP-FPM’s
pm.max_requeststo recycle worker processes more frequently.

PHP configuration is inspected, showing session.upload_progress.enabled still set to On.

A custom PHP configuration file is written to force session.upload_progress.enabled = Off.

Re-checking the PHP settings confirms that upload progress has been disabled.

A final grep verifies session.upload_progress.enabled is now Off.
4. Review PHP Security Settings
Once PHP has been patched, review the remaining configuration to keep the runtime properly restricted.
- Set appropriate memory and request-size limits.
- Keep
post_max_sizewithin the application’s requirements. - Consider setting
expose_php = 0to reduce PHP version disclosure. - Use SELinux or AppArmor where supported and compatible with the application.