Maybe I’ve been busy at work lately,Maybe I’m lazy,The efficiency is not very high,In recent months, I have read some PHP kernel source code intermittently.
In this year’s TCTF,there was a scene of attacking FPM to bypass the sandbox. Decided to explore the FPM life cycle and disable_function source code implementation , phpinfo cannot be displayed accurately. There are still many places that I am not familiar with,I will slowly add later,Worship RR and President P,ORZ.
【tips: Please swipe left and right to view all the code lines in this article】Debugging environment
Install the debugging tool gdb
apt install gdb
Download php source code :
wget https://www.php .net/distributions/php-7.1.0.tar.gz
Then configure ./configure as follows
./configure –prefix=/root/php7.1.0 –enable-phpdbg-debug –enable-debug –enable-fpm CFLAGS=”-g3 -gdwarf-4″
View the Makefile as follows:
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
Only the necessary debug module & # 43; fpm module & # xff0c; other modules are installed as required.
CFLAGS=”-g3 -gdwarf-4″ is an additional configuration of compilation parameters , turn off all compilation optimization mechanisms , the symbol information necessary to generate gdb (symbol table ), and set the dwarf debug information format. Many macros are defined in the PHP kernel , In gdb debugging, the macro expand xxxx command can be used to expand macros more conveniently.
Compile and install php
as follows:
make && make install
bin directory contains commonly used php command line interpreters
The sbin directory contains configuration files that fpm, still needs to run.
-
Specify the fpm configuration file , copy php-fpm.conf.default from the compiled directory and rename it to php-fpm.conf
-
Specify php configuration file , Copy php.ini-development from the source code directory and rename it to php.ini
Configure php.ini by yourself, mainly configure php-fpm here .conf
php-fpm is a multi-process model , a master process , multiple worker processes.
The master process is responsible for managing the scheduling , The worker process is responsible for processing the client (nginx) request.
The master process has three modes of managing the work process :
-
ondemand,on-demand mode,when there is a request The worker will be started
-
static,static mode,Start a fixed number of workers
-
dynamic& #xff0c;Dynamic Mode,Initialize some workers,Dynamically adjust the number of workers during operation
Let the working mode of fpm be static, and the work process There is only one & # xff0c; convenient for debugging & # xff0c; set the configuration file as follows & # xff1a;
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' 39; 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
Run fpm
./php-fpm -c php.ini -y php-fpm.conf
ps can find that the work process only starts one as scheduled:
<img src="https://img8.php1.cn/3cdc5/y *ini_entry = (zend_ini_entry*)Z_PTR_P(el);
int module_number = *(int *)arg;
if (ini_entry->module_number ! = module_number) {
return 0;
}
if (!sapi_module.phpinfo_as_text) {
PUTS(”
“);
PUTS(“
“);
PHPWRITE(ZSTR_VAL(ini_entry->name), ZSTR_LEN(ini_entry->name));
PUTS (“
“);
php_ini_displayer_cb (ini_entry, zend_ini_display_active);
puts (“
“); Ini_display_orig);
PUTS(“
\n”);
} else {
PHPWRITE(ZSTR_VAL(ini_entry->name), ZSTR_LEN(ini_entry->name));
PUTS(” => “);
php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ACTIVE);
PUTS(” => “);
php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ORIG);
PUTS(“\n”);
}
return 0;
}
After entering the if (!sapi_module.phpinfo_as_text) branch, it is the output of each configuration , php_ini_displayer_cb is the output function of the package , the general order is like this , the specific call stack As follows :
phpinfo obtains and prints information according to EG (ini_directives).
Attack PHP-FPM analysis
Trace PHP-FPM to accept malicious FastCgi protocol and parse according to PHP_VALUE to set disable_functions=.
The fcgi_accept_request function accepts the socket connection from the client through the accept function, and assigns it to req->fd.
will pass The outer while loop keeps accepting connections
At the same time, store the request variable containing the request handle in the global variable ,SG(server_context),The macro definition is as follows:
# define SG(v) (sapi_globals.v)
extern SAPI_API sapi_globals_struct sapi_globals;
Then enter the init_request_info function :
p>
static void init_request_info(void)
{
fcgi_request *request = (fcgi_request* ) SG(server_context);
char *env_script_filename = FCGI_GETENV(request, “SCRIPT_FILENAME”);
char *env_path_translated = FCGI_GETENV(request, “PATH_TRANSLATED” );
char *script_path_translated = env_script_filename;
char *ini;
int apache_was_here = 0;
//…………..
//…..
//…………. .
/* INI stuff */
ini = FCGI_GETENV(request, “PHP_VALUE”);
if (ini) {
int mode = ZEND_INI_USER;
char *tmp;
spprintf(&tmp, 0, “%s\n”, ini);
zend_parse_ini_string(tmp, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t)fastcgi_ini_parser, &mode);
efree(tmp);
}
ini = ; FCGI_GETENV(request, “PHP_ADMIN_VALUE”);
if (ini) {
int mode = ZEND_INI_SYSTEM;
char *tmp;
p>
spprintf(&tmp, 0, “%s\n”, ini);
zend_parse_ini_string(tmp, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t)fastcgi_ini_parser, &mode);
efree(tmp);
}
}
Get the PHP_VALUE in the FastCgi request through the FCGI_GETENV macro:
fcgi_quick_getenv(request, name, sizeof(name)-1, FCGI_HASH_FUNC(name, sizeof(name)-1))
Then enter the zend_parse_ini_string function &# xff1a;
ZEND_API int zend_parse_ini_string(char *str, zend_bool unbuffered_errors, int scanner_mode, zend_ini_parser_cb_t ini_parser_cb, void *arg)
{
int retval;
zend_ini_parser_param ini_parser_param;
ini_parser_param.ini_parser_cb = ini_parser_cb;
ini_parser_param.arg = 61; arg;
CG(ini_parser_param) = &ini_parser_param;
if (zend_ini_prepare_string_for_scanning(str, scanner_mode) == FAILURE) {
return FAILURE;
}
CG(ini_parser_unbuffered_errors) = unbuffered_errors;
retval = ini_parse();
p>
shutdown_ini_scanner();
if (retval == 0) {
return SUCCESS;
} else {
return FAILURE;
}
}
ini_parser_param.ini_parser_cb = ini_parser_cb real assigned fastcgi_ini_parser function, Then enter ini_parse for parsing , and then use the ZEND_INI_PARSER_CB macro
View definition :
#define ZEND_INI_PARSER_CB (CG(ini_parser_param))- >ini_parser_cb
Actually call the fastcgi_ini_parser function :
Continue to enter the fpm_php_apply_defines_ex function :
The call information at this time is as follows :
Continue to enter the fpm_php_zend_ini_alter_master function
From EG (ini_directives), find the ini_entry for disable_functions, Then modify the value to us The incoming content , and the value displayed by phpinfo comes from here.
The function string to be disabled will also be passed to the fpm_php_disable function :
Call the zend_disable_function function to modify func->handler to complete the disable.
Summary
-
The essence of disable_function is to modify func->handler to disable the function.
-
When the malicious FastCgi containing PHP_VALUE==disable_function= attacks FPM , can only modify the `EG( ini_directives) `, that is, surface modification , invalid for already disabled functions, but new functions can be disabled via FPM.
-
The more common and effective options for attacking FPM are extension_dir + extension, open_basedir, allow_url_include = On + auto_prepend_file = php:/ /input.
Reference
https://blog.csdn.net/st091zsc/article/ details/83822122
https://segmentfault.com/a/1190000013321594
Pay attention to the pulse , more security information and technical articles are waiting for you, xff01;
8525abdccb83da540df.png” />
View definition :
#define ZEND_INI_PARSER_CB (CG(ini_parser_param))- >ini_parser_cb
Actually call the fastcgi_ini_parser function :
Continue to enter the fpm_php_apply_defines_ex function :
The call information at this time is as follows :
Continue to enter the fpm_php_zend_ini_alter_master function
From EG (ini_directives), find the ini_entry for disable_functions, Then modify the value to us The incoming content , and the value displayed by phpinfo comes from here.
The function string to be disabled will also be passed to the fpm_php_disable function :
Call the zend_disable_function function to modify func->handler to complete the disable .
Summary
-
The essence of disable_function is to modify func->handler to disable the function.
-
When the malicious FastCgi containing PHP_VALUE==disable_function= attacks FPM , can only modify the `EG( ini_directives) `, that is, surface modification , invalid for already disabled functions, but new functions can be disabled via FPM.
-
The more common and effective options for attacking FPM are extension_dir + extension, open_basedir, allow_url_include = On + auto_prepend_file = php:/ /input.
Reference
https://blog.csdn.net/st091zsc/article/ details/83822122
https://segmentfault.com/a/1190000013321594
Pay attention to the pulse , more security information and technical articles are waiting for you, xff01;