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/suhosin.rules') 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 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/suhosin.rules') 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