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命令行解释器

PHP内核分析FPM和disable_function安全问题-1

sbin目录下包含fpm,还需要运行的配置文件。

  • 指定fpm的配置文件,从编译后的目录复制php-fpm.conf.default并重命名为php-fpm.conf
  • 指定php的配置文件,从源码目录中复制php.ini-development并重命名为php.ini

PHP内核分析FPM和disable_function安全问题-2

自行配置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进程如期只启动一个:

PHP内核分析FPM和disable_function安全问题-3