From 1602780f3c85ca127c77d28ae93a4e2a4eeae180 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 9 Oct 2017 11:54:31 +0200 Subject: Better hooking of language constructs (#26) * Vastly improve the support of language construct hooking--- src/snuffleupagus.c | 4 +- src/sp_config.h | 7 ++-- src/sp_config_keywords.c | 67 ++++++++++++++++++++++++------- src/sp_execute.c | 40 ++++++++++-------- src/tests/disabled_functions_require.phpt | 10 ++--- 5 files changed, 88 insertions(+), 40 deletions(-) diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c index 52b975e..98649d6 100644 --- a/src/snuffleupagus.c +++ b/src/snuffleupagus.c @@ -81,9 +81,9 @@ PHP_GINIT_FUNCTION(snuffleupagus) { SP_INIT(snuffleupagus_globals->config.config_disabled_functions); SP_INIT(snuffleupagus_globals->config.config_disabled_functions_ret); SP_INIT(snuffleupagus_globals->config.config_cookie_encryption); - SP_INIT(snuffleupagus_globals->config.config_regexp_inclusion); + SP_INIT(snuffleupagus_globals->config.config_disabled_constructs); - snuffleupagus_globals->config.config_regexp_inclusion->regexp_inclusion = sp_new_list(); + snuffleupagus_globals->config.config_disabled_constructs->construct_include = sp_new_list(); snuffleupagus_globals->config.config_disabled_functions->disabled_functions = sp_new_list(); snuffleupagus_globals->config.config_disabled_functions_ret->disabled_functions = sp_new_list(); diff --git a/src/sp_config.h b/src/sp_config.h index d5cd1dc..e51d6cc 100644 --- a/src/sp_config.h +++ b/src/sp_config.h @@ -105,8 +105,9 @@ typedef struct { } sp_config_disabled_functions; typedef struct { - sp_node_t *regexp_inclusion; // list of regexp for inclusion -} sp_config_regexp_inclusion; + sp_node_t *construct_include; // list of rules for `(include|require)_(once)?` + sp_node_t *construct_echo; +} sp_config_disabled_constructs; typedef struct { char *script; @@ -126,7 +127,7 @@ typedef struct { sp_config_auto_cookie_secure *config_auto_cookie_secure; sp_config_global_strict *config_global_strict; sp_config_disable_xxe *config_disable_xxe; - sp_config_regexp_inclusion *config_regexp_inclusion; + sp_config_disabled_constructs *config_disabled_constructs; } sp_config; typedef struct { diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c index d6107c3..8fba868 100644 --- a/src/sp_config_keywords.c +++ b/src/sp_config_keywords.c @@ -2,6 +2,51 @@ ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) + +static int get_construct_type(sp_disabled_function const *const df) { + const struct { + unsigned int type; + char *keys[5]; + } mapping[] = { + { + .type = ZEND_INCLUDE_OR_EVAL, + .keys = {"include", "include_once", "require", "require_once", NULL} + },{ + .type = ZEND_ECHO, + .keys = {"echo", NULL} + },{ + .type = ZEND_NEW, + .keys = {"new", NULL} + },{ + .type = ZEND_EXIT, + .keys = {"exit", NULL} + },{ + .type = ZEND_STRLEN, + .keys = {"strlen", NULL} + },{ + .type = 0, + .keys = {NULL} + } + }; + + // FIXME: This can be optimized + // FIXME the ->function and r_fonction tests are _wrong_ + for (size_t i=0; 0 != mapping[i].type; i++) { + for (size_t j=0; NULL != mapping[i].keys[j]; j++) { + if (df->function) { + if (0 == strcmp(df->function, mapping[i].keys[j])) { + return mapping[i].type; + } + } else if (df->r_function) { + if (true == is_regexp_matching(df->r_function, mapping[i].keys[j])) { + return mapping[i].type; + } + } + } + } + return -1; +} + static int parse_enable(char *line, bool * restrict retval, bool * restrict simulation) { bool enable = false, disable = false; sp_config_functions sp_config_funcs[] = { @@ -207,22 +252,16 @@ int parse_disabled_functions(char *line) { df->var_is_array = 1; } - bool match = false; - const char *key[4] = {"include", "include_once", "require", "require_once"}; - for (size_t i = 0; i < 4; i++) { - if (df->r_function && true == is_regexp_matching(df->r_function, key[i])) { - match = true; + switch (get_construct_type(df)) { + case ZEND_INCLUDE_OR_EVAL: + sp_list_insert(SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include, df); + return ret; + case ZEND_ECHO: + default: break; - } else if (df->function && 0 == strcmp(df->function, key[i])) { - match = true; - break; - } } - if (true == match && df->regexp) { - sp_list_insert( - SNUFFLEUPAGUS_G(config).config_regexp_inclusion->regexp_inclusion, - df->regexp); - } else if (df->ret || df->r_ret || df->ret_type) { + + if (df->ret || df->r_ret || df->ret_type) { sp_list_insert( SNUFFLEUPAGUS_G(config).config_disabled_functions_ret->disabled_functions, df); diff --git a/src/sp_execute.c b/src/sp_execute.c index 7d62e88..014a049 100644 --- a/src/sp_execute.c +++ b/src/sp_execute.c @@ -28,17 +28,20 @@ ZEND_COLD static inline void terminate_if_writable(const char *filename) { } } -static void check_inclusion_regexp(const char * const filename) { - if (SNUFFLEUPAGUS_G(config).config_regexp_inclusion->regexp_inclusion) { - const sp_node_t* config = SNUFFLEUPAGUS_G(config).config_regexp_inclusion->regexp_inclusion; +static void construct_include_handler(const char * const filename) { + if (SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include) { + const sp_node_t* config = SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include; if (!config || !config->data) { return; } + while (config) { - pcre *config_node = (pcre*)(config->data); - if (false == is_regexp_matching(config_node, filename)) { - sp_log_msg("include", SP_LOG_DROP, "Inclusion of a forbidden file (%s).", filename); - sp_terminate(); + sp_disabled_function *config_node = (sp_disabled_function*)(config->data); + if (true == is_regexp_matching(config_node->regexp, filename)) { + sp_log_disable("include", "inclusion path", filename, config_node); + if (false == config_node->simulation) { + sp_terminate(); + } } config = config->next; } @@ -68,17 +71,22 @@ execute: orig_execute_ex(execute_data); } -static int sp_stream_open(const char *filename, - zend_file_handle *handle) { - const zend_execute_data *data = EG(current_execute_data); +static int sp_stream_open(const char *filename, zend_file_handle *handle) { + zend_execute_data const * const data = EG(current_execute_data); - if ((NULL != data) && (NULL != data->opline) && - (ZEND_INCLUDE_OR_EVAL == data->opline->opcode)) { - if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) { - terminate_if_writable(filename); - } - check_inclusion_regexp(filename); + if ((NULL == data) || (NULL == data->opline)) { + goto end; } + + switch(data->opline->opcode) { + case ZEND_INCLUDE_OR_EVAL: + if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) { + terminate_if_writable(filename); + } + construct_include_handler(filename); + } + +end: return orig_zend_stream_open(filename, handle); } diff --git a/src/tests/disabled_functions_require.phpt b/src/tests/disabled_functions_require.phpt index cc10ef6..f848f8b 100644 --- a/src/tests/disabled_functions_require.phpt +++ b/src/tests/disabled_functions_require.phpt @@ -7,17 +7,17 @@ sp.configuration_file={PWD}/config/config_disabled_functions_require.ini --FILE-- --EXPECTF-- -[snuffleupagus][0.0.0.0][include][drop] Inclusion of a forbidden file (%a/test.bla). +BLA[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'include' in %a/disabled_functions_require.php:%d has been disabled, because its argument 'inclusion path' content (%a/test.meh) matched a rule. --CLEAN-- -- cgit v1.3