diff --git a/Changelog b/Changelog index e0f4f54..7f6b53d 100644 --- a/Changelog +++ b/Changelog @@ -5,6 +5,8 @@ - Fix read after efree() that lets function_exists() malfunction - Fix build with clang compiler - Added a request variable drop statistic log message + - Added optional logging and termination of function_exists()-calls on non-whitelisted + or blacklisted function-names, including new error class S_EXISTENCE 2012-01-19 - 0.9.33 diff --git a/execute.c b/execute.c index aa3ca46..e14192e 100644 --- a/execute.c +++ b/execute.c @@ -1078,10 +1078,18 @@ static int ih_function_exists(IH_HANDLER_PARAMS) if (SUHOSIN_G(in_code_type) == SUHOSIN_EVAL) { if (SUHOSIN_G(eval_whitelist) != NULL) { if (!zend_hash_exists(SUHOSIN_G(eval_whitelist), lcname, func_name_len+1)) { + suhosin_log(S_EXISTENCE, "evaluated existence of a function not within eval whitelist: eval('function_exists(\"%s\");')", lcname); + if (SUHOSIN_G(eval_exists_forbidden) && !SUHOSIN_G(simulation)) { + zend_error(E_ERROR, "SUHOSIN - Evaluating existence of functions not within eval whitelist is forbidden by configuration"); + } retval = 0; } } else if (SUHOSIN_G(eval_blacklist) != NULL) { if (zend_hash_exists(SUHOSIN_G(eval_blacklist), lcname, func_name_len+1)) { + suhosin_log(S_EXISTENCE, "evaluated existence of a function within eval blacklist: eval('function_exists(\"%s\");')", lcname); + if (SUHOSIN_G(eval_exists_forbidden) && !SUHOSIN_G(simulation)) { + zend_error(E_ERROR, "SUHOSIN - Evaluating existence of functions within eval blacklist is forbidden by configuration"); + } retval = 0; } } @@ -1089,10 +1097,18 @@ static int ih_function_exists(IH_HANDLER_PARAMS) if (SUHOSIN_G(func_whitelist) != NULL) { if (!zend_hash_exists(SUHOSIN_G(func_whitelist), lcname, func_name_len+1)) { + suhosin_log(S_EXISTENCE, "tested existence of a function not within whitelist: function_exists('%s')", lcname); + if (SUHOSIN_G(func_exists_forbidden) && !SUHOSIN_G(simulation)) { + zend_error(E_ERROR, "SUHOSIN - Testing existence of functions not within whitelist is forbidden by configuration"); + } retval = 0; } } else if (SUHOSIN_G(func_blacklist) != NULL) { if (zend_hash_exists(SUHOSIN_G(func_blacklist), lcname, func_name_len+1)) { + suhosin_log(S_EXISTENCE, "tested existence of a blacklisted function: function_exists('%s')", lcname); + if (SUHOSIN_G(func_exists_forbidden) && !SUHOSIN_G(simulation)) { + zend_error(E_ERROR, "SUHOSIN - Testing existence of blacklisted functions is forbidden by configuration"); + } retval = 0; } } diff --git a/log.c b/log.c index 1d0a722..b512748 100644 --- a/log.c +++ b/log.c @@ -78,6 +78,8 @@ static char *loglevel2string(int loglevel) return "SQL"; case S_EXECUTOR: return "EXECUTOR"; + case S_EXISTENCE: + return "EXISTENCE"; case S_VARS: return "VARS"; default: diff --git a/php_suhosin.h b/php_suhosin.h index e689b9c..72dfb83 100644 --- a/php_suhosin.h +++ b/php_suhosin.h @@ -95,6 +95,8 @@ ZEND_BEGIN_MODULE_GLOBALS(suhosin) HashTable *eval_whitelist; HashTable *eval_blacklist; + zend_bool func_exists_forbidden; + zend_bool eval_exists_forbidden; zend_bool executor_disable_eval; zend_bool executor_disable_emod; @@ -272,6 +274,10 @@ ZEND_END_MODULE_GLOBALS(suhosin) #define zend_symtable_exists zend_hash_exists #endif +/* Error Constants not part of the patch */ +#ifndef S_EXISTENCE +#define S_EXISTENCE (1<<9L) +#endif /* Error Constants */ #ifndef S_MEMORY @@ -285,7 +291,7 @@ ZEND_END_MODULE_GLOBALS(suhosin) #define S_MAIL (1<<7L) #define S_SESSION (1<<8L) #define S_INTERNAL (1<<29L) -#define S_ALL (S_MEMORY | S_VARS | S_INCLUDE | S_FILES | S_MAIL | S_SESSION | S_MISC | S_SQL | S_EXECUTOR) +#define S_ALL (S_MEMORY | S_VARS | S_INCLUDE | S_FILES | S_MAIL | S_SESSION | S_MISC | S_SQL | S_EXECUTOR | S_EXISTENCE) #endif #define SUHOSIN_NORMAL 0 diff --git a/suhosin.c b/suhosin.c index c04655b..0e59c11 100644 --- a/suhosin.c +++ b/suhosin.c @@ -873,8 +873,10 @@ PHP_INI_BEGIN() STD_ZEND_INI_BOOLEAN("suhosin.executor.include.allow_writable_files", "1", ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdateExecBool, executor_include_allow_writable_files, zend_suhosin_globals, suhosin_globals) ZEND_INI_ENTRY("suhosin.executor.eval.whitelist", NULL, ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdate_eval_whitelist) ZEND_INI_ENTRY("suhosin.executor.eval.blacklist", NULL, ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdate_eval_blacklist) + STD_ZEND_INI_BOOLEAN("suhosin.executor.eval.exists_forbidden", "0", ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdateExecBool, eval_exists_forbidden, zend_suhosin_globals, suhosin_globals) ZEND_INI_ENTRY("suhosin.executor.func.whitelist", NULL, ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdate_func_whitelist) ZEND_INI_ENTRY("suhosin.executor.func.blacklist", NULL, ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdate_func_blacklist) + STD_ZEND_INI_BOOLEAN("suhosin.executor.func.exists_forbidden", "0", ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdateExecBool, func_exists_forbidden, zend_suhosin_globals, suhosin_globals) STD_ZEND_INI_BOOLEAN("suhosin.executor.disable_eval", "0", ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdateExecBool, executor_disable_eval, zend_suhosin_globals, suhosin_globals) STD_ZEND_INI_BOOLEAN("suhosin.executor.disable_emodifier", "0", ZEND_INI_PERDIR|ZEND_INI_SYSTEM, OnUpdateExecBool, executor_disable_emod, zend_suhosin_globals, suhosin_globals) @@ -1044,6 +1046,12 @@ PHP_MINIT_FUNCTION(suhosin) REGISTER_MAIN_LONG_CONSTANT("S_INTERNAL", S_INTERNAL, CONST_PERSISTENT | CONST_CS); REGISTER_MAIN_LONG_CONSTANT("S_ALL", S_ALL, CONST_PERSISTENT | CONST_CS); } + + /* register constants which never have been part of any suhosin-patch, + * hence they have not previously been registered by a possible patched PHP */ + if (zend_hash_exists(EG(zend_constants), "S_EXISTENCE", sizeof("S_EXISTENCE"))==0) { + REGISTER_MAIN_LONG_CONSTANT("S_EXISTENCE", S_EXISTENCE, CONST_PERSISTENT | CONST_CS); + } /* check if shared ini directives are already known (maybe a patched PHP) */ if (zend_hash_exists(EG(ini_directives), "suhosin.log.syslog", sizeof("suhosin.log.syslog"))) { diff --git a/suhosin.ini b/suhosin.ini index 2420501..ef97435 100644 --- a/suhosin.ini +++ b/suhosin.ini @@ -91,25 +91,51 @@ extension = suhosin.so ; Comma separated whitelist of functions that are allowed to be called. If the ; whitelist is empty the blacklist is evaluated, otherwise calling a function -; not in the whitelist will terminate the script and get logged. +; not in the whitelist will terminate the script and get logged. Executing +; function_exists() on a function not in the whitelist will log an event with +; the class S_EXISTENCE and if the flag suhosin.executor.func.exists_forbidden +; is enabled also terminate the script. ;suhosin.executor.func.whitelist = ; Comma separated blacklist of functions that are not allowed to be called. If ; no whitelist is given, calling a function within the blacklist will terminate -; the script and get logged. +; the script and get logged. Executing function_exists() on a function within +; the blacklist will log an event with the class S_EXISTENCE and if the flag +; suhosin.executor.func.exists_forbidden is enabled also terminate the script. ;suhosin.executor.func.blacklist = +; When this configuration flag is turned on, the script will terminate, if +; function_exists() is executed on a function not in the whitelist (if given) +; or within the blacklist (if given), after the problem has been logged. This +; flag is meant for development, deployment or paranoid use only. Unless you +; really want to annoy users, there are (very) good reasons NOT to enable this +; flag. If it's enabled, you deserve all the heat you'll get. +;suhosin.executor.func.exists_forbidden = Off + ; Comma separated whitelist of functions that are allowed to be called from ; within eval(). If the whitelist is empty the blacklist is evaluated, ; otherwise calling a function not in the whitelist will terminate the script -; and get logged. +; and get logged. The execution of function_exists() on a function not in the +; non-empty whitelist will log an event with the class S_EXISTENCE and if the +; flag suhosin.executor.eval.exists_forbidden is enabled terminate the script. ;suhosin.executor.eval.whitelist = ; Comma separated blacklist of functions that are not allowed to be called from ; within eval(). If no whitelist is given, calling a function within the -; blacklist will terminate the script and get logged. +; blacklist will terminate the script and get logged and testing existence via +; function_exists() will log an event with the class S_EXISTENCE. If the flag +; suhosin.executor.eval.exists_forbidden is enabled the latter case will also +; terminate the script. ;suhosin.executor.eval.blacklist = +; When this configuration flag is turned on, the script will terminate, if +; function_exists() is executed from within eval() on a function not in the +; whitelist (if given) or within the blacklist (if given), after the problem +; has been logged. This flag is meant for development, deployment or paranoid +; use only. Unless you really want to annoy users, there are (very) good reasons +; NOT to enable this flag. If it's enabled, you deserve all the heat you'll get. +;suhosin.executor.eval.exists_forbidden = Off + ; eval() is a very dangerous statement and therefore you might want to disable ; it completely. Deactivating it will however break lots of scripts. Because ; every violation is logged, this allows finding all places where eval() is diff --git a/tests/executor/eval_blacklist_with_exists_forbidden_off.phpt b/tests/executor/eval_blacklist_with_exists_forbidden_off.phpt new file mode 100644 index 0000000..4e68993 --- /dev/null +++ b/tests/executor/eval_blacklist_with_exists_forbidden_off.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testing: suhosin.executor.eval.blacklist with suhosin.executor.eval.exists_forbidden=0 +--SKIPIF-- + +--INI-- +suhosin.log.sapi=512 +suhosin.executor.disable_eval=0 +suhosin.executor.eval.whitelist= +suhosin.executor.eval.blacklist=intval +suhosin.executor.eval.exists_forbidden=0 +--FILE-- + +--EXPECTF-- +ALERT - evaluated existence of a function within eval blacklist: eval('function_exists("intval");') (attacker 'REMOTE_ADDR not set', file '%s(3) : eval()'d code', line 1) +bool(false) diff --git a/tests/executor/eval_blacklist_with_exists_forbidden_on.phpt b/tests/executor/eval_blacklist_with_exists_forbidden_on.phpt new file mode 100644 index 0000000..f9c5e0d --- /dev/null +++ b/tests/executor/eval_blacklist_with_exists_forbidden_on.phpt @@ -0,0 +1,18 @@ +--TEST-- +Testing: suhosin.executor.eval.blacklist with suhosin.executor.eval.exists_forbidden=1 +--SKIPIF-- + +--INI-- +suhosin.log.sapi=512 +suhosin.executor.disable_eval=0 +suhosin.executor.eval.whitelist= +suhosin.executor.eval.blacklist=intval +suhosin.executor.eval.exists_forbidden=1 +--FILE-- + +--EXPECTF-- +ALERT - evaluated existence of a function within eval blacklist: eval('function_exists("intval");') (attacker 'REMOTE_ADDR not set', file '%s(2) : eval()'d code', line 1) + +Fatal error: SUHOSIN - Evaluating existence of functions within eval blacklist is forbidden by configuration in %s(2) : eval()'d code on line 1 diff --git a/tests/executor/eval_whitelist_with_exists_forbidden_off.phpt b/tests/executor/eval_whitelist_with_exists_forbidden_off.phpt new file mode 100644 index 0000000..ab60800 --- /dev/null +++ b/tests/executor/eval_whitelist_with_exists_forbidden_off.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testing: suhosin.executor.eval.whitelist with suhosin.executor.eval.exists_forbidden=0 +--SKIPIF-- + +--INI-- +suhosin.log.sapi=512 +suhosin.executor.disable_eval=0 +suhosin.executor.eval.whitelist=function_exists,var_dump +suhosin.executor.eval.blacklist= +suhosin.executor.eval.exists_forbidden=0 +--FILE-- + +--EXPECTF-- +ALERT - evaluated existence of a function not within eval whitelist: eval('function_exists("intval");') (attacker 'REMOTE_ADDR not set', file '%s(3) : eval()'d code', line 1) +bool(false) diff --git a/tests/executor/eval_whitelist_with_exists_forbidden_on.phpt b/tests/executor/eval_whitelist_with_exists_forbidden_on.phpt new file mode 100644 index 0000000..66a35d5 --- /dev/null +++ b/tests/executor/eval_whitelist_with_exists_forbidden_on.phpt @@ -0,0 +1,18 @@ +--TEST-- +Testing: suhosin.executor.eval.whitelist with suhosin.executor.eval.exists_forbidden=1 +--SKIPIF-- + +--INI-- +suhosin.log.sapi=512 +suhosin.executor.disable_eval=0 +suhosin.executor.eval.whitelist=function_exists +suhosin.executor.eval.blacklist= +suhosin.executor.eval.exists_forbidden=1 +--FILE-- + +--EXPECTF-- +ALERT - evaluated existence of a function not within eval whitelist: eval('function_exists("intval");') (attacker 'REMOTE_ADDR not set', file '%s(2) : eval()'d code', line 1) + +Fatal error: SUHOSIN - Evaluating existence of functions not within eval whitelist is forbidden by configuration in %s(2) : eval()'d code on line 1 diff --git a/tests/executor/func_blacklist_with_exists_forbidden_off.phpt b/tests/executor/func_blacklist_with_exists_forbidden_off.phpt new file mode 100644 index 0000000..cb334bc --- /dev/null +++ b/tests/executor/func_blacklist_with_exists_forbidden_off.phpt @@ -0,0 +1,17 @@ +--TEST-- +Testing: suhosin.executor.func.blacklist with suhosin.executor.func.exists_forbidden=0 +--SKIPIF-- + +--INI-- +suhosin.log.sapi=512 +suhosin.executor.func.whitelist= +suhosin.executor.func.blacklist=intval +suhosin.executor.func.exists_forbidden=0 +--FILE-- + +--EXPECTF-- +ALERT - tested existence of a blacklisted function: function_exists('intval') (attacker 'REMOTE_ADDR not set', file '%s', line 2) +bool(false) diff --git a/tests/executor/func_blacklist_with_exists_forbidden_on.phpt b/tests/executor/func_blacklist_with_exists_forbidden_on.phpt new file mode 100644 index 0000000..6ead058 --- /dev/null +++ b/tests/executor/func_blacklist_with_exists_forbidden_on.phpt @@ -0,0 +1,17 @@ +--TEST-- +Testing: suhosin.executor.func.blacklist with suhosin.executor.func.exists_forbidden=1 +--SKIPIF-- + +--INI-- +suhosin.log.sapi=512 +suhosin.executor.func.whitelist= +suhosin.executor.func.blacklist=intval +suhosin.executor.func.exists_forbidden=1 +--FILE-- + +--EXPECTF-- +ALERT - tested existence of a blacklisted function: function_exists('intval') (attacker 'REMOTE_ADDR not set', file '%s', line 2) + +Fatal error: SUHOSIN - Testing existence of blacklisted functions is forbidden by configuration in %s on line 2 diff --git a/tests/executor/func_whitelist_with_exists_forbidden_off.phpt b/tests/executor/func_whitelist_with_exists_forbidden_off.phpt new file mode 100644 index 0000000..918d34f --- /dev/null +++ b/tests/executor/func_whitelist_with_exists_forbidden_off.phpt @@ -0,0 +1,17 @@ +--TEST-- +Testing: suhosin.executor.func.whitelist with suhosin.executor.func.exists_forbidden=0 +--SKIPIF-- + +--INI-- +suhosin.log.sapi=512 +suhosin.executor.func.whitelist=function_exists,var_dump +suhosin.executor.func.blacklist= +suhosin.executor.func.exists_forbidden=0 +--FILE-- + +--EXPECTF-- +ALERT - tested existence of a function not within whitelist: function_exists('intval') (attacker 'REMOTE_ADDR not set', file '%s', line 2) +bool(false) diff --git a/tests/executor/func_whitelist_with_exists_forbidden_on.phpt b/tests/executor/func_whitelist_with_exists_forbidden_on.phpt new file mode 100644 index 0000000..b6f4916 --- /dev/null +++ b/tests/executor/func_whitelist_with_exists_forbidden_on.phpt @@ -0,0 +1,17 @@ +--TEST-- +Testing: suhosin.executor.func.whitelist with suhosin.executor.func.exists_forbidden=1 +--SKIPIF-- + +--INI-- +suhosin.log.sapi=512 +suhosin.executor.func.whitelist=function_exists +suhosin.executor.func.blacklist= +suhosin.executor.func.exists_forbidden=1 +--FILE-- + +--EXPECTF-- +ALERT - tested existence of a function not within whitelist: function_exists('intval') (attacker 'REMOTE_ADDR not set', file '%s', line 2) + +Fatal error: SUHOSIN - Testing existence of functions not within whitelist is forbidden by configuration in %s on line 2