summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorkkadosh2018-05-29 19:34:16 +0000
committerjvoisin2018-05-29 19:34:16 +0000
commit7832438b7abedf567ce6376f99949f419abcdff1 (patch)
tree560e43918d1dc36ce4cf760a5b27aed0c563bc1c /src
parent9eebe8c67e03e3041d454ea28e93996f7a67740b (diff)
Support session encryption
Implement session encryption.
Diffstat (limited to 'src')
-rw-r--r--src/config.m42
-rw-r--r--src/php_snuffleupagus.h2
-rw-r--r--src/snuffleupagus.c6
-rw-r--r--src/sp_config.c1
-rw-r--r--src/sp_config.h7
-rw-r--r--src/sp_config_keywords.c43
-rw-r--r--src/sp_config_keywords.h1
-rw-r--r--src/sp_cookie_encryption.c6
-rw-r--r--src/sp_crypt.c52
-rw-r--r--src/sp_crypt.h2
-rw-r--r--src/sp_session.c159
-rw-r--r--src/sp_session.h11
-rw-r--r--src/tests/config/config_crypt_session.ini2
-rw-r--r--src/tests/config/config_crypt_session_simul.ini3
-rw-r--r--src/tests/crypt_session_invalid.phpt24
-rw-r--r--src/tests/crypt_session_invalid_simul.phpt27
-rw-r--r--src/tests/crypt_session_read_uncrypt.phpt33
-rw-r--r--src/tests/crypt_session_valid.phpt27
-rw-r--r--src/tests/crypt_session_valid_simul.phpt27
-rw-r--r--src/tests/samesite_cookies.phpt51
20 files changed, 438 insertions, 48 deletions
diff --git a/src/config.m4 b/src/config.m4
index 9909da2..a4fea4d 100644
--- a/src/config.m4
+++ b/src/config.m4
@@ -6,7 +6,7 @@ sources="$sources sp_unserialize.c sp_utils.c sp_disable_xxe.c sp_list.c"
6sources="$sources sp_disabled_functions.c sp_execute.c sp_upload_validation.c" 6sources="$sources sp_disabled_functions.c sp_execute.c sp_upload_validation.c"
7sources="$sources sp_cookie_encryption.c sp_network_utils.c tweetnacl.c" 7sources="$sources sp_cookie_encryption.c sp_network_utils.c tweetnacl.c"
8sources="$sources sp_config_keywords.c sp_var_parser.c sp_var_value.c sp_tree.c" 8sources="$sources sp_config_keywords.c sp_var_parser.c sp_var_value.c sp_tree.c"
9sources="$sources sp_pcre_compat.c sp_crypt.c" 9sources="$sources sp_pcre_compat.c sp_crypt.c sp_session.c"
10 10
11PHP_ARG_ENABLE(snuffleupagus, whether to enable snuffleupagus support, 11PHP_ARG_ENABLE(snuffleupagus, whether to enable snuffleupagus support,
12[ --enable-snuffleupagus Enable snuffleupagus support]) 12[ --enable-snuffleupagus Enable snuffleupagus support])
diff --git a/src/php_snuffleupagus.h b/src/php_snuffleupagus.h
index c658dac..f80ae66 100644
--- a/src/php_snuffleupagus.h
+++ b/src/php_snuffleupagus.h
@@ -18,6 +18,7 @@
18#include "ext/standard/info.h" 18#include "ext/standard/info.h"
19#include "ext/standard/php_var.h" 19#include "ext/standard/php_var.h"
20#include "ext/pcre/php_pcre.h" 20#include "ext/pcre/php_pcre.h"
21#include "ext/session/php_session.h"
21#include "php.h" 22#include "php.h"
22#include "php_ini.h" 23#include "php_ini.h"
23#include "zend_hash.h" 24#include "zend_hash.h"
@@ -41,6 +42,7 @@
41#include "sp_upload_validation.h" 42#include "sp_upload_validation.h"
42#include "sp_utils.h" 43#include "sp_utils.h"
43#include "sp_crypt.h" 44#include "sp_crypt.h"
45#include "sp_session.h"
44 46
45 47
46extern zend_module_entry snuffleupagus_module_entry; 48extern zend_module_entry snuffleupagus_module_entry;
diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c
index 3cdcfb9..c3fc686 100644
--- a/src/snuffleupagus.c
+++ b/src/snuffleupagus.c
@@ -81,6 +81,7 @@ PHP_GINIT_FUNCTION(snuffleupagus) {
81 SP_INIT(snuffleupagus_globals->config.config_disabled_functions); 81 SP_INIT(snuffleupagus_globals->config.config_disabled_functions);
82 SP_INIT(snuffleupagus_globals->config.config_disabled_functions_ret); 82 SP_INIT(snuffleupagus_globals->config.config_disabled_functions_ret);
83 SP_INIT(snuffleupagus_globals->config.config_cookie); 83 SP_INIT(snuffleupagus_globals->config.config_cookie);
84 SP_INIT(snuffleupagus_globals->config.config_session);
84 SP_INIT(snuffleupagus_globals->config.config_disabled_constructs); 85 SP_INIT(snuffleupagus_globals->config.config_disabled_constructs);
85 SP_INIT(snuffleupagus_globals->config.config_eval); 86 SP_INIT(snuffleupagus_globals->config.config_eval);
86 87
@@ -124,6 +125,7 @@ PHP_MSHUTDOWN_FUNCTION(snuffleupagus) {
124 pefree(SNUFFLEUPAGUS_G(config.config_snuffleupagus), 1); 125 pefree(SNUFFLEUPAGUS_G(config.config_snuffleupagus), 1);
125 pefree(SNUFFLEUPAGUS_G(config.config_disable_xxe), 1); 126 pefree(SNUFFLEUPAGUS_G(config.config_disable_xxe), 1);
126 pefree(SNUFFLEUPAGUS_G(config.config_upload_validation), 1); 127 pefree(SNUFFLEUPAGUS_G(config.config_upload_validation), 1);
128 pefree(SNUFFLEUPAGUS_G(config.config_session), 1);
127 129
128#define FREE_LST_DISABLE(L) \ 130#define FREE_LST_DISABLE(L) \
129 do { \ 131 do { \
@@ -229,6 +231,10 @@ static PHP_INI_MH(OnUpdateConfiguration) {
229 } 231 }
230 hook_cookies(); 232 hook_cookies();
231 233
234 if (SNUFFLEUPAGUS_G(config).config_session->encrypt) {
235 hook_session();
236 }
237
232 if (true == SNUFFLEUPAGUS_G(config).config_global_strict->enable) { 238 if (true == SNUFFLEUPAGUS_G(config).config_global_strict->enable) {
233 if (!zend_get_extension(PHP_SNUFFLEUPAGUS_EXTNAME)) { 239 if (!zend_get_extension(PHP_SNUFFLEUPAGUS_EXTNAME)) {
234 zend_extension_entry.startup = NULL; 240 zend_extension_entry.startup = NULL;
diff --git a/src/sp_config.c b/src/sp_config.c
index 67140a0..a89174a 100644
--- a/src/sp_config.c
+++ b/src/sp_config.c
@@ -21,6 +21,7 @@ sp_config_tokens const sp_func[] = {
21 {.func = parse_disable_xxe, .token = SP_TOKEN_DISABLE_XXE}, 21 {.func = parse_disable_xxe, .token = SP_TOKEN_DISABLE_XXE},
22 {.func = parse_eval_blacklist, .token = SP_TOKEN_EVAL_BLACKLIST}, 22 {.func = parse_eval_blacklist, .token = SP_TOKEN_EVAL_BLACKLIST},
23 {.func = parse_eval_whitelist, .token = SP_TOKEN_EVAL_WHITELIST}, 23 {.func = parse_eval_whitelist, .token = SP_TOKEN_EVAL_WHITELIST},
24 {.func = parse_session, .token = SP_TOKEN_SESSION_ENCRYPTION},
24 {NULL, NULL}}; 25 {NULL, NULL}};
25 26
26/* Top level keyword parsing */ 27/* Top level keyword parsing */
diff --git a/src/sp_config.h b/src/sp_config.h
index e537ec2..b44960f 100644
--- a/src/sp_config.h
+++ b/src/sp_config.h
@@ -66,6 +66,11 @@ typedef struct {
66} sp_cookie; 66} sp_cookie;
67 67
68typedef struct { 68typedef struct {
69 bool encrypt;
70 bool simulation;
71} sp_config_session;
72
73typedef struct {
69 bool enable; 74 bool enable;
70 bool simulation; 75 bool simulation;
71 char *dump; 76 char *dump;
@@ -158,6 +163,7 @@ typedef struct {
158 sp_config_disable_xxe *config_disable_xxe; 163 sp_config_disable_xxe *config_disable_xxe;
159 sp_config_disabled_constructs *config_disabled_constructs; 164 sp_config_disabled_constructs *config_disabled_constructs;
160 sp_config_eval *config_eval; 165 sp_config_eval *config_eval;
166 sp_config_session *config_session;
161} sp_config; 167} sp_config;
162 168
163typedef struct { 169typedef struct {
@@ -175,6 +181,7 @@ typedef struct {
175 181
176#define SP_TOKEN_AUTO_COOKIE_SECURE ".auto_cookie_secure" 182#define SP_TOKEN_AUTO_COOKIE_SECURE ".auto_cookie_secure"
177#define SP_TOKEN_COOKIE_ENCRYPTION ".cookie" 183#define SP_TOKEN_COOKIE_ENCRYPTION ".cookie"
184#define SP_TOKEN_SESSION_ENCRYPTION ".session"
178#define SP_TOKEN_DISABLE_FUNC ".disable_function" 185#define SP_TOKEN_DISABLE_FUNC ".disable_function"
179#define SP_TOKEN_GLOBAL ".global" 186#define SP_TOKEN_GLOBAL ".global"
180#define SP_TOKEN_GLOBAL_STRICT ".global_strict" 187#define SP_TOKEN_GLOBAL_STRICT ".global_strict"
diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c
index 9faaafb..f702f4d 100644
--- a/src/sp_config_keywords.c
+++ b/src/sp_config_keywords.c
@@ -60,6 +60,49 @@ static int parse_enable(char *line, bool *restrict retval,
60 return ret; 60 return ret;
61} 61}
62 62
63int parse_session(char *line) {
64 sp_config_session *session =
65 pecalloc(sizeof(sp_config_session), 1, 0);
66
67 sp_config_functions sp_config_funcs_session_encryption[] = {
68 {parse_empty, SP_TOKEN_ENCRYPT, &(session->encrypt)},
69 {parse_empty, SP_TOKEN_SIMULATION, &(session->simulation)},
70 {0}};
71 int ret = parse_keywords(sp_config_funcs_session_encryption, line);
72 if (0 != ret) {
73 return ret;
74 }
75 if (session->encrypt) {
76 if (0 == (SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var)) {
77 sp_log_err(
78 "config",
79 "You're trying to use the session cookie encryption feature"
80 "on line %zu without having set the `.cookie_env_var` option in"
81 "`sp.global`: please set it first.",
82 sp_line_no);
83 pefree(session, 0);
84 return -1;
85 } else if (0 ==
86 (SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key)) {
87 sp_log_err(
88 "config",
89 "You're trying to use the session cookie encryption feature"
90 "on line %zu without having set the `.encryption_key` option in"
91 "`sp.global`: please set it first.",
92 sp_line_no);
93 pefree(session, 0);
94 return -1;
95 }
96 }
97
98 SNUFFLEUPAGUS_G(config).config_session->encrypt =
99 session->encrypt;
100 SNUFFLEUPAGUS_G(config).config_session->simulation =
101 session->simulation;
102 pefree(session, 0);
103 return ret;
104}
105
63int parse_random(char *line) { 106int parse_random(char *line) {
64 return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_random->enable), 107 return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_random->enable),
65 NULL); 108 NULL);
diff --git a/src/sp_config_keywords.h b/src/sp_config_keywords.h
index 4205dac..f1f414a 100644
--- a/src/sp_config_keywords.h
+++ b/src/sp_config_keywords.h
@@ -14,5 +14,6 @@ int parse_disabled_functions(char *line);
14int parse_upload_validation(char *line); 14int parse_upload_validation(char *line);
15int parse_eval_blacklist(char *line); 15int parse_eval_blacklist(char *line);
16int parse_eval_whitelist(char *line); 16int parse_eval_whitelist(char *line);
17int parse_session(char *line);
17 18
18#endif // __SP_CONFIG_KEYWORDS_H 19#endif // __SP_CONFIG_KEYWORDS_H
diff --git a/src/sp_cookie_encryption.c b/src/sp_cookie_encryption.c
index 9030112..72223ad 100644
--- a/src/sp_cookie_encryption.c
+++ b/src/sp_cookie_encryption.c
@@ -21,7 +21,6 @@ static inline const sp_cookie *sp_lookup_cookie_config(const char *key) {
21int decrypt_cookie(zval *pDest, int num_args, va_list args, 21int decrypt_cookie(zval *pDest, int num_args, va_list args,
22 zend_hash_key *hash_key) { 22 zend_hash_key *hash_key) {
23 const sp_cookie *cookie = sp_lookup_cookie_config(ZSTR_VAL(hash_key->key)); 23 const sp_cookie *cookie = sp_lookup_cookie_config(ZSTR_VAL(hash_key->key));
24 int ret = 0;
25 24
26 /* If the cookie isn't in the conf, it shouldn't be encrypted. */ 25 /* If the cookie isn't in the conf, it shouldn't be encrypted. */
27 if (!cookie || !cookie->encrypt) { 26 if (!cookie || !cookie->encrypt) {
@@ -36,11 +35,6 @@ int decrypt_cookie(zval *pDest, int num_args, va_list args,
36 return decrypt_zval(pDest, cookie->simulation, hash_key); 35 return decrypt_zval(pDest, cookie->simulation, hash_key);
37} 36}
38 37
39/*
40** This function will return the `data` of length `data_len` encrypted in the
41** form `base64(nonce | encrypted_data)` (with `|` being the concatenation
42** operation).
43*/
44static zend_string *encrypt_data(char *data, unsigned long long data_len) { 38static zend_string *encrypt_data(char *data, unsigned long long data_len) {
45 zend_string *z = encrypt_zval(data, data_len); 39 zend_string *z = encrypt_zval(data, data_len);
46 sp_log_debug("cookie_encryption", "Cookie value:%s:", z->val); 40 sp_log_debug("cookie_encryption", "Cookie value:%s:", z->val);
diff --git a/src/sp_crypt.c b/src/sp_crypt.c
index 0c40f1f..55ae37b 100644
--- a/src/sp_crypt.c
+++ b/src/sp_crypt.c
@@ -4,9 +4,7 @@
4 4
5ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) 5ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
6 6
7static zend_long nonce_d = 0; 7void generate_key(unsigned char *key) {
8
9static void generate_key(unsigned char *key) {
10 PHP_SHA256_CTX ctx; 8 PHP_SHA256_CTX ctx;
11 const char *user_agent = getenv("HTTP_USER_AGENT"); 9 const char *user_agent = getenv("HTTP_USER_AGENT");
12 const char *env_var = 10 const char *env_var =
@@ -50,14 +48,13 @@ int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) {
50 debase64 = php_base64_decode((unsigned char *)(Z_STRVAL_P(pDest)), 48 debase64 = php_base64_decode((unsigned char *)(Z_STRVAL_P(pDest)),
51 Z_STRLEN_P(pDest)); 49 Z_STRLEN_P(pDest));
52 50
53 if (ZSTR_LEN(debase64) < 51 if (ZSTR_LEN(debase64) < crypto_secretbox_NONCEBYTES) {
54 crypto_secretbox_NONCEBYTES + crypto_secretbox_ZEROBYTES) {
55 if (true == simulation) { 52 if (true == simulation) {
56 sp_log_msg( 53 sp_log_msg(
57 "cookie_encryption", SP_LOG_SIMULATION, 54 "cookie_encryption", SP_LOG_SIMULATION,
58 "Buffer underflow tentative detected in cookie encryption handling " 55 "Buffer underflow tentative detected in cookie encryption handling "
59 "for %s. Using the cookie 'as it' instead of decrypting it.", 56 "for %s. Using the cookie 'as it' instead of decrypting it.",
60 ZSTR_VAL(hash_key->key)); 57 hash_key ? ZSTR_VAL(hash_key->key) : "the session");
61 return ZEND_HASH_APPLY_KEEP; 58 return ZEND_HASH_APPLY_KEEP;
62 } else { 59 } else {
63 sp_log_msg( 60 sp_log_msg(
@@ -67,9 +64,26 @@ int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) {
67 } 64 }
68 } 65 }
69 66
67
68 if (ZSTR_LEN(debase64) + (size_t)crypto_secretbox_ZEROBYTES < ZSTR_LEN(debase64)) {
69 if (true == simulation) {
70 sp_log_msg(
71 "cookie_encryption", SP_LOG_SIMULATION,
72 "Integer overflow tentative detected in cookie encryption handling "
73 "for %s. Using the cookie 'as it' instead of decrypting it.",
74 hash_key ? ZSTR_VAL(hash_key->key) : "the session");
75 return ZEND_HASH_APPLY_KEEP;
76 } else {
77 sp_log_msg(
78 "cookie_encryption", SP_LOG_DROP,
79 "Integer overflow tentative detected in cookie encryption handling.");
80 return ZEND_HASH_APPLY_REMOVE;
81 }
82 }
83
70 generate_key(key); 84 generate_key(key);
71 85
72 decrypted = ecalloc(ZSTR_LEN(debase64), 1); 86 decrypted = ecalloc(ZSTR_LEN(debase64) + crypto_secretbox_ZEROBYTES, 1);
73 87
74 ret = crypto_secretbox_open( 88 ret = crypto_secretbox_open(
75 decrypted, 89 decrypted,
@@ -83,12 +97,12 @@ int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) {
83 "cookie_encryption", SP_LOG_SIMULATION, 97 "cookie_encryption", SP_LOG_SIMULATION,
84 "Something went wrong with the decryption of %s. Using the cookie " 98 "Something went wrong with the decryption of %s. Using the cookie "
85 "'as it' instead of decrypting it", 99 "'as it' instead of decrypting it",
86 ZSTR_VAL(hash_key->key)); 100 hash_key ? ZSTR_VAL(hash_key->key) : "the session");
87 return ZEND_HASH_APPLY_KEEP; 101 return ZEND_HASH_APPLY_KEEP;
88 } else { 102 } else {
89 sp_log_msg("cookie_encryption", SP_LOG_DROP, 103 sp_log_msg("cookie_encryption", SP_LOG_DROP,
90 "Something went wrong with the decryption of %s.", 104 "Something went wrong with the decryption of %s.",
91 ZSTR_VAL(hash_key->key)); 105 hash_key ? ZSTR_VAL(hash_key->key) : "the session");
92 return ZEND_HASH_APPLY_REMOVE; 106 return ZEND_HASH_APPLY_REMOVE;
93 } 107 }
94 } 108 }
@@ -100,8 +114,14 @@ int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) {
100 return ZEND_HASH_APPLY_KEEP; 114 return ZEND_HASH_APPLY_KEEP;
101} 115}
102 116
117/*
118** This function will return the `data` of length `data_len` encrypted in the
119** form `base64(nonce | encrypted_data)` (with `|` being the concatenation
120** operation).
121*/
103zend_string *encrypt_zval(char *data, unsigned long long data_len) { 122zend_string *encrypt_zval(char *data, unsigned long long data_len) {
104 const size_t encrypted_msg_len = crypto_secretbox_ZEROBYTES + data_len + 1; 123 const size_t encrypted_msg_len = crypto_secretbox_ZEROBYTES + data_len + 1;
124 // FIXME : We know that this len is too long
105 const size_t emsg_and_nonce_len = 125 const size_t emsg_and_nonce_len =
106 encrypted_msg_len + crypto_secretbox_NONCEBYTES; 126 encrypted_msg_len + crypto_secretbox_NONCEBYTES;
107 127
@@ -112,25 +132,21 @@ zend_string *encrypt_zval(char *data, unsigned long long data_len) {
112 132
113 generate_key(key); 133 generate_key(key);
114 134
135 // Put random bytes in the nonce
136 php_random_bytes(nonce, sizeof(nonce), 0);
137
115 /* tweetnacl's API requires the message to be padded with 138 /* tweetnacl's API requires the message to be padded with
116 crypto_secretbox_ZEROBYTES zeroes. */ 139 crypto_secretbox_ZEROBYTES zeroes. */
117 memcpy(data_to_encrypt + crypto_secretbox_ZEROBYTES, data, data_len); 140 memcpy(data_to_encrypt + crypto_secretbox_ZEROBYTES, data, data_len);
118 141
119 assert(sizeof(zend_long) <= crypto_secretbox_NONCEBYTES); 142 assert(sizeof(zend_long) <= crypto_secretbox_NONCEBYTES);
120 143
121 if (0 == nonce_d) {
122 /* A zend_long should be enough to avoid collisions */
123 if (php_random_int_throw(0, ZEND_LONG_MAX, &nonce_d) == FAILURE) {
124 return NULL; // LCOV_EXCL_LINE
125 }
126 }
127 nonce_d++;
128 sscanf((char *)nonce, "%ld", &nonce_d);
129
130 memcpy(encrypted_data, nonce, crypto_secretbox_NONCEBYTES); 144 memcpy(encrypted_data, nonce, crypto_secretbox_NONCEBYTES);
145
131 crypto_secretbox(encrypted_data + crypto_secretbox_NONCEBYTES, 146 crypto_secretbox(encrypted_data + crypto_secretbox_NONCEBYTES,
132 data_to_encrypt, encrypted_msg_len, nonce, key); 147 data_to_encrypt, encrypted_msg_len, nonce, key);
133 148
134 zend_string *z = php_base64_encode(encrypted_data, emsg_and_nonce_len); 149 zend_string *z = php_base64_encode(encrypted_data, emsg_and_nonce_len);
150
135 return z; 151 return z;
136} \ No newline at end of file 152} \ No newline at end of file
diff --git a/src/sp_crypt.h b/src/sp_crypt.h
index 1852a0a..3ede104 100644
--- a/src/sp_crypt.h
+++ b/src/sp_crypt.h
@@ -10,7 +10,7 @@
10#include "ext/hash/php_hash_sha.h" 10#include "ext/hash/php_hash_sha.h"
11#include "ext/standard/base64.h" 11#include "ext/standard/base64.h"
12 12
13static void generate_key(unsigned char *key); 13void generate_key(unsigned char *key);
14int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hask_key); 14int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hask_key);
15zend_string *encrypt_zval(char *data, unsigned long long data_len); 15zend_string *encrypt_zval(char *data, unsigned long long data_len);
16 16
diff --git a/src/sp_session.c b/src/sp_session.c
new file mode 100644
index 0000000..4085007
--- /dev/null
+++ b/src/sp_session.c
@@ -0,0 +1,159 @@
1#include "php_snuffleupagus.h"
2#include "ext/session/php_session.h"
3
4ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus);
5
6#ifdef ZTS
7static ts_rsrc_id session_globals_id = 0;
8#define SESSION_G(v) ZEND_TSRMG(session_globals_id, php_ps_globals *, v)
9#ifdef COMPILE_DL_SESSION
10ZEND_TSRMLS_CACHE_EXTERN();
11#endif
12#else
13#define SESSION_G(v) (ps_globals.v)
14#endif
15
16static php_ps_globals *session_globals = NULL;
17static ps_module *s_module;
18static ps_module *s_original_mod;
19static int (*old_s_read)(PS_READ_ARGS);
20static int (*old_s_write)(PS_WRITE_ARGS);
21static int (*previous_sessionRINIT)(INIT_FUNC_ARGS) = NULL;
22static ZEND_INI_MH((*old_OnUpdateSaveHandler)) = NULL;
23
24
25static int sp_hook_s_read(PS_READ_ARGS) {
26 int r = old_s_read(mod_data, key, val, maxlifetime);
27 if (r == SUCCESS && SNUFFLEUPAGUS_G(config).config_session->encrypt &&
28 val != NULL && *val != NULL && ZSTR_LEN(*val)) {
29 zend_string *orig_val = *val;
30 zval val_zval;
31 ZVAL_PSTRINGL(&val_zval, ZSTR_VAL(*val), ZSTR_LEN(*val));
32
33 int ret = decrypt_zval(
34 &val_zval, SNUFFLEUPAGUS_G(config).config_session->simulation,
35 NULL);
36 if (0 != ret) {
37 if (SNUFFLEUPAGUS_G(config).config_session->simulation) {
38 return ret;
39 } else {
40 sp_terminate();
41 }
42 }
43
44 *val = zend_string_dup(val_zval.value.str, 0);
45 if (*val == NULL) {
46 *val = ZSTR_EMPTY_ALLOC();
47 }
48 zend_string_release(orig_val);
49 }
50
51 return r;
52}
53
54
55static int sp_hook_s_write(PS_WRITE_ARGS) {
56 if (ZSTR_LEN(val) > 0 &&
57 SNUFFLEUPAGUS_G(config).config_session->encrypt) {
58 zend_string *new_val = encrypt_zval(ZSTR_VAL(val), ZSTR_LEN(val));
59 return old_s_write(mod_data, key, new_val, maxlifetime);
60 }
61 return old_s_write(mod_data, key, val, maxlifetime);
62}
63
64static void sp_hook_session_module() {
65 ps_module *old_mod = SESSION_G(mod);
66 ps_module *mod;
67
68 if (old_mod == NULL || s_module == old_mod) {
69 return;
70 }
71
72 if (s_module == NULL) {
73 s_module = mod = malloc(sizeof(ps_module));
74 if (mod == NULL) {
75 return;
76 }
77 }
78
79 s_original_mod = old_mod;
80
81 mod = s_module;
82 memcpy(mod, old_mod, sizeof(ps_module));
83
84 old_s_read = mod->s_read;
85 mod->s_read = sp_hook_s_read;
86
87 old_s_write = mod->s_write;
88 mod->s_write = sp_hook_s_write;
89
90 SESSION_G(mod) = mod;
91}
92
93static PHP_INI_MH(sp_OnUpdateSaveHandler) {
94 if (stage == PHP_INI_STAGE_RUNTIME &&
95 SESSION_G(session_status) == php_session_none &&
96 s_original_mod &&
97 zend_string_equals_literal(new_value, "user") == 0 &&
98 strcmp(((ps_module *)s_original_mod)->s_name, "user") ==
99 0) {
100 return SUCCESS;
101 }
102
103 SESSION_G(mod) = s_original_mod;
104
105 int r = old_OnUpdateSaveHandler(entry, new_value, mh_arg1, mh_arg2, mh_arg3,
106 stage);
107
108 sp_hook_session_module();
109
110 return r;
111}
112
113static int sp_hook_session_RINIT(INIT_FUNC_ARGS) {
114 if (SESSION_G(mod) == NULL) {
115 zend_ini_entry *ini_entry;
116 if ((ini_entry = zend_hash_str_find_ptr(
117 EG(ini_directives), ZEND_STRL("session.save_handler")))) {
118 if (ini_entry->value) {
119 sp_OnUpdateSaveHandler(NULL, ini_entry->value, NULL, NULL, NULL, 0);
120 }
121 }
122 }
123 return previous_sessionRINIT(INIT_FUNC_ARGS_PASSTHRU);
124}
125
126void hook_session() {
127 zend_module_entry *module;
128
129 if ((module = zend_hash_str_find_ptr(&module_registry,
130 ZEND_STRL("session"))) == NULL) {
131 return;
132 }
133
134#ifdef ZTS
135 if (session_globals_id == 0) {
136 session_globals_id = *module->globals_id_ptr;
137 }
138#else
139 if (session_globals == NULL) {
140 session_globals = module->globals_ptr;
141 }
142#endif
143 if (old_OnUpdateSaveHandler != NULL) {
144 return;
145 }
146
147 previous_sessionRINIT = module->request_startup_func;
148 module->request_startup_func = sp_hook_session_RINIT;
149
150 zend_ini_entry *ini_entry;
151 if ((ini_entry = zend_hash_str_find_ptr(
152 EG(ini_directives), ZEND_STRL("session.save_handler"))) != NULL) {
153 old_OnUpdateSaveHandler = ini_entry->on_modify;
154 ini_entry->on_modify = sp_OnUpdateSaveHandler;
155 }
156 s_module = NULL;
157
158 sp_hook_session_module();
159} \ No newline at end of file
diff --git a/src/sp_session.h b/src/sp_session.h
new file mode 100644
index 0000000..c2a0357
--- /dev/null
+++ b/src/sp_session.h
@@ -0,0 +1,11 @@
1#include "SAPI.h"
2#include "tweetnacl.h"
3
4#include "sp_utils.h"
5
6#include "ext/hash/php_hash.h"
7#include "ext/hash/php_hash_sha.h"
8#include "ext/standard/base64.h"
9
10
11void hook_session();
diff --git a/src/tests/config/config_crypt_session.ini b/src/tests/config/config_crypt_session.ini
new file mode 100644
index 0000000..14b0c2c
--- /dev/null
+++ b/src/tests/config/config_crypt_session.ini
@@ -0,0 +1,2 @@
1sp.global.secret_key("abcdef").cookie_env_var("REMOTE_ADDR");
2sp.session.encrypt(); \ No newline at end of file
diff --git a/src/tests/config/config_crypt_session_simul.ini b/src/tests/config/config_crypt_session_simul.ini
new file mode 100644
index 0000000..fbd43eb
--- /dev/null
+++ b/src/tests/config/config_crypt_session_simul.ini
@@ -0,0 +1,3 @@
1sp.global.secret_key("abcdef").cookie_env_var("REMOTE_ADDR");
2sp.session.encrypt();
3sp.session.simulation(); \ No newline at end of file
diff --git a/src/tests/crypt_session_invalid.phpt b/src/tests/crypt_session_invalid.phpt
new file mode 100644
index 0000000..687a407
--- /dev/null
+++ b/src/tests/crypt_session_invalid.phpt
@@ -0,0 +1,24 @@
1--TEST--
2SESSION crypt and bad decrypt
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) die "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_crypt_session.ini
7--ENV--
8return <<<EOF
9REMOTE_ADDR=127.0.0.1
10EOF;
11--FILE--
12<?php
13// Do it like that to write (encrypt) the session and then to read (decrypt) the session
14session_start(); // Start new_session , it will read an empty session
15$_SESSION["toto"] = "tata"; // Encrypt and write the session
16$id = session_id(); // Get the session_id to use it later
17session_write_close(); // Close the session
18putenv("REMOTE_ADDR=127.0.0.2");
19session_id($id); // Recover the session with the previous session_id
20session_start(); // Re start the session, It will read and decrypt the non empty session
21var_dump($_SESSION); // Dump the session
22?>
23--EXPECTF--
24[snuffleupagus][127.0.0.2][cookie_encryption][drop] Something went wrong with the decryption of the session. \ No newline at end of file
diff --git a/src/tests/crypt_session_invalid_simul.phpt b/src/tests/crypt_session_invalid_simul.phpt
new file mode 100644
index 0000000..7bfefcb
--- /dev/null
+++ b/src/tests/crypt_session_invalid_simul.phpt
@@ -0,0 +1,27 @@
1--TEST--
2SESSION crypt and bad decrypt
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) die "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_crypt_session_simul.ini
7--ENV--
8return <<<EOF
9REMOTE_ADDR=127.0.0.1
10EOF;
11--FILE--
12<?php
13// Do it like that to write (encrypt) the session and then to read (decrypt) the session
14session_start(); // Start new_session , it will read an empty session
15$_SESSION["toto"] = "tata"; // Encrypt and write the session
16$id = session_id(); // Get the session_id to use it later
17session_write_close(); // Close the session
18putenv("REMOTE_ADDR=127.0.0.2");
19session_id($id); // Recover the session with the previous session_id
20session_start(); // Re start the session, It will read and decrypt the non empty session
21var_dump($_SESSION); // Dump the session
22?>
23--EXPECTF--
24array(1) {
25 ["toto"]=>
26 string(4) "tata"
27}
diff --git a/src/tests/crypt_session_read_uncrypt.phpt b/src/tests/crypt_session_read_uncrypt.phpt
new file mode 100644
index 0000000..f15d8b6
--- /dev/null
+++ b/src/tests/crypt_session_read_uncrypt.phpt
@@ -0,0 +1,33 @@
1--TEST--
2SESSION crypt/decrypt valid
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) die "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_crypt_session_simul.ini
7--ENV--
8return <<<EOF
9REMOTE_ADDR=127.0.0.1
10EOF;
11--FILE--
12<?php
13$current_path = dirname(getcwd()) . "/src/tests/" ;
14ini_set("session.save_path", $current_path);
15
16session_start();
17$id = session_id(); // Get the session_id to use it later
18$filename_sess = $current_path . "sess_" . $id;
19file_put_contents($filename_sess, "toto|s:4:\"tata\";"); // Write a unencrypted session
20session_write_close(); // Close the session
21
22session_id($id);
23session_start(); // Try to read the unencrypted session, it will fail to decrypt but it must return the session
24var_dump($_SESSION);
25echo "OK";
26unlink($filename_sess);
27?>
28--EXPECTF--
29array(1) {
30 ["toto"]=>
31 string(4) "tata"
32}
33OK
diff --git a/src/tests/crypt_session_valid.phpt b/src/tests/crypt_session_valid.phpt
new file mode 100644
index 0000000..bf9fea0
--- /dev/null
+++ b/src/tests/crypt_session_valid.phpt
@@ -0,0 +1,27 @@
1--TEST--
2SESSION crypt/decrypt valid
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) die "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_crypt_session.ini
7--ENV--
8return <<<EOF
9REMOTE_ADDR=127.0.0.1
10EOF;
11--FILE--
12<?php
13// Do it like that to write (encrypt) the session and then to read (decrypt) the session
14session_start(); // Start new_session , it will read an empty session
15$_SESSION["toto"] = "tata"; // Encrypt and write the session
16$id = session_id(); // Get the session_id to use it later
17
18session_write_close(); // Close the session
19session_id($id); // Recover the session with the previous session_id
20session_start(); // Re start the session, It will read and decrypt the non empty session
21var_dump($_SESSION); // Dump the session
22?>
23--EXPECTF--
24array(1) {
25 ["toto"]=>
26 string(4) "tata"
27}
diff --git a/src/tests/crypt_session_valid_simul.phpt b/src/tests/crypt_session_valid_simul.phpt
new file mode 100644
index 0000000..28083cf
--- /dev/null
+++ b/src/tests/crypt_session_valid_simul.phpt
@@ -0,0 +1,27 @@
1--TEST--
2SESSION crypt/decrypt valid
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) die "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_crypt_session_simul.ini
7--ENV--
8return <<<EOF
9REMOTE_ADDR=127.0.0.1
10EOF;
11--FILE--
12<?php
13// Do it like that to write (encrypt) the session and then to read (decrypt) the session
14session_start(); // Start new_session , it will read an empty session
15$_SESSION["toto"] = "tata"; // Encrypt and write the session
16$id = session_id(); // Get the session_id to use it later
17session_write_close(); // Close the session
18
19session_id($id); // Recover the session with the previous session_id
20session_start(); // Re start the session, It will read and decrypt the non empty session
21var_dump($_SESSION); // Dump the session
22?>
23--EXPECTF--
24array(1) {
25 ["toto"]=>
26 string(4) "tata"
27}
diff --git a/src/tests/samesite_cookies.phpt b/src/tests/samesite_cookies.phpt
index fe74172..d010963 100644
--- a/src/tests/samesite_cookies.phpt
+++ b/src/tests/samesite_cookies.phpt
@@ -27,12 +27,13 @@ if (!setcookie("nice_cookie", "nice_value", 1, "1", "1", true, true)) {
27 echo "setcookie failed.\n"; 27 echo "setcookie failed.\n";
28} 28}
29 29
30// If the cookie value start with "!", it means that we don't want the value in the headers, but the encrypted cookie
30$expected = array( 31$expected = array(
31 'Set-Cookie: super_cookie=super_value; path=; samesite=Lax', 32 "awful_cookie" => "!awful_value",
32 'Set-Cookie: awful_cookie=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFyZcYjfEskB0AU0V3%2BvwazcRuU%2Ft6KpcUahvxw%3D; path=; samesite=Strict; HttpOnly', 33 "not_encrypted" => "test_value",
33 'Set-Cookie: not_encrypted=test_value; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=1; domain=1; HttpOnly', 34 "nice_cookie" => "!nice_value",
34 'Set-Cookie: nice_cookie=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ8ko%2ByA4y%2Bmw5MGBx8fgc3TWOAvhIu%2BfF%2Bx2g%3D%3D; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=1; samesite=Strict; domain=1; secure; HttpOnly', 35 "super_cookie" => "super_value",
35 ); 36);
36 37
37$headers = headers_list(); 38$headers = headers_list();
38if (($i = count($expected)) > count($headers)) 39if (($i = count($expected)) > count($headers))
@@ -41,31 +42,37 @@ if (($i = count($expected)) > count($headers))
41 return; 42 return;
42} 43}
43 44
44do 45$i = 0;
45{ 46
47do {
46 if (strncmp(current($headers), 'Set-Cookie:', 11) !== 0) 48 if (strncmp(current($headers), 'Set-Cookie:', 11) !== 0)
47 { 49 {
48 continue; 50 continue;
49 } 51 }
50 52 foreach ($expected as $key => $value) {
51 if (current($headers) === current($expected)) 53 if (strpos(current($headers), $key) !== false) { // If the header contains the cookie
52 { 54 if (substr($value, 0, 1) === "!") { // ! is because we don't want to see the cookie value in plaintext, it must be encrypted
53 $i--; 55 if (strpos(current($headers), substr($value,1,-1)) === false) { // If the header doesn't contain de cookie value, it's good
56 $i++;
57 break;
58 }
59 echo "Received : " . current($headers) . " and the cookie isn't encrypted . \n";
60 } else {
61 if (strpos(current($headers), $value) !== false) {
62 $i++;
63 break;
64 }
65 echo "Received : " . current($headers) . " and the cookie value of " . $key . " doesn't match it's value. \n";
66 }
67 break;
68 }
54 } 69 }
55 else
56 {
57 echo "Header mismatch:\n\tExpected: "
58 .current($expected)
59 ."\n\tReceived: ".current($headers)."\n";
60 }
61
62 next($expected);
63} 70}
64while (next($headers) !== FALSE); 71while (next($headers));
65 72
66echo ($i === 0) 73echo ($i === 4)
67 ? 'OK' 74 ? 'OK'
68 : 'A total of '.$i.' errors found.'; 75 : 'A total of '. (count($expected) - $i) .' errors found.';
69?> 76?>
70--EXPECT-- 77--EXPECT--
71OK 78OK