From bd8b5bb241ca359b65c1a3717c9905d034b9703b Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Sat, 7 Aug 2021 15:56:57 +0200 Subject: more ini protection features --- src/sp_config.h | 8 ++++++-- src/sp_config_keywords.c | 21 ++++++++++++++----- src/sp_ini.c | 52 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 57 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/sp_config.h b/src/sp_config.h index bd2530a..0ba2e7f 100644 --- a/src/sp_config.h +++ b/src/sp_config.h @@ -170,17 +170,21 @@ typedef struct { zend_string *min; zend_string *max; sp_pcre *regexp; - bool simulation; zend_string *msg; zend_string *set; + bool allow_null; + bool simulation; + bool drop; PHP_INI_MH((*orig_onmodify)); } sp_ini_entry; typedef struct { bool enable; bool simulation; - // sp_ini_permission access_policy; bool policy_readonly; + bool policy_silent_ro; + bool policy_silent_fail; + bool policy_drop; HashTable *entries; // ht of sp_ini_entry } sp_config_ini; diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c index e6eb05e..c547f10 100644 --- a/src/sp_config_keywords.c +++ b/src/sp_config_keywords.c @@ -566,14 +566,19 @@ int parse_upload_validation(char *line) { int parse_ini_protection(char *line) { bool disable = false, enable = false; bool rw = false, ro = false; // rw is ignored, but declaring .policy_rw is valid for readability + sp_config_ini *cfg = SNUFFLEUPAGUS_G(config).config_ini; sp_config_functions sp_config_ini_protection[] = { {parse_empty, SP_TOKEN_ENABLE, &(enable)}, {parse_empty, SP_TOKEN_DISABLE, &(disable)}, - {parse_empty, SP_TOKEN_SIMULATION, &(SNUFFLEUPAGUS_G(config).config_ini->simulation)}, + {parse_empty, SP_TOKEN_SIMULATION, &cfg->simulation}, {parse_empty, ".policy_readonly(", &ro}, {parse_empty, ".policy_ro(", &ro}, {parse_empty, ".policy_readwrite(", &rw}, {parse_empty, ".policy_rw(", &rw}, + {parse_empty, ".policy_silent_ro(", &cfg->policy_silent_ro}, + {parse_empty, ".policy_silent_fail(", &cfg->policy_silent_fail}, + {parse_empty, ".policy_no_log(", &cfg->policy_silent_fail}, + {parse_empty, ".policy_drop(", &cfg->policy_drop}, {0, 0, 0}}; int ret = parse_keywords(sp_config_ini_protection, line); @@ -585,15 +590,19 @@ int parse_ini_protection(char *line) { return -1; } if (enable || disable) { - SNUFFLEUPAGUS_G(config).config_ini->enable = (enable || !disable); + cfg->enable = (enable || !disable); } if (ro && rw) { sp_log_err("config", "rule cannot be both read-write and read-only on line %zu", sp_line_no); return -1; } - SNUFFLEUPAGUS_G(config).config_ini->policy_readonly = ro; + cfg->policy_readonly = ro; + if (cfg->policy_silent_fail && cfg->policy_drop) { + sp_log_err("config", "policy cannot be drop and silent at the same time on line %zu", sp_line_no); + return -1; + } return ret; } @@ -611,8 +620,10 @@ int parse_ini_entry(char *line) { {parse_regexp, ".regexp(", &entry->regexp}, {parse_empty, ".readonly(", &ro}, {parse_empty, ".ro(", &ro}, - {parse_empty, ".readwrite()", &rw}, - {parse_empty, ".rw()", &rw}, + {parse_empty, ".readwrite(", &rw}, + {parse_empty, ".rw(", &rw}, + {parse_empty, ".drop(", &entry->drop}, + {parse_empty, ".allow_null(", &entry->allow_null}, {0, 0, 0}}; int ret = parse_keywords(sp_config_ini_protection, line); diff --git a/src/sp_ini.c b/src/sp_ini.c index 05d7d99..5777ca3 100644 --- a/src/sp_ini.c +++ b/src/sp_ini.c @@ -3,6 +3,15 @@ #define SP_INI_HAS_CHECKS_COND(entry) (entry->min || entry->max || entry->regexp) #define SP_INI_ACCESS_READONLY_COND(entry, cfg) (entry->access == SP_READONLY || (!entry->access && cfg->policy_readonly)) +#define sp_log_auto2(feature, is_simulation, drop, ...) \ + sp_log_msgf(feature, ((is_simulation || !drop) ? SP_LOG_WARN : SP_LOG_ERROR), \ + (is_simulation ? SP_TYPE_SIMULATION : (drop ? SP_TYPE_DROP : SP_TYPE_LOG)), \ + __VA_ARGS__) +#define sp_log_ini_check_violation(...) if (simulation || cfg->policy_drop || (entry && entry->drop) || !cfg->policy_silent_fail) { \ + sp_log_auto2("ini_protection", simulation, (cfg->policy_drop || (entry && entry->drop)), __VA_ARGS__); \ + } + + static bool /* success */ sp_ini_check(zend_string *varname, zend_string *new_value, sp_ini_entry **sp_entry_p) { if (!varname || ZSTR_LEN(varname) == 0) { return false; @@ -17,40 +26,49 @@ static bool /* success */ sp_ini_check(zend_string *varname, zend_string *new_va if (!entry) { if (cfg->policy_readonly) { - sp_log_auto("ini_protection", simulation, "INI setting is read-only"); - if (simulation) { return true; } - return false; + if (!cfg->policy_silent_ro) { + sp_log_ini_check_violation("INI setting is read-only"); + } + return simulation; } return true; } + // we have an entry. + if (SP_INI_ACCESS_READONLY_COND(entry, cfg)) { - sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI setting is read-only")); - if (simulation) { return true; } - return false; + if (!cfg->policy_silent_ro) { + sp_log_ini_check_violation("%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI setting is read-only")); + } + return simulation; } - if (!new_value && SP_INI_HAS_CHECKS_COND(entry)) { - sp_log_auto("ini_protection", simulation, "new INI value must not be NULL"); - if (simulation) { return true; } - return false; + if (!new_value || ZSTR_LEN(new_value) == 0) { + if (entry->allow_null) { + return true; // allow NULL value and skip other tests + } + if (SP_INI_HAS_CHECKS_COND(entry)) { + sp_log_ini_check_violation("new INI value must not be NULL or empty"); + return simulation; + } + return true; // no new_value, but no checks to perform } + // we have a new_value. + if (entry->min || entry->max) { zend_long lvalue = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); if ((entry->min && zend_atol(ZSTR_VAL(entry->min), ZSTR_LEN(entry->min)) > lvalue) || (entry->max && zend_atol(ZSTR_VAL(entry->max), ZSTR_LEN(entry->max)) < lvalue)) { - sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value out of range")); - if (simulation) { return true; } - return false; + sp_log_ini_check_violation("%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value out of range")); + return simulation; } } if (entry->regexp) { if (!sp_is_regexp_matching_len(entry->regexp, ZSTR_VAL(new_value), ZSTR_LEN(new_value))) { - sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value does not match regex")); - if (simulation) { return true; } - return false; + sp_log_ini_check_violation("%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value does not match regex")); + return simulation; } } @@ -83,7 +101,7 @@ void sp_hook_ini() { sp_log_warn("ini_protection", "Cannot hook INI var `%s`. Maybe a typo or the PHP extension providing this var is not loaded yet.", ZSTR_VAL(sp_entry->key)); continue; } - if (SP_INI_ACCESS_READONLY_COND(sp_entry, cfg)) { + if (SP_INI_ACCESS_READONLY_COND(sp_entry, cfg) && (cfg->policy_silent_ro || cfg->policy_silent_fail) && !sp_entry->drop && !(sp_entry->simulation || cfg->simulation)) { ini_entry->modifiable = ini_entry->orig_modifiable = 0; } PHP_INI_MH((*orig_onmodify)) = ini_entry->on_modify; -- cgit v1.3