summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJulien Voisin2022-12-08 20:59:29 +0100
committerGitHub2022-12-08 20:59:29 +0100
commit93c2c5632e27549d95fb7d9493769f013e49a749 (patch)
tree97dcd84aed33b1d98095d0cf3f467e9dfb975f0c /src
parent3134f49d4de12d1e2507272e0e5022bdf5d60093 (diff)
parentccfaf3e4713b1878241f1235a6fcb66ad0582d47 (diff)
Add an `unserialize_noclass` option
Diffstat (limited to 'src')
-rw-r--r--src/php_snuffleupagus.h1
-rw-r--r--src/snuffleupagus.c8
-rw-r--r--src/sp_config.c1
-rw-r--r--src/sp_config.h6
-rw-r--r--src/sp_config_keywords.c18
-rw-r--r--src/sp_config_keywords.h1
-rw-r--r--src/sp_unserialize.c37
-rw-r--r--src/tests/unserialize/config/config_serialize_noclass.ini1
-rw-r--r--src/tests/unserialize/unserialize_noclass_forced.phpt21
-rw-r--r--src/tests/unserialize/unserialize_wrong_call.phpt2
-rw-r--r--src/tests/unserialize_php8/config/config_serialize_noclass.ini1
-rw-r--r--src/tests/unserialize_php8/config/config_serialize_noclass_disabled.ini1
-rw-r--r--src/tests/unserialize_php8/unserialize_noclass_forced.phpt38
-rw-r--r--src/tests/unserialize_php8/unserialize_noclass_forced_disabled.phpt35
14 files changed, 155 insertions, 16 deletions
diff --git a/src/php_snuffleupagus.h b/src/php_snuffleupagus.h
index 7c534a8..2117462 100644
--- a/src/php_snuffleupagus.h
+++ b/src/php_snuffleupagus.h
@@ -119,6 +119,7 @@ ZEND_BEGIN_MODULE_GLOBALS(snuffleupagus)
119sp_config_random config_random; 119sp_config_random config_random;
120sp_config_sloppy config_sloppy; 120sp_config_sloppy config_sloppy;
121sp_config_unserialize config_unserialize; 121sp_config_unserialize config_unserialize;
122sp_config_unserialize_noclass config_unserialize_noclass;
122sp_config_readonly_exec config_readonly_exec; 123sp_config_readonly_exec config_readonly_exec;
123sp_config_upload_validation config_upload_validation; 124sp_config_upload_validation config_upload_validation;
124sp_config_cookie config_cookie; 125sp_config_cookie config_cookie;
diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c
index be6240e..8454fc1 100644
--- a/src/snuffleupagus.c
+++ b/src/snuffleupagus.c
@@ -332,6 +332,8 @@ static void dump_config() {
332 array_init(&arr); 332 array_init(&arr);
333 add_assoc_string(&arr, "version", PHP_SNUFFLEUPAGUS_VERSION); 333 add_assoc_string(&arr, "version", PHP_SNUFFLEUPAGUS_VERSION);
334 334
335 add_assoc_bool(&arr, SP_TOKEN_UNSERIALIZE_NOCLASS "." SP_TOKEN_ENABLE, SPCFG(unserialize_noclass).enable);
336
335 add_assoc_bool(&arr, SP_TOKEN_UNSERIALIZE_HMAC "." SP_TOKEN_ENABLE, SPCFG(unserialize).enable); 337 add_assoc_bool(&arr, SP_TOKEN_UNSERIALIZE_HMAC "." SP_TOKEN_ENABLE, SPCFG(unserialize).enable);
336 add_assoc_bool(&arr, SP_TOKEN_UNSERIALIZE_HMAC "." SP_TOKEN_SIM, SPCFG(unserialize).simulation); 338 add_assoc_bool(&arr, SP_TOKEN_UNSERIALIZE_HMAC "." SP_TOKEN_SIM, SPCFG(unserialize).simulation);
337 ADD_ASSOC_ZSTR(&arr, SP_TOKEN_UNSERIALIZE_HMAC "." SP_TOKEN_DUMP, SPCFG(unserialize).dump); 339 ADD_ASSOC_ZSTR(&arr, SP_TOKEN_UNSERIALIZE_HMAC "." SP_TOKEN_DUMP, SPCFG(unserialize).dump);
@@ -562,10 +564,8 @@ static PHP_INI_MH(OnUpdateConfiguration) {
562 hook_session(); 564 hook_session();
563 } 565 }
564 566
565 if (NULL != SPCFG(encryption_key)) { 567 if ((NULL != SPCFG(encryption_key) && SPCFG(unserialize).enable) || SPCFG(unserialize_noclass).enable) {
566 if (SPCFG(unserialize).enable) { 568 hook_serialize();
567 hook_serialize();
568 }
569 } 569 }
570 570
571 hook_execute(); 571 hook_execute();
diff --git a/src/sp_config.c b/src/sp_config.c
index dbccb1a..8bd238a 100644
--- a/src/sp_config.c
+++ b/src/sp_config.c
@@ -8,6 +8,7 @@
8static zend_result sp_process_config_root(sp_parsed_keyword *parsed_rule) { 8static zend_result sp_process_config_root(sp_parsed_keyword *parsed_rule) {
9 sp_config_keyword sp_func[] = { 9 sp_config_keyword sp_func[] = {
10 {parse_unserialize, SP_TOKEN_UNSERIALIZE_HMAC, &(SPCFG(unserialize))}, 10 {parse_unserialize, SP_TOKEN_UNSERIALIZE_HMAC, &(SPCFG(unserialize))},
11 {parse_unserialize_noclass, SP_TOKEN_UNSERIALIZE_NOCLASS, &(SPCFG(unserialize_noclass))},
11 {parse_enable, SP_TOKEN_HARDEN_RANDOM, &(SPCFG(random).enable)}, 12 {parse_enable, SP_TOKEN_HARDEN_RANDOM, &(SPCFG(random).enable)},
12 {parse_log_media, SP_TOKEN_LOG_MEDIA, &(SPCFG(log_media))}, 13 {parse_log_media, SP_TOKEN_LOG_MEDIA, &(SPCFG(log_media))},
13 {parse_disabled_functions, SP_TOKEN_DISABLE_FUNC, NULL}, 14 {parse_disabled_functions, SP_TOKEN_DISABLE_FUNC, NULL},
diff --git a/src/sp_config.h b/src/sp_config.h
index ec73310..cddf816 100644
--- a/src/sp_config.h
+++ b/src/sp_config.h
@@ -83,6 +83,11 @@ typedef struct {
83 83
84typedef struct { 84typedef struct {
85 bool enable; 85 bool enable;
86 zend_string *textual_representation;
87} sp_config_unserialize_noclass;
88
89typedef struct {
90 bool enable;
86 bool simulation; 91 bool simulation;
87 zend_string *dump; 92 zend_string *dump;
88 zend_string *textual_representation; 93 zend_string *textual_representation;
@@ -202,6 +207,7 @@ typedef struct {
202#define SP_TOKEN_HARDEN_RANDOM "harden_random" 207#define SP_TOKEN_HARDEN_RANDOM "harden_random"
203#define SP_TOKEN_READONLY_EXEC "readonly_exec" 208#define SP_TOKEN_READONLY_EXEC "readonly_exec"
204#define SP_TOKEN_UNSERIALIZE_HMAC "unserialize_hmac" 209#define SP_TOKEN_UNSERIALIZE_HMAC "unserialize_hmac"
210#define SP_TOKEN_UNSERIALIZE_NOCLASS "unserialize_noclass"
205#define SP_TOKEN_UPLOAD_VALIDATION "upload_validation" 211#define SP_TOKEN_UPLOAD_VALIDATION "upload_validation"
206#define SP_TOKEN_XXE_PROTECTION "xxe_protection" 212#define SP_TOKEN_XXE_PROTECTION "xxe_protection"
207#define SP_TOKEN_EVAL_BLACKLIST "eval_blacklist" 213#define SP_TOKEN_EVAL_BLACKLIST "eval_blacklist"
diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c
index fa26635..ff834dd 100644
--- a/src/sp_config_keywords.c
+++ b/src/sp_config_keywords.c
@@ -74,6 +74,24 @@ SP_PARSEKW_FN(parse_log_media) {
74 return SP_PARSER_ERROR; 74 return SP_PARSER_ERROR;
75} 75}
76 76
77SP_PARSE_FN(parse_unserialize_noclass) {
78 bool enable = false, disable = false;
79 sp_config_unserialize_noclass *cfg = (sp_config_unserialize_noclass*)retval;
80
81 sp_config_keyword config_keywords[] = {
82 {parse_empty, SP_TOKEN_ENABLE, &(enable)},
83 {parse_empty, SP_TOKEN_DISABLE, &(disable)},
84 {0, 0, 0}};
85
86 SP_PROCESS_CONFIG_KEYWORDS_ERR();
87
88 SP_SET_ENABLE_DISABLE(enable, disable, cfg->enable);
89
90 cfg->textual_representation = sp_get_textual_representation(parsed_rule);
91
92 return SP_PARSER_STOP;
93}
94
77SP_PARSE_FN(parse_unserialize) { 95SP_PARSE_FN(parse_unserialize) {
78 bool enable = false, disable = false; 96 bool enable = false, disable = false;
79 sp_config_unserialize *cfg = (sp_config_unserialize*)retval; 97 sp_config_unserialize *cfg = (sp_config_unserialize*)retval;
diff --git a/src/sp_config_keywords.h b/src/sp_config_keywords.h
index 01eb0d1..27050e3 100644
--- a/src/sp_config_keywords.h
+++ b/src/sp_config_keywords.h
@@ -6,6 +6,7 @@ SP_PARSE_FN(parse_enable);
6SP_PARSE_FN(parse_global); 6SP_PARSE_FN(parse_global);
7SP_PARSE_FN(parse_cookie); 7SP_PARSE_FN(parse_cookie);
8SP_PARSE_FN(parse_unserialize); 8SP_PARSE_FN(parse_unserialize);
9SP_PARSE_FN(parse_unserialize_noclass);
9SP_PARSE_FN(parse_readonly_exec); 10SP_PARSE_FN(parse_readonly_exec);
10SP_PARSE_FN(parse_disabled_functions); 11SP_PARSE_FN(parse_disabled_functions);
11SP_PARSE_FN(parse_upload_validation); 12SP_PARSE_FN(parse_upload_validation);
diff --git a/src/sp_unserialize.c b/src/sp_unserialize.c
index 64cf1b5..641d989 100644
--- a/src/sp_unserialize.c
+++ b/src/sp_unserialize.c
@@ -61,6 +61,10 @@ PHP_FUNCTION(sp_serialize) {
61 orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); 61 orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
62 } 62 }
63 63
64 if (!SPCFG(unserialize).enable) {
65 return;
66 }
67
64 /* Compute the HMAC of the textual representation of the serialized data*/ 68 /* Compute the HMAC of the textual representation of the serialized data*/
65 zend_string *hmac = sp_do_hash_hmac_sha256(Z_STRVAL_P(return_value), Z_STRLEN_P(return_value), ZSTR_VAL(SPCFG(encryption_key)), ZSTR_LEN(SPCFG(encryption_key))); 69 zend_string *hmac = sp_do_hash_hmac_sha256(Z_STRVAL_P(return_value), Z_STRLEN_P(return_value), ZSTR_VAL(SPCFG(encryption_key)), ZSTR_LEN(SPCFG(encryption_key)));
66 70
@@ -84,19 +88,28 @@ PHP_FUNCTION(sp_serialize) {
84} 88}
85 89
86PHP_FUNCTION(sp_unserialize) { 90PHP_FUNCTION(sp_unserialize) {
87 zif_handler orig_handler;
88
89 char *buf = NULL; 91 char *buf = NULL;
90 char *serialized_str = NULL;
91 char *hmac = NULL;
92 size_t buf_len = 0; 92 size_t buf_len = 0;
93 zval *opts = NULL; 93 HashTable *opts = NULL;
94 94
95 const sp_config_unserialize *config_unserialize = &(SPCFG(unserialize)); 95 ZEND_PARSE_PARAMETERS_START(1, 2)
96 Z_PARAM_STRING(buf, buf_len)
97 Z_PARAM_OPTIONAL
98 Z_PARAM_ARRAY_HT(opts)
99 ZEND_PARSE_PARAMETERS_END();
96 100
97 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &buf, &buf_len, &opts) == 101 if (SPCFG(unserialize_noclass).enable) {
98 FAILURE) { 102#if PHP_VERSION_ID > 80000
99 RETURN_FALSE; 103 HashTable ht;
104 zend_hash_init(&ht, 1, NULL, NULL, 0);
105 zval zv;
106 ZVAL_FALSE(&zv);
107 zend_hash_str_add(&ht, "allowed_classes", sizeof("allowed_classes")-1, &zv);
108 php_unserialize_with_options(return_value, buf, buf_len, &ht, "unserialize");
109 return;
110#else
111 sp_log_drop("unserialize_noclass", "unserialize_noclass is only supported on PHP8+");
112#endif
100 } 113 }
101 114
102 /* 64 is the length of HMAC-256 */ 115 /* 64 is the length of HMAC-256 */
@@ -104,8 +117,8 @@ PHP_FUNCTION(sp_unserialize) {
104 sp_log_drop("unserialize", "The serialized object is too small."); 117 sp_log_drop("unserialize", "The serialized object is too small.");
105 } 118 }
106 119
107 hmac = buf + buf_len - 64; 120 char* hmac = buf + buf_len - 64;
108 serialized_str = ecalloc(buf_len - 64 + 1, 1); 121 char* serialized_str = ecalloc(buf_len - 64 + 1, 1);
109 memcpy(serialized_str, buf, buf_len - 64); 122 memcpy(serialized_str, buf, buf_len - 64);
110 123
111 zend_string *expected_hmac = sp_do_hash_hmac_sha256(serialized_str, strlen(serialized_str), ZSTR_VAL(SPCFG(encryption_key)), ZSTR_LEN(SPCFG(encryption_key))); 124 zend_string *expected_hmac = sp_do_hash_hmac_sha256(serialized_str, strlen(serialized_str), ZSTR_VAL(SPCFG(encryption_key)), ZSTR_LEN(SPCFG(encryption_key)));
@@ -117,11 +130,13 @@ PHP_FUNCTION(sp_unserialize) {
117 } 130 }
118 } else { status = 1; } 131 } else { status = 1; }
119 132
133 zif_handler orig_handler;
120 if (0 == status) { 134 if (0 == status) {
121 if ((orig_handler = zend_hash_str_find_ptr(SPG(sp_internal_functions_hook), ZEND_STRL("unserialize")))) { 135 if ((orig_handler = zend_hash_str_find_ptr(SPG(sp_internal_functions_hook), ZEND_STRL("unserialize")))) {
122 orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); 136 orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
123 } 137 }
124 } else { 138 } else {
139 const sp_config_unserialize *config_unserialize = &(SPCFG(unserialize));
125 if (config_unserialize->dump) { 140 if (config_unserialize->dump) {
126 sp_log_request(config_unserialize->dump, 141 sp_log_request(config_unserialize->dump,
127 config_unserialize->textual_representation); 142 config_unserialize->textual_representation);
diff --git a/src/tests/unserialize/config/config_serialize_noclass.ini b/src/tests/unserialize/config/config_serialize_noclass.ini
new file mode 100644
index 0000000..b84de51
--- /dev/null
+++ b/src/tests/unserialize/config/config_serialize_noclass.ini
@@ -0,0 +1 @@
sp.unserialize_noclass.enable();
diff --git a/src/tests/unserialize/unserialize_noclass_forced.phpt b/src/tests/unserialize/unserialize_noclass_forced.phpt
new file mode 100644
index 0000000..3b1e8d3
--- /dev/null
+++ b/src/tests/unserialize/unserialize_noclass_forced.phpt
@@ -0,0 +1,21 @@
1--TEST--
2Unserialize with noclass forced
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus") || PHP_VERSION_ID >= 80000) print "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_serialize_noclass.ini
7--FILE--
8<?php
9class C {
10 public $name;
11}
12$c = new C;
13$c->name = "test";
14
15$a = serialize($c);
16var_dump(unserialize($a, ['allowed_classes' => false]));
17var_dump(unserialize($a, ['allowed_classes' => true ]));
18var_dump(unserialize($a));
19?>
20--EXPECTF--
21Fatal error: [snuffleupagus][0.0.0.0][unserialize_noclass][drop] unserialize_noclass is only supported on PHP8+ in %s/tests/unserialize/unserialize_noclass_forced.php on line 9
diff --git a/src/tests/unserialize/unserialize_wrong_call.phpt b/src/tests/unserialize/unserialize_wrong_call.phpt
index a6fe140..afa42f6 100644
--- a/src/tests/unserialize/unserialize_wrong_call.phpt
+++ b/src/tests/unserialize/unserialize_wrong_call.phpt
@@ -12,4 +12,4 @@ var_dump(unserialize($a, "too", "many", "aaaaaaaarguments!"));
12?> 12?>
13--EXPECTF-- 13--EXPECTF--
14Warning: unserialize() expects at most 2 parameters, 4 given in %a/unserialize_wrong_call.php on line %d 14Warning: unserialize() expects at most 2 parameters, 4 given in %a/unserialize_wrong_call.php on line %d
15bool(false) 15NULL
diff --git a/src/tests/unserialize_php8/config/config_serialize_noclass.ini b/src/tests/unserialize_php8/config/config_serialize_noclass.ini
new file mode 100644
index 0000000..b84de51
--- /dev/null
+++ b/src/tests/unserialize_php8/config/config_serialize_noclass.ini
@@ -0,0 +1 @@
sp.unserialize_noclass.enable();
diff --git a/src/tests/unserialize_php8/config/config_serialize_noclass_disabled.ini b/src/tests/unserialize_php8/config/config_serialize_noclass_disabled.ini
new file mode 100644
index 0000000..0238772
--- /dev/null
+++ b/src/tests/unserialize_php8/config/config_serialize_noclass_disabled.ini
@@ -0,0 +1 @@
sp.unserialize_noclass.disable();
diff --git a/src/tests/unserialize_php8/unserialize_noclass_forced.phpt b/src/tests/unserialize_php8/unserialize_noclass_forced.phpt
new file mode 100644
index 0000000..9f276c5
--- /dev/null
+++ b/src/tests/unserialize_php8/unserialize_noclass_forced.phpt
@@ -0,0 +1,38 @@
1--TEST--
2Unserialize with noclass forced
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_serialize_noclass.ini
7--FILE--
8<?php
9class C {
10 public $name;
11}
12$c = new C;
13$c->name = "test";
14
15$a = serialize($c);
16var_dump(unserialize($a, ['allowed_classes' => false]));
17var_dump(unserialize($a, ['allowed_classes' => true ]));
18var_dump(unserialize($a));
19?>
20--EXPECT--
21object(__PHP_Incomplete_Class)#2 (2) {
22 ["__PHP_Incomplete_Class_Name"]=>
23 string(1) "C"
24 ["name"]=>
25 string(4) "test"
26}
27object(__PHP_Incomplete_Class)#2 (2) {
28 ["__PHP_Incomplete_Class_Name"]=>
29 string(1) "C"
30 ["name"]=>
31 string(4) "test"
32}
33object(__PHP_Incomplete_Class)#2 (2) {
34 ["__PHP_Incomplete_Class_Name"]=>
35 string(1) "C"
36 ["name"]=>
37 string(4) "test"
38}
diff --git a/src/tests/unserialize_php8/unserialize_noclass_forced_disabled.phpt b/src/tests/unserialize_php8/unserialize_noclass_forced_disabled.phpt
new file mode 100644
index 0000000..2c4223a
--- /dev/null
+++ b/src/tests/unserialize_php8/unserialize_noclass_forced_disabled.phpt
@@ -0,0 +1,35 @@
1--TEST--
2Unserialize with noclass forced disabled
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_serialize_noclass_disabled.ini
7--FILE--
8<?php
9class C {
10 public $name;
11}
12$c = new C;
13$c->name = "test";
14
15$a = serialize($c);
16var_dump(unserialize($a, ['allowed_classes' => false]));
17var_dump(unserialize($a, ['allowed_classes' => true ]));
18var_dump(unserialize($a));
19?>
20--EXPECT--
21object(__PHP_Incomplete_Class)#2 (2) {
22 ["__PHP_Incomplete_Class_Name"]=>
23 string(1) "C"
24 ["name"]=>
25 string(4) "test"
26}
27object(C)#2 (1) {
28 ["name"]=>
29 string(4) "test"
30}
31object(C)#2 (1) {
32 ["name"]=>
33 string(4) "test"
34}
35