PHP插件获取网页返回的html源码

2019-08-17 约 1239 字 预计阅读 6 分钟

声明:本文 【PHP插件获取网页返回的html源码】 由作者 Lou00 于 2019-08-17 10:23:00 首发 先知社区 曾经 浏览数 112 次

感谢 Lou00 的辛苦付出!

起因

想做这个的起因是因为在AWD比赛中,如果能获取到页面返回的response,就有可能能发现返回flag的流量,从而拿到对应exp的流量
最简单最有效的方法是ob_start
在每个php源码最上面挂一个ob_start();
最下面来一个ob_get_contents就可以获取到
但这样也存在一些问题,有一些框架它本身是自带ob_start

如上面那做情况,他就获取不到1,会产生一些偏差
这个问题再php层面好像没有比较好的解决方案,于是就想到了php扩展

ob_start的源码实现

先看看ob_start的实现
main/output.cmain/php_output.h

PHP_FUNCTION(ob_start)
{
    zval *output_handler = NULL;
    zend_long chunk_size = 0;
    zend_long flags = PHP_OUTPUT_HANDLER_STDFLAGS;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z/ll", &output_handler, &chunk_size, &flags) == FAILURE) {
        return;
    }

    if (chunk_size < 0) {
        chunk_size = 0;
    }

    if (php_output_start_user(output_handler, chunk_size, flags) == FAILURE) {
        php_error_docref("ref.outcontrol", E_NOTICE, "failed to create buffer");
        RETURN_FALSE;
    }
    RETURN_TRUE;
}

跟进到最后会发现php_output_handler_start函数

PHPAPI int php_output_handler_start(php_output_handler *handler)
{
    ...
    /* zend_stack_push returns stack level */
    handler->level = zend_stack_push(&OG(handlers), &handler);
    OG(active) = handler;
    return SUCCESS;
}

其中OG是一个叫output_globals的全局变量ob_start()后的缓冲区
经过调试,发现所有于输出有关的函数都会调用一个叫php_output_op的函数

static inline void php_output_op(int op, const char *str, size_t len)
{
    php_output_context context;
    ...
    if (OG(active) && (obh_cnt = zend_stack_count(&OG(handlers)))) {
        context.in.data = (char *) str;
        context.in.used = len;
        ...
    } else {
        context.out.data = (char *) str;
        context.out.used = len;
    }
    ...
}

通过判断OG(active)是否为NULL来决定进不进入缓存区
接着来看看字符串如何进入缓冲区

static inline int php_output_handler_append(php_output_handler *handler, const php_output_buffer *buf)
{
    if (buf->used) {
        OG(flags) |= PHP_OUTPUT_WRITTEN;
        /* store it away */
        //空间不够时会去申请
        if ((handler->buffer.size - handler->buffer.used) <= buf->used) {
            size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(handler->size);
            size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(buf->used - (handler->buffer.size - handler->buffer.used));
            size_t grow_max = MAX(grow_int, grow_buf);

            handler->buffer.data = erealloc(handler->buffer.data, handler->buffer.size + grow_max);
            handler->buffer.size += grow_max;
        }
        //将数据复制过去
        memcpy(handler->buffer.data + handler->buffer.used, buf->data, buf->used);
        handler->buffer.used += buf->used;

        /* chunked buffering */
        if (handler->size && (handler->buffer.used >= handler->size)) {
            /* store away errors and/or any intermediate output */
            return OG(running) ? 1 : 0;
        }
    }
    return 1;
}

编写插件

思路

可以和output_globals.active->buffer相似,创造一个全局的缓存区
MINIT阶段初始化这个全局变量并hook各输出函数的opcode,写入缓冲区
RSHUTDOWN阶段将全局变量的数据保存在文件内

创建一个插件

在php的源码下进入ext目录,输入
./ext_skel --extname=myext

全局变量的定义与初始化

编辑php_hook_output_ext.h
先来看一下output_globals.active->buffer的结构

typedef struct _php_output_buffer {
    char *data;
    size_t size;
    size_t used;
    uint free:1;
    uint _reserved:31;
} php_output_buffer;

在上文的php_output_handler_append函数中可看到只用了前3个
于是编写全局变量如下

ZEND_BEGIN_MODULE_GLOBALS(myext)
    char *data; //缓存区
    size_t size; //缓存区大小
    size_t used; //数据长度
ZEND_END_MODULE_GLOBALS(myext)

完成定义,在hook_output_ext.c下进行初始化与析构

static void php_myext_globals_ctor(zend_myext_globals *G TSRMLS_DC)
{
    G->data = NULL;
    G->size = 0;
    G->used = 0;
}

static void php_myext_globals_dtor(zend_myext_globals *G TSRMLS_DC)
{
    efree(G->data);
}

并分别在MINIT阶段和RSHUTDOWN调用

hook opcode

这里以ZEND_ECHO这条opcode为例
当php执行echo xxxx;时会调用这条opcode
关于如何hook具体可以参考https://xz.aliyun.com/t/4214#toc-2
这里主要讲hook后数据的处理

static int get_data(char *str, size_t str_len)
{
    if(str_len){
        //size如果不够就申请更大的空间
        if ((MYEXT_G(size) - MYEXT_G(used)) <= str_len){
            size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(MYEXT_G(size));
            size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(str_len - (MYEXT_G(size) - MYEXT_G(used)));
            size_t grow_max = MAX(grow_int, grow_buf);
            MYEXT_G(data) = erealloc(MYEXT_G(data), MYEXT_G(size) + grow_max);
            MYEXT_G(size) += grow_max;
        }
        memcpy(MYEXT_G(data) + MYEXT_G(used), str, str_len);
        MYEXT_G(used) += str_len;
    }
    return 1;
}

static int hook_echo(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_op *opline = execute_data->opline;
    zval *z = EX_CONSTANT(opline->op1);
    if (Z_TYPE_P(z) == IS_STRING) {
        zend_string *str = Z_STR_P(z);
        if (ZSTR_LEN(str) != 0) {
            get_data(ZSTR_VAL(str), ZSTR_LEN(str));
        }
    } else {
        zend_string *str = _zval_get_string_func(z);

        if (ZSTR_LEN(str) != 0) {
            get_data(ZSTR_VAL(str), ZSTR_LEN(str));
        } 
        zend_string_release(str);
    }
    return ZEND_USER_OPCODE_DISPATCH;
}

可以看到get_data是直接根据php_output_handler_append改的
hookecho是根据ZEND_ECHO的一个hander编成的

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ECHO_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE

    zval *z;

    SAVE_OPLINE();
    z = _get_zval_ptr_cv_undef(execute_data, opline->op1.var);

    if (Z_TYPE_P(z) == IS_STRING) {
        zend_string *str = Z_STR_P(z);

        if (ZSTR_LEN(str) != 0) {
            zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
        }
    } else {
        zend_string *str = _zval_get_string_func(z);

        if (ZSTR_LEN(str) != 0) {
            zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
        } else if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) {
            GET_OP1_UNDEF_CV(z, BP_VAR_R);
        }
        zend_string_release(str);
    }

    ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

文件的保存

RSHUTDOWN处保存,文件名可以根据时间\,如果是用apache或者nginx起的话,默认是要将文件放在web根目录里否则要更改相关配置

代码(demo)

//php_myext.h
/*
  +----------------------------------------------------------------------+
  | PHP Version 7                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2018 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author:      lou00                                                   |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#ifndef PHP_MYEXT_H
#define PHP_MYEXT_H

extern zend_module_entry myext_module_entry;
#define phpext_myext_ptr &myext_module_entry

#define PHP_MYEXT_VERSION "0.1.0" /* Replace with version number for your extension */

#ifdef PHP_WIN32
#   define PHP_MYEXT_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#   define PHP_MYEXT_API __attribute__ ((visibility("default")))
#else
#   define PHP_MYEXT_API
#endif

#ifdef ZTS
#include "TSRM.h"
#endif

/*
    Declare any global variables you may need between the BEGIN
    and END macros here:

ZEND_BEGIN_MODULE_GLOBALS(myext)
    zend_long  global_value;
    char *global_string;
ZEND_END_MODULE_GLOBALS(myext)
*/

/* Always refer to the globals in your function as MYEXT_G(variable).
   You are encouraged to rename these macros something shorter, see
   examples in any other php module directory.
*/

#if defined(ZTS) && defined(COMPILE_DL_MYEXT)
ZEND_TSRMLS_CACHE_EXTERN()
#endif

#endif  /* PHP_MYEXT_H */


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */


# define MYEXT_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(myext,v)
ZEND_BEGIN_MODULE_GLOBALS(myext)
  char *data;
    size_t size;
  size_t used;
ZEND_END_MODULE_GLOBALS(myext)
# define ZEND_OPCODE_HANDLER_ARGS zend_execute_data *execute_data
PHP_FUNCTION(confirm_myext_compiled);
static int hookecho(ZEND_OPCODE_HANDLER_ARGS);
static int get_data(char *str, size_t str_len);
static void init_myext_global();
//myext.c
/*
  +----------------------------------------------------------------------+
  | PHP Version 7                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2018 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author:      lou00                                                   |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif


#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_myext.h"
#include "ext/standard/head.h"
#include "ext/standard/url_scanner_ex.h"
#include "main/php_output.h"
#include "SAPI.h"
#include "zend_stack.h"

static int le_myext;

//resgin from TRSM
ZEND_DECLARE_MODULE_GLOBALS(myext);

PHP_FUNCTION(confirm_myext_compiled)
{
    char *arg = NULL;
    size_t arg_len, len;
    zend_string *strg;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
        return;
    }

    strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "myext", arg);

    RETURN_STR(strg);
}

static void php_myext_globals_ctor(zend_myext_globals *G TSRMLS_DC)
{
    G->data = NULL;
    G->size = 0;
    G->used = 0;
}

static void php_myext_globals_dtor(zend_myext_globals *G TSRMLS_DC)
{
    efree(G->data);
}

static int get_data(char *str, size_t str_len)
{
    if(str_len){
        //size如果不够就申请更大的空间
        if ((MYEXT_G(size) - MYEXT_G(used)) <= str_len){
            size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(MYEXT_G(size));
            size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(str_len - (MYEXT_G(size) - MYEXT_G(used)));
            size_t grow_max = MAX(grow_int, grow_buf);
            MYEXT_G(data) = erealloc(MYEXT_G(data), MYEXT_G(size) + grow_max);
            MYEXT_G(size) += grow_max;
        }
        memcpy(MYEXT_G(data) + MYEXT_G(used), str, str_len);
        MYEXT_G(used) += str_len;
    }
    return 1;
}

static int hookecho(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_op *opline = execute_data->opline;
    zval *z = EX_CONSTANT(opline->op1);
    if (Z_TYPE_P(z) == IS_STRING) {
        zend_string *str = Z_STR_P(z);
        if (ZSTR_LEN(str) != 0) {
            get_data(ZSTR_VAL(str), ZSTR_LEN(str));
        }
    } else {
        zend_string *str = _zval_get_string_func(z);

        if (ZSTR_LEN(str) != 0) {
            get_data(ZSTR_VAL(str), ZSTR_LEN(str));
        } 
        zend_string_release(str);
    }
    return ZEND_USER_OPCODE_DISPATCH;
}


PHP_MINIT_FUNCTION(myext)
{
#ifdef ZTS
    ts_allocate_id(&myext_globals_id,
                        sizeof(zend_myext_globals),
                        (ts_allocate_ctor)php_myext_globals_ctor,
                        (ts_allocate_dtor)php_myext_globals_dtor);
#else
    php_myext_globals_ctor(&myext_globals TSRMLS_CC);
#endif
    zend_set_user_opcode_handler(ZEND_ECHO, hookecho);
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(myext)
{
    /* uncomment this line if you have INI entries
    UNREGISTER_INI_ENTRIES();
    */
    return SUCCESS;
}

PHP_RINIT_FUNCTION(myext)
{
#if defined(COMPILE_DL_MYEXT) && defined(ZTS)
    ZEND_TSRMLS_CACHE_UPDATE();
#endif
    //init_myext_global();
    return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(myext)
{
    #ifndef ZTS
        php_myext_globals_dtor(&myext_globals TSRMLS_CC);
    #endif
    FILE *fp;
    fp = fopen("/web/php/log","a");
    fwrite(MYEXT_G(data),MYEXT_G(used) , 1, fp );
    fwrite("\n------------------\n",21 , 1, fp );
    fclose(fp);
    return SUCCESS;
}

PHP_MINFO_FUNCTION(myext)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "myext support", "enabled");
    php_info_print_table_end();

    /* Remove comments if you have entries in php.ini
    DISPLAY_INI_ENTRIES();
    */
}

const zend_function_entry myext_functions[] = {
    PHP_FE(confirm_myext_compiled,  NULL)       /* For testing, remove later. */
    PHP_FE_END  /* Must be the last line in myext_functions[] */
};

zend_module_entry myext_module_entry = {
    STANDARD_MODULE_HEADER,
    "myext",
    myext_functions,
    PHP_MINIT(myext),
    PHP_MSHUTDOWN(myext),
    PHP_RINIT(myext),       /* Replace with NULL if there's nothing to do at request start */
    PHP_RSHUTDOWN(myext),   /* Replace with NULL if there's nothing to do at request end */
    PHP_MINFO(myext),
    PHP_MYEXT_VERSION,
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_MYEXT
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(myext)
#endif

结果

访问前

不受ob_start的影响

参考

https://xz.aliyun.com/t/4214

关键词:[‘安全技术’, ‘WEB安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now