📈 GH Trending Explorer 🎲 Surprise me

← all repos

9bie / php2shell-bypass-aslr-exp

git clone https://github.com/9bie/php2shell-bypass-aslr-exp.git

A php2shell exploit that requires no modifications to the official version, and does not require display_errors or limit_memory.

+326
stars, 24h · #94
+999
stars, 7 days · #211
+999
stars, 30 days · #834
999
★ total · +999% this week
352
forks · 4 open issues

Star history (as recorded here)

2026-10-22 · 673 ★ 2026-10-23 · 999 ★
LanguagePython
License—
Created2026-10-21 (1d ago)
Last pushtoday
Watchers8
OwnerUser

Links

README excerpt
# php2shell - 官方环境 无需display_errors,不修改memory_limit
中文 | English
**官方 prefork 配置(MaxRequestWorkers=150)下**,在完全远程、只用 HTTP、目标页面只有 ` `、不写目标侧
PHP 的约束下,一条命令拿到 RCE:
- stock 官方镜像 + 官方 ini + 官方 worker:**10/10**
- 单 worker 基线(早期标定/调试):**20/20**
- 单次约 3–4 秒(含 149 个占位连接),被固定的 worker 不重启,目标 ini 未改
# 通用利用
理论上只要你计算出所有php版本的偏移,然后利用这些偏移批量去fuzz,总有一条fuzz是能rce的。反正worker崩了也会重启,不怕目标打挂。
关于 worker:官方 prefork 的 `MaxRequestWorkers=150` 也已打通。脚本先占住 149 个 worker 的未完成请求,再把 A1/B1/A2/B2 固定到剩下的同一个 child;单 worker 只是早期的调试形态(`--workers 1` 仍可复现)。
## 快速开始(官方 php.ini-production / display_errors=Off / 128M)
 [code] 
不编译 PHP/Apache、不 build 镜像、不挂 gdb、不改目标 ini。
`pure/run_pure.sh` 默认容器 `p2s-pure-18110`、端口 `18110`,默认使用
官方 prefork 配置(MaxRequestWorkers=150),起完会打印实际 ini 和 MPM
值;`--single` 才挂单 worker 的 `docker/apache.conf`(配合 `--workers 1`)。
Linux 上没有 docker 组权限时脚本会自动改用 `sudo docker`,也可以显式
`DOCKER="sudo docker" ./pure/run_pure.sh`;`--dbg` 仅用于换成带符号的
同一构建做离线标定。
常用参数:
```bash
# 换新 ASLR 复测(重启容器)
python3 exploit.py --port 18110 --restart p2s-pure-18110
# 官方 worker 是默认值;--single 起的单 worker 目标用 --workers 1
python3 exploit.py --port 18110 --workers 1
# 自定义命令:把脚本写进挂载目录,再用 sh 执行
printf 'id; uname -a > /out/p1\n' > out/c
python3 exploit.py --port 18110 --cmd 'sh `,攻击流量只有 chunked multipart
和普通 GET/POST:
0. **worker pinning**:官方 `MaxRequestWorkers=150`;先开 149 个只发了
 一半 body 的 POST 占住其它 child,A1/B1/A2/B2 就都会落到剩下的同一个
 child,跨请求的悬空指针才有效。
1. **chunked 绕过 `post_max_size`**:不带 `Content-Length` 时
 `content_length=0`,`rfc1867_post_handler` 开头的检查被跳过,
 可以发送远大于 8M 的 body 做堆整形。
2. **A1**:860 个 depth-64 嵌套文件字段 + filler + 大值 + 4000B
 `PHP_SESSION_UPLOAD_PROGRESS`,在 END cleanup 的 `php_url_encode`
 里 OOM;bailout 留下悬空的 `PS(id)` / `PS(mod_data)`,`send_cookie=1`。
3. **B1(越界读)**:安全 close 后让 `PS(id)` 指向伪造的
 `zend_string` 头(len=0xC8000,data 含 `/`),`Set-Cookie` 把 id
 chunk 之后 800KB 内存编码出来。窗口里同时有:
 - 2MiB 对齐的 chunk 指针簇 → id chunk / main chunk base;
 - `G:400` 在 id chunk 尾部留下的洞被 900 个嵌套数组字段填满,
 真实 HashTable 的 `pDestructor = zval_ptr_dtor` 出现 1095 次
 → `libphp = 值 - 0x65EB60`。
4. **A2 + B2**:cheap-OOM 把 `PS(hash_serialize_verifier)` 指到堆上
 (`main+0x7D720`);B2 用 384B 引用值把它覆盖成指向
 `S=main+0xF8398` 的 zval,S 处伪造 zend_array 的
 `pDestructor = zif_system` (`libphp+0x465370`),shutdown 时
 `zend_hash_destroy` 调用 `system("id>/out/p1")`。
偏移(本构建,ASLR 无关):
| 符号/位置 | 值 |
|---|---|
| `zval_ptr_dtor` | `libphp+0x65EB60` |
| `zif_system` | `libphp+0x465370` |
| fake array `S` | `main+0xF8398` |
| A2 的 `hsv` | `main+0x7D720` |
目标环境:Debian 13 (trixie)、Apache 2.4.69 prefork、PHP 8.4.26 NTS
mod_php、files session handler。换 PHP 构建/版本需要重新标定偏移;
ASLR 每次容器启动会变,链上两次泄漏会自己算出来。
## 证据
- 机制与标定:pure/RESEARCH_PURE_128M.md
- 全链脚本:exploit.py(`pure/pwn_pure.py` 保留为别名)
- 调试镜像批量日志:pure/evidence/pwn_pure_batch_20261022.log
- 官方镜像日志:pure/evidence/pwn_pure_official_20261022.log
- 官方 worker 日志:pure/evidence/official_workers_20261022.log
- proof:pure/evidence/pwn_pure_proof_p1.txt
## 旧线(legacy,非本目标)
早期提交里的 `exploit.py` 走的是**自定义** `docker/php.ini`
(`display_errors=On`、`memory_limit