diff options
| -rw-r--r-- | doc/source/config.rst | 100 | ||||
| -rw-r--r-- | doc/source/encryption.rst | 132 | ||||
| -rw-r--r-- | doc/source/features.rst | 9 | ||||
| -rw-r--r-- | doc/source/index.rst | 1 | ||||
| -rw-r--r-- | src/config.m4 | 2 | ||||
| -rw-r--r-- | src/php_snuffleupagus.h | 2 | ||||
| -rw-r--r-- | src/snuffleupagus.c | 6 | ||||
| -rw-r--r-- | src/sp_config.c | 1 | ||||
| -rw-r--r-- | src/sp_config.h | 7 | ||||
| -rw-r--r-- | src/sp_config_keywords.c | 43 | ||||
| -rw-r--r-- | src/sp_config_keywords.h | 1 | ||||
| -rw-r--r-- | src/sp_cookie_encryption.c | 6 | ||||
| -rw-r--r-- | src/sp_crypt.c | 52 | ||||
| -rw-r--r-- | src/sp_crypt.h | 2 | ||||
| -rw-r--r-- | src/sp_session.c | 159 | ||||
| -rw-r--r-- | src/sp_session.h | 11 | ||||
| -rw-r--r-- | src/tests/config/config_crypt_session.ini | 2 | ||||
| -rw-r--r-- | src/tests/config/config_crypt_session_simul.ini | 3 | ||||
| -rw-r--r-- | src/tests/crypt_session_invalid.phpt | 24 | ||||
| -rw-r--r-- | src/tests/crypt_session_invalid_simul.phpt | 27 | ||||
| -rw-r--r-- | src/tests/crypt_session_read_uncrypt.phpt | 33 | ||||
| -rw-r--r-- | src/tests/crypt_session_valid.phpt | 27 | ||||
| -rw-r--r-- | src/tests/crypt_session_valid_simul.phpt | 27 | ||||
| -rw-r--r-- | src/tests/samesite_cookies.phpt | 51 |
24 files changed, 577 insertions, 151 deletions
diff --git a/doc/source/config.rst b/doc/source/config.rst index d8389b6..b5bcad4 100644 --- a/doc/source/config.rst +++ b/doc/source/config.rst | |||
| @@ -68,7 +68,7 @@ This configuration variable contains parameters that are used by multiple featur | |||
| 68 | sp.global.secret_key("44239bd400aa82e125337c9d4eb8315767411ccd"); | 68 | sp.global.secret_key("44239bd400aa82e125337c9d4eb8315767411ccd"); |
| 69 | 69 | ||
| 70 | - ``cookie_env_var``: A environment variable used as part of cookies encryption. | 70 | - ``cookie_env_var``: A environment variable used as part of cookies encryption. |
| 71 | See the :ref:`relevant documentation <env-var-config>`. | 71 | See the :ref:`relevant documentation <cookie-encryption-config>` |
| 72 | 72 | ||
| 73 | Bugclass-killer features | 73 | Bugclass-killer features |
| 74 | ------------------------ | 74 | ------------------------ |
| @@ -123,103 +123,9 @@ It can either be ``enabled`` or ``disabled`` and can be used in ``simulation`` m | |||
| 123 | Cookies-related mitigations | 123 | Cookies-related mitigations |
| 124 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 124 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 125 | 125 | ||
| 126 | .. warning:: | 126 | Since snuffleupagus is providing several hardening features for cookies, |
| 127 | Those features are **not** available for session cookies `yet <https://github.com/nbs-system/snuffleupagus/issues/122>`_. | 127 | there is a :dedicated web page:`here <cookie-encryption-config>` about them. |
| 128 | |||
| 129 | auto_cookie_secure | ||
| 130 | """""""""""""""""" | ||
| 131 | |||
| 132 | :ref:`auto_cookie_secure <auto-cookie-secure-feature>`, disabled by default, | ||
| 133 | will automatically mark cookies as `secure | ||
| 134 | <https://en.wikipedia.org/wiki/HTTP_cookie#Secure_cookie>`_ when the web page | ||
| 135 | is requested over HTTPS. | ||
| 136 | |||
| 137 | It can either be ``enabled`` or ``disabled``. | ||
| 138 | |||
| 139 | :: | ||
| 140 | |||
| 141 | sp.auto_cookie_secure.enable(); | ||
| 142 | sp.auto_cookie_secure.disable(); | ||
| 143 | |||
| 144 | cookie_samesite | ||
| 145 | """"""""""""""" | ||
| 146 | |||
| 147 | :ref:`samesite <samesite-feature>`, disabled by default, will add the `samesite | ||
| 148 | <https://tools.ietf.org/html/draft-west-first-party-cookies-07>`_ attribute to | ||
| 149 | cookies. It `prevents CSRF <https://www.owasp.org/index.php/SameSite>`_ but is | ||
| 150 | not implemented by `all web browsers <https://caniuse.com/#search=samesite>`_ | ||
| 151 | yet. | ||
| 152 | |||
| 153 | It can either be set to ``strict`` or ``lax``: | ||
| 154 | |||
| 155 | - The ``lax`` attribute prevents cookies from being sent cross-domain for | ||
| 156 | "dangerous" methods, like ``POST``, ``PUT`` or ``DELETE``. | ||
| 157 | |||
| 158 | - The ``strict`` one prevents any cookies from beind sent cross-domain. | ||
| 159 | |||
| 160 | :: | ||
| 161 | |||
| 162 | sp.cookie.name("cookie1").samesite("lax"); | ||
| 163 | sp.cookie.name("cookie2").samesite("strict");; | ||
| 164 | |||
| 165 | .. _cookie-encryption_config: | ||
| 166 | |||
| 167 | cookie_encryption | ||
| 168 | """"""""""""""""" | ||
| 169 | |||
| 170 | .. warning:: | ||
| 171 | |||
| 172 | To use this feature, you **must** set the :ref:`global.secret_key <config_global>` | ||
| 173 | and the :ref:`global.cookie_env_var <config_global>` variables. | ||
| 174 | This design decision prevents an attacker from | ||
| 175 | `trivially bruteforcing <https://www.idontplaydarts.com/2011/11/decrypting-suhosin-sessions-and-cookies/>`_ | ||
| 176 | or re-using session cookies. | ||
| 177 | |||
| 178 | :ref:`cookie_secure <cookie-encryption-feature>`, disabled by default, will activate transparent encryption of specific cookies. | ||
| 179 | |||
| 180 | It can either be ``enabled`` or ``disabled`` and can be used in ``simulation`` mode. | ||
| 181 | |||
| 182 | :: | ||
| 183 | |||
| 184 | sp.cookie.name("my_cookie_name").encrypt(); | ||
| 185 | sp.cookie.name("another_cookie_name").encrypt(); | ||
| 186 | |||
| 187 | |||
| 188 | Removing the user-agent part | ||
| 189 | ............................ | ||
| 190 | |||
| 191 | Some web browser extensions, such as `uMatrix <https://github.com/gorhill/uMatrix/wiki>`__ | ||
| 192 | might be configured to change the user-agent on a regular basis. If you think that | ||
| 193 | some of your users might be using configurations like this, you might want to disable | ||
| 194 | the mixing of the user-agent in the cookie's encryption key. The simplest way to do | ||
| 195 | so is to set the environment variable ``HTTP_USER_AGENT`` to a fixed value before passing | ||
| 196 | it to your php process. | ||
| 197 | |||
| 198 | We think that this use case is too exotic to be worth implementing as a | ||
| 199 | proper configuration directive. | ||
| 200 | |||
| 201 | .. _env-var-config: | ||
| 202 | |||
| 203 | Choosing the proper environment variable | ||
| 204 | ........................................ | ||
| 205 | |||
| 206 | It's up to you to choose a meaningful environment variable to derive the key from. | ||
| 207 | Suhosin `is using <https://www.suhosin.org/stories/configuration.html#suhosin-session-cryptraddr>`_ | ||
| 208 | the ``REMOTE_ADDR`` one, tying the validity of the cookie to the IP address of the user; | ||
| 209 | unfortunately, nowadays, people are `roaming <https://en.wikipedia.org/wiki/Roaming>`_ a lot on their smartphone, | ||
| 210 | hopping from WiFi to 4G. | ||
| 211 | |||
| 212 | This is why we recommend, if possible, to use the *extended master secret* | ||
| 213 | from TLS connections (`RFC7627 <https://tools.ietf.org/html/rfc7627>`_) | ||
| 214 | instead. The will make the validity of the cookie TLS-dependent, by using the ``SSL_SESSION_ID`` variable. | ||
| 215 | |||
| 216 | - In `Apache <https://httpd.apache.org/docs/current/mod/mod_ssl.html>`_, | ||
| 217 | it is possible to enable by adding ``SSLOptions StdEnvVars`` in your Apache2 configuration. | ||
| 218 | - In `nginx <https://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables>`_, | ||
| 219 | you have to use ``fastcgi_param SSL_SESSION_ID $ssl_session_id if_not_empty;``. | ||
| 220 | 128 | ||
| 221 | If you aren't using TLS (you should be), you can always use the ``REMOTE_ADDR`` one, | ||
| 222 | or ``X-Real-IP`` if you're behind a reverse proxy. | ||
| 223 | 129 | ||
| 224 | readonly_exec | 130 | readonly_exec |
| 225 | ^^^^^^^^^^^^^ | 131 | ^^^^^^^^^^^^^ |
diff --git a/doc/source/encryption.rst b/doc/source/encryption.rst new file mode 100644 index 0000000..8ac6861 --- /dev/null +++ b/doc/source/encryption.rst | |||
| @@ -0,0 +1,132 @@ | |||
| 1 | .. _cookie-encryption-config: | ||
| 2 | |||
| 3 | Cookies | ||
| 4 | ======= | ||
| 5 | |||
| 6 | auto_cookie_secure | ||
| 7 | """""""""""""""""" | ||
| 8 | |||
| 9 | :ref:`auto_cookie_secure <auto-cookie-secure-feature>`, disabled by default, | ||
| 10 | will automatically mark cookies as `secure | ||
| 11 | <https://en.wikipedia.org/wiki/HTTP_cookie#Secure_cookie>`_ when the web page | ||
| 12 | is requested over HTTPS. | ||
| 13 | |||
| 14 | It can either be ``enabled`` or ``disabled``. | ||
| 15 | |||
| 16 | :: | ||
| 17 | |||
| 18 | sp.auto_cookie_secure.enable(); | ||
| 19 | sp.auto_cookie_secure.disable(); | ||
| 20 | |||
| 21 | cookie_samesite | ||
| 22 | """"""""""""""" | ||
| 23 | |||
| 24 | :ref:`samesite <samesite-feature>`, disabled by default, will add the `samesite | ||
| 25 | <https://tools.ietf.org/html/draft-west-first-party-cookies-07>`_ attribute to | ||
| 26 | cookies. It `prevents CSRF <https://www.owasp.org/index.php/SameSite>`_ but is | ||
| 27 | not implemented by `all web browsers <https://caniuse.com/#search=samesite>`_ | ||
| 28 | yet. | ||
| 29 | |||
| 30 | It can either be set to ``strict`` or ``lax``: | ||
| 31 | |||
| 32 | - The ``lax`` attribute prevents cookies from being sent cross-domain for | ||
| 33 | "dangerous" methods, like ``POST``, ``PUT`` or ``DELETE``. | ||
| 34 | |||
| 35 | - The ``strict`` one prevents any cookies from beind sent cross-domain. | ||
| 36 | |||
| 37 | :: | ||
| 38 | |||
| 39 | sp.cookie.name("cookie1").samesite("lax"); | ||
| 40 | sp.cookie.name("cookie2").samesite("strict");; | ||
| 41 | |||
| 42 | .. _cookie-encryption_config: | ||
| 43 | |||
| 44 | Cookie encryption | ||
| 45 | """"""""""""""""" | ||
| 46 | |||
| 47 | The encryption is done via the `tweetnacl library <https://tweetnacl.cr.yp.to/>`_, | ||
| 48 | thus using `curve25519<https://en.wikipedia.org/wiki/Curve25519>`__, `xsalsa20 <https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant>`__ and `poly1305 <https://en.wikipedia.org/wiki/Poly1305>`__ for the encryption. We chose this | ||
| 49 | library because of its portability, simplicity and reduced size (a single `.h` and | ||
| 50 | `.c` file.). | ||
| 51 | |||
| 52 | The key is derived from multiple sources, such as : | ||
| 53 | * The ``secret_key`` provided in the configuration. | ||
| 54 | * An environment variable, such as the ``REMOTE_ADDR`` (remote IP address) or the *extended master secret* from TLS connections (`RFC7627 <https://tools.ietf.org/html/rfc7627>`_) | ||
| 55 | * The user-agent. | ||
| 56 | |||
| 57 | |||
| 58 | .. warning:: | ||
| 59 | |||
| 60 | To use this feature, you **must** set the :ref:`global.secret_key <config_global>` | ||
| 61 | and the :ref:`global.cookie_env_var <config_global>` variables. | ||
| 62 | This design decision prevents an attacker from | ||
| 63 | `trivially bruteforcing <https://www.idontplaydarts.com/2011/11/decrypting-suhosin-sessions-and-cookies/>`_ | ||
| 64 | or re-using session cookies. | ||
| 65 | If the simulation mode isn’t specified in the configuration, snuffleupagus will drop any request that it was unable to decrypt. | ||
| 66 | |||
| 67 | Since PHP doesn't handle session cookie and non-session cookie in the same way, | ||
| 68 | thus we are providing two different options: | ||
| 69 | |||
| 70 | * For the session cookie, the encryption happens server-side: The cookie's value isn't encrypted, only the session content is. | ||
| 71 | * For the non-session cookie, the value is encrypted. | ||
| 72 | |||
| 73 | Session cookie | ||
| 74 | .............. | ||
| 75 | |||
| 76 | :ref:`Session encryption <cookie-encryption-feature>`, disabled by default, will activate transparent session encryption. | ||
| 77 | It can either be ``enabled`` or ``disabled`` and can be used in ``simulation`` mode. | ||
| 78 | |||
| 79 | :: | ||
| 80 | |||
| 81 | sp.session.encrypt(); | ||
| 82 | sp.session.simulation(); | ||
| 83 | |||
| 84 | |||
| 85 | Non-session cookie | ||
| 86 | .................. | ||
| 87 | |||
| 88 | :ref:`Cookie encryption <cookie-encryption-feature>`, disabled by default, will activate transparent encryption of specific cookies. | ||
| 89 | |||
| 90 | It can either be ``enabled`` or ``disabled`` and can be used in ``simulation`` mode. | ||
| 91 | |||
| 92 | :: | ||
| 93 | |||
| 94 | sp.cookie.name("my_cookie_name").encrypt(); | ||
| 95 | sp.cookie.name("another_cookie_name").encrypt(); | ||
| 96 | |||
| 97 | |||
| 98 | Removing the user-agent part | ||
| 99 | ............................ | ||
| 100 | |||
| 101 | Some web browser extensions, such as `uMatrix <https://github.com/gorhill/uMatrix/wiki>`__ | ||
| 102 | might be configured to change the user-agent on a regular basis. If you think that | ||
| 103 | some of your users might be using configurations like this, you might want to disable | ||
| 104 | the mixing of the user-agent in the cookie's encryption key. The simplest way to do | ||
| 105 | so is to set the environment variable ``HTTP_USER_AGENT`` to a fixed value before passing | ||
| 106 | it to your php process. | ||
| 107 | |||
| 108 | We think that this use case is too exotic to be worth implementing as a | ||
| 109 | proper configuration directive. | ||
| 110 | |||
| 111 | .. _env-var-config: | ||
| 112 | |||
| 113 | Choosing the proper environment variable | ||
| 114 | ........................................ | ||
| 115 | |||
| 116 | It's up to you to choose a meaningful environment variable to derive the key from. | ||
| 117 | Suhosin `is using <https://www.suhosin.org/stories/configuration.html#suhosin-session-cryptraddr>`_ | ||
| 118 | the ``REMOTE_ADDR`` one, tying the validity of the cookie to the IP address of the user; | ||
| 119 | unfortunately, nowadays, people are `roaming <https://en.wikipedia.org/wiki/Roaming>`_ a lot on their smartphone, | ||
| 120 | hopping from WiFi to 4G. | ||
| 121 | |||
| 122 | This is why we recommend, if possible, to use the *extended master secret* | ||
| 123 | from TLS connections (`RFC7627 <https://tools.ietf.org/html/rfc7627>`_) | ||
| 124 | instead. The will make the validity of the cookie TLS-dependent, by using the ``SSL_SESSION_ID`` variable. | ||
| 125 | |||
| 126 | - In `Apache <https://httpd.apache.org/docs/current/mod/mod_ssl.html>`_, | ||
| 127 | it is possible to enable by adding ``SSLOptions StdEnvVars`` in your Apache2 configuration. | ||
| 128 | - In `nginx <https://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables>`_, | ||
| 129 | you have to use ``fastcgi_param SSL_SESSION_ID $ssl_session_id if_not_empty;``. | ||
| 130 | |||
| 131 | If you aren't using TLS (you should be), you can always use the ``REMOTE_ADDR`` one, | ||
| 132 | or ``X-Real-IP`` if you're behind a reverse proxy. | ||
diff --git a/doc/source/features.rst b/doc/source/features.rst index 24c5074..08ad3d4 100644 --- a/doc/source/features.rst +++ b/doc/source/features.rst | |||
| @@ -63,8 +63,8 @@ Examples of related vulnerabilities | |||
| 63 | 63 | ||
| 64 | .. _cookie-encryption-feature: | 64 | .. _cookie-encryption-feature: |
| 65 | 65 | ||
| 66 | Session-cookie stealing via XSS | 66 | Cookie stealing via XSS |
| 67 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 67 | ^^^^^^^^^^^^^^^^^^^^^^^ |
| 68 | 68 | ||
| 69 | The goto payload for XSS is often to steal cookies. | 69 | The goto payload for XSS is often to steal cookies. |
| 70 | Like *Suhosin*, we are encrypting the cookies with a secret key, | 70 | Like *Suhosin*, we are encrypting the cookies with a secret key, |
| @@ -79,10 +79,7 @@ This feature is roughly the same than the `Suhosin one <https://suhosin.org/stor | |||
| 79 | Having a secret server-side key will prevent anyone (even the user himself) | 79 | Having a secret server-side key will prevent anyone (even the user himself) |
| 80 | from reading the content of the cookie, reducing the impact of an application storing sensitive data client-side. | 80 | from reading the content of the cookie, reducing the impact of an application storing sensitive data client-side. |
| 81 | 81 | ||
| 82 | The encryption is done via the `tweetnacl library <https://tweetnacl.cr.yp.to/>`_, | 82 | |
| 83 | thus using curve25519, xsalsa20 and poly1305 for the encryption. We chose this | ||
| 84 | library because of its portability, simplicity and reduced size (a single `.h` and | ||
| 85 | `.c` file.). | ||
| 86 | 83 | ||
| 87 | 84 | ||
| 88 | .. _fileupload-feature: | 85 | .. _fileupload-feature: |
diff --git a/doc/source/index.rst b/doc/source/index.rst index 28d0474..22cdcb8 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst | |||
| @@ -19,6 +19,7 @@ Documentation | |||
| 19 | changelog | 19 | changelog |
| 20 | faq | 20 | faq |
| 21 | papers | 21 | papers |
| 22 | encryption | ||
| 22 | 23 | ||
| 23 | Greetings | 24 | Greetings |
| 24 | --------- | 25 | --------- |
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" | |||
| 6 | sources="$sources sp_disabled_functions.c sp_execute.c sp_upload_validation.c" | 6 | sources="$sources sp_disabled_functions.c sp_execute.c sp_upload_validation.c" |
| 7 | sources="$sources sp_cookie_encryption.c sp_network_utils.c tweetnacl.c" | 7 | sources="$sources sp_cookie_encryption.c sp_network_utils.c tweetnacl.c" |
| 8 | sources="$sources sp_config_keywords.c sp_var_parser.c sp_var_value.c sp_tree.c" | 8 | sources="$sources sp_config_keywords.c sp_var_parser.c sp_var_value.c sp_tree.c" |
| 9 | sources="$sources sp_pcre_compat.c sp_crypt.c" | 9 | sources="$sources sp_pcre_compat.c sp_crypt.c sp_session.c" |
| 10 | 10 | ||
| 11 | PHP_ARG_ENABLE(snuffleupagus, whether to enable snuffleupagus support, | 11 | PHP_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 | ||
| 46 | extern zend_module_entry snuffleupagus_module_entry; | 48 | extern 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 | ||
| 68 | typedef struct { | 68 | typedef struct { |
| 69 | bool encrypt; | ||
| 70 | bool simulation; | ||
| 71 | } sp_config_session; | ||
| 72 | |||
| 73 | typedef 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 | ||
| 163 | typedef struct { | 169 | typedef 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 | ||
| 63 | int 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 | |||
| 63 | int parse_random(char *line) { | 106 | int 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); | |||
| 14 | int parse_upload_validation(char *line); | 14 | int parse_upload_validation(char *line); |
| 15 | int parse_eval_blacklist(char *line); | 15 | int parse_eval_blacklist(char *line); |
| 16 | int parse_eval_whitelist(char *line); | 16 | int parse_eval_whitelist(char *line); |
| 17 | int 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) { | |||
| 21 | int decrypt_cookie(zval *pDest, int num_args, va_list args, | 21 | int 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 | */ | ||
| 44 | static zend_string *encrypt_data(char *data, unsigned long long data_len) { | 38 | static 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 | ||
| 5 | ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) | 5 | ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) |
| 6 | 6 | ||
| 7 | static zend_long nonce_d = 0; | 7 | void generate_key(unsigned char *key) { |
| 8 | |||
| 9 | static 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 | */ | ||
| 103 | zend_string *encrypt_zval(char *data, unsigned long long data_len) { | 122 | zend_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 | ||
| 13 | static void generate_key(unsigned char *key); | 13 | void generate_key(unsigned char *key); |
| 14 | int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hask_key); | 14 | int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hask_key); |
| 15 | zend_string *encrypt_zval(char *data, unsigned long long data_len); | 15 | zend_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 | |||
| 4 | ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus); | ||
| 5 | |||
| 6 | #ifdef ZTS | ||
| 7 | static 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 | ||
| 10 | ZEND_TSRMLS_CACHE_EXTERN(); | ||
| 11 | #endif | ||
| 12 | #else | ||
| 13 | #define SESSION_G(v) (ps_globals.v) | ||
| 14 | #endif | ||
| 15 | |||
| 16 | static php_ps_globals *session_globals = NULL; | ||
| 17 | static ps_module *s_module; | ||
| 18 | static ps_module *s_original_mod; | ||
| 19 | static int (*old_s_read)(PS_READ_ARGS); | ||
| 20 | static int (*old_s_write)(PS_WRITE_ARGS); | ||
| 21 | static int (*previous_sessionRINIT)(INIT_FUNC_ARGS) = NULL; | ||
| 22 | static ZEND_INI_MH((*old_OnUpdateSaveHandler)) = NULL; | ||
| 23 | |||
| 24 | |||
| 25 | static 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 | |||
| 55 | static 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 | |||
| 64 | static 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 | |||
| 93 | static 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 | |||
| 113 | static 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 | |||
| 126 | void 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 | |||
| 11 | void 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 @@ | |||
| 1 | sp.global.secret_key("abcdef").cookie_env_var("REMOTE_ADDR"); | ||
| 2 | sp.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 @@ | |||
| 1 | sp.global.secret_key("abcdef").cookie_env_var("REMOTE_ADDR"); | ||
| 2 | sp.session.encrypt(); | ||
| 3 | sp.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-- | ||
| 2 | SESSION crypt and bad decrypt | ||
| 3 | --SKIPIF-- | ||
| 4 | <?php if (!extension_loaded("snuffleupagus")) die "skip"; ?> | ||
| 5 | --INI-- | ||
| 6 | sp.configuration_file={PWD}/config/config_crypt_session.ini | ||
| 7 | --ENV-- | ||
| 8 | return <<<EOF | ||
| 9 | REMOTE_ADDR=127.0.0.1 | ||
| 10 | EOF; | ||
| 11 | --FILE-- | ||
| 12 | <?php | ||
| 13 | // Do it like that to write (encrypt) the session and then to read (decrypt) the session | ||
| 14 | session_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 | session_write_close(); // Close the session | ||
| 18 | putenv("REMOTE_ADDR=127.0.0.2"); | ||
| 19 | session_id($id); // Recover the session with the previous session_id | ||
| 20 | session_start(); // Re start the session, It will read and decrypt the non empty session | ||
| 21 | var_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-- | ||
| 2 | SESSION crypt and bad decrypt | ||
| 3 | --SKIPIF-- | ||
| 4 | <?php if (!extension_loaded("snuffleupagus")) die "skip"; ?> | ||
| 5 | --INI-- | ||
| 6 | sp.configuration_file={PWD}/config/config_crypt_session_simul.ini | ||
| 7 | --ENV-- | ||
| 8 | return <<<EOF | ||
| 9 | REMOTE_ADDR=127.0.0.1 | ||
| 10 | EOF; | ||
| 11 | --FILE-- | ||
| 12 | <?php | ||
| 13 | // Do it like that to write (encrypt) the session and then to read (decrypt) the session | ||
| 14 | session_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 | session_write_close(); // Close the session | ||
| 18 | putenv("REMOTE_ADDR=127.0.0.2"); | ||
| 19 | session_id($id); // Recover the session with the previous session_id | ||
| 20 | session_start(); // Re start the session, It will read and decrypt the non empty session | ||
| 21 | var_dump($_SESSION); // Dump the session | ||
| 22 | ?> | ||
| 23 | --EXPECTF-- | ||
| 24 | array(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-- | ||
| 2 | SESSION crypt/decrypt valid | ||
| 3 | --SKIPIF-- | ||
| 4 | <?php if (!extension_loaded("snuffleupagus")) die "skip"; ?> | ||
| 5 | --INI-- | ||
| 6 | sp.configuration_file={PWD}/config/config_crypt_session_simul.ini | ||
| 7 | --ENV-- | ||
| 8 | return <<<EOF | ||
| 9 | REMOTE_ADDR=127.0.0.1 | ||
| 10 | EOF; | ||
| 11 | --FILE-- | ||
| 12 | <?php | ||
| 13 | $current_path = dirname(getcwd()) . "/src/tests/" ; | ||
| 14 | ini_set("session.save_path", $current_path); | ||
| 15 | |||
| 16 | session_start(); | ||
| 17 | $id = session_id(); // Get the session_id to use it later | ||
| 18 | $filename_sess = $current_path . "sess_" . $id; | ||
| 19 | file_put_contents($filename_sess, "toto|s:4:\"tata\";"); // Write a unencrypted session | ||
| 20 | session_write_close(); // Close the session | ||
| 21 | |||
| 22 | session_id($id); | ||
| 23 | session_start(); // Try to read the unencrypted session, it will fail to decrypt but it must return the session | ||
| 24 | var_dump($_SESSION); | ||
| 25 | echo "OK"; | ||
| 26 | unlink($filename_sess); | ||
| 27 | ?> | ||
| 28 | --EXPECTF-- | ||
| 29 | array(1) { | ||
| 30 | ["toto"]=> | ||
| 31 | string(4) "tata" | ||
| 32 | } | ||
| 33 | OK | ||
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-- | ||
| 2 | SESSION crypt/decrypt valid | ||
| 3 | --SKIPIF-- | ||
| 4 | <?php if (!extension_loaded("snuffleupagus")) die "skip"; ?> | ||
| 5 | --INI-- | ||
| 6 | sp.configuration_file={PWD}/config/config_crypt_session.ini | ||
| 7 | --ENV-- | ||
| 8 | return <<<EOF | ||
| 9 | REMOTE_ADDR=127.0.0.1 | ||
| 10 | EOF; | ||
| 11 | --FILE-- | ||
| 12 | <?php | ||
| 13 | // Do it like that to write (encrypt) the session and then to read (decrypt) the session | ||
| 14 | session_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 | |||
| 18 | session_write_close(); // Close the session | ||
| 19 | session_id($id); // Recover the session with the previous session_id | ||
| 20 | session_start(); // Re start the session, It will read and decrypt the non empty session | ||
| 21 | var_dump($_SESSION); // Dump the session | ||
| 22 | ?> | ||
| 23 | --EXPECTF-- | ||
| 24 | array(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-- | ||
| 2 | SESSION crypt/decrypt valid | ||
| 3 | --SKIPIF-- | ||
| 4 | <?php if (!extension_loaded("snuffleupagus")) die "skip"; ?> | ||
| 5 | --INI-- | ||
| 6 | sp.configuration_file={PWD}/config/config_crypt_session_simul.ini | ||
| 7 | --ENV-- | ||
| 8 | return <<<EOF | ||
| 9 | REMOTE_ADDR=127.0.0.1 | ||
| 10 | EOF; | ||
| 11 | --FILE-- | ||
| 12 | <?php | ||
| 13 | // Do it like that to write (encrypt) the session and then to read (decrypt) the session | ||
| 14 | session_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 | session_write_close(); // Close the session | ||
| 18 | |||
| 19 | session_id($id); // Recover the session with the previous session_id | ||
| 20 | session_start(); // Re start the session, It will read and decrypt the non empty session | ||
| 21 | var_dump($_SESSION); // Dump the session | ||
| 22 | ?> | ||
| 23 | --EXPECTF-- | ||
| 24 | array(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(); |
| 38 | if (($i = count($expected)) > count($headers)) | 39 | if (($i = count($expected)) > count($headers)) |
| @@ -41,31 +42,37 @@ if (($i = count($expected)) > count($headers)) | |||
| 41 | return; | 42 | return; |
| 42 | } | 43 | } |
| 43 | 44 | ||
| 44 | do | 45 | $i = 0; |
| 45 | { | 46 | |
| 47 | do { | ||
| 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 | } |
| 64 | while (next($headers) !== FALSE); | 71 | while (next($headers)); |
| 65 | 72 | ||
| 66 | echo ($i === 0) | 73 | echo ($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-- |
| 71 | OK | 78 | OK |
