From e399c93db185bfd95ff003dd89e2af49462bf8b6 Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Fri, 6 Aug 2021 22:43:37 +0200 Subject: default ruleset for ini protection feature --- config/ini_protection.php8.rules | 257 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 config/ini_protection.php8.rules (limited to 'config') diff --git a/config/ini_protection.php8.rules b/config/ini_protection.php8.rules new file mode 100644 index 0000000..081048f --- /dev/null +++ b/config/ini_protection.php8.rules @@ -0,0 +1,257 @@ +## INI protection - prevent unwanted runtime ini changes made by ini_set() or other functions or by .htaccess +sp.ini_protection.enable(); + +## access policy can be one of +## .policy_readonly(): All entries are read-only by default. +## Individual entries can be set read-write using .readwrite() or .rw() +## .policy_readwrite(): This is the default and can be omitted. +## Individual entries can be set read-only using .readonly() or .ro() +#sp.ini_protection.policy_readonly(); + +## sp.ini entries can have the following attributes +## .key("..."): mandatory ini name. +## .set("..."): set the value. This overrides php.ini. +## .min("...") / .max("..."): value must be an integer between .min and .max. +## shorthand notation (e.g. 1k = 1024) is allowed +## .regexp("..."): value must match the regular expression +## .msg("..."): message is shown in logs on rule violation instead of default message +## .readonly() / .ro() / .readwrite() / .rw(): set entry to read-only or read-write respectively +## If no access keyword is provided, the entry inherits the default policy set by sp.ini_protection.policy_*-rules. + +## FOR PRODUCTION SYSTEMS: disable error messages and version numbers +sp.ini.key("display_errors").set("0").ro(); +sp.ini.key("display_startup_errors").set("0").ro(); +sp.ini.key("expose_php").set("0").ro(); +## FOR DEVELOPMENT/TESTING: allow enabling error messages and version numbers +#sp.ini.key("display_errors").rw(); +#sp.ini.key("display_startup_errors").rw(); +#sp.ini.key("expose_php").rw(); + +## error logging options should not be set during runtime -> read-only. +sp.ini.key("error_log").ro(); +sp.ini.key("error_reporting").ro(); +sp.ini.key("log_errors").ro(); +sp.ini.key("log_errors_max_len").set("2048").ro(); +sp.ini.key("ignore_repeated_errors").ro(); +sp.ini.key("ignore_repeated_source").ro(); +sp.ini.key("syslog.filter").ro(); + +## disable assert. assert() in PHP can evaluate arbitrary code just like eval() +sp.ini.key("assert.active").set("0").ro(); +sp.ini.key("assert.bail").ro(); +sp.ini.key("assert.callback").ro(); +sp.ini.key("assert.exception").ro(); +sp.ini.key("assert.warning").ro(); + +## enforce color codes. prevents potential XSS +sp.ini.key("highlight.comment").regexp("^#[0-9a-fA-F]{6}$"); +sp.ini.key("highlight.default").regexp("^#[0-9a-fA-F]{6}$"); +sp.ini.key("highlight.html").regexp("^#[0-9a-fA-F]{6}$"); +sp.ini.key("highlight.keyword").regexp("^#[0-9a-fA-F]{6}$"); +sp.ini.key("highlight.string").regexp("^#[0-9a-fA-F]{6}$"); + +## prevent remote access via fopen/include +sp.ini.key("allow_url_fopen").set("0").ro(); +sp.ini.key("allow_url_include").set("0").ro(); + +## prevent code execution from auto-included files +sp.ini.key("auto_append_file").ro(); +sp.ini.key("auto_prepend_file").ro(); + +## make rarely used features read-only. you can always set the value in php.ini +sp.ini.key("arg_separator.input").ro(); +sp.ini.key("arg_separator.output").ro(); +sp.ini.key("auto_detect_line_endings").ro(); +sp.ini.key("auto_globals_jit").ro(); +sp.ini.key("browscap").ro(); +sp.ini.key("default_charset").ro(); +sp.ini.key("register_argc_argv").ro(); +sp.ini.key("report_memleaks").ro(); +sp.ini.key("report_zend_debug").ro(); +sp.ini.key("request_order").ro(); +sp.ini.key("url_rewriter.hosts").ro(); +sp.ini.key("url_rewriter.tags").ro(); +sp.ini.key("variables_order").ro(); +sp.ini.key("from").ro(); +sp.ini.key("short_open_tag").ro(); +sp.ini.key("unserialize_callback_func").ro(); +sp.ini.key("zend.detect_unicode").ro(); +sp.ini.key("zend.enable_gc").ro(); +sp.ini.key("zend.exception_ignore_args").ro(); +sp.ini.key("zend.exception_string_param_max_len").ro(); +sp.ini.key("zend.multibyte").ro(); +sp.ini.key("zend.script_encoding").ro(); + +## date and timezone settings +#sp.ini.key("date.default_latitude").set("31.7667").ro(); +#sp.ini.key("date.default_longitude").set("35.2333").ro(); +#sp.ini.key("date.sunrise_zenith").set("90.833333").ro(); +#sp.ini.key("date.sunset_zenith").set("90.833333").ro(); +#sp.ini.key("date.timezone").set("").ro(); + +## setting a default mime type other than text/html can be a way to prevent XSS, so this will be set to read-write by default +sp.ini.key("default_mimetype").rw(); + +## allow reasonable socket timeouts +sp.ini.key("default_socket_timeout").min("1").max("300").rw(); + +## disable dynamic loading of PHP extensions in an apache/mod_php environment as it is a security risk. +sp.ini.key("enable_dl").set("0").ro(); + +## links to manual pages in error pages should not be set during runtime. +sp.ini.key("docref_ext").ro(); +sp.ini.key("docref_root").ro(); +sp.ini.key("html_errors").set("0").ro(); + +## preventing $_POST and $_FILES to be populated can be a security feature, so read-write is ok. +sp.ini.key("enable_post_data_reading").rw(); + +## disable error append/prepend which may lead to log forging. +sp.ini.key("error_append_string").ro(); +sp.ini.key("error_prepend_string").set("").ro(); + +## restrict limit settings to prevent Denial-of-Service +sp.ini.key("max_execution_time").min("30").max("600").rw(); +sp.ini.key("max_file_uploads").min("0").max("25").rw(); +sp.ini.key("max_input_nesting_level").min("16").max("64").rw(); +sp.ini.key("max_input_time").set("-1").ro(); +sp.ini.key("max_input_vars").min("0").max("1024").rw(); +sp.ini.key("memory_limit").min("4M").max("256M").rw(); +sp.ini.key("post_max_size").max("256M").rw(); +sp.ini.key("upload_max_filesize").max("256M").rw(); +sp.ini.key("precision").max("14").rv(); +sp.ini.key("unserialize_max_depth").min("128").max("4096").rw(); +sp.ini.key("serialize_precision").ro(); + +## some applications rely on these filters for security +## even though they should implement proper input validation for each input field separately. +sp.ini.key("filter.default").rw(); +sp.ini.key("filter.default_flags").rw(); + +## scripts will not be terminated after a client has aborted their connection. +## this feature may be needed for some time consuming server-side calculation +sp.ini.key("ignore_user_abort").rw(); + +## flushing may be needed +sp.ini.key("implicit_flush").rw(); + +## this feature may be required for some frameworks to work properly, +## but setting the include path to a fixed value is always more secure. +sp.ini.key("include_path").ro(); + +## input/output and encoding options +sp.ini.key("input_encoding").ro(); +sp.ini.key("internal_encoding").ro(); +sp.ini.key("output_encoding").ro(); +sp.ini.key("output_buffering").min("0").max("4096").rw(); +sp.ini.key("output_handler").ro(); + +## mail options +#sp.ini.key("mail.add_x_header").set("0").ro(); +#sp.ini.key("mail.force_extra_parameters").set("").ro(); +#sp.ini.key("mail.log").set("").ro(); +## windows smtp options +#sp.ini.key("SMTP").set("localhost").ro(); +#sp.ini.key("smtp_port").set("25").ro(); +#sp.ini.key("sendmail_from").ro(); + +## mysqli/mysqlnd options +#sp.ini.key("mysqli.allow_local_infile").ro(); +#sp.ini.key("mysqli.allow_persistent").ro(); +#sp.ini.key("mysqli.default_host").ro(); +#sp.ini.key("mysqli.default_port").ro(); +#sp.ini.key("mysqli.default_pw").ro(); +#sp.ini.key("mysqli.default_socket").ro(); +#sp.ini.key("mysqli.default_user").ro(); +#sp.ini.key("mysqli.max_links").set("-1").ro(); +#sp.ini.key("mysqli.max_persistent").set("-1").ro(); +#sp.ini.key("mysqli.reconnect").set("0").ro(); +#sp.ini.key("mysqli.rollback_on_cached_plink").set("0").ro(); +#sp.ini.key("mysqlnd.collect_memory_statistics").set("0").ro(); +#sp.ini.key("mysqlnd.collect_statistics").set("1").ro(); +#sp.ini.key("mysqlnd.debug").set("").ro(); +#sp.ini.key("mysqlnd.fetch_data_copy").set("0").ro(); +#sp.ini.key("mysqlnd.log_mask").set("0").ro(); +#sp.ini.key("mysqlnd.mempool_default_size").set("16000").ro(); +#sp.ini.key("mysqlnd.net_cmd_buffer_size").set("4096").ro(); +#sp.ini.key("mysqlnd.net_read_buffer_size").set("32768").ro(); +#sp.ini.key("mysqlnd.net_read_timeout").set("86400").ro(); +#sp.ini.key("mysqlnd.sha256_server_public_key").set("").ro(); +#sp.ini.key("mysqlnd.trace_alloc").set("").ro(); + +## open basedir is a security feature similar to chroot. +## why should it be allowed to disable this feature during runtime? +sp.ini.key("open_basedir").ro(); + +## pcre options +sp.ini.key("pcre.backtrack_limit").min("1000").max("1000000").rw(); +sp.ini.key("pcre.jit").rw(); +sp.ini.key("pcre.recursion_limit").min("1000").max("100000").ro(); + +## phar options +sp.ini.key("phar.cache_list").ro(); +sp.ini.key("phar.readonly").ro(); +sp.ini.key("phar.require_hash").ro(); + +## session options +#sp.ini.key("session.auto_start").set("0").ro(); +#sp.ini.key("session.cache_expire").set("180").ro(); +#sp.ini.key("session.cache_limiter").set("nocache").ro(); +#sp.ini.key("session.cookie_domain").set("").ro(); +#sp.ini.key("session.cookie_httponly").set("0").ro(); +#sp.ini.key("session.cookie_lifetime").set("0").ro(); +#sp.ini.key("session.cookie_path").set("/").ro(); +#sp.ini.key("session.cookie_samesite").set("").ro(); +#sp.ini.key("session.cookie_secure").set("0").ro(); +#sp.ini.key("session.gc_divisor").set("100").ro(); +#sp.ini.key("session.gc_maxlifetime").set("1440").ro(); +#sp.ini.key("session.gc_probability").set("1").ro(); +#sp.ini.key("session.lazy_write").set("1").ro(); +#sp.ini.key("session.name").set("PHPSESSID").ro(); +#sp.ini.key("session.referer_check").set("").ro(); +#sp.ini.key("session.save_handler").set("files").ro(); +#sp.ini.key("session.save_path").set("").ro(); +#sp.ini.key("session.serialize_handler").set("php").ro(); +#sp.ini.key("session.sid_bits_per_character").set("4").ro(); +sp.ini.key("session.sid_length").min("32").max("128").rw(); +#sp.ini.key("session.trans_sid_hosts").set("").ro(); +#sp.ini.key("session.trans_sid_tags").set("a=href,area=href,frame=src,form=").ro(); +#sp.ini.key("session.upload_progress.cleanup").set("1").ro(); +#sp.ini.key("session.upload_progress.enabled").set("1").ro(); +#sp.ini.key("session.upload_progress.freq").set("1%").ro(); +#sp.ini.key("session.upload_progress.min_freq").set("1").ro(); +#sp.ini.key("session.upload_progress.name").set("PHP_SESSION_UPLOAD_PROGRESS").ro(); +#sp.ini.key("session.upload_progress.prefix").set("upload_progress_").ro(); +#sp.ini.key("session.use_cookies").set("1").ro(); +#sp.ini.key("session.use_only_cookies").set("1").ro(); +#sp.ini.key("session.use_strict_mode").set("0").ro(); +#sp.ini.key("session.use_trans_sid").set("0").ro(); + +## allow setting the user agent +sp.ini.key("user_agent").rw(); + +## allow setting the xmlrpc fault code +sp.ini.key("xmlrpc_error_number").rw(); + +## these ini entries can only be set by php.ini anyway, +## but better set them to read-only anyway, just to be sure. +sp.ini.key("disable_classes").ro(); +sp.ini.key("disable_functions").ro(); +sp.ini.key("doc_root").ro(); +sp.ini.key("extension_dir").ro(); +sp.ini.key("file_uploads").ro(); +sp.ini.key("hard_timeout").ro(); +sp.ini.key("realpath_cache_size").ro(); +sp.ini.key("realpath_cache_ttl").ro(); +sp.ini.key("sendmail_path").ro(); +sp.ini.key("sqlite3.defensive").ro(); +sp.ini.key("sqlite3.extension_dir").ro(); +sp.ini.key("sys_temp_dir").set("").ro(); +sp.ini.key("syslog.facility").ro(); +sp.ini.key("syslog.ident").ro(); +sp.ini.key("upload_tmp_dir").ro(); +sp.ini.key("user_dir").ro(); +sp.ini.key("user_ini.cache_ttl").ro(); +sp.ini.key("user_ini.filename").ro(); +sp.ini.key("zend.assertions").ro(); +sp.ini.key("zend.signal_check").set("0").ro(); -- cgit v1.3 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 --- config/ini_protection.php8.rules | 20 +++++++++++++++- src/sp_config.h | 8 +++++-- src/sp_config_keywords.c | 21 ++++++++++++---- src/sp_ini.c | 52 +++++++++++++++++++++++++++------------- 4 files changed, 76 insertions(+), 25 deletions(-) (limited to 'config') diff --git a/config/ini_protection.php8.rules b/config/ini_protection.php8.rules index 081048f..b4ddb30 100644 --- a/config/ini_protection.php8.rules +++ b/config/ini_protection.php8.rules @@ -1,6 +1,20 @@ ## INI protection - prevent unwanted runtime ini changes made by ini_set() or other functions or by .htaccess sp.ini_protection.enable(); +## simulation mode: only log violations +#sp.ini_protection.simulation(); + +## drop policy: drop request on rule violation +#sp.ini_protection.policy_drop(); + +## do not log violations. +## this setting has no effect in simulation or drop mode +#sp.ini_protection.policy_silent_fail(); + +## do not log read-only violations +## this setting has no effect in simulation or drop mode +sp.ini_protection.policy_silent_ro(); + ## access policy can be one of ## .policy_readonly(): All entries are read-only by default. ## Individual entries can be set read-write using .readwrite() or .rw() @@ -10,13 +24,17 @@ sp.ini_protection.enable(); ## sp.ini entries can have the following attributes ## .key("..."): mandatory ini name. -## .set("..."): set the value. This overrides php.ini. +## .set("..."): set the initial value. This overrides php.ini. +## checks are not performed for this initial value. ## .min("...") / .max("..."): value must be an integer between .min and .max. ## shorthand notation (e.g. 1k = 1024) is allowed ## .regexp("..."): value must match the regular expression +## .allow_null(): allow setting a NULL-value ## .msg("..."): message is shown in logs on rule violation instead of default message ## .readonly() / .ro() / .readwrite() / .rw(): set entry to read-only or read-write respectively ## If no access keyword is provided, the entry inherits the default policy set by sp.ini_protection.policy_*-rules. +## .drop(): drop request on rule violation for this entry +## .simulation(): only log rule violation for this entry ## FOR PRODUCTION SYSTEMS: disable error messages and version numbers sp.ini.key("display_errors").set("0").ro(); 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 From 6bf949fdd0870316f13340c08462288fffce9508 Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Wed, 18 Aug 2021 13:32:42 +0200 Subject: updated documentation URL --- config/default.rules | 2 +- config/default_php8.rules | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'config') diff --git a/config/default.rules b/config/default.rules index ea65e01..b964073 100644 --- a/config/default.rules +++ b/config/default.rules @@ -13,7 +13,7 @@ sp.disable_xxe.enable(); # sp.global.secret_key("YOU _DO_ NEED TO CHANGE THIS WITH SOME RANDOM CHARACTERS."); # Globally activate strict mode -# https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.strict +# https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict # sp.global_strict.enable(); # Prevent unserialize-related exploits diff --git a/config/default_php8.rules b/config/default_php8.rules index c024176..de2da5c 100644 --- a/config/default_php8.rules +++ b/config/default_php8.rules @@ -14,7 +14,7 @@ sp.disable_xxe.enable(); # sp.global.secret_key("YOU _DO_ NEED TO CHANGE THIS WITH SOME RANDOM CHARACTERS."); # Globally activate strict mode -# https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.strict +# https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict # sp.global_strict.enable(); # Prevent unserialize-related exploits -- cgit v1.3 From 06e1790f1054dd9e02af0e469abfb18d6ca0ff8d Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Wed, 18 Aug 2021 13:34:29 +0200 Subject: ported Suhosin rules to Snuffleupagus rules --- config/suhosin.rules | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 config/suhosin.rules (limited to 'config') diff --git a/config/suhosin.rules b/config/suhosin.rules new file mode 100644 index 0000000..4beb4c8 --- /dev/null +++ b/config/suhosin.rules @@ -0,0 +1,281 @@ +## This file is part of the Suhosin-NG (SNG) PHP Hardening project +## - see https://suhosin.org/ for more information. +## +## Snuffleupagus compatibility: Version 0.7.0 with SNG patches and above +## https://github.com/sektioneins/snuffleupagus +## +############################# ### # --------- +## +## This file documents the effort to port as many Suhosin features as possible to Snuffleupagus rules. +## It is meant as a proof of concept and it will serve multiple purposes: +## * Create a reasonably secure configuration for Snuffleupagus. +## * Make it apparent which Suhosin features cannot be easily implemented in SP by just using configuration +## rules. These will be collected and some features will be implemented as part of the Suhosin-NG project. +## * Create a base set of Snuffleupagus rules to make the expert task of configuring a PHP hardening +## extension accessible to a broader audience. This base set of rules will be integrated into a Tool - +## see Milestone 6 (MS6) here: https://github.com/sektioneins/suhosin-ng/projects/1 +## * Provide more configuration examples. +## +############################# ### # --------- + +## Let's enable relevant features first. +sp.ini_protection.enable(); + +## ===================== +## Logging Configuration +## ===================== + +## suhosin.log.syslog +## suhosin.log.syslog.facility +## suhosin.log.syslog.priority +## suhosin.log.sapi +## suhosin.log.stdout +## suhosin.log.file +## suhosin.log.file.name +## suhosin.log.file.time +## suhosin.log.script +## suhosin.log.script.name +## suhosin.log.phpscript +## suhosin.log.phpscript.name +## suhosin.log.phpscript.is_safe +## suhosin.log.use-x-forwarded-for + +## SP will always use either REMOTE_ADDR or HTTP_X_FORWARDED_FOR. + +## Logging output can be one of +sp.log_media("php"); +#sp.log_media("syslog"); + +## More logging options are not implemented in SP. + +## ================ +## Executor Options +## ================ + +## suhosin.executor.max_depth +## Not implemented in SP. + +## suhosin.executor.include.max_traversal +## SP example for max_traversal = 3 +sp.disable_function.function_r("^(require|include)(_once)?$").value_r("\\.\\./+\\.\\./+\\.\\./+").drop(); + +## suhosin.executor.include.whitelist +## suhosin.executor.include.blacklist +## SP version with wrapper whitelist and regex matching include/require: +sp.wrappers_whitelist.list("file,php,phar"); +sp.disable_function.function_r("^(require|include)(_once)?$").value_r("^php://(stdin|stdout|stderr|input|output|memory|temp)").drop(); + +## suhosin.executor.include.allow_writable_files +## SP can enable readonly protection +#sp.readonly_exec.enable(); + +## suhosin.executor.func.whitelist +## suhosin.executor.func.blacklist +#sp.disable_function.function("...").drop(); + +## suhosin.executor.eval.whitelist +## suhosin.executor.eval.blacklist +#sp.eval_blacklist.list("system,exec,shell_exec"); + +## suhosin.executor.disable_eval +#sp.disable_function.function("eval").drop(); + + +## suhosin.executor.disable_emodifier +## This is actually not needed anymore in PHP 7 and above + +## suhosin.executor.allow_symlink +## SP can simply disable the symlink function. +## Other ways to create symlinks should be disabled as well, e.g. system("ln -s ...") +#sp.disable_function.function("symlink").drop(); +#sp.disable_function.function("system").drop(); +#sp.disable_function.function("shell_exec").drop(); +#sp.disable_function.function("exec").drop(); +#sp.disable_function.function("proc_open").drop(); + + +## +## ============ +## Misc Options +## ============ +## + +## suhosin.simulation +## SP provides individual .simulation() switches for most features. + +## suhosin.perdir +## Not implemented in SP. + + +## suhosin.protectkey +## SP does not actually need to protect its secret key this way, as it is not a native PHP ini directive shown in phpinfo() + +## suhosin.coredump +## SP can be started in gdb/lldb, so this feature may not be needed. + +## suhosin.stealth +## Not implemented in SP. +## If ionCube support or similar extensions should ever be requested to run with SP, this feature may be implemented some day. + +## suhosin.apc_bug_workaround +## This is not a thing anymore with PHP7+ + +## suhosin.disable.display_errors +#sp.ini.key("display_errors").set("0").ro(); +#sp.ini.key("display_startup_errors").set("0").ro(); +#sp.ini.key("expose_php").set("0").ro(); + +## suhosin.multiheader +## There is a PHP filter in place to prevent multiple headers in one header() call - let's hope it works. + +## suhosin.mail.protect +sp.disable_function.function("mail").param("to").value_r("\\n").alias("newline in mail() To:").drop(); +sp.disable_function.function("mail").param("subject").value_r("\\n").alias("newline in mail() Subject:").drop(); +sp.disable_function.function("mail").param("additional_headers").param_type("STRING").drop(); +sp.disable_function.function("mail").param("additional_headers").param_type("NULL").allow(); +sp.disable_function.function("mail").param("additional_headers").key_r("^(to|b?cc)$").drop(); + + +## suhosin.memory_limit +## SP can either disable or limit the memory_limit setting +#sp.ini.key("memory_limit").ro(); +sp.ini.key("memory_limit").min("4M").max("256M").rw(); + + +## ======================== +## SQL Injection Protection +## ======================== + +## suhosin.sql.bailout_on_error +## SP example for mysqli and PDO +#sp.disable_function.function("mysqli_query").ret("FALSE").drop(); +#sp.disable_function.function("mysqli::query").ret("FALSE").drop(); +#sp.disable_function.function("mysqli_real_query").ret("FALSE").drop(); +#sp.disable_function.function("mysqli::real_query").ret("FALSE").drop(); +#sp.disable_function.function("mysqli_prepare").ret("FALSE").drop(); +#sp.disable_function.function("mysqli::prepare").ret("FALSE").drop(); +#sp.disable_function.function("mysqli_stmt_execute").ret("FALSE").drop(); +#sp.disable_function.function("mysqli_stmt::execute").ret("FALSE").drop(); +#sp.disable_function.function("mysqli_execute").ret("FALSE").drop(); +#sp.disable_function.function("PDO::query").ret("FALSE").drop(); +#sp.disable_function.function("PDO::prepare").ret("FALSE").drop(); +#sp.disable_function.function("PDO::exec").ret("FALSE").drop(); +#sp.disable_function.function("PDOStatement::execute").ret("FALSE").drop(); + + +## suhosin.sql.user_match +## suhosin.sql.user_prefix +## suhosin.sql.user_postfix +## SP example for mysqli +set SQL_USER "^public_"; +sp.disable_function.function("mysqli::__construct").param("username").value_r(SQL_USER).allow(); +sp.disable_function.function("mysqli::__construct").drop(); +sp.disable_function.function("mysqli_connect").param("username").value_r(SQL_USER).allow(); +sp.disable_function.function("mysqli_connect").drop(); +sp.disable_function.function("mysqli::change_user").param("username").value_r(SQL_USER).allow(); +sp.disable_function.function("mysqli::change_user").drop(); +sp.disable_function.function("mysqli_change_user").param("username").value_r(SQL_USER).allow(); +sp.disable_function.function("mysqli_change_user").drop(); + +## suhosin.sql.comment +## suhosin.sql.opencomment +## Not implemented in SP. +## It is possible to try and find common injection patterns such as ' or 1=1 -- via regex, +## but that would likely trigger valid SQL strings containing these patterns as well. The same argument +## applies to multiselect and union: + +## suhosin.sql.multiselect +## suhosin.sql.union +## Not implemented in SP. + + +## ============================== +## Transparent Encryption Options +## ============================== + +## suhosin.session.cryptkey +## suhosin.session.cryptua +## suhosin.cookie.cryptkey +## suhosin.cookie.cryptua + +## SP's session encryption and cookie encryption features rely on a secret key that is derived from +## * the user agent string from the environment variable HTTP_USER_AGENT +## * the value of the environment variable specified using sp.global.cookie_env_var(), +## usually either REMOTE_ADDR or SSL_SESSION_ID +## * a very secret key as specified using sp.global.secret_key() +sp.global.secret_key("c6a0e02b3b818f7559d5f85303d8fe44"); ## this key should be unique to your installation. +sp.global.cookie_env_var("REMOTE_ADDR"); +#sp.global.cookie_env_var("SSL_SESSION_ID"); + +## suhosin.session.encrypt +sp.session.encrypt(); + +## suhosin.cookie.encrypt +## suhosin.cookie.cryptlist +## suhosin.cookie.plainlist +#sp.cookie.name("my_cookie_name").encrypt(); +#sp.cookie.name_r("^enc_[a-z]+$").encrypt(); + +## suhosin.session.cryptdocroot +## suhosin.session.cryptraddr +## suhosin.session.checkraddr +## suhosin.cookie.cryptdocroot +## suhosin.cookie.cryptraddr +## suhosin.cookie.checkraddr +## For SP to include the document root or part of the IP address in the encryption key, the .cookie_env_var() +## can be constructed by the web server to include these values. + + +## ================= +## Filtering Options +## ================= + +## suhosin.filter.action +## suhosin.cookie|get|post|request.max_array_depth +## suhosin.cookie|get|post|request.max_array_index_length +## suhosin.cookie|get|post.max_name_length +## suhosin.request.max_varname_length +## suhosin.cookie|get|post|request.max_totalname_length +## suhosin.cookie|get|post|request.max_value_length +## suhosin.cookie|get|post|request.max_vars +## suhosin.cookie|get|post|request.disallow_nul +## suhosin.cookie|get|post|request.disallow_ws +## suhosin.request.array_index_blacklist +## suhosin.request.array_index_whitelist +## suhosin.upload.max_uploads +## suhosin.upload.max_newlines +## suhosin.upload.disallow_elf +## suhosin.upload.disallow_binary +## suhosin.upload.remove_binary +## suhosin.upload.allow_utf8 +## Not implemented in SP. + +## suhosin.upload.verification_script +#sp.upload_validation.script("/var/www/is_valid_php.py").enable(); + + +## suhosin.session.max_id_length +## suhosin.server.encode +## suhosin.server.strip +## Not implemented in SP. + +## suhosin.rand.seedingkey +## suhosin.rand.reseed_every_request +## suhosin.srand.ignore +## suhosin.mt_srand.ignore +## Instead of removing srand()/mt_srand(), SP basically replaces rand()/mt_rand() with the more secure random_int() +sp.harden_random.enable(); + +############################# ### # --------- +## +## features included in SP that are not covered by Suhosin rule equivalents +## - see https://snuffleupagus.readthedocs.io/ for more information + +# sp.unserialize_hmac.enable(); +# sp.global_strict.enable(); +sp.auto_cookie_secure.enable(); +#sp.cookie.name("cookie1").samesite("lax"); +#sp.cookie.name("cookie2").samesite("strict");; +sp.disable_xxe.enable(); +#sp.sloppy_comparison.enable(); + -- cgit v1.3 From 8f689157b3857f82e0ad0a8b87609e83561d81fb Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Mon, 30 Aug 2021 22:09:30 +0200 Subject: fixed typo --- config/ini_protection.php8.rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'config') diff --git a/config/ini_protection.php8.rules b/config/ini_protection.php8.rules index b4ddb30..175f527 100644 --- a/config/ini_protection.php8.rules +++ b/config/ini_protection.php8.rules @@ -137,7 +137,7 @@ sp.ini.key("max_input_vars").min("0").max("1024").rw(); sp.ini.key("memory_limit").min("4M").max("256M").rw(); sp.ini.key("post_max_size").max("256M").rw(); sp.ini.key("upload_max_filesize").max("256M").rw(); -sp.ini.key("precision").max("14").rv(); +sp.ini.key("precision").max("14").rw(); sp.ini.key("unserialize_max_depth").min("128").max("4096").rw(); sp.ini.key("serialize_precision").ro(); -- cgit v1.3 From 713cb08b58d4e5dd5e7e80b1f82e27cbe52d4381 Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Thu, 11 Nov 2021 13:15:52 +0100 Subject: inverted logic. set xxe_protection.enable() instead of disable_xxe.disable() --- config/default.rules | 2 +- config/default_php8.rules | 2 +- config/suhosin.rules | 2 +- doc/source/config.rst | 7 ++++--- src/php_snuffleupagus.h | 2 +- src/snuffleupagus.c | 2 +- src/sp_config.c | 2 +- src/sp_config.h | 4 ++-- src/tests/xxe/config/disable_xxe.ini | 2 +- src/tests/xxe/config/disable_xxe_disable.ini | 2 +- src/tests/xxe/disable_xxe_dom_disabled.phpt | 4 ++-- src/tests/xxe/disable_xxe_simplexml.phpt | 3 ++- src/tests/xxe/disable_xxe_simplexml_oop.phpt | 3 ++- src/tests/xxe/disable_xxe_xml_parse.phpt | 5 ++++- 14 files changed, 24 insertions(+), 18 deletions(-) (limited to 'config') diff --git a/config/default.rules b/config/default.rules index b964073..2de703b 100644 --- a/config/default.rules +++ b/config/default.rules @@ -7,7 +7,7 @@ sp.harden_random.enable(); # Disabled XXE -sp.disable_xxe.enable(); +sp.xxe_protection.enable(); # Global configuration variables # sp.global.secret_key("YOU _DO_ NEED TO CHANGE THIS WITH SOME RANDOM CHARACTERS."); diff --git a/config/default_php8.rules b/config/default_php8.rules index de2da5c..1d16191 100644 --- a/config/default_php8.rules +++ b/config/default_php8.rules @@ -8,7 +8,7 @@ sp.harden_random.enable(); # Disabled XXE -sp.disable_xxe.enable(); +sp.xxe_protection.enable(); # Global configuration variables # sp.global.secret_key("YOU _DO_ NEED TO CHANGE THIS WITH SOME RANDOM CHARACTERS."); diff --git a/config/suhosin.rules b/config/suhosin.rules index 4beb4c8..0bdc453 100644 --- a/config/suhosin.rules +++ b/config/suhosin.rules @@ -276,6 +276,6 @@ sp.harden_random.enable(); sp.auto_cookie_secure.enable(); #sp.cookie.name("cookie1").samesite("lax"); #sp.cookie.name("cookie2").samesite("strict");; -sp.disable_xxe.enable(); +sp.xxe_protection.enable(); #sp.sloppy_comparison.enable(); diff --git a/doc/source/config.rst b/doc/source/config.rst index 10b0afd..63ddf7b 100644 --- a/doc/source/config.rst +++ b/doc/source/config.rst @@ -293,14 +293,15 @@ It can either be ``enabled`` or ``disabled`` and can be used in ``simulation`` m sp.upload_validation.script("/var/www/is_valid_php.py").enable(); -disable_xxe +xxe_protection ^^^^^^^^^^^ -:ref:`disable_xxe `, enabled by default, will prevent XXE attacks by disabling the loading of external entities (``libxml_disable_entity_loader``) in the XML parser. +:ref:`xxe_protection `, disabled by default, will prevent XXE attacks by disabling the loading of external entities (``libxml_disable_entity_loader``) in the XML parser. :: - sp.disable_xxe.enable(); + sp.xxe_protection.enable(); + sp.xxe_protection.disable(); Whitelist of stream-wrappers diff --git a/src/php_snuffleupagus.h b/src/php_snuffleupagus.h index 308031b..03c9bb6 100644 --- a/src/php_snuffleupagus.h +++ b/src/php_snuffleupagus.h @@ -116,7 +116,7 @@ sp_config_upload_validation config_upload_validation; sp_config_cookie config_cookie; 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_xxe_protection config_xxe_protection; sp_config_eval config_eval; sp_config_wrapper config_wrapper; sp_config_session config_session; diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c index 6fd6f25..c96a911 100644 --- a/src/snuffleupagus.c +++ b/src/snuffleupagus.c @@ -314,7 +314,7 @@ static PHP_INI_MH(OnUpdateConfiguration) { hook_upload(); } - if (SPCFG(disable_xxe).enable == 0) { + if (SPCFG(xxe_protection).enable) { hook_libxml_disable_entity_loader(); } diff --git a/src/sp_config.c b/src/sp_config.c index ec6c5a8..bc9aa0d 100644 --- a/src/sp_config.c +++ b/src/sp_config.c @@ -17,7 +17,7 @@ static zend_result sp_process_config_root(sp_parsed_keyword *parsed_rule) { {parse_cookie, SP_TOKEN_COOKIE_ENCRYPTION, NULL}, {parse_global, SP_TOKEN_GLOBAL, NULL}, {parse_enable, SP_TOKEN_AUTO_COOKIE_SECURE, &(SPCFG(auto_cookie_secure).enable)}, - {parse_enable, SP_TOKEN_DISABLE_XXE, &(SPCFG(disable_xxe).enable)}, + {parse_enable, SP_TOKEN_XXE_PROTECTION, &(SPCFG(xxe_protection).enable)}, {parse_eval_filter_conf, SP_TOKEN_EVAL_BLACKLIST, &(SPCFG(eval).blacklist)}, {parse_eval_filter_conf, SP_TOKEN_EVAL_WHITELIST, &(SPCFG(eval).whitelist)}, {parse_session, SP_TOKEN_SESSION_ENCRYPTION, &(SPCFG(session))}, diff --git a/src/sp_config.h b/src/sp_config.h index 262050b..a557105 100644 --- a/src/sp_config.h +++ b/src/sp_config.h @@ -57,7 +57,7 @@ typedef struct { typedef struct { bool enable; -} sp_config_disable_xxe; +} sp_config_xxe_protection; typedef struct { enum samesite_type { strict = 1, lax = 2 } samesite; @@ -202,7 +202,7 @@ typedef struct { #define SP_TOKEN_READONLY_EXEC "readonly_exec" #define SP_TOKEN_UNSERIALIZE_HMAC "unserialize_hmac" #define SP_TOKEN_UPLOAD_VALIDATION "upload_validation" -#define SP_TOKEN_DISABLE_XXE "disable_xxe" +#define SP_TOKEN_XXE_PROTECTION "xxe_protection" #define SP_TOKEN_EVAL_BLACKLIST "eval_blacklist" #define SP_TOKEN_EVAL_WHITELIST "eval_whitelist" #define SP_TOKEN_SLOPPY_COMPARISON "sloppy_comparison" diff --git a/src/tests/xxe/config/disable_xxe.ini b/src/tests/xxe/config/disable_xxe.ini index bc9d1f2..a50a3b9 100644 --- a/src/tests/xxe/config/disable_xxe.ini +++ b/src/tests/xxe/config/disable_xxe.ini @@ -1 +1 @@ -sp.disable_xxe.enable(); +sp.xxe_protection.enable(); diff --git a/src/tests/xxe/config/disable_xxe_disable.ini b/src/tests/xxe/config/disable_xxe_disable.ini index bb1e432..eaf5755 100644 --- a/src/tests/xxe/config/disable_xxe_disable.ini +++ b/src/tests/xxe/config/disable_xxe_disable.ini @@ -1 +1 @@ -sp.disable_xxe.disable(); +sp.xxe_protection.disable(); diff --git a/src/tests/xxe/disable_xxe_dom_disabled.phpt b/src/tests/xxe/disable_xxe_dom_disabled.phpt index a49e094..107171c 100644 --- a/src/tests/xxe/disable_xxe_dom_disabled.phpt +++ b/src/tests/xxe/disable_xxe_dom_disabled.phpt @@ -1,10 +1,10 @@ --TEST-- -Disable XXE +Disable XXE (feature enabled) --SKIPIF-- = 80000) print "skip"; ?> --INI-- -sp.configuration_file={PWD}/config/disable_xxe_disable.ini +sp.configuration_file={PWD}/config/disable_xxe.ini --EXTENSIONS-- dom --FILE-- diff --git a/src/tests/xxe/disable_xxe_simplexml.phpt b/src/tests/xxe/disable_xxe_simplexml.phpt index 1d3ef4c..9560156 100644 --- a/src/tests/xxe/disable_xxe_simplexml.phpt +++ b/src/tests/xxe/disable_xxe_simplexml.phpt @@ -2,8 +2,9 @@ Disable XXE --SKIPIF-- += 80000) print "skip"; ?> --INI-- -sp.configuration_file={PWD}/config/disable_xxe.ini +sp.configuration_file={PWD}/config/disable_xxe_disable.ini --EXTENSIONS-- simplexml --XFAIL-- diff --git a/src/tests/xxe/disable_xxe_simplexml_oop.phpt b/src/tests/xxe/disable_xxe_simplexml_oop.phpt index e101337..1b2c4ca 100644 --- a/src/tests/xxe/disable_xxe_simplexml_oop.phpt +++ b/src/tests/xxe/disable_xxe_simplexml_oop.phpt @@ -2,8 +2,9 @@ Disable XXE --SKIPIF-- += 80000) print "skip"; ?> --INI-- -sp.configuration_file={PWD}/config/disable_xxe.ini +sp.configuration_file={PWD}/config/disable_xxe_disable.ini --EXTENSIONS-- simplexml --XFAIL-- diff --git a/src/tests/xxe/disable_xxe_xml_parse.phpt b/src/tests/xxe/disable_xxe_xml_parse.phpt index 6b48bea..bc7e338 100644 --- a/src/tests/xxe/disable_xxe_xml_parse.phpt +++ b/src/tests/xxe/disable_xxe_xml_parse.phpt @@ -70,7 +70,8 @@ $parser = create_parser(); $doc = xml_parse($parser, $xml, true); xml_parser_free($parser); ---EXPECT-- +--EXPECTF-- +Warning: [snuffleupagus][0.0.0.0][xxe][log] A call to libxml_disable_entity_loader was tried and nopped in %a.php on line 41 string(4) "TEST" array(0) { @@ -81,6 +82,8 @@ array(0) { } string(7) "TESTING" string(4) "TEST" + +Warning: [snuffleupagus][0.0.0.0][xxe][log] A call to libxml_disable_entity_loader was tried and nopped in %a.php on line 46 string(4) "TEST" array(0) { -- cgit v1.3 From b4996788445272c9f18e2cba84783b1b13bf6cf0 Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Fri, 7 Jan 2022 18:59:52 +0100 Subject: added dangerous extension check --- config/detect_dangerous_extensions.rules | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 config/detect_dangerous_extensions.rules (limited to 'config') diff --git a/config/detect_dangerous_extensions.rules b/config/detect_dangerous_extensions.rules new file mode 100644 index 0000000..8f10532 --- /dev/null +++ b/config/detect_dangerous_extensions.rules @@ -0,0 +1,12 @@ +## This example rules file shows how to detect and disable certain potentially +## dangerous or unwanted extensions. + +@condition extension_loaded("runkit7"); +@error "The runkit7 extension can be used to rename classes and functions, thereby circumventing any filters set by Snuffleupagus. Please disable runkit7."; + +@condition extension_loaded("FFI"); +@warning "FFI extension is loaded. Disabling via 'ffi.enable=false'"; +sp.ini_protection.enable(); +sp.ini.key("ffi.enable").set("false").ro(); +@end_condition; + -- cgit v1.3 From f4afb2a0396251f45a31f470cb6ad916671a9686 Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Mon, 10 Jan 2022 16:22:44 +0100 Subject: added conditions to ini protection example --- config/ini_protection.php8.rules | 62 +++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 23 deletions(-) (limited to 'config') diff --git a/config/ini_protection.php8.rules b/config/ini_protection.php8.rules index 175f527..bde5815 100644 --- a/config/ini_protection.php8.rules +++ b/config/ini_protection.php8.rules @@ -49,7 +49,9 @@ sp.ini.key("expose_php").set("0").ro(); sp.ini.key("error_log").ro(); sp.ini.key("error_reporting").ro(); sp.ini.key("log_errors").ro(); +@condition PHP_VERSION_ID < 80000; sp.ini.key("log_errors_max_len").set("2048").ro(); +@end_condition; sp.ini.key("ignore_repeated_errors").ro(); sp.ini.key("ignore_repeated_source").ro(); sp.ini.key("syslog.filter").ro(); @@ -143,8 +145,10 @@ sp.ini.key("serialize_precision").ro(); ## some applications rely on these filters for security ## even though they should implement proper input validation for each input field separately. +@condition extension_loaded("filter"); sp.ini.key("filter.default").rw(); sp.ini.key("filter.default_flags").rw(); +@end_condition; ## scripts will not be terminated after a client has aborted their connection. ## this feature may be needed for some time consuming server-side calculation @@ -174,44 +178,53 @@ sp.ini.key("output_handler").ro(); #sp.ini.key("sendmail_from").ro(); ## mysqli/mysqlnd options -#sp.ini.key("mysqli.allow_local_infile").ro(); -#sp.ini.key("mysqli.allow_persistent").ro(); -#sp.ini.key("mysqli.default_host").ro(); -#sp.ini.key("mysqli.default_port").ro(); -#sp.ini.key("mysqli.default_pw").ro(); -#sp.ini.key("mysqli.default_socket").ro(); -#sp.ini.key("mysqli.default_user").ro(); -#sp.ini.key("mysqli.max_links").set("-1").ro(); -#sp.ini.key("mysqli.max_persistent").set("-1").ro(); -#sp.ini.key("mysqli.reconnect").set("0").ro(); -#sp.ini.key("mysqli.rollback_on_cached_plink").set("0").ro(); -#sp.ini.key("mysqlnd.collect_memory_statistics").set("0").ro(); -#sp.ini.key("mysqlnd.collect_statistics").set("1").ro(); -#sp.ini.key("mysqlnd.debug").set("").ro(); -#sp.ini.key("mysqlnd.fetch_data_copy").set("0").ro(); -#sp.ini.key("mysqlnd.log_mask").set("0").ro(); -#sp.ini.key("mysqlnd.mempool_default_size").set("16000").ro(); -#sp.ini.key("mysqlnd.net_cmd_buffer_size").set("4096").ro(); -#sp.ini.key("mysqlnd.net_read_buffer_size").set("32768").ro(); -#sp.ini.key("mysqlnd.net_read_timeout").set("86400").ro(); -#sp.ini.key("mysqlnd.sha256_server_public_key").set("").ro(); -#sp.ini.key("mysqlnd.trace_alloc").set("").ro(); +@condition extension_loaded("mysqli"); +sp.ini.key("mysqli.allow_local_infile").ro(); +sp.ini.key("mysqli.allow_persistent").ro(); +sp.ini.key("mysqli.default_host").ro(); +sp.ini.key("mysqli.default_port").ro(); +sp.ini.key("mysqli.default_pw").ro(); +sp.ini.key("mysqli.default_socket").ro(); +sp.ini.key("mysqli.default_user").ro(); +sp.ini.key("mysqli.max_links").set("-1").ro(); +sp.ini.key("mysqli.max_persistent").set("-1").ro(); +sp.ini.key("mysqli.reconnect").set("0").ro(); +sp.ini.key("mysqli.rollback_on_cached_plink").set("0").ro(); +@condition extension_loaded("mysqlnd"); +sp.ini.key("mysqlnd.collect_memory_statistics").set("0").ro(); +sp.ini.key("mysqlnd.collect_statistics").set("1").ro(); +sp.ini.key("mysqlnd.debug").set("").ro(); +sp.ini.key("mysqlnd.log_mask").set("0").ro(); +sp.ini.key("mysqlnd.mempool_default_size").set("16000").ro(); +sp.ini.key("mysqlnd.net_cmd_buffer_size").set("4096").ro(); +sp.ini.key("mysqlnd.net_read_buffer_size").set("32768").ro(); +sp.ini.key("mysqlnd.net_read_timeout").set("86400").ro(); +sp.ini.key("mysqlnd.sha256_server_public_key").set("").ro(); +sp.ini.key("mysqlnd.trace_alloc").set("").ro(); +@condition extension_loaded("mysqlnd") && PHP_VERSION_ID < 80100; +sp.ini.key("mysqlnd.fetch_data_copy").set("0").ro(); +@end_condition; ## open basedir is a security feature similar to chroot. ## why should it be allowed to disable this feature during runtime? sp.ini.key("open_basedir").ro(); ## pcre options +@condition extension_loaded("pcre"); sp.ini.key("pcre.backtrack_limit").min("1000").max("1000000").rw(); sp.ini.key("pcre.jit").rw(); sp.ini.key("pcre.recursion_limit").min("1000").max("100000").ro(); +@end_condition; ## phar options +@condition extension_loaded("phar"); sp.ini.key("phar.cache_list").ro(); sp.ini.key("phar.readonly").ro(); sp.ini.key("phar.require_hash").ro(); +@end_condition; ## session options +@condition extension_loaded("session"); #sp.ini.key("session.auto_start").set("0").ro(); #sp.ini.key("session.cache_expire").set("180").ro(); #sp.ini.key("session.cache_limiter").set("nocache").ro(); @@ -244,6 +257,7 @@ sp.ini.key("session.sid_length").min("32").max("128").rw(); #sp.ini.key("session.use_only_cookies").set("1").ro(); #sp.ini.key("session.use_strict_mode").set("0").ro(); #sp.ini.key("session.use_trans_sid").set("0").ro(); +@end_condition; ## allow setting the user agent sp.ini.key("user_agent").rw(); @@ -262,9 +276,11 @@ sp.ini.key("hard_timeout").ro(); sp.ini.key("realpath_cache_size").ro(); sp.ini.key("realpath_cache_ttl").ro(); sp.ini.key("sendmail_path").ro(); +@condition extension_loaded("sqlite3"); sp.ini.key("sqlite3.defensive").ro(); sp.ini.key("sqlite3.extension_dir").ro(); -sp.ini.key("sys_temp_dir").set("").ro(); +@end_condition; +sp.ini.key("sys_temp_dir").ro(); sp.ini.key("syslog.facility").ro(); sp.ini.key("syslog.ident").ro(); sp.ini.key("upload_tmp_dir").ro(); -- cgit v1.3 From c9430a45205f3a94fe46c7fd4f5fd3a6ab5202f4 Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Mon, 10 Jan 2022 16:23:28 +0100 Subject: renamed ini protection example rules --- config/ini_protection.php8.rules | 291 --------------------------------------- config/ini_protection.rules | 291 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+), 291 deletions(-) delete mode 100644 config/ini_protection.php8.rules create mode 100644 config/ini_protection.rules (limited to 'config') diff --git a/config/ini_protection.php8.rules b/config/ini_protection.php8.rules deleted file mode 100644 index bde5815..0000000 --- a/config/ini_protection.php8.rules +++ /dev/null @@ -1,291 +0,0 @@ -## INI protection - prevent unwanted runtime ini changes made by ini_set() or other functions or by .htaccess -sp.ini_protection.enable(); - -## simulation mode: only log violations -#sp.ini_protection.simulation(); - -## drop policy: drop request on rule violation -#sp.ini_protection.policy_drop(); - -## do not log violations. -## this setting has no effect in simulation or drop mode -#sp.ini_protection.policy_silent_fail(); - -## do not log read-only violations -## this setting has no effect in simulation or drop mode -sp.ini_protection.policy_silent_ro(); - -## access policy can be one of -## .policy_readonly(): All entries are read-only by default. -## Individual entries can be set read-write using .readwrite() or .rw() -## .policy_readwrite(): This is the default and can be omitted. -## Individual entries can be set read-only using .readonly() or .ro() -#sp.ini_protection.policy_readonly(); - -## sp.ini entries can have the following attributes -## .key("..."): mandatory ini name. -## .set("..."): set the initial value. This overrides php.ini. -## checks are not performed for this initial value. -## .min("...") / .max("..."): value must be an integer between .min and .max. -## shorthand notation (e.g. 1k = 1024) is allowed -## .regexp("..."): value must match the regular expression -## .allow_null(): allow setting a NULL-value -## .msg("..."): message is shown in logs on rule violation instead of default message -## .readonly() / .ro() / .readwrite() / .rw(): set entry to read-only or read-write respectively -## If no access keyword is provided, the entry inherits the default policy set by sp.ini_protection.policy_*-rules. -## .drop(): drop request on rule violation for this entry -## .simulation(): only log rule violation for this entry - -## FOR PRODUCTION SYSTEMS: disable error messages and version numbers -sp.ini.key("display_errors").set("0").ro(); -sp.ini.key("display_startup_errors").set("0").ro(); -sp.ini.key("expose_php").set("0").ro(); -## FOR DEVELOPMENT/TESTING: allow enabling error messages and version numbers -#sp.ini.key("display_errors").rw(); -#sp.ini.key("display_startup_errors").rw(); -#sp.ini.key("expose_php").rw(); - -## error logging options should not be set during runtime -> read-only. -sp.ini.key("error_log").ro(); -sp.ini.key("error_reporting").ro(); -sp.ini.key("log_errors").ro(); -@condition PHP_VERSION_ID < 80000; -sp.ini.key("log_errors_max_len").set("2048").ro(); -@end_condition; -sp.ini.key("ignore_repeated_errors").ro(); -sp.ini.key("ignore_repeated_source").ro(); -sp.ini.key("syslog.filter").ro(); - -## disable assert. assert() in PHP can evaluate arbitrary code just like eval() -sp.ini.key("assert.active").set("0").ro(); -sp.ini.key("assert.bail").ro(); -sp.ini.key("assert.callback").ro(); -sp.ini.key("assert.exception").ro(); -sp.ini.key("assert.warning").ro(); - -## enforce color codes. prevents potential XSS -sp.ini.key("highlight.comment").regexp("^#[0-9a-fA-F]{6}$"); -sp.ini.key("highlight.default").regexp("^#[0-9a-fA-F]{6}$"); -sp.ini.key("highlight.html").regexp("^#[0-9a-fA-F]{6}$"); -sp.ini.key("highlight.keyword").regexp("^#[0-9a-fA-F]{6}$"); -sp.ini.key("highlight.string").regexp("^#[0-9a-fA-F]{6}$"); - -## prevent remote access via fopen/include -sp.ini.key("allow_url_fopen").set("0").ro(); -sp.ini.key("allow_url_include").set("0").ro(); - -## prevent code execution from auto-included files -sp.ini.key("auto_append_file").ro(); -sp.ini.key("auto_prepend_file").ro(); - -## make rarely used features read-only. you can always set the value in php.ini -sp.ini.key("arg_separator.input").ro(); -sp.ini.key("arg_separator.output").ro(); -sp.ini.key("auto_detect_line_endings").ro(); -sp.ini.key("auto_globals_jit").ro(); -sp.ini.key("browscap").ro(); -sp.ini.key("default_charset").ro(); -sp.ini.key("register_argc_argv").ro(); -sp.ini.key("report_memleaks").ro(); -sp.ini.key("report_zend_debug").ro(); -sp.ini.key("request_order").ro(); -sp.ini.key("url_rewriter.hosts").ro(); -sp.ini.key("url_rewriter.tags").ro(); -sp.ini.key("variables_order").ro(); -sp.ini.key("from").ro(); -sp.ini.key("short_open_tag").ro(); -sp.ini.key("unserialize_callback_func").ro(); -sp.ini.key("zend.detect_unicode").ro(); -sp.ini.key("zend.enable_gc").ro(); -sp.ini.key("zend.exception_ignore_args").ro(); -sp.ini.key("zend.exception_string_param_max_len").ro(); -sp.ini.key("zend.multibyte").ro(); -sp.ini.key("zend.script_encoding").ro(); - -## date and timezone settings -#sp.ini.key("date.default_latitude").set("31.7667").ro(); -#sp.ini.key("date.default_longitude").set("35.2333").ro(); -#sp.ini.key("date.sunrise_zenith").set("90.833333").ro(); -#sp.ini.key("date.sunset_zenith").set("90.833333").ro(); -#sp.ini.key("date.timezone").set("").ro(); - -## setting a default mime type other than text/html can be a way to prevent XSS, so this will be set to read-write by default -sp.ini.key("default_mimetype").rw(); - -## allow reasonable socket timeouts -sp.ini.key("default_socket_timeout").min("1").max("300").rw(); - -## disable dynamic loading of PHP extensions in an apache/mod_php environment as it is a security risk. -sp.ini.key("enable_dl").set("0").ro(); - -## links to manual pages in error pages should not be set during runtime. -sp.ini.key("docref_ext").ro(); -sp.ini.key("docref_root").ro(); -sp.ini.key("html_errors").set("0").ro(); - -## preventing $_POST and $_FILES to be populated can be a security feature, so read-write is ok. -sp.ini.key("enable_post_data_reading").rw(); - -## disable error append/prepend which may lead to log forging. -sp.ini.key("error_append_string").ro(); -sp.ini.key("error_prepend_string").set("").ro(); - -## restrict limit settings to prevent Denial-of-Service -sp.ini.key("max_execution_time").min("30").max("600").rw(); -sp.ini.key("max_file_uploads").min("0").max("25").rw(); -sp.ini.key("max_input_nesting_level").min("16").max("64").rw(); -sp.ini.key("max_input_time").set("-1").ro(); -sp.ini.key("max_input_vars").min("0").max("1024").rw(); -sp.ini.key("memory_limit").min("4M").max("256M").rw(); -sp.ini.key("post_max_size").max("256M").rw(); -sp.ini.key("upload_max_filesize").max("256M").rw(); -sp.ini.key("precision").max("14").rw(); -sp.ini.key("unserialize_max_depth").min("128").max("4096").rw(); -sp.ini.key("serialize_precision").ro(); - -## some applications rely on these filters for security -## even though they should implement proper input validation for each input field separately. -@condition extension_loaded("filter"); -sp.ini.key("filter.default").rw(); -sp.ini.key("filter.default_flags").rw(); -@end_condition; - -## scripts will not be terminated after a client has aborted their connection. -## this feature may be needed for some time consuming server-side calculation -sp.ini.key("ignore_user_abort").rw(); - -## flushing may be needed -sp.ini.key("implicit_flush").rw(); - -## this feature may be required for some frameworks to work properly, -## but setting the include path to a fixed value is always more secure. -sp.ini.key("include_path").ro(); - -## input/output and encoding options -sp.ini.key("input_encoding").ro(); -sp.ini.key("internal_encoding").ro(); -sp.ini.key("output_encoding").ro(); -sp.ini.key("output_buffering").min("0").max("4096").rw(); -sp.ini.key("output_handler").ro(); - -## mail options -#sp.ini.key("mail.add_x_header").set("0").ro(); -#sp.ini.key("mail.force_extra_parameters").set("").ro(); -#sp.ini.key("mail.log").set("").ro(); -## windows smtp options -#sp.ini.key("SMTP").set("localhost").ro(); -#sp.ini.key("smtp_port").set("25").ro(); -#sp.ini.key("sendmail_from").ro(); - -## mysqli/mysqlnd options -@condition extension_loaded("mysqli"); -sp.ini.key("mysqli.allow_local_infile").ro(); -sp.ini.key("mysqli.allow_persistent").ro(); -sp.ini.key("mysqli.default_host").ro(); -sp.ini.key("mysqli.default_port").ro(); -sp.ini.key("mysqli.default_pw").ro(); -sp.ini.key("mysqli.default_socket").ro(); -sp.ini.key("mysqli.default_user").ro(); -sp.ini.key("mysqli.max_links").set("-1").ro(); -sp.ini.key("mysqli.max_persistent").set("-1").ro(); -sp.ini.key("mysqli.reconnect").set("0").ro(); -sp.ini.key("mysqli.rollback_on_cached_plink").set("0").ro(); -@condition extension_loaded("mysqlnd"); -sp.ini.key("mysqlnd.collect_memory_statistics").set("0").ro(); -sp.ini.key("mysqlnd.collect_statistics").set("1").ro(); -sp.ini.key("mysqlnd.debug").set("").ro(); -sp.ini.key("mysqlnd.log_mask").set("0").ro(); -sp.ini.key("mysqlnd.mempool_default_size").set("16000").ro(); -sp.ini.key("mysqlnd.net_cmd_buffer_size").set("4096").ro(); -sp.ini.key("mysqlnd.net_read_buffer_size").set("32768").ro(); -sp.ini.key("mysqlnd.net_read_timeout").set("86400").ro(); -sp.ini.key("mysqlnd.sha256_server_public_key").set("").ro(); -sp.ini.key("mysqlnd.trace_alloc").set("").ro(); -@condition extension_loaded("mysqlnd") && PHP_VERSION_ID < 80100; -sp.ini.key("mysqlnd.fetch_data_copy").set("0").ro(); -@end_condition; - -## open basedir is a security feature similar to chroot. -## why should it be allowed to disable this feature during runtime? -sp.ini.key("open_basedir").ro(); - -## pcre options -@condition extension_loaded("pcre"); -sp.ini.key("pcre.backtrack_limit").min("1000").max("1000000").rw(); -sp.ini.key("pcre.jit").rw(); -sp.ini.key("pcre.recursion_limit").min("1000").max("100000").ro(); -@end_condition; - -## phar options -@condition extension_loaded("phar"); -sp.ini.key("phar.cache_list").ro(); -sp.ini.key("phar.readonly").ro(); -sp.ini.key("phar.require_hash").ro(); -@end_condition; - -## session options -@condition extension_loaded("session"); -#sp.ini.key("session.auto_start").set("0").ro(); -#sp.ini.key("session.cache_expire").set("180").ro(); -#sp.ini.key("session.cache_limiter").set("nocache").ro(); -#sp.ini.key("session.cookie_domain").set("").ro(); -#sp.ini.key("session.cookie_httponly").set("0").ro(); -#sp.ini.key("session.cookie_lifetime").set("0").ro(); -#sp.ini.key("session.cookie_path").set("/").ro(); -#sp.ini.key("session.cookie_samesite").set("").ro(); -#sp.ini.key("session.cookie_secure").set("0").ro(); -#sp.ini.key("session.gc_divisor").set("100").ro(); -#sp.ini.key("session.gc_maxlifetime").set("1440").ro(); -#sp.ini.key("session.gc_probability").set("1").ro(); -#sp.ini.key("session.lazy_write").set("1").ro(); -#sp.ini.key("session.name").set("PHPSESSID").ro(); -#sp.ini.key("session.referer_check").set("").ro(); -#sp.ini.key("session.save_handler").set("files").ro(); -#sp.ini.key("session.save_path").set("").ro(); -#sp.ini.key("session.serialize_handler").set("php").ro(); -#sp.ini.key("session.sid_bits_per_character").set("4").ro(); -sp.ini.key("session.sid_length").min("32").max("128").rw(); -#sp.ini.key("session.trans_sid_hosts").set("").ro(); -#sp.ini.key("session.trans_sid_tags").set("a=href,area=href,frame=src,form=").ro(); -#sp.ini.key("session.upload_progress.cleanup").set("1").ro(); -#sp.ini.key("session.upload_progress.enabled").set("1").ro(); -#sp.ini.key("session.upload_progress.freq").set("1%").ro(); -#sp.ini.key("session.upload_progress.min_freq").set("1").ro(); -#sp.ini.key("session.upload_progress.name").set("PHP_SESSION_UPLOAD_PROGRESS").ro(); -#sp.ini.key("session.upload_progress.prefix").set("upload_progress_").ro(); -#sp.ini.key("session.use_cookies").set("1").ro(); -#sp.ini.key("session.use_only_cookies").set("1").ro(); -#sp.ini.key("session.use_strict_mode").set("0").ro(); -#sp.ini.key("session.use_trans_sid").set("0").ro(); -@end_condition; - -## allow setting the user agent -sp.ini.key("user_agent").rw(); - -## allow setting the xmlrpc fault code -sp.ini.key("xmlrpc_error_number").rw(); - -## these ini entries can only be set by php.ini anyway, -## but better set them to read-only anyway, just to be sure. -sp.ini.key("disable_classes").ro(); -sp.ini.key("disable_functions").ro(); -sp.ini.key("doc_root").ro(); -sp.ini.key("extension_dir").ro(); -sp.ini.key("file_uploads").ro(); -sp.ini.key("hard_timeout").ro(); -sp.ini.key("realpath_cache_size").ro(); -sp.ini.key("realpath_cache_ttl").ro(); -sp.ini.key("sendmail_path").ro(); -@condition extension_loaded("sqlite3"); -sp.ini.key("sqlite3.defensive").ro(); -sp.ini.key("sqlite3.extension_dir").ro(); -@end_condition; -sp.ini.key("sys_temp_dir").ro(); -sp.ini.key("syslog.facility").ro(); -sp.ini.key("syslog.ident").ro(); -sp.ini.key("upload_tmp_dir").ro(); -sp.ini.key("user_dir").ro(); -sp.ini.key("user_ini.cache_ttl").ro(); -sp.ini.key("user_ini.filename").ro(); -sp.ini.key("zend.assertions").ro(); -sp.ini.key("zend.signal_check").set("0").ro(); diff --git a/config/ini_protection.rules b/config/ini_protection.rules new file mode 100644 index 0000000..bde5815 --- /dev/null +++ b/config/ini_protection.rules @@ -0,0 +1,291 @@ +## INI protection - prevent unwanted runtime ini changes made by ini_set() or other functions or by .htaccess +sp.ini_protection.enable(); + +## simulation mode: only log violations +#sp.ini_protection.simulation(); + +## drop policy: drop request on rule violation +#sp.ini_protection.policy_drop(); + +## do not log violations. +## this setting has no effect in simulation or drop mode +#sp.ini_protection.policy_silent_fail(); + +## do not log read-only violations +## this setting has no effect in simulation or drop mode +sp.ini_protection.policy_silent_ro(); + +## access policy can be one of +## .policy_readonly(): All entries are read-only by default. +## Individual entries can be set read-write using .readwrite() or .rw() +## .policy_readwrite(): This is the default and can be omitted. +## Individual entries can be set read-only using .readonly() or .ro() +#sp.ini_protection.policy_readonly(); + +## sp.ini entries can have the following attributes +## .key("..."): mandatory ini name. +## .set("..."): set the initial value. This overrides php.ini. +## checks are not performed for this initial value. +## .min("...") / .max("..."): value must be an integer between .min and .max. +## shorthand notation (e.g. 1k = 1024) is allowed +## .regexp("..."): value must match the regular expression +## .allow_null(): allow setting a NULL-value +## .msg("..."): message is shown in logs on rule violation instead of default message +## .readonly() / .ro() / .readwrite() / .rw(): set entry to read-only or read-write respectively +## If no access keyword is provided, the entry inherits the default policy set by sp.ini_protection.policy_*-rules. +## .drop(): drop request on rule violation for this entry +## .simulation(): only log rule violation for this entry + +## FOR PRODUCTION SYSTEMS: disable error messages and version numbers +sp.ini.key("display_errors").set("0").ro(); +sp.ini.key("display_startup_errors").set("0").ro(); +sp.ini.key("expose_php").set("0").ro(); +## FOR DEVELOPMENT/TESTING: allow enabling error messages and version numbers +#sp.ini.key("display_errors").rw(); +#sp.ini.key("display_startup_errors").rw(); +#sp.ini.key("expose_php").rw(); + +## error logging options should not be set during runtime -> read-only. +sp.ini.key("error_log").ro(); +sp.ini.key("error_reporting").ro(); +sp.ini.key("log_errors").ro(); +@condition PHP_VERSION_ID < 80000; +sp.ini.key("log_errors_max_len").set("2048").ro(); +@end_condition; +sp.ini.key("ignore_repeated_errors").ro(); +sp.ini.key("ignore_repeated_source").ro(); +sp.ini.key("syslog.filter").ro(); + +## disable assert. assert() in PHP can evaluate arbitrary code just like eval() +sp.ini.key("assert.active").set("0").ro(); +sp.ini.key("assert.bail").ro(); +sp.ini.key("assert.callback").ro(); +sp.ini.key("assert.exception").ro(); +sp.ini.key("assert.warning").ro(); + +## enforce color codes. prevents potential XSS +sp.ini.key("highlight.comment").regexp("^#[0-9a-fA-F]{6}$"); +sp.ini.key("highlight.default").regexp("^#[0-9a-fA-F]{6}$"); +sp.ini.key("highlight.html").regexp("^#[0-9a-fA-F]{6}$"); +sp.ini.key("highlight.keyword").regexp("^#[0-9a-fA-F]{6}$"); +sp.ini.key("highlight.string").regexp("^#[0-9a-fA-F]{6}$"); + +## prevent remote access via fopen/include +sp.ini.key("allow_url_fopen").set("0").ro(); +sp.ini.key("allow_url_include").set("0").ro(); + +## prevent code execution from auto-included files +sp.ini.key("auto_append_file").ro(); +sp.ini.key("auto_prepend_file").ro(); + +## make rarely used features read-only. you can always set the value in php.ini +sp.ini.key("arg_separator.input").ro(); +sp.ini.key("arg_separator.output").ro(); +sp.ini.key("auto_detect_line_endings").ro(); +sp.ini.key("auto_globals_jit").ro(); +sp.ini.key("browscap").ro(); +sp.ini.key("default_charset").ro(); +sp.ini.key("register_argc_argv").ro(); +sp.ini.key("report_memleaks").ro(); +sp.ini.key("report_zend_debug").ro(); +sp.ini.key("request_order").ro(); +sp.ini.key("url_rewriter.hosts").ro(); +sp.ini.key("url_rewriter.tags").ro(); +sp.ini.key("variables_order").ro(); +sp.ini.key("from").ro(); +sp.ini.key("short_open_tag").ro(); +sp.ini.key("unserialize_callback_func").ro(); +sp.ini.key("zend.detect_unicode").ro(); +sp.ini.key("zend.enable_gc").ro(); +sp.ini.key("zend.exception_ignore_args").ro(); +sp.ini.key("zend.exception_string_param_max_len").ro(); +sp.ini.key("zend.multibyte").ro(); +sp.ini.key("zend.script_encoding").ro(); + +## date and timezone settings +#sp.ini.key("date.default_latitude").set("31.7667").ro(); +#sp.ini.key("date.default_longitude").set("35.2333").ro(); +#sp.ini.key("date.sunrise_zenith").set("90.833333").ro(); +#sp.ini.key("date.sunset_zenith").set("90.833333").ro(); +#sp.ini.key("date.timezone").set("").ro(); + +## setting a default mime type other than text/html can be a way to prevent XSS, so this will be set to read-write by default +sp.ini.key("default_mimetype").rw(); + +## allow reasonable socket timeouts +sp.ini.key("default_socket_timeout").min("1").max("300").rw(); + +## disable dynamic loading of PHP extensions in an apache/mod_php environment as it is a security risk. +sp.ini.key("enable_dl").set("0").ro(); + +## links to manual pages in error pages should not be set during runtime. +sp.ini.key("docref_ext").ro(); +sp.ini.key("docref_root").ro(); +sp.ini.key("html_errors").set("0").ro(); + +## preventing $_POST and $_FILES to be populated can be a security feature, so read-write is ok. +sp.ini.key("enable_post_data_reading").rw(); + +## disable error append/prepend which may lead to log forging. +sp.ini.key("error_append_string").ro(); +sp.ini.key("error_prepend_string").set("").ro(); + +## restrict limit settings to prevent Denial-of-Service +sp.ini.key("max_execution_time").min("30").max("600").rw(); +sp.ini.key("max_file_uploads").min("0").max("25").rw(); +sp.ini.key("max_input_nesting_level").min("16").max("64").rw(); +sp.ini.key("max_input_time").set("-1").ro(); +sp.ini.key("max_input_vars").min("0").max("1024").rw(); +sp.ini.key("memory_limit").min("4M").max("256M").rw(); +sp.ini.key("post_max_size").max("256M").rw(); +sp.ini.key("upload_max_filesize").max("256M").rw(); +sp.ini.key("precision").max("14").rw(); +sp.ini.key("unserialize_max_depth").min("128").max("4096").rw(); +sp.ini.key("serialize_precision").ro(); + +## some applications rely on these filters for security +## even though they should implement proper input validation for each input field separately. +@condition extension_loaded("filter"); +sp.ini.key("filter.default").rw(); +sp.ini.key("filter.default_flags").rw(); +@end_condition; + +## scripts will not be terminated after a client has aborted their connection. +## this feature may be needed for some time consuming server-side calculation +sp.ini.key("ignore_user_abort").rw(); + +## flushing may be needed +sp.ini.key("implicit_flush").rw(); + +## this feature may be required for some frameworks to work properly, +## but setting the include path to a fixed value is always more secure. +sp.ini.key("include_path").ro(); + +## input/output and encoding options +sp.ini.key("input_encoding").ro(); +sp.ini.key("internal_encoding").ro(); +sp.ini.key("output_encoding").ro(); +sp.ini.key("output_buffering").min("0").max("4096").rw(); +sp.ini.key("output_handler").ro(); + +## mail options +#sp.ini.key("mail.add_x_header").set("0").ro(); +#sp.ini.key("mail.force_extra_parameters").set("").ro(); +#sp.ini.key("mail.log").set("").ro(); +## windows smtp options +#sp.ini.key("SMTP").set("localhost").ro(); +#sp.ini.key("smtp_port").set("25").ro(); +#sp.ini.key("sendmail_from").ro(); + +## mysqli/mysqlnd options +@condition extension_loaded("mysqli"); +sp.ini.key("mysqli.allow_local_infile").ro(); +sp.ini.key("mysqli.allow_persistent").ro(); +sp.ini.key("mysqli.default_host").ro(); +sp.ini.key("mysqli.default_port").ro(); +sp.ini.key("mysqli.default_pw").ro(); +sp.ini.key("mysqli.default_socket").ro(); +sp.ini.key("mysqli.default_user").ro(); +sp.ini.key("mysqli.max_links").set("-1").ro(); +sp.ini.key("mysqli.max_persistent").set("-1").ro(); +sp.ini.key("mysqli.reconnect").set("0").ro(); +sp.ini.key("mysqli.rollback_on_cached_plink").set("0").ro(); +@condition extension_loaded("mysqlnd"); +sp.ini.key("mysqlnd.collect_memory_statistics").set("0").ro(); +sp.ini.key("mysqlnd.collect_statistics").set("1").ro(); +sp.ini.key("mysqlnd.debug").set("").ro(); +sp.ini.key("mysqlnd.log_mask").set("0").ro(); +sp.ini.key("mysqlnd.mempool_default_size").set("16000").ro(); +sp.ini.key("mysqlnd.net_cmd_buffer_size").set("4096").ro(); +sp.ini.key("mysqlnd.net_read_buffer_size").set("32768").ro(); +sp.ini.key("mysqlnd.net_read_timeout").set("86400").ro(); +sp.ini.key("mysqlnd.sha256_server_public_key").set("").ro(); +sp.ini.key("mysqlnd.trace_alloc").set("").ro(); +@condition extension_loaded("mysqlnd") && PHP_VERSION_ID < 80100; +sp.ini.key("mysqlnd.fetch_data_copy").set("0").ro(); +@end_condition; + +## open basedir is a security feature similar to chroot. +## why should it be allowed to disable this feature during runtime? +sp.ini.key("open_basedir").ro(); + +## pcre options +@condition extension_loaded("pcre"); +sp.ini.key("pcre.backtrack_limit").min("1000").max("1000000").rw(); +sp.ini.key("pcre.jit").rw(); +sp.ini.key("pcre.recursion_limit").min("1000").max("100000").ro(); +@end_condition; + +## phar options +@condition extension_loaded("phar"); +sp.ini.key("phar.cache_list").ro(); +sp.ini.key("phar.readonly").ro(); +sp.ini.key("phar.require_hash").ro(); +@end_condition; + +## session options +@condition extension_loaded("session"); +#sp.ini.key("session.auto_start").set("0").ro(); +#sp.ini.key("session.cache_expire").set("180").ro(); +#sp.ini.key("session.cache_limiter").set("nocache").ro(); +#sp.ini.key("session.cookie_domain").set("").ro(); +#sp.ini.key("session.cookie_httponly").set("0").ro(); +#sp.ini.key("session.cookie_lifetime").set("0").ro(); +#sp.ini.key("session.cookie_path").set("/").ro(); +#sp.ini.key("session.cookie_samesite").set("").ro(); +#sp.ini.key("session.cookie_secure").set("0").ro(); +#sp.ini.key("session.gc_divisor").set("100").ro(); +#sp.ini.key("session.gc_maxlifetime").set("1440").ro(); +#sp.ini.key("session.gc_probability").set("1").ro(); +#sp.ini.key("session.lazy_write").set("1").ro(); +#sp.ini.key("session.name").set("PHPSESSID").ro(); +#sp.ini.key("session.referer_check").set("").ro(); +#sp.ini.key("session.save_handler").set("files").ro(); +#sp.ini.key("session.save_path").set("").ro(); +#sp.ini.key("session.serialize_handler").set("php").ro(); +#sp.ini.key("session.sid_bits_per_character").set("4").ro(); +sp.ini.key("session.sid_length").min("32").max("128").rw(); +#sp.ini.key("session.trans_sid_hosts").set("").ro(); +#sp.ini.key("session.trans_sid_tags").set("a=href,area=href,frame=src,form=").ro(); +#sp.ini.key("session.upload_progress.cleanup").set("1").ro(); +#sp.ini.key("session.upload_progress.enabled").set("1").ro(); +#sp.ini.key("session.upload_progress.freq").set("1%").ro(); +#sp.ini.key("session.upload_progress.min_freq").set("1").ro(); +#sp.ini.key("session.upload_progress.name").set("PHP_SESSION_UPLOAD_PROGRESS").ro(); +#sp.ini.key("session.upload_progress.prefix").set("upload_progress_").ro(); +#sp.ini.key("session.use_cookies").set("1").ro(); +#sp.ini.key("session.use_only_cookies").set("1").ro(); +#sp.ini.key("session.use_strict_mode").set("0").ro(); +#sp.ini.key("session.use_trans_sid").set("0").ro(); +@end_condition; + +## allow setting the user agent +sp.ini.key("user_agent").rw(); + +## allow setting the xmlrpc fault code +sp.ini.key("xmlrpc_error_number").rw(); + +## these ini entries can only be set by php.ini anyway, +## but better set them to read-only anyway, just to be sure. +sp.ini.key("disable_classes").ro(); +sp.ini.key("disable_functions").ro(); +sp.ini.key("doc_root").ro(); +sp.ini.key("extension_dir").ro(); +sp.ini.key("file_uploads").ro(); +sp.ini.key("hard_timeout").ro(); +sp.ini.key("realpath_cache_size").ro(); +sp.ini.key("realpath_cache_ttl").ro(); +sp.ini.key("sendmail_path").ro(); +@condition extension_loaded("sqlite3"); +sp.ini.key("sqlite3.defensive").ro(); +sp.ini.key("sqlite3.extension_dir").ro(); +@end_condition; +sp.ini.key("sys_temp_dir").ro(); +sp.ini.key("syslog.facility").ro(); +sp.ini.key("syslog.ident").ro(); +sp.ini.key("upload_tmp_dir").ro(); +sp.ini.key("user_dir").ro(); +sp.ini.key("user_ini.cache_ttl").ro(); +sp.ini.key("user_ini.filename").ro(); +sp.ini.key("zend.assertions").ro(); +sp.ini.key("zend.signal_check").set("0").ro(); -- cgit v1.3 From 2b69b20c0de6b0cee746190e386273c1e095e71b Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Tue, 11 Jan 2022 15:54:34 +0100 Subject: enable strict_mode in example config --- config/ini_protection.rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'config') diff --git a/config/ini_protection.rules b/config/ini_protection.rules index bde5815..9b42a18 100644 --- a/config/ini_protection.rules +++ b/config/ini_protection.rules @@ -255,7 +255,7 @@ sp.ini.key("session.sid_length").min("32").max("128").rw(); #sp.ini.key("session.upload_progress.prefix").set("upload_progress_").ro(); #sp.ini.key("session.use_cookies").set("1").ro(); #sp.ini.key("session.use_only_cookies").set("1").ro(); -#sp.ini.key("session.use_strict_mode").set("0").ro(); +sp.ini.key("session.use_strict_mode").set("1").ro(); #sp.ini.key("session.use_trans_sid").set("0").ro(); @end_condition; -- cgit v1.3 From dd8f34013ea55eb8e6f4e3e4de9c9dc16e02eb9c Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Tue, 11 Jan 2022 19:58:46 +0100 Subject: make xxe protection conditional in default rules --- config/default.rules | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'config') diff --git a/config/default.rules b/config/default.rules index 2de703b..7cb18b5 100644 --- a/config/default.rules +++ b/config/default.rules @@ -6,8 +6,10 @@ # Harden the PRNG sp.harden_random.enable(); -# Disabled XXE +# Enable XXE protection +@condition extension_loaded("xml"); sp.xxe_protection.enable(); +@end_condition; # Global configuration variables # sp.global.secret_key("YOU _DO_ NEED TO CHANGE THIS WITH SOME RANDOM CHARACTERS."); -- cgit v1.3