深入理解PHP内核(三)概览-SAPI概述

来源:http://www.chinese-glasses.com 作者:Web前端 人气:125 发布时间:2020-03-24
摘要:时间: 2019-11-08阅读: 72标签: ApacheApache调用解析器的三种方式  本文链接: http://www.orlion.ml/234/ 在普遍使用的LAMP架构中,Apache与PHP之间的交互,有三种常见的方式。 1、在PHP生命周期的各

时间: 2019-11-08阅读: 72标签: ApacheApache调用解析器的三种方式

 本文链接:http://www.orlion.ml/234/

在普遍使用的LAMP架构中,Apache与PHP之间的交互,有三种常见的方式。

1、在PHP生命周期的各个阶段,一些与服务相关的操作都是通过SAPI接口实现。这些内置实现的物理位置在PHP源码的SAPI目录。这个目录存放了PHP对各个服务器抽象层的代码,例如命令行程序的实现,Apache的mod_php模块实现以及fastcgi的实现等等

10bet,第一种是最通用最常见的Module方式,即在中使用LoadModule的方式,将php的dll或者so文件加载到apache当中。

在各个服务器抽象层之间遵守着相同的约定,这里我们称之为SAPI接口。每个SAPI实现都是一个_sapi_module_struct结构体变量。(SAPI接口)。在PHP的源码中,当需要调用服务器相关信息时,全部通过SAPI接口中对应的方法调用实现,而这些方法在各个服务器抽象层实现时都会有各自的实现。由于很多操作的通用性,有很大一部分接口方法使用的是默认方法。下图为SPAI的简单示意图

还有两种是CGI方式和FastCGI方式。其实后者用的越来越广泛了。一般PHP-FPM也是与FastCGI进行配合使用的。

10bet 1

可以参考CGI、FastCGI和PHP-FPM关系图解和Apache下PHP的几种工作方式来了解更多。

以cgi模式和apache2服务器为例,它们的启动方法如下:

CGI启动方式的RCE利用姿势

cgi_sapi_module.startup(&cgi_sapi_module) // cgi模式 cgi/cgi_main.c文件

apache_sapi_module.startup(&apache_sapi_module); // apache服务器  apache2handler/sapi_apache2.c文件

当我们了解原理后,Apache是需要调用第三方CGI程序,但是一个程序是不是CGI程序这个事很难界定,我们能否通过调用特定的CGI程序(普通程序)来执行任意系统命令呢。答案是可以的。

这里的cgi_sapi_module是sapi_module_struct结构体的静态变量。它的startup方法指向php_cgi_startup函数指针。在这个结构体中除了startup函数指针,还有许多其他方法或字段,这些结构在服务器的接口实现中都有定义

利用条件

 

 1.保证htaccess会被解析,即当前目录中配置了`AllowOverride all或AllowOverride Options FileInfo。AllowOverride参数具体作用可参考Apache之AllowOverride参数详解。(Require all granted也是需要的) 2.cgi_module被加载。即apache配置文件中有LoadModule cgi_module modules/mod_cgi.so这么一句且没有被注释。 3.有目录的上传、写入权限。

整个SAPI类似于一个面向对象中的模板方法模式的应用。SAPI.c和SAPI.h文件所包含的一些函数就是模板方法模式中的抽象模板,各个服务器对于sapi_module的定义及相关实现则是一个个具体的模板

利用姿势

 

上传.htaccess 文件, 内容如下:

2、Apache模块

Options ExecCGIAddHandler cgi-script .xx

(1)当PHP需要在Apache服务器下运行时,一般来说,它可以mod_php5模块的形式集成,此时mod_php5模块的作用是接收Aapche传递过来的PHP文件请求,并处理这些请求,然后将处理后的结果返回给Apache。如果我们在Apache启动前在其配置文件中配置了PHP模块,PHP模块通过注册apache2的ap_hook_post_config挂钩,在Apache启动的时候启动此模块以接收PHP文件的请求。

Options ExecCGI表示允许CGI执行,如果AllowOverride只有FileInfo权限且本身就开启了ExecCGI的话,就可以不需要这句话了。

除了这种启动时的加载方式,Apache的模块可以在运行的时候动态装载,这意味着对服务器可以进行功能扩展而不需要重新对源代码进行编译,甚至不需要重启服务器。我们所需要做的仅仅是给服务器发送信号HUP或者AP_SIG_GEACEFUL通知服务器重新载入模块。但是在动态装载之前我们需要将模块编译成为动态链接库。此时的动态加载就是加载动态链接库。Apache中对动态链接库的处理是通过模块mod_so来完成的,因此mod_so模块不能被动态加载,它只能本静态编译进Apache的核心。这意味着它和Apache一起启动的。

第二句告诉Apache将xx后缀名的文件,当做CGI程序进行解析。

 

接下来,以Windows平台为例,上传poc.xx文件,内容如下:

Apache是如何加载模块的呢?以mod_php5为例,首先在httpd.conf中添加一行:

#!C:/Windows/System32/cmd.exe /c start calc.exe1
LoadModule php5_module modules/mod_php5.so

第一行用来表示CGI程序的路径。可以随便开你的脑洞。

在配置文件中添加了所示的指令后,Apache在加载模块时会根据模块名查找模块并加载。Apache的每一个模块都是以module结构体的形式存在,module结构的name属性在最后是通过宏STANDARD20_MODULE_STUFF以__FILE__体现。通过之前的指令中指定的路径找到相关的动态链接库文件后,Apache通过内部的函数获取动态链接库中的内容,并将模块的内容加载到内存中指定变量中。

因为CGI程序处理完成后,会被Apache关闭,所以我们这里要用启动新进程的方式来启动。

在真正激活模块之前,Apache会检查所有加载的模块是否为真正的Apache模块。最后Apache会调用相关的函数(ap_add_loaded_module)将模块激活,此处的激活就是将模块放入相应的链表中(ap_top_modules链表)

结果

Apache加载的是PHP模块,那么这个模块时怎么实现的呢?Apache2的mod_php5模块包括sapi/apache2handler和sapi/apache2filter两个目录,在apache2_handle/mod_php5.c文件中,模块定义的相关代码如下:

这时访问poc.xx。计算器就出来啦~~

AP_MODULE_DECLARE_DATA module php5_module = {
    STANDARD20_MODULE_STUFF,
        /* 宏,包括版本,小版本,模块索引,模块名,下一个模块指针等信息,其中模块名以__FILE__体现*/
    create_php_config,      /* create per-directory config structure */
    merge_php_config,       /* merge per-directory config structures */
    NULL,                   /* create per-server config structure */
    NULL,                   /* merge per-server config structures */
    php_dir_cmds,           /*模块定义的所有命令*/
    php_ap2_register_hook  /*注册钩子,此函数通过ap_hoo_开头的函数在一次处理过程中对于指定的步骤注册钩子*/
};

拿火绒剑来看下~

它所对应的是Apache的module结构,module的结构定义如下:

一目了然,读取了两个文件后,的mod_cgi.so模块执行了我们的命令。

typedef struct module_struct module;
struct module_struct {
    int version;
    int minor_version;
    int module_index;
    const char *name;
    void *dynamic_load_handle;
    struct module_struct *next;
    unsigned long magic;
    void (*rewrite_args) (process_rec *process);
    void *(*create_dir_config) (apr_pool_t *p, char *dir);
    void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf);
    void *(*create_server_config) (apr_pool_t *p, server_rec *s);
    void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void 
*new_conf);
    const command_rec *cmds;
    void (*register_hooks) (apr_pool_t *p);
}

linux环境下,也是随你玩,是直接调用/bin/bash还是调用/usr/bin/python来反弹Shell。都是可以的。这其实也就是正常使用方式,因为Python也会被用作为CGI解析程序。

 

FastCGI启动方式的RCE利用姿势

上面的模块结构与我们在mod_php5.c中所看到的结构有一点不同,这是由于STANDARD20_MODULE_STUFF的原因,这个宏它包含了前面8个字段的定义。STANDARD20_MODULE_STUFF宏的定义如下:

我们再来看看FastCGI模式的,这个依赖的是mod_fcgid.so,默认安装包里甚至没有这个so文件,不过在PHPStudy的默认配置中,就已经是加载了的,并且AllowOverride也是All权限,手动斜眼。

/** Use this in all standard modules */
#define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, 
                MODULE_MAGIC_NUMBER_MINOR, 
                -1, 
                __FILE__, 
                NULL, 
                NULL, 
                MODULE_MAGIC_COOKIE, 
                                NULL      /* rewrite args spot */

其实还有mod_proxy_fcgi,更为常见,也是默认开启的,还不清楚能否利用,表哥表姐们可以尝试一下。

在php5_module定义的结构中,php_dir_cmds是模块定义的所有的指令集合,定义的内容如下:

利用条件

const command_rec php_dir_cmds[] =
{
    AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL,
        OR_OPTIONS, "PHP Value Modifier"),
    AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL,
        OR_OPTIONS, "PHP Flag Modifier"),
    AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler,
        NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)"),
    AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler,
        NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"),
    AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL,
        RSRC_CONF, "Directory containing the php.ini file"),
    {NULL}
};
1.AllowOverride all或AllowOverride Options FileInfo。2.mod_fcgid.so被加载。即apache配置文件中有LoadModule fcgid_module modules/mod_fcgid.so3. 有目录的上传、写入权限。

 

利用姿势

这是mod_php5模块定义的指令表。它实际上是一个commond_rec结构的数组。当Apache遇到指令的时候将逐一遍历各个模块中的指令表,查找是否有那个模块能够处理该指令,如果找到,则调用响应的处理函数,如果所有指令表中的模块都不能处理该指令,那么将报错,如上所见,mod_php5模块仅提供php_value等5个指令。

上传.htaccess 文件, 内容如下:

php_ap2_register_hook函数的定义如下:

Options +ExecCGIAddHandler fcgid-script .abcFcgidWrapper "C:/Windows/System32/cmd.exe /c start cmd.exe" .abc
void php_ap2_register_hook(apr_pool_t *p)
{
    ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(php_apache_server_startup, NULL, NULL, 
APR_HOOK_MIDDLE);
    ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}

老样子,如果默认就开启了ExecCGI,则第一句可以省略。

以上代码声明了pre_config,post_config,handler和child_init4个挂钩以及对应的处理函数。其中pre_config,post_config,child_init是启动挂钩,它们在服务器启动时调用。handler挂钩是请求挂钩,它在服务器处理请求时调用。其中在post_config挂钩中启动php。它通过php_apache_server_startup函数实现,php_apache_server_startup函数通过调用sapi_startup启动sapi,并通过调用php_apache2_startup来注册sapi module struct,最后调用php_module_startup初始化php,其中又会初始化Zend引擎,以及填充zend_module_struct中的treat_data成员(通过php_startup_sapi_content_types)等。

第二句表示,abc后缀名的文件需要被fcgi来解析。AddHandler还可以换成AddType。

  到这里,我们知道了Apache加载mod_php5模块的整个过程,可是这个过程与我们的饿SAPI有什么关系呢?mod_php5也定义了属于Apache的sapi_module_struct结构:

再上传1.abc。内容无所谓。

static sapi_module_struct apache2_sapi_module = {
"apache2handler",
"Apache 2.0 Handler",

php_apache2_startup,                /* startup */
php_module_shutdown_wrapper,            /* shutdown */

NULL,                       /* activate */
NULL,                       /* deactivate */

php_apache_sapi_ub_write,           /* unbuffered write */
php_apache_sapi_flush,              /* flush */
php_apache_sapi_get_stat,           /* get uid */
php_apache_sapi_getenv,             /* getenv */
php_error,                  /* error handler */

php_apache_sapi_header_handler,         /* header handler */
php_apache_sapi_send_headers,           /* send headers handler */
NULL,                       /* send header handler */

php_apache_sapi_read_post,          /* read POST data */
php_apache_sapi_read_cookies,           /* read Cookies */

php_apache_sapi_register_variables,
php_apache_sapi_log_message,            /* Log message */
php_apache_sapi_get_request_time,       /* Request Time */
NULL,                       /* Child Terminate */

STANDARD_SAPI_MODULE_PROPERTIES
};

结果

 

访问1.abc,计算器就出来了~再拿火绒剑看下。

这些方法都属于Apache服务器,以读取cookie为例,当我们在Apache服务器环境下,在PHP中调用读取Cookie时,最终获取的数据的位置是在激活SAPI时,它所调用的方法是read_cookie。

PS:若拥有上传权限,以上两种利用方式,在PHPstudy默认配置当中,都是可以直接使用的。

SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);

本文由10bet发布于Web前端,转载请注明出处:深入理解PHP内核(三)概览-SAPI概述

关键词:

最火资讯