From 10437787b0e8ede80976de4a1c22775fc1282f36 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 29 Nov 2017 11:36:57 +0100 Subject: Implement eval hooking It's not possible to hook the `eval` builtin like other functions.--- src/snuffleupagus.c | 2 + src/sp_config.h | 1 + src/sp_config_keywords.c | 6 + src/sp_disabled_functions.c | 155 +++++++++++++-------- src/sp_disabled_functions.h | 2 +- src/sp_execute.c | 79 +++++++---- src/sp_execute.h | 1 + src/sp_utils.c | 2 + .../config_disabled_functions_eval_filename.ini | 1 + src/tests/config/disabled_functions.ini | 1 + src/tests/config/disabled_functions_eval.ini | 1 + .../config/disabled_functions_eval_simulation.ini | 1 + src/tests/deny_writable_execution.phpt | 5 +- src/tests/disabled_functions_eval.phpt | 9 +- src/tests/disabled_functions_eval_filename.phpt | 14 ++ src/tests/disabled_functions_eval_simulation.phpt | 15 ++ src/tests/disabled_functions_require.phpt | 2 +- .../disabled_functions_require_simulation.phpt | 2 +- 18 files changed, 204 insertions(+), 95 deletions(-) create mode 100644 src/tests/config/config_disabled_functions_eval_filename.ini create mode 100644 src/tests/config/disabled_functions_eval.ini create mode 100644 src/tests/config/disabled_functions_eval_simulation.ini create mode 100644 src/tests/disabled_functions_eval_filename.phpt create mode 100644 src/tests/disabled_functions_eval_simulation.phpt (limited to 'src') diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c index 9467a5d..bf18588 100644 --- a/src/snuffleupagus.c +++ b/src/snuffleupagus.c @@ -75,6 +75,7 @@ PHP_GINIT_FUNCTION(snuffleupagus) { SP_INIT(snuffleupagus_globals->config.config_disabled_constructs); snuffleupagus_globals->config.config_disabled_constructs->construct_include = sp_list_new(); + snuffleupagus_globals->config.config_disabled_constructs->construct_eval = sp_list_new(); snuffleupagus_globals->config.config_disabled_functions->disabled_functions = sp_list_new(); snuffleupagus_globals->config.config_disabled_functions_ret->disabled_functions = sp_list_new(); SP_INIT_HT(snuffleupagus_globals->config.config_cookie->cookies); @@ -118,6 +119,7 @@ PHP_MSHUTDOWN_FUNCTION(snuffleupagus) { FREE_LST(config.config_disabled_functions->disabled_functions); FREE_LST(config.config_disabled_functions_ret->disabled_functions); FREE_LST(config.config_disabled_constructs->construct_include); + FREE_LST(config.config_disabled_constructs->construct_eval); #undef FREE_LST diff --git a/src/sp_config.h b/src/sp_config.h index 12f12e8..6d091ae 100644 --- a/src/sp_config.h +++ b/src/sp_config.h @@ -115,6 +115,7 @@ typedef struct { typedef struct { sp_node_t *construct_include; // list of rules for `(include|require)_(once)?` + sp_node_t *construct_eval; sp_node_t *construct_echo; } sp_config_disabled_constructs; diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c index 077d78f..0e52846 100644 --- a/src/sp_config_keywords.c +++ b/src/sp_config_keywords.c @@ -23,6 +23,9 @@ static int get_construct_type(sp_disabled_function const *const df) { },{ .type = ZEND_STRLEN, .keys = {"strlen", NULL} + },{ + .type = ZEND_EVAL_CODE, + .keys = {"eval", NULL} },{ .type = 0, .keys = {NULL} @@ -299,6 +302,9 @@ int parse_disabled_functions(char *line) { case ZEND_INCLUDE_OR_EVAL: sp_list_insert(SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include, df); return ret; + case ZEND_EVAL_CODE: + sp_list_insert(SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_eval, df); + return ret; case ZEND_ECHO: default: break; diff --git a/src/sp_disabled_functions.c b/src/sp_disabled_functions.c index 44a215c..e233259 100644 --- a/src/sp_disabled_functions.c +++ b/src/sp_disabled_functions.c @@ -101,16 +101,44 @@ static bool is_local_var_matching(zend_execute_data *execute_data, const sp_disa return false; } -bool should_disable(zend_execute_data* execute_data) { +static sp_node_t *get_config(const char *builtin_name) { + if (!builtin_name) { + return SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions; + } + if (!strcmp(builtin_name, "eval")) { + return SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_eval; + } + if (!strcmp(builtin_name, "include") || + !strcmp(builtin_name, "include_once") || + !strcmp(builtin_name, "require") || + !strcmp(builtin_name, "require_once")) { + return SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include; + } + return NULL; +} + +bool should_disable(zend_execute_data* execute_data, const char *builtin_name, const char *builtin_param, + const char *builtin_param_name) { char current_file_hash[SHA256_SIZE * 2 + 1] = {0}; - const char* current_filename = zend_get_executed_filename(TSRMLS_C); - const sp_node_t* config = - SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions; - char* complete_path_function = get_complete_function_path(execute_data);; + const sp_node_t* config = get_config(builtin_name); + char* complete_path_function = get_complete_function_path(execute_data); char const* client_ip = sp_getenv("REMOTE_ADDR"); + const char* current_filename; + if (builtin_name && !strcmp(builtin_name, "eval")) { + current_filename = get_eval_filename(zend_get_executed_filename()); + } + else { + current_filename = zend_get_executed_filename(); + } + if (!complete_path_function) { - return false; + if (builtin_name) { + complete_path_function = (char *)builtin_name; + } + else { + return false; + } } if (!config || !config->data) { @@ -133,7 +161,7 @@ bool should_disable(zend_execute_data* execute_data) { } } else if (config_node->function) { /* Litteral match against the function name. */ if (0 != strcmp(config_node->function, complete_path_function)) { - goto next; + goto next; } } else if (config_node->r_function) { if (false == @@ -149,6 +177,7 @@ bool should_disable(zend_execute_data* execute_data) { } if (config_node->filename) { /* Check the current file name. */ + if (0 != strcmp(current_filename, config_node->filename)) { goto next; } @@ -199,58 +228,66 @@ bool should_disable(zend_execute_data* execute_data) { } } - for (; i < nb_param; i++) { - arg_matched = false; - if (ZEND_USER_CODE(execute_data->func->type)) { // yay consistency - arg_name = ZSTR_VAL(execute_data->func->common.arg_info[i].name); - } else { - arg_name = execute_data->func->internal_function.arg_info[i].name; - } + if (builtin_name) { + // we are matching on a builtin param, but for PHP, it's not the same a function param + arg_matched = sp_match_value(builtin_param, config_node->value, config_node->value_r); + arg_name = builtin_param_name; + arg_value_str = builtin_param; + } + else { + for (; i < nb_param; i++) { + arg_matched = false; + if (ZEND_USER_CODE(execute_data->func->type)) { // yay consistency + arg_name = ZSTR_VAL(execute_data->func->common.arg_info[i].name); + } else { + arg_name = execute_data->func->internal_function.arg_info[i].name; + } - const bool arg_matching = + const bool arg_matching = config_node->param && (0 == strcmp(arg_name, config_node->param)); - const bool pcre_matching = + const bool pcre_matching = config_node->r_param && (true == is_regexp_matching(config_node->r_param, arg_name)); - /* This is the parameter name we're looking for. */ - if (true == arg_matching || true == pcre_matching || (config_node->pos != -1)) { - zval* arg_value = ZEND_CALL_VAR_NUM(execute_data, i); - - if (config_node->param_type) { // Are we matching on the `type`? - if (config_node->param_type == Z_TYPE_P(arg_value)) { - arg_matched = true; - break; - } - } else if (Z_TYPE_P(arg_value) == IS_ARRAY) { - arg_value_str = estrdup("Array"); - // match on arr -> match on all key content, if a key is an array, - // ignore it - // match on arr[foo] -> match only on key foo, if the key is an - // array, match on all keys content - if (config_node->param_is_array == true) { - if (true == sp_match_array_key_recurse( - arg_value, config_node->param_array_keys, - config_node->value, config_node->value_r)) { - arg_matched = true; - break; - } - } else { // match on all keys, but don't go into subarray - if (true == sp_match_array_key(arg_value, config_node->value, - config_node->value_r)) { - arg_matched = true; - break; - } - } - } else { - arg_value_str = sp_convert_to_string(arg_value); - if (true == sp_match_value(arg_value_str, config_node->value, - config_node->value_r)) { - arg_matched = true; - break; - } - } - } + /* This is the parameter name we're looking for. */ + if (true == arg_matching || true == pcre_matching || (config_node->pos != -1)) { + zval* arg_value = ZEND_CALL_VAR_NUM(execute_data, i); + + if (config_node->param_type) { // Are we matching on the `type`? + if (config_node->param_type == Z_TYPE_P(arg_value)) { + arg_matched = true; + break; + } + } else if (Z_TYPE_P(arg_value) == IS_ARRAY) { + arg_value_str = estrdup("Array"); + // match on arr -> match on all key content, if a key is an array, + // ignore it + // match on arr[foo] -> match only on key foo, if the key is an + // array, match on all keys content + if (config_node->param_is_array == true) { + if (true == sp_match_array_key_recurse( + arg_value, config_node->param_array_keys, + config_node->value, config_node->value_r)) { + arg_matched = true; + break; + } + } else { // match on all keys, but don't go into subarray + if (true == sp_match_array_key(arg_value, config_node->value, + config_node->value_r)) { + arg_matched = true; + break; + } + } + } else { + arg_value_str = sp_convert_to_string(arg_value); + if (true == sp_match_value(arg_value_str, config_node->value, + config_node->value_r)) { + arg_matched = true; + break; + } + } + } + } } if (false == arg_matched) { goto next; @@ -273,14 +310,18 @@ bool should_disable(zend_execute_data* execute_data) { if (true == config_node->simulation) { goto next; } else { // We've got a match, the function won't be executed - efree(complete_path_function); + if (builtin_name == NULL) { + efree(complete_path_function); + } return true; } next: config = config->next; } allow: - efree(complete_path_function); + if (builtin_name == NULL) { + efree(complete_path_function); + } return false; } @@ -370,7 +411,7 @@ ZEND_FUNCTION(check_disabled_function) { void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS); const char* current_function_name = get_active_function_name(TSRMLS_C); - if (true == should_disable(execute_data)) { + if (true == should_disable(execute_data, NULL, NULL, NULL)) { sp_terminate(); } diff --git a/src/sp_disabled_functions.h b/src/sp_disabled_functions.h index 260d430..e9180c9 100644 --- a/src/sp_disabled_functions.h +++ b/src/sp_disabled_functions.h @@ -2,6 +2,6 @@ #define __SP_DISABLE_FUNCTIONS_H int hook_disabled_functions(); -bool should_disable(zend_execute_data* function_name); +bool should_disable(zend_execute_data*, const char *, const char *, const char *); #endif /* __SP_DISABLE_FUNCTIONS_H */ diff --git a/src/sp_execute.c b/src/sp_execute.c index 45a4ae6..086d769 100644 --- a/src/sp_execute.c +++ b/src/sp_execute.c @@ -28,49 +28,55 @@ ZEND_COLD static inline void terminate_if_writable(const char *filename) { } } -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; - } +static void is_builtin_matching(const char * const filename, char* function_name, + char *param_name, sp_node_t *config) { + if (!config || !config->data) { + return; + } + + if (true == should_disable(EG(current_execute_data), function_name, filename, param_name)) { + sp_terminate(); + } +} - while (config) { - sp_disabled_function *config_node = (sp_disabled_function*)(config->data); - if (true == sp_match_value(filename, config_node->value, config_node->value_r)) { - if (true == config_node->allow) { - return; - } - sp_log_disable("include", "inclusion path", filename, config_node); - if (false == config_node->simulation) { - sp_terminate(); - } +char *get_eval_filename(const char *filename) { + size_t i = strlen(filename) - 1; + int count = 0; + char *clean_filename = estrdup(filename); + + //ghetto as fuck + //get the filename in which eval() is called from "foo.php(1) : eval()'d code" + while (i) { + if (clean_filename[i] == '(') { + if (count == 1) { + clean_filename[i] = 0; + break; } - config = config->next; + count++; } + i--; } + return clean_filename; } static void sp_execute_ex(zend_execute_data *execute_data) { - if (NULL == execute_data->func->common.function_name) { - goto execute; - } - - if (true == should_disable(execute_data)) { + if (true == should_disable(execute_data, NULL, NULL, NULL)) { sp_terminate(); } - + if (execute_data->func->op_array.type == ZEND_EVAL_CODE) { - sp_log_debug("Currently in an eval\n"); + sp_node_t* config = SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_eval; + char *filename = get_eval_filename((char *)zend_get_executed_filename()); + is_builtin_matching(filename, "eval", NULL, config); + efree(filename); } if (NULL != execute_data->func->op_array.filename) { if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) { terminate_if_writable(ZSTR_VAL(execute_data->func->op_array.filename)); } -} + } -execute: orig_execute_ex(execute_data); } @@ -86,7 +92,26 @@ static int sp_stream_open(const char *filename, zend_file_handle *handle) { if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) { terminate_if_writable(filename); } - construct_include_handler(filename); + sp_node_t* config = SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include; + switch (data->opline->extended_value) { + case ZEND_INCLUDE: + is_builtin_matching(filename, "include", "inclusion path", config); + break; + case ZEND_REQUIRE: + is_builtin_matching(filename, "require", "inclusion path", config); + break; + case ZEND_REQUIRE_ONCE: + is_builtin_matching(filename, "require_once", "inclusion path", config); + break; + case ZEND_INCLUDE_ONCE: + is_builtin_matching(filename, "include_once", "inclusion path", config); + break; + case ZEND_EVAL: + is_builtin_matching(filename, "eval", NULL, config); + break; + default: + break; + } } end: diff --git a/src/sp_execute.h b/src/sp_execute.h index 8345736..6ef50ee 100644 --- a/src/sp_execute.h +++ b/src/sp_execute.h @@ -2,5 +2,6 @@ #define SP_EXECUTE_H int hook_execute(void); +char *get_eval_filename(const char *filename); #endif /* SP_EXECUTE_H */ diff --git a/src/sp_utils.c b/src/sp_utils.c index 514fc10..28c8a8b 100644 --- a/src/sp_utils.c +++ b/src/sp_utils.c @@ -229,6 +229,8 @@ bool sp_match_value(const char* value, const char* to_match, const pcre* rx) { } } else if (rx) { return is_regexp_matching(rx, value); + } else { + return true; } return false; } diff --git a/src/tests/config/config_disabled_functions_eval_filename.ini b/src/tests/config/config_disabled_functions_eval_filename.ini new file mode 100644 index 0000000..f66cef3 --- /dev/null +++ b/src/tests/config/config_disabled_functions_eval_filename.ini @@ -0,0 +1 @@ +sp.disable_function.function("eval").filename_r("^.*tests/disabled_functions_eval_filename.php$").drop(); diff --git a/src/tests/config/disabled_functions.ini b/src/tests/config/disabled_functions.ini index 226a107..df7013f 100644 --- a/src/tests/config/disabled_functions.ini +++ b/src/tests/config/disabled_functions.ini @@ -5,4 +5,5 @@ sp.disable_function.function("printf").simulation().drop(); sp.disable_function.function("print").disable().drop(); # this is a comment sp.disable_function.function_r("^var_dump$").drop(); sp.disable_function.function("sprintf").filename("/wrong file name").drop(); +sp.disable_function.function("sprintf").filename("/wrong file name").drop(); sp.disable_function.function("eval").drop(); diff --git a/src/tests/config/disabled_functions_eval.ini b/src/tests/config/disabled_functions_eval.ini new file mode 100644 index 0000000..f761259 --- /dev/null +++ b/src/tests/config/disabled_functions_eval.ini @@ -0,0 +1 @@ +sp.disable_function.function("eval").drop(); diff --git a/src/tests/config/disabled_functions_eval_simulation.ini b/src/tests/config/disabled_functions_eval_simulation.ini new file mode 100644 index 0000000..f1dc58c --- /dev/null +++ b/src/tests/config/disabled_functions_eval_simulation.ini @@ -0,0 +1 @@ +sp.disable_function.function("eval").drop().simulation(); diff --git a/src/tests/deny_writable_execution.phpt b/src/tests/deny_writable_execution.phpt index 2870561..c399d35 100644 --- a/src/tests/deny_writable_execution.phpt +++ b/src/tests/deny_writable_execution.phpt @@ -32,8 +32,7 @@ include "$dir/non_writable_file.txt"; include "$dir/writable_file.txt"; ?> --EXPECTF-- -Code execution within a non-writable file. -[snuffleupagus][0.0.0.0][readonly_exec][drop] Attempted execution of a writable file (%a/writable_file.txt). +[snuffleupagus][0.0.0.0][readonly_exec][drop] Attempted execution of a writable file (%a/tests/deny_writable_execution.php). --CLEAN-- \ No newline at end of file +?> diff --git a/src/tests/disabled_functions_eval.phpt b/src/tests/disabled_functions_eval.phpt index 0beaefe..7bd6b4b 100644 --- a/src/tests/disabled_functions_eval.phpt +++ b/src/tests/disabled_functions_eval.phpt @@ -3,13 +3,12 @@ Disable functions - eval --SKIPIF-- --INI-- -sp.configuration_file={PWD}/config/disabled_functions.ini ---XFAIL-- +sp.configuration_file={PWD}/config/disabled_functions_eval.ini --FILE-- --EXPECTF-- -[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'eval' in %a/tests/disabled_functions_eval.php:%d has been disabled, because it matched a rule. +[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'eval' in %a/tests/disabled_functions_eval.php(%d) : eval()'d code:%d has been disabled. diff --git a/src/tests/disabled_functions_eval_filename.phpt b/src/tests/disabled_functions_eval_filename.phpt new file mode 100644 index 0000000..5e64acc --- /dev/null +++ b/src/tests/disabled_functions_eval_filename.phpt @@ -0,0 +1,14 @@ +--TEST-- +Disable functions - eval +--SKIPIF-- + +--INI-- +sp.configuration_file={PWD}/config/config_disabled_functions_eval_filename.ini +--FILE-- + +--EXPECTF-- +[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'eval' in %a/tests/disabled_functions_eval_filename.php(%d) : eval()'d code:%d has been disabled. diff --git a/src/tests/disabled_functions_eval_simulation.phpt b/src/tests/disabled_functions_eval_simulation.phpt new file mode 100644 index 0000000..06a006e --- /dev/null +++ b/src/tests/disabled_functions_eval_simulation.phpt @@ -0,0 +1,15 @@ +--TEST-- +Disable functions - eval (simulation) +--SKIPIF-- + +--INI-- +sp.configuration_file={PWD}/config/disabled_functions_eval_simulation.ini +--FILE-- + +--EXPECTF-- +[snuffleupagus][0.0.0.0][disabled_function][simulation] The call to the function 'eval' in %a/tests/disabled_functions_eval_simulation.php(%d) : eval()'d code:%d has been disabled. +Variable: 2674 diff --git a/src/tests/disabled_functions_require.phpt b/src/tests/disabled_functions_require.phpt index f848f8b..cc904f1 100644 --- a/src/tests/disabled_functions_require.phpt +++ b/src/tests/disabled_functions_require.phpt @@ -14,7 +14,7 @@ require $dir . '/test.meh'; echo "1337"; ?> --EXPECTF-- -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. +BLA[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'require' in %a/disabled_functions_require.php:%d has been disabled, because its argument 'inclusion path' content (%a/test.meh) matched a rule. --CLEAN-- --EXPECTF-- BLA -[snuffleupagus][0.0.0.0][disabled_function][simulation] The call to the function 'include' in %a/disabled_functions_require_simulation.php:%d has been disabled, because its argument 'inclusion path' content (%a/test.sim) matched a rule. +[snuffleupagus][0.0.0.0][disabled_function][simulation] The call to the function 'require' in %a/disabled_functions_require_simulation.php:%d has been disabled, because its argument 'inclusion path' content (%a/test.sim) matched a rule. MEH 1337 --CLEAN-- -- cgit v1.3