diff options
| -rw-r--r-- | ifilter.c | 2 | ||||
| -rw-r--r-- | php_suhosin.h | 1 | ||||
| -rw-r--r-- | suhosin.c | 1 | ||||
| -rw-r--r-- | tests/filter/suhosin_upload_disallow_binary_utf8.phpt | 44 | ||||
| -rw-r--r-- | tests/filter/suhosin_upload_disallow_binary_utf8fail.phpt | 45 | ||||
| -rw-r--r-- | tests/filter/suhosin_upload_remove_binary.phpt | bin | 0 -> 796 bytes | |||
| -rw-r--r-- | tests/filter/suhosin_upload_remove_binary_utf8.phpt | 32 | ||||
| -rw-r--r-- | tests/filter/suhosin_upload_remove_binary_utf8fail.phpt | 32 | ||||
| -rw-r--r-- | ufilter.c | 108 |
9 files changed, 233 insertions, 32 deletions
| @@ -366,7 +366,7 @@ unsigned int suhosin_input_filter(int arg, char *var, char **val, unsigned int v | |||
| 366 | } | 366 | } |
| 367 | 367 | ||
| 368 | /* Drop this variable if it begins with whitespace which is disallowed */ | 368 | /* Drop this variable if it begins with whitespace which is disallowed */ |
| 369 | if (*var == ' ') { | 369 | if (isspace(*var)) { |
| 370 | if (SUHOSIN_G(disallow_ws)) { | 370 | if (SUHOSIN_G(disallow_ws)) { |
| 371 | suhosin_log(S_VARS, "request variable name begins with disallowed whitespace - dropped variable '%s'", var); | 371 | suhosin_log(S_VARS, "request variable name begins with disallowed whitespace - dropped variable '%s'", var); |
| 372 | if (!SUHOSIN_G(simulation)) { | 372 | if (!SUHOSIN_G(simulation)) { |
diff --git a/php_suhosin.h b/php_suhosin.h index e85fc38..4b460e4 100644 --- a/php_suhosin.h +++ b/php_suhosin.h | |||
| @@ -151,6 +151,7 @@ ZEND_BEGIN_MODULE_GLOBALS(suhosin) | |||
| 151 | zend_bool upload_disallow_elf; | 151 | zend_bool upload_disallow_elf; |
| 152 | zend_bool upload_disallow_binary; | 152 | zend_bool upload_disallow_binary; |
| 153 | zend_bool upload_remove_binary; | 153 | zend_bool upload_remove_binary; |
| 154 | zend_bool upload_allow_utf8; | ||
| 154 | char *upload_verification_script; | 155 | char *upload_verification_script; |
| 155 | 156 | ||
| 156 | zend_bool no_more_variables; | 157 | zend_bool no_more_variables; |
| @@ -986,6 +986,7 @@ PHP_INI_BEGIN() | |||
| 986 | STD_PHP_INI_ENTRY("suhosin.upload.disallow_elf", "1", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadBool, upload_disallow_elf, zend_suhosin_globals, suhosin_globals) | 986 | STD_PHP_INI_ENTRY("suhosin.upload.disallow_elf", "1", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadBool, upload_disallow_elf, zend_suhosin_globals, suhosin_globals) |
| 987 | STD_PHP_INI_ENTRY("suhosin.upload.disallow_binary", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadBool, upload_disallow_binary, zend_suhosin_globals, suhosin_globals) | 987 | STD_PHP_INI_ENTRY("suhosin.upload.disallow_binary", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadBool, upload_disallow_binary, zend_suhosin_globals, suhosin_globals) |
| 988 | STD_PHP_INI_ENTRY("suhosin.upload.remove_binary", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadBool, upload_remove_binary, zend_suhosin_globals, suhosin_globals) | 988 | STD_PHP_INI_ENTRY("suhosin.upload.remove_binary", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadBool, upload_remove_binary, zend_suhosin_globals, suhosin_globals) |
| 989 | STD_PHP_INI_ENTRY("suhosin.upload.allow_utf8", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadBool, upload_allow_utf8, zend_suhosin_globals, suhosin_globals) | ||
| 989 | STD_PHP_INI_ENTRY("suhosin.upload.verification_script", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadString, upload_verification_script, zend_suhosin_globals, suhosin_globals) | 990 | STD_PHP_INI_ENTRY("suhosin.upload.verification_script", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateUploadString, upload_verification_script, zend_suhosin_globals, suhosin_globals) |
| 990 | 991 | ||
| 991 | 992 | ||
diff --git a/tests/filter/suhosin_upload_disallow_binary_utf8.phpt b/tests/filter/suhosin_upload_disallow_binary_utf8.phpt new file mode 100644 index 0000000..557a8d5 --- /dev/null +++ b/tests/filter/suhosin_upload_disallow_binary_utf8.phpt | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | --TEST-- | ||
| 2 | Testing: suhosin.upload.disallow_binary=On with UTF-8 | ||
| 3 | --INI-- | ||
| 4 | suhosin.log.syslog=0 | ||
| 5 | suhosin.log.sapi=0 | ||
| 6 | suhosin.log.stdout=255 | ||
| 7 | suhosin.log.script=0 | ||
| 8 | file_uploads=1 | ||
| 9 | suhosin.upload.disallow_binary=On | ||
| 10 | suhosin.upload.allow_utf8=On | ||
| 11 | max_file_uploads=40 | ||
| 12 | suhosin.upload.max_uploads=40 | ||
| 13 | --SKIPIF-- | ||
| 14 | <?php include('skipif.inc'); ?> | ||
| 15 | --COOKIE-- | ||
| 16 | --GET-- | ||
| 17 | --POST_RAW-- | ||
| 18 | Content-Type: multipart/form-data; boundary=bound | ||
| 19 | --bound | ||
| 20 | Content-Disposition: form-data; name="test"; filename="test" | ||
| 21 | |||
| 22 | Spaß am Gerät! | ||
| 23 | |||
| 24 | --bound-- | ||
| 25 | --FILE-- | ||
| 26 | <?php | ||
| 27 | var_dump($_FILES); | ||
| 28 | ?> | ||
| 29 | --EXPECTF-- | ||
| 30 | array(1) { | ||
| 31 | ["test"]=> | ||
| 32 | array(5) { | ||
| 33 | ["name"]=> | ||
| 34 | string(4) "test" | ||
| 35 | ["type"]=> | ||
| 36 | string(0) "" | ||
| 37 | ["tmp_name"]=> | ||
| 38 | string(%d) "%s" | ||
| 39 | ["error"]=> | ||
| 40 | int(0) | ||
| 41 | ["size"]=> | ||
| 42 | int(17) | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/tests/filter/suhosin_upload_disallow_binary_utf8fail.phpt b/tests/filter/suhosin_upload_disallow_binary_utf8fail.phpt new file mode 100644 index 0000000..413d25a --- /dev/null +++ b/tests/filter/suhosin_upload_disallow_binary_utf8fail.phpt | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | --TEST-- | ||
| 2 | Testing: suhosin.upload.disallow_binary=On with UTF-8 and allow_utf8=Off | ||
| 3 | --INI-- | ||
| 4 | suhosin.log.syslog=0 | ||
| 5 | suhosin.log.sapi=0 | ||
| 6 | suhosin.log.stdout=255 | ||
| 7 | suhosin.log.script=0 | ||
| 8 | file_uploads=1 | ||
| 9 | suhosin.upload.disallow_binary=On | ||
| 10 | suhosin.upload.allow_utf8=Off | ||
| 11 | max_file_uploads=40 | ||
| 12 | suhosin.upload.max_uploads=40 | ||
| 13 | --SKIPIF-- | ||
| 14 | <?php include('skipif.inc'); ?> | ||
| 15 | --COOKIE-- | ||
| 16 | --GET-- | ||
| 17 | --POST_RAW-- | ||
| 18 | Content-Type: multipart/form-data; boundary=bound | ||
| 19 | --bound | ||
| 20 | Content-Disposition: form-data; name="test"; filename="test" | ||
| 21 | |||
| 22 | Spaß am Gerät! | ||
| 23 | |||
| 24 | --bound-- | ||
| 25 | --FILE-- | ||
| 26 | <?php | ||
| 27 | var_dump($_FILES); | ||
| 28 | ?> | ||
| 29 | --EXPECTF-- | ||
| 30 | array(1) { | ||
| 31 | ["test"]=> | ||
| 32 | array(5) { | ||
| 33 | ["name"]=> | ||
| 34 | string(4) "test" | ||
| 35 | ["type"]=> | ||
| 36 | string(0) "" | ||
| 37 | ["tmp_name"]=> | ||
| 38 | string(0) "" | ||
| 39 | ["error"]=> | ||
| 40 | int(8) | ||
| 41 | ["size"]=> | ||
| 42 | int(0) | ||
| 43 | } | ||
| 44 | } | ||
| 45 | ALERT - uploaded file contains binary data - file dropped (attacker 'REMOTE_ADDR not set', file '%s') | ||
diff --git a/tests/filter/suhosin_upload_remove_binary.phpt b/tests/filter/suhosin_upload_remove_binary.phpt new file mode 100644 index 0000000..f4337d9 --- /dev/null +++ b/tests/filter/suhosin_upload_remove_binary.phpt | |||
| Binary files differ | |||
diff --git a/tests/filter/suhosin_upload_remove_binary_utf8.phpt b/tests/filter/suhosin_upload_remove_binary_utf8.phpt new file mode 100644 index 0000000..6fbd240 --- /dev/null +++ b/tests/filter/suhosin_upload_remove_binary_utf8.phpt | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | --TEST-- | ||
| 2 | Testing: suhosin.upload.remove_binary=On with UTF-8 | ||
| 3 | --INI-- | ||
| 4 | suhosin.log.syslog=0 | ||
| 5 | suhosin.log.sapi=0 | ||
| 6 | suhosin.log.stdout=255 | ||
| 7 | suhosin.log.script=0 | ||
| 8 | file_uploads=1 | ||
| 9 | suhosin.upload.disallow_binary=Off | ||
| 10 | suhosin.upload.remove_binary=On | ||
| 11 | suhosin.upload.allow_utf8=On | ||
| 12 | max_file_uploads=40 | ||
| 13 | suhosin.upload.max_uploads=40 | ||
| 14 | --SKIPIF-- | ||
| 15 | <?php include('skipif.inc'); ?> | ||
| 16 | --COOKIE-- | ||
| 17 | --GET-- | ||
| 18 | --POST_RAW-- | ||
| 19 | Content-Type: multipart/form-data; boundary=bound | ||
| 20 | --bound | ||
| 21 | Content-Disposition: form-data; name="test"; filename="test" | ||
| 22 | |||
| 23 | Spaß am Gerät! | ||
| 24 | |||
| 25 | --bound-- | ||
| 26 | --FILE-- | ||
| 27 | <?php | ||
| 28 | var_dump(file_get_contents($_FILES['test']['tmp_name'])); | ||
| 29 | ?> | ||
| 30 | --EXPECTF-- | ||
| 31 | string(17) "Spaß am Gerät! | ||
| 32 | " \ No newline at end of file | ||
diff --git a/tests/filter/suhosin_upload_remove_binary_utf8fail.phpt b/tests/filter/suhosin_upload_remove_binary_utf8fail.phpt new file mode 100644 index 0000000..5c31115 --- /dev/null +++ b/tests/filter/suhosin_upload_remove_binary_utf8fail.phpt | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | --TEST-- | ||
| 2 | Testing: suhosin.upload.remove_binary=On with UTF-8 and allow_utf8=Off | ||
| 3 | --INI-- | ||
| 4 | suhosin.log.syslog=0 | ||
| 5 | suhosin.log.sapi=0 | ||
| 6 | suhosin.log.stdout=255 | ||
| 7 | suhosin.log.script=0 | ||
| 8 | file_uploads=1 | ||
| 9 | suhosin.upload.disallow_binary=Off | ||
| 10 | suhosin.upload.remove_binary=On | ||
| 11 | suhosin.upload.allow_utf8=Off | ||
| 12 | max_file_uploads=40 | ||
| 13 | suhosin.upload.max_uploads=40 | ||
| 14 | --SKIPIF-- | ||
| 15 | <?php include('skipif.inc'); ?> | ||
| 16 | --COOKIE-- | ||
| 17 | --GET-- | ||
| 18 | --POST_RAW-- | ||
| 19 | Content-Type: multipart/form-data; boundary=bound | ||
| 20 | --bound | ||
| 21 | Content-Disposition: form-data; name="test"; filename="test" | ||
| 22 | |||
| 23 | Spaß am Gerät! | ||
| 24 | |||
| 25 | --bound-- | ||
| 26 | --FILE-- | ||
| 27 | <?php | ||
| 28 | var_dump(file_get_contents($_FILES['test']['tmp_name'])); | ||
| 29 | ?> | ||
| 30 | --EXPECTF-- | ||
| 31 | string(13) "Spa am Gert! | ||
| 32 | " \ No newline at end of file | ||
| @@ -197,6 +197,29 @@ return_failure: | |||
| 197 | } | 197 | } |
| 198 | /* }}} */ | 198 | /* }}} */ |
| 199 | 199 | ||
| 200 | static inline int suhosin_validate_utf8_multibyte(const char* cp) | ||
| 201 | { | ||
| 202 | if ((*cp & 0xe0) == 0xc0 && // 1st byte is 110xxxxx | ||
| 203 | (*(cp+1) & 0xc0) == 0x80 && // 2nd byte is 10xxxxxx | ||
| 204 | (*cp & 0x1e)) { // overlong check 110[xxxx]x 10xxxxxx | ||
| 205 | return 2; | ||
| 206 | } | ||
| 207 | if ((*cp & 0xf0) == 0xe0 && // 1st byte is 1110xxxx | ||
| 208 | (*(cp+1) & 0xc0) == 0x80 && // 2nd byte is 10xxxxxx | ||
| 209 | (*(cp+2) & 0xc0) == 0x80 && // 3rd byte is 10xxxxxx | ||
| 210 | ((*cp & 0x0f) | (*(cp+1) & 0x20))) { // 1110[xxxx] 10[x]xxxxx 10xxxxxx | ||
| 211 | return 3; | ||
| 212 | } | ||
| 213 | if ((*cp & 0xf8) == 0xf0 && // 1st byte is 11110xxx | ||
| 214 | (*(cp+1) & 0xc0) == 0x80 && // 2nd byte is 10xxxxxx | ||
| 215 | (*(cp+2) & 0xc0) == 0x80 && // 3rd byte is 10xxxxxx | ||
| 216 | (*(cp+3) & 0xc0) == 0x80 && // 4th byte is 10xxxxxx | ||
| 217 | ((*cp & 0x07) | (*(cp+1) & 0x30))) { // 11110[xxx] 10[xx]xxxx 10xxxxxx 10xxxxxx | ||
| 218 | return 4; | ||
| 219 | } | ||
| 220 | return 0; | ||
| 221 | } | ||
| 222 | |||
| 200 | int suhosin_rfc1867_filter(unsigned int event, void *event_data, void **extra TSRMLS_DC) | 223 | int suhosin_rfc1867_filter(unsigned int event, void *event_data, void **extra TSRMLS_DC) |
| 201 | { | 224 | { |
| 202 | int retval = SUCCESS; | 225 | int retval = SUCCESS; |
| @@ -250,38 +273,61 @@ int suhosin_rfc1867_filter(unsigned int event, void *event_data, void **extra TS | |||
| 250 | } | 273 | } |
| 251 | } | 274 | } |
| 252 | 275 | ||
| 253 | if (SUHOSIN_G(upload_disallow_binary)) { | 276 | if (SUHOSIN_G(upload_disallow_binary)) { |
| 254 | 277 | ||
| 255 | multipart_event_file_data *mefd = (multipart_event_file_data *) event_data; | 278 | multipart_event_file_data *mefd = (multipart_event_file_data *) event_data; |
| 256 | size_t i; | ||
| 257 | |||
| 258 | for (i=0; i<mefd->length; i++) { | ||
| 259 | if (mefd->data[i] < 32 && !isspace(mefd->data[i])) { | ||
| 260 | suhosin_log(S_FILES, "uploaded file contains binary data - file dropped"); | ||
| 261 | if (!SUHOSIN_G(simulation)) { | ||
| 262 | goto continue_with_failure; | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | } | ||
| 267 | 279 | ||
| 268 | if (SUHOSIN_G(upload_remove_binary)) { | 280 | char *cp, *cpend; |
| 269 | 281 | int n; | |
| 270 | multipart_event_file_data *mefd = (multipart_event_file_data *) event_data; | 282 | cpend = mefd->data + mefd->length; |
| 271 | size_t i, j; | 283 | for (char *cp = mefd->data; cp < cpend; cp++) { |
| 272 | 284 | if (*cp >= 32 || isspace(*cp)) { | |
| 273 | for (i=0, j=0; i<mefd->length; i++) { | 285 | continue; |
| 274 | if (mefd->data[i] >= 32 || isspace(mefd->data[i])) { | 286 | } |
| 275 | mefd->data[j++] = mefd->data[i]; | 287 | if ((*cp & 0x80) && SUHOSIN_G(upload_allow_utf8)) { |
| 276 | } | 288 | SDEBUG("checking char %x", *cp); |
| 277 | } | 289 | if ((n = suhosin_validate_utf8_multibyte(cp))) { // valid UTF8 multibyte character |
| 278 | SDEBUG("removing binary %u %u",i,j); | 290 | cp += n - 1; |
| 279 | /* IMPORTANT FOR DAISY CHAINING */ | 291 | continue; |
| 280 | mefd->length = j; | 292 | } |
| 281 | if (mefd->newlength) { | 293 | } |
| 282 | *mefd->newlength = j; | 294 | |
| 283 | } | 295 | suhosin_log(S_FILES, "uploaded file contains binary data - file dropped"); |
| 284 | } | 296 | if (!SUHOSIN_G(simulation)) { |
| 297 | goto continue_with_failure; | ||
| 298 | } | ||
| 299 | break; | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | if (SUHOSIN_G(upload_remove_binary)) { | ||
| 304 | |||
| 305 | multipart_event_file_data *mefd = (multipart_event_file_data *) event_data; | ||
| 306 | size_t i, j; | ||
| 307 | int n; | ||
| 308 | |||
| 309 | for (i=0, j=0; i<mefd->length; i++) { | ||
| 310 | if (mefd->data[i] >= 32 || isspace(mefd->data[i])) { | ||
| 311 | mefd->data[j++] = mefd->data[i]; | ||
| 312 | } else if (SUHOSIN_G(upload_allow_utf8) && mefd->data[i] & 0x80) { | ||
| 313 | n = suhosin_validate_utf8_multibyte(mefd->data + i); | ||
| 314 | if (!n) { continue; } | ||
| 315 | while (n) { | ||
| 316 | mefd->data[j++] = mefd->data[i++]; | ||
| 317 | n--; | ||
| 318 | } | ||
| 319 | i--; | ||
| 320 | } | ||
| 321 | } | ||
| 322 | mefd->data[j] = '\0'; | ||
| 323 | |||
| 324 | SDEBUG("removing binary %zu %zu",i,j); | ||
| 325 | /* IMPORTANT FOR DAISY CHAINING */ | ||
| 326 | mefd->length = j; | ||
| 327 | if (mefd->newlength) { | ||
| 328 | *mefd->newlength = j; | ||
| 329 | } | ||
| 330 | } | ||
| 285 | 331 | ||
| 286 | break; | 332 | break; |
| 287 | 333 | ||
