PHP内核分析FPM和disable_function安全问题
今年的TCTF中,出现了攻击FPM绕过沙盒的场景。决定探究下FPM生命周期和disable_function源码实现,phpinfo不能准确显示。还很多地方还不熟悉,后面再慢慢补充,膜拜RR和P总,ORZ。
apt install gdb
下载php源码:
wget https://www.php.net/distributions/php-7.1.0.tar.gz
然后对./configure 的配置如下
./configure --prefix=/root/php7.1.0 --enable-phpdbg-debug --enable-debug --enable-fpm CFLAGS="-g3 -gdwarf-4"
查看Makefile文件如下:
CC = gcc
CFLAGS = $(CFLAGS_CLEAN) -prefer-non-pic -static
CFLAGS_CLEAN = -I/usr/include -g3 -gdwarf-4 -fvisibility=hidden -O0 -Wall -DZEND_SIGNALS $(PROF_FLAGS)
CPP = gcc -E
CPPFLAGS =
CXX =
CXXFLAGS = -g -O0 -prefer-non-pic -static $(PROF_FLAGS)
CXXFLAGS_CLEAN = -g -O0
DEBUG_CFLAGS = -Wall
这里只安装必要的debug模块+fpm模块,其他模块视需求安装。
CFLAGS="-g3 -gdwarf-4"是对编译参数进行额外配置,关闭所有的编译优化机制,产生 gdb所必要的符号信息(符号表),并设置dwarf调试信息格式。PHP内核中定义了很多宏,gdb调试中可以通过macro expand xxxx命令比较方便的展开宏。
编译安装php
件如下:
make && make install
bin目录下包含常用的php命令行解释器
sbin目录下包含fpm,还需要运行的配置文件。
- 指定fpm的配置文件,从编译后的目录复制php-fpm.conf.default并重命名为php-fpm.conf
- 指定php的配置文件,从源码目录中复制php.ini-development并重命名为php.ini
自行配置php.ini,这里主要配置php-fpm.conf
php-fpm为多进程模型,一个master进程,多个worker进程。
master进程负责管理调度,worker进程负责处理客户端(nginx)的请求。
master进程对work进程管理一共有三种模式:
- ondemand,按需模式,当有请求时才会启动worker
- static,静态模式,启动采用固定大小数量的worker
- dynamic,动态模式,初始化一些worker,运行过程中动态调整worker数量
让fpm的工作模式为static,并且work进程只有一个,方便进行调试,设置配置文件如下:
pm = static
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI. The below defaults are based on a server without much resources. Don't
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
; Note: This value is mandatory.
pm.max_children = 1
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 1
; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 1
; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 1
运行fpm
./php-fpm -c php.ini -y php-fpm.conf
ps可以发现work进程如期只启动一个: