diff options
| author | Ben Fuhrmannek | 2021-02-16 11:16:59 +0100 |
|---|---|---|
| committer | Ben Fuhrmannek | 2021-02-16 11:16:59 +0100 |
| commit | 5484bcb5eb2714e7438927e2566c86a74d7c51af (patch) | |
| tree | b78326d2999397be4c08e06b23209981f82a4ea9 /src/sp_utils.c | |
| parent | 7ac1e3866ef4f146c6c93a5ca13b9aebb14e936a (diff) | |
| parent | cecfdd808da67be908dbe7144cc8c74dfb3f855e (diff) | |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'src/sp_utils.c')
| -rw-r--r-- | src/sp_utils.c | 158 |
1 files changed, 111 insertions, 47 deletions
diff --git a/src/sp_utils.c b/src/sp_utils.c index 0f87f17..a7a3d27 100644 --- a/src/sp_utils.c +++ b/src/sp_utils.c | |||
| @@ -7,7 +7,41 @@ bool sp_zend_string_equals(const zend_string* s1, const zend_string* s2) { | |||
| 7 | !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1)); | 7 | !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1)); |
| 8 | } | 8 | } |
| 9 | 9 | ||
| 10 | void sp_log_msg(char const* feature, int type, const char* fmt, ...) { | 10 | static const char* default_ipaddr = "0.0.0.0"; |
| 11 | const char* get_ipaddr() { | ||
| 12 | const char* client_ip = getenv("REMOTE_ADDR"); | ||
| 13 | if (client_ip) { | ||
| 14 | return client_ip; | ||
| 15 | } | ||
| 16 | |||
| 17 | const char* fwd_ip = getenv("HTTP_X_FORWARDED_FOR"); | ||
| 18 | if (fwd_ip) { | ||
| 19 | return fwd_ip; | ||
| 20 | } | ||
| 21 | |||
| 22 | /* Some hosters (like heroku, see | ||
| 23 | * https://github.com/jvoisin/snuffleupagus/issues/336) are clearing the | ||
| 24 | * environment variables, so we don't have access to them, hence why we're | ||
| 25 | * resorting to $_SERVER['REMOTE_ADDR']. | ||
| 26 | */ | ||
| 27 | if (!Z_ISUNDEF(PG(http_globals)[TRACK_VARS_SERVER])) { | ||
| 28 | const zval* const globals_client_ip = | ||
| 29 | zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), | ||
| 30 | "REMOTE_ADDR", sizeof("REMOTE_ADDR") - 1); | ||
| 31 | if (globals_client_ip) { | ||
| 32 | if (Z_TYPE_P(globals_client_ip) == IS_STRING) { | ||
| 33 | if (Z_STRLEN_P(globals_client_ip) != 0) { | ||
| 34 | return estrdup(Z_STRVAL_P(globals_client_ip)); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | return default_ipaddr; | ||
| 41 | } | ||
| 42 | |||
| 43 | void sp_log_msgf(char const* restrict feature, int level, int type, | ||
| 44 | const char* restrict fmt, ...) { | ||
| 11 | char* msg; | 45 | char* msg; |
| 12 | va_list args; | 46 | va_list args; |
| 13 | 47 | ||
| @@ -15,31 +49,45 @@ void sp_log_msg(char const* feature, int type, const char* fmt, ...) { | |||
| 15 | vspprintf(&msg, 0, fmt, args); | 49 | vspprintf(&msg, 0, fmt, args); |
| 16 | va_end(args); | 50 | va_end(args); |
| 17 | 51 | ||
| 18 | const char *client_ip = getenv("REMOTE_ADDR"); | 52 | const char* client_ip = get_ipaddr(); |
| 19 | if (!client_ip) { | 53 | const char* logtype = NULL; |
| 20 | client_ip = "0.0.0.0"; | 54 | switch (type) { |
| 55 | case SP_TYPE_SIMULATION: | ||
| 56 | logtype = "simulation"; | ||
| 57 | break; | ||
| 58 | case SP_TYPE_DROP: | ||
| 59 | logtype = "drop"; | ||
| 60 | break; | ||
| 61 | case SP_TYPE_LOG: | ||
| 62 | default: | ||
| 63 | logtype = "log"; | ||
| 64 | break; | ||
| 21 | } | 65 | } |
| 66 | |||
| 22 | switch (SNUFFLEUPAGUS_G(config).log_media) { | 67 | switch (SNUFFLEUPAGUS_G(config).log_media) { |
| 23 | case SP_SYSLOG: | 68 | case SP_SYSLOG: { |
| 24 | openlog(PHP_SNUFFLEUPAGUS_EXTNAME, LOG_PID, LOG_AUTH); | ||
| 25 | const char* error_filename = zend_get_executed_filename(); | 69 | const char* error_filename = zend_get_executed_filename(); |
| 26 | int syslog_level = SP_LOG_DROP ? LOG_ERR : LOG_INFO; | 70 | int syslog_level = (level == E_ERROR) ? LOG_ERR : LOG_INFO; |
| 27 | int error_lineno = zend_get_executed_lineno(TSRMLS_C); | 71 | int error_lineno = zend_get_executed_lineno(TSRMLS_C); |
| 28 | syslog(syslog_level, "[snuffleupagus][%s][%s] %s in %s on line %d", client_ip, feature, msg, | 72 | openlog(PHP_SNUFFLEUPAGUS_EXTNAME, LOG_PID, LOG_AUTH); |
| 29 | error_filename, error_lineno); | 73 | syslog(syslog_level, "[snuffleupagus][%s][%s][%s] %s in %s on line %d", |
| 74 | client_ip, feature, logtype, msg, error_filename, error_lineno); | ||
| 30 | closelog(); | 75 | closelog(); |
| 31 | if (type == SP_LOG_DROP) { | 76 | if (type == SP_TYPE_DROP) { |
| 32 | zend_bailout(); | 77 | zend_bailout(); |
| 33 | } | 78 | } |
| 34 | break; | 79 | break; |
| 80 | } | ||
| 35 | case SP_ZEND: | 81 | case SP_ZEND: |
| 36 | default: | 82 | default: |
| 37 | zend_error(type, "[snuffleupagus][%s][%s] %s", client_ip, feature, msg); | 83 | zend_error(level, "[snuffleupagus][%s][%s][%s] %s", client_ip, feature, |
| 84 | logtype, msg); | ||
| 38 | break; | 85 | break; |
| 39 | } | 86 | } |
| 40 | } | 87 | } |
| 41 | 88 | ||
| 42 | int compute_hash(const char* const filename, char* file_hash) { | 89 | int compute_hash(const char* const restrict filename, |
| 90 | char* restrict file_hash) { | ||
| 43 | unsigned char buf[1024]; | 91 | unsigned char buf[1024]; |
| 44 | unsigned char digest[SHA256_SIZE]; | 92 | unsigned char digest[SHA256_SIZE]; |
| 45 | PHP_SHA256_CTX context; | 93 | PHP_SHA256_CTX context; |
| @@ -65,8 +113,9 @@ int compute_hash(const char* const filename, char* file_hash) { | |||
| 65 | return SUCCESS; | 113 | return SUCCESS; |
| 66 | } | 114 | } |
| 67 | 115 | ||
| 68 | static int construct_filename(char* filename, const zend_string* folder, | 116 | static int construct_filename(char* filename, |
| 69 | const zend_string* textual) { | 117 | const zend_string* restrict folder, |
| 118 | const zend_string* restrict textual) { | ||
| 70 | PHP_SHA256_CTX context; | 119 | PHP_SHA256_CTX context; |
| 71 | unsigned char digest[SHA256_SIZE] = {0}; | 120 | unsigned char digest[SHA256_SIZE] = {0}; |
| 72 | char strhash[65] = {0}; | 121 | char strhash[65] = {0}; |
| @@ -78,7 +127,7 @@ static int construct_filename(char* filename, const zend_string* folder, | |||
| 78 | } | 127 | } |
| 79 | 128 | ||
| 80 | /* We're using the sha256 sum of the rule's textual representation | 129 | /* We're using the sha256 sum of the rule's textual representation |
| 81 | * as filename, in order to only have one dump per rule, to migitate | 130 | * as filename, in order to only have one dump per rule, to mitigate |
| 82 | * DoS attacks. */ | 131 | * DoS attacks. */ |
| 83 | PHP_SHA256Init(&context); | 132 | PHP_SHA256Init(&context); |
| 84 | PHP_SHA256Update(&context, (const unsigned char*)ZSTR_VAL(textual), | 133 | PHP_SHA256Update(&context, (const unsigned char*)ZSTR_VAL(textual), |
| @@ -90,14 +139,15 @@ static int construct_filename(char* filename, const zend_string* folder, | |||
| 90 | return 0; | 139 | return 0; |
| 91 | } | 140 | } |
| 92 | 141 | ||
| 93 | int sp_log_request(const zend_string* folder, const zend_string* text_repr, | 142 | int sp_log_request(const zend_string* restrict folder, |
| 94 | char* from) { | 143 | const zend_string* restrict text_repr, |
| 144 | char const* const from) { | ||
| 95 | FILE* file; | 145 | FILE* file; |
| 96 | const char* current_filename = zend_get_executed_filename(TSRMLS_C); | 146 | const char* current_filename = zend_get_executed_filename(TSRMLS_C); |
| 97 | const int current_line = zend_get_executed_lineno(TSRMLS_C); | 147 | const int current_line = zend_get_executed_lineno(TSRMLS_C); |
| 98 | char filename[PATH_MAX] = {0}; | 148 | char filename[PATH_MAX] = {0}; |
| 99 | const struct { | 149 | const struct { |
| 100 | const char* str; | 150 | char const* const str; |
| 101 | const int key; | 151 | const int key; |
| 102 | } zones[] = {{"GET", TRACK_VARS_GET}, {"POST", TRACK_VARS_POST}, | 152 | } zones[] = {{"GET", TRACK_VARS_GET}, {"POST", TRACK_VARS_POST}, |
| 103 | {"COOKIE", TRACK_VARS_COOKIE}, {"SERVER", TRACK_VARS_SERVER}, | 153 | {"COOKIE", TRACK_VARS_COOKIE}, {"SERVER", TRACK_VARS_SERVER}, |
| @@ -115,7 +165,22 @@ int sp_log_request(const zend_string* folder, const zend_string* text_repr, | |||
| 115 | fprintf(file, "RULE: sp%s%s\n", from, ZSTR_VAL(text_repr)); | 165 | fprintf(file, "RULE: sp%s%s\n", from, ZSTR_VAL(text_repr)); |
| 116 | 166 | ||
| 117 | fprintf(file, "FILE: %s:%d\n", current_filename, current_line); | 167 | fprintf(file, "FILE: %s:%d\n", current_filename, current_line); |
| 118 | for (size_t i = 0; i < (sizeof(zones) / sizeof(zones[0])) - 1; i++) { | 168 | |
| 169 | zend_execute_data* orig_execute_data = EG(current_execute_data); | ||
| 170 | zend_execute_data* current = EG(current_execute_data); | ||
| 171 | while (current) { | ||
| 172 | EG(current_execute_data) = current; | ||
| 173 | char* const complete_path_function = get_complete_function_path(current); | ||
| 174 | if (complete_path_function) { | ||
| 175 | const int current_line = zend_get_executed_lineno(TSRMLS_C); | ||
| 176 | fprintf(file, "STACKTRACE: %s:%d\n", complete_path_function, | ||
| 177 | current_line); | ||
| 178 | } | ||
| 179 | current = current->prev_execute_data; | ||
| 180 | } | ||
| 181 | EG(current_execute_data) = orig_execute_data; | ||
| 182 | |||
| 183 | for (size_t i = 0; zones[i].str; i++) { | ||
| 119 | zval* variable_value; | 184 | zval* variable_value; |
| 120 | zend_string* variable_key; | 185 | zend_string* variable_key; |
| 121 | 186 | ||
| @@ -232,26 +297,27 @@ void sp_log_disable(const char* restrict path, const char* restrict arg_name, | |||
| 232 | char_repr = zend_string_to_char(arg_value); | 297 | char_repr = zend_string_to_char(arg_value); |
| 233 | } | 298 | } |
| 234 | if (alias) { | 299 | if (alias) { |
| 235 | sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, | 300 | sp_log_auto( |
| 236 | "Aborted execution on call of the function '%s', " | 301 | "disabled_function", sim, |
| 237 | "because its argument '%s' content (%s) matched the rule '%s'", | 302 | "Aborted execution on call of the function '%s', " |
| 238 | path, arg_name, char_repr ? char_repr : "?", ZSTR_VAL(alias)); | 303 | "because its argument '%s' content (%s) matched the rule '%s'", |
| 304 | path, arg_name, char_repr ? char_repr : "?", ZSTR_VAL(alias)); | ||
| 239 | } else { | 305 | } else { |
| 240 | sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, | 306 | sp_log_auto("disabled_function", sim, |
| 241 | "Aborted execution on call of the function '%s', " | 307 | "Aborted execution on call of the function '%s', " |
| 242 | "because its argument '%s' content (%s) matched a rule", | 308 | "because its argument '%s' content (%s) matched a rule", |
| 243 | path, arg_name, char_repr ? char_repr : "?"); | 309 | path, arg_name, char_repr ? char_repr : "?"); |
| 244 | } | 310 | } |
| 245 | efree(char_repr); | 311 | efree(char_repr); |
| 246 | } else { | 312 | } else { |
| 247 | if (alias) { | 313 | if (alias) { |
| 248 | sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, | 314 | sp_log_auto("disabled_function", sim, |
| 249 | "Aborted execution on call of the function '%s', " | 315 | "Aborted execution on call of the function '%s', " |
| 250 | "because of the the rule '%s'", | 316 | "because of the the rule '%s'", |
| 251 | path, ZSTR_VAL(alias)); | 317 | path, ZSTR_VAL(alias)); |
| 252 | } else { | 318 | } else { |
| 253 | sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, | 319 | sp_log_auto("disabled_function", sim, |
| 254 | "Aborted execution on call of the function '%s'", path); | 320 | "Aborted execution on call of the function '%s'", path); |
| 255 | } | 321 | } |
| 256 | } | 322 | } |
| 257 | } | 323 | } |
| @@ -272,16 +338,16 @@ void sp_log_disable_ret(const char* restrict path, | |||
| 272 | char_repr = zend_string_to_char(ret_value); | 338 | char_repr = zend_string_to_char(ret_value); |
| 273 | } | 339 | } |
| 274 | if (alias) { | 340 | if (alias) { |
| 275 | sp_log_msg( | 341 | sp_log_auto( |
| 276 | "disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, | 342 | "disabled_function", sim, |
| 277 | "Aborted execution on return of the function '%s', " | 343 | "Aborted execution on return of the function '%s', " |
| 278 | "because the function returned '%s', which matched the rule '%s'", | 344 | "because the function returned '%s', which matched the rule '%s'", |
| 279 | path, char_repr ? char_repr : "?", ZSTR_VAL(alias)); | 345 | path, char_repr ? char_repr : "?", ZSTR_VAL(alias)); |
| 280 | } else { | 346 | } else { |
| 281 | sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, | 347 | sp_log_auto("disabled_function", sim, |
| 282 | "Aborted execution on return of the function '%s', " | 348 | "Aborted execution on return of the function '%s', " |
| 283 | "because the function returned '%s', which matched a rule", | 349 | "because the function returned '%s', which matched a rule", |
| 284 | path, char_repr ? char_repr : "?"); | 350 | path, char_repr ? char_repr : "?"); |
| 285 | } | 351 | } |
| 286 | efree(char_repr); | 352 | efree(char_repr); |
| 287 | } | 353 | } |
| @@ -317,10 +383,8 @@ bool sp_match_array_value(const zval* arr, const zend_string* to_match, | |||
| 317 | 383 | ||
| 318 | ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arr), value) { | 384 | ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arr), value) { |
| 319 | if (Z_TYPE_P(value) != IS_ARRAY) { | 385 | if (Z_TYPE_P(value) != IS_ARRAY) { |
| 320 | const zend_string* value_str = sp_zval_to_zend_string(value); | 386 | if (sp_match_value(sp_zval_to_zend_string(value), to_match, rx)) { |
| 321 | if (sp_match_value(value_str, to_match, rx)) { | ||
| 322 | return true; | 387 | return true; |
| 323 | } else { | ||
| 324 | } | 388 | } |
| 325 | } else if (sp_match_array_value(value, to_match, rx)) { | 389 | } else if (sp_match_array_value(value, to_match, rx)) { |
| 326 | return true; | 390 | return true; |
| @@ -330,10 +394,10 @@ bool sp_match_array_value(const zval* arr, const zend_string* to_match, | |||
| 330 | return false; | 394 | return false; |
| 331 | } | 395 | } |
| 332 | 396 | ||
| 333 | int hook_function(const char* original_name, HashTable* hook_table, | 397 | bool hook_function(const char* original_name, HashTable* hook_table, |
| 334 | zif_handler new_function) { | 398 | zif_handler new_function) { |
| 335 | zend_internal_function* func; | 399 | zend_internal_function* func; |
| 336 | bool ret = FAILURE; | 400 | bool ret = false; |
| 337 | 401 | ||
| 338 | /* The `mb` module likes to hook functions, like strlen->mb_strlen, | 402 | /* The `mb` module likes to hook functions, like strlen->mb_strlen, |
| 339 | * so we have to hook both of them. */ | 403 | * so we have to hook both of them. */ |
| @@ -352,7 +416,7 @@ int hook_function(const char* original_name, HashTable* hook_table, | |||
| 352 | // LCOV_EXCL_STOP | 416 | // LCOV_EXCL_STOP |
| 353 | } | 417 | } |
| 354 | func->handler = new_function; | 418 | func->handler = new_function; |
| 355 | ret = SUCCESS; | 419 | ret = true; |
| 356 | } | 420 | } |
| 357 | } | 421 | } |
| 358 | 422 | ||
| @@ -406,7 +470,7 @@ bool check_is_in_eval_whitelist(const zend_string* const function_name) { | |||
| 406 | } | 470 | } |
| 407 | 471 | ||
| 408 | /* yes, we could use a HashTable instead, but since the list is pretty | 472 | /* yes, we could use a HashTable instead, but since the list is pretty |
| 409 | * small, it doesn't maka a difference in practise. */ | 473 | * small, it doesn't make a difference in practise. */ |
| 410 | while (it && it->data) { | 474 | while (it && it->data) { |
| 411 | if (sp_zend_string_equals(function_name, (const zend_string*)(it->data))) { | 475 | if (sp_zend_string_equals(function_name, (const zend_string*)(it->data))) { |
| 412 | /* We've got a match, the function is whiteslited. */ | 476 | /* We've got a match, the function is whiteslited. */ |
