php pwn
国内比赛最近非常喜欢出php的pwn,php解释器本身没有太多的可利用点,出题一般把漏洞埋在php的拓展。掌握了php的调试、函数传参、堆内存管理以后这类题难度都不大。
由于php题的难度主要在调试方面,但是又没有很好用的gdb插件,因此自己写了一个phpgdb用于调试。
基础知识
php环境配置
apt安装
安装php,并查看版本:
❯ sudo apt install php php-dev❯ php -vPHP 8.3.6 (cli) (built: Mar 19 2025 10:08:38) (NTS)Copyright (c) The PHP GroupZend Engine v4.3.6, Copyright (c) Zend Technologies with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies
源码安装(推荐)
推荐使用源码安装,因为这样会有调试符号,便于本地调试(尤其是学习堆的时候)
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
这样是由完整调试符号和源码的:
php配置文件
主要关注其中的disable_functions
、disable_classes
和extension
,前二者限制了可以用于编写php利用脚本的函数和类,后者一般是pwn选手需要关注的带有漏洞的拓展文件。
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
php拓展
拓展开发
下载对应版本的php源码,进入ext
目录,创建一个拓展
❯ php ./ext/ext_skel.php --ext easy_phppwn --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /home/l1qu1d/pwn/chall/php_pwn/php_test/php-src-php-8.3.15/ext/easy_phppwnphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
在拓展名对应的目录具有如下结构:
❯ tree ./easy_phppwn./easy_phppwn├── config.m4├── easy_phppwn.c├── easy_phppwn.stub.php├── easy_phppwn_arginfo.h├── php_easy_phppwn.h└── tests ├── 001.phpt ├── 002.phpt └── 003.phpt2 directories, 8 files
其中easy_phppwn_arginfo.h
头文件与拓展的参数信息有关,不需要手动修改,在easy_phppwn.stub.php
中修改对应的文件即可。默认生成的只有test
和test2
两个函数,加入test3
:
<?php/** * @generate-class-entries * @undocumentable */function test1(): void {}function test2(string$str = ""): string {}function test3(string$name): string {}
随后自动构建easy_phppwn_arginfo.h
php ../../build/gen_stub.php --ext=easy_phppwn ./easy_phppwn.stub.php
添加函数功能
PHP_FUNCTION(test3){char *arg = NULL; size_t arg_len, len;char buf[100];if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {return; } memcpy(buf, arg, arg_len); php_printf("The baby phppwn.n");return SUCCESS;}
编译,configure
生成的Makefile
需要删去-O2
优化,否则会加上FORTIFY
保护,导致memcpy
函数加上长度检查变为__memcpy_chk
函数:
❯ phpizeConfiguring for:PHP Api Version:20230831Zend Module Api No:20230831Zend Extension Api No:420230831configure.ac:165: warning:The macro `AC_PROG_LIBTOOL' is obsolete.configure.ac:165:You should run autoupdate.build/libtool.m4:100:AC_PROG_LIBTOOL is expanded from...configure.ac:165:the top level❯ ./configure --with-php-config=/usr/bin/php-config...❯ make
在modules
目录下会生成编译好的拓展文件easy_phppwn.so
。
导入拓展
默认的拓展路径通过命令查看:
php -i | grep -i extension_dir
拓展在Linux下是一个动态链接库,通常在php.ini
中导入,并将so文件移动到上步输出的拓展路径下:
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
0
或者直接通过命令运行,而无需导入:
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
1
调试php拓展
自己写了个gdb python脚本phpdbg,用于php调试,功能会逐步完善。
首先编写一个php代码:
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
2
运行说明成功
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
3
gdb进行调试
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
4
根据php启动过程,在php_module_startup()
函数中加载拓展:
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
5
因此下断点跑完这个函数就能看到模块被加载进来:
此时可以接着下断点到zif_test1
,这里需要注意php编译之后的是函数名会加上zif_
前缀
test3
中可以看到栈溢出:
这里就不再赘述这个案例的利用方式了,结合后续题目进行介绍。
函数传参
传参约定
反编译的代码来看基本上除了参数处理以外就是原生的C代码,可读性比较强。
但是从题目来看,一般都是直接给的二进制文件,所以需要具体了解zend_parse_parameters
的传参规则,这里的讲解不会涉及[底层细节](https://www.bookstack.cn/read/php7-internal/7-func.md.6.2 %E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0%E8%A7%A3%E6%9E%90):
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
6
◆num_args
为参数个数。
◆type_spec
通过字符串表示参数的类型。
◆省略号表示具体接受参数的指针
对于参数类型而言,常用参数对照表:
b 或i | b 通常表示bool类型,而i 表示int类型 | |
l | ||
d | ||
s | ||
S | ||
a | ||
o | ||
r | ||
z | ||
N |
字符串类型解析:
在PHP 7中,字符串解析有两种形式:char*和zend_string。其中:
◆"s"
将参数解析到char*,并且需要额外提供一个size_t类型的变量用于获取字符串长度
◆"S"
将解析到zend_string,这是PHP 7中推荐使用的字符串类型[0]
复合类型规范:
在实际使用中,可以将多个类型规范符组合使用,以表示多个参数的类型。例如:
◆"la"
表示第一个参数为长整型,第二个参数为数组类型
◆"z|l"
表示要接受一个zval类型的参数和一个可选的long类型的参数[20]
可选参数:
在类型规范字符串中,可以使用|
符号来表示后续的参数是可选的。例如:
◆"z|l"
表示第一个参数是必需的zval类型,第二个参数是可选的long类型[20]
传参结构体
但是很多时候都是直接用z
来代替参数,在后面通常会有一个形如v15[8] == 6
的比较操作,这实际上是在确定参数的类型:
具体对应关系是:
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
7
参考
https://github.com/php/php-src/blob/212b2834e9fbcb9a48b9cb709713b6cb197607cc/docs/source/core/data-structures/zval.rst
php堆内存管理
虽说是php的内存管理,但是实际上是其内部zend引擎的内存管理机制。PHP采取“预分配方案”,提前向操作系统申请一个chunk(2M,利用到hugepage特性),并且将这2M内存切割为不同规格(大小)的若干内存块,当程序申请内存时,直接查找现有的空闲内存块即可;
PHP将内存分配请求分为3种情况:
huge内存:针对大于2M-4K的分配请求,直接调用mmap分配;
large内存:针对小于2M-4K,大于3K的分配请求,在chunk上查找满足条件的若干个连续page;
small内存:针对小于3K的分配请求;PHP拿出若干个页切割为8字节大小的内存块,拿出若干个页切割为16字节大小的内存块,24字节,32字节等等,将其组织成若干个空闲链表;每当有分配请求时,只在对应的空闲链表获取一个内存块即可;
相关结构体
在large和small两类chunk的第一个page里,会存储chunk的控制信息,这个结构体是_zend_mm_chunk
,所有的chunk会形成一个双向链表,zend_mm_page_map
利用位图记录512个page的使用情况,0代表空闲,1代表已经分配。zend_mm_page_info
通过uint32_t
存储FLAG信息,
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
8
然后是_zend_mm_heap
,是chunk的上级管理结构,存储与堆分配相关的全局信息:
$ git clone https://github.com/php/php-src.git --branch=PHP-8.3.15$ cd php-src$ ./buildconf --force$ ./configure --enable-cli --enable-debug$ make && make test && make install
9
如果不方便看的话可以直接看gdb的结果:
堆的最上层结构体是封装了zend_mm_heap
的zend_alloc_globals
:
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
0
alloc_globals
是类似于glibc中main_arena
的变量,通过它即可逐步获取整个堆:
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
1
small内存
这里只介绍small类型的内存分配,而这也是与我们攻击直接相关的部分。简单来说,small类型内存的空闲链表类似于2.27下的tcache空闲链表,也是单链表形式,并且没有任何保护,因此只需要修改链表中任一节点,即可劫持free的空闲链表。它的结构类似于:
源码分析
下面从源码来分析一下,当申请small类型heap时:
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
2
如果free_slot
资源不够,则会调用zend_mm_alloc_small_slow
创建一个对应大小的free_slot
:
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
3
释放时,直接将free的small heap链入末尾:
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
4
php堆调试
网上没有搜到比较合适的,自己写了个phpgdb,目前支持4个命令。
pstart
运行到php加载完所有拓展之后,此时可以设置断点。
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
5
pheap
查看最上层的堆信息:
psmall
查看small slot链表:
pelement
查看给定地址所属于的element(最终分配的堆块)
参考链接
https://deepunk.icu/php-pwn/
https://www.imooc.com/article/51124
利用链
泄露地址
php类题型一般能够通过include
包含文件,因此可以直接从/proc/self/maps
中读出地址(其实vmmap
命令就是在读这个文件):
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
6
劫持执行流
一般而言,php拓展编译成动态链接库,默认编译选项下其got表是可写的,因此通常可以利用任意写劫持got表来劫持执行流。
getshell
一般php pwn都会在远程服务器运行一个php代码,很可能不能通过nc拿到交互的shell,因此通常执行反弹shell或者sendfile等。
例题分析
栈溢出:mixture
php pwn部分就是泄露地址+溢出ret2libc,可以作为入门题目。
题目来源:De1CTF 2020
参考:https://a1ex.online/2021/03/19/webpwn%E5%AD%A6%E4%B9%A0/
数组越界:numbergame
题目来源:第一届“长城杯”信息安全铁人三项赛决 夺取闯关 pwn numbergame
分析给的numberGame.so
文件,发现是一个类似堆题的增删改查功能,其中zif_show_chunk
调用了一个自定义的_quicksort
,漏洞点在这个位置:
但是要去具体分析_quicksort
的代码来找到漏洞形成原因会比较困难,这里使用LLM生成fuzz代码来把这个漏洞测出来:
这是deepseek r1自动生成的代码,根据ida的代码可以进行细微的调整:
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
7
拿到代码不需要改,直接跑,几秒钟找到十几个error输入:
这个测试了一下,主要报错都是由于edit(16....)
导致的,这个属于是没什么用的洞。在fuzz里把这个问题修一下,顺便改一改参数:
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
8
这样跑起来几分钟就可以测出段错误:
; This directive allows you to disable certain functions.; It receives a comma-delimited list of function names.; https://php.net/disable-functionsdisable_functions = "zend_version","func_num_args" ...; This directive allows you to disable certain classes.; It receives a comma-delimited list of class names.; https://php.net/disable-classesdisable_classes = "stdClass","InternalIterator" ...;;;;;;;;;;;;;;;;;;;;;;; Dynamic Extensions ;;;;;;;;;;;;;;;;;;;;;;;extension = vuln.so
9
跑起来验证一下也就是_quicksort
排序的时候越界的问题,把size修改得任意大了,甚至name字段也被覆盖了:
这个时候可以手工删减poc,以确定触发漏洞的输入:
❯ php ./ext/ext_skel.php --ext easy_phppwn --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /home/l1qu1d/pwn/chall/php_pwn/php_test/php-src-php-8.3.15/ext/easy_phppwnphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
0
这样就可以确定是排序导致的问题了,这个时候可以进一步针对这个数组序列构造fuzz:
❯ php ./ext/ext_skel.php --ext easy_phppwn --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /home/l1qu1d/pwn/chall/php_pwn/php_test/php-src-php-8.3.15/ext/easy_phppwnphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
1
这样跑起来就得到了很简单的poc:
❯ php ./ext/ext_skel.php --ext easy_phppwn --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /home/l1qu1d/pwn/chall/php_pwn/php_test/php-src-php-8.3.15/ext/easy_phppwnphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
2
会把size改大:
这样可以得到一个在php堆上的下溢任意地址写:
思路也比较简单,就是第一次利用越界写修改下一个chunk的name
指针,再利用这个name
指针实现任意地址写。这里给出直接打本地的脚本,远程同理:
❯ php ./ext/ext_skel.php --ext easy_phppwn --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /home/l1qu1d/pwn/chall/php_pwn/php_test/php-src-php-8.3.15/ext/easy_phppwnphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
3
堆off by null:PwnShell
题目还是一个典型的堆菜单,分析结构体有点抽象,感觉是为了埋洞之后方便利用搞的:
漏洞点是addHakcer
的时候存在一个off by null的漏洞:
按照如下布局:
❯ php ./ext/ext_skel.php --ext easy_phppwn --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /home/l1qu1d/pwn/chall/php_pwn/php_test/php-src-php-8.3.15/ext/easy_phppwnphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
4
即可覆盖chunkList[1].ptr->str1_ptr
:
结合editHacker
的修改能力:
❯ php ./ext/ext_skel.php --ext easy_phppwn --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /home/l1qu1d/pwn/chall/php_pwn/php_test/php-src-php-8.3.15/ext/easy_phppwnphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
5
能在堆上进行一定的篡改:
通过适当构造可以得到任意地址写:
exp:
❯ php ./ext/ext_skel.php --ext easy_phppwn --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /home/l1qu1d/pwn/chall/php_pwn/php_test/php-src-php-8.3.15/ext/easy_phppwnphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
6
题目来源:D3CTF 2024 PwnShell
参考:https://9anux.org/2024/04/29/d3ctf2024/index.html
堆UAF:hackphp
题目来源:D3CTF 2021 hackphp
参考:
https://github.com/UESuperGate/D3CTF-Source/blob/master/hackphp/exp.php
https://www.anquanke.com/post/id/235237#h2-5
UAF:phpmaster
题目来源:第二届长城杯半决赛 phpmaster
参考:https://bbs.kanxue.com/thread-286086.htm
参考文章
https://www.anquanke.com/post/id/204404
https://imlzh1.github.io/posts/PHP-So-Pwn/#zend_parse_parameters
https://www.bookstack.cn/read/php7-internal/7-implement.md
https://xuanxuanblingbling.github.io/ctf/pwn/2020/05/05/mixture/
题目附件
https://pan.baidu.com/s/1zUoi76y5MoUOYPsVVZvnmQ?pwd=x49f
看雪ID:GeekCmore
https://bbs.kanxue.com/user-home-950404.htm
# 往期推荐
1、
2、
3、
4、
5、
6、
球分享
球点赞
球在看
点击阅读原文查看更多
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...