zf1s / zf1

Monorepo of a fork of Zend Framework 1, with all components split into individual composer packages. PHP 5.3-8.3 compatible.
BSD 3-Clause "New" or "Revised" License
57 stars 22 forks source link

Zend Session would throw an exception "The session has already been started. The session id must be set first." upon receiving an invalid session ID #165

Open fredericgboutin-yapla opened 1 year ago

fredericgboutin-yapla commented 1 year ago

Long story short, We monitored a lot of exceptions this morning on our website, all stating "The session has already been started. The session id must be set first."

There are a lot of different reasons why this message can happen.

Our logs indicated that the user ID values were in fact all attempting to break things up, with PHPSESSID cookie having values like:

Etc.

Upon analysis I stumbled on this old issue https://github.com/zendframework/zend-session/issues/119 which helped me to reproduce the issue on my dev. env. locally with a simple curl command like suggested,

curl -I 'http://your-local-website.com/' -H 'Cookie: PHPSESSID=_test_'

I then Xdebug-ed it to realize that Zend doesn't properly account for the situation when session_id is given a rejected ID. In that situation, the SID constant is defined BUT its value is an empty string (at least, on PHP 7.0.33),

session_id() returns the session id for the current session or the empty string ("") if there is no current session

Source: https://www.php.net/manual/en/function.session-id.php

fredericgboutin-yapla commented 1 year ago

When trying to unit-test my fix, I realized that, as a workaround, one can simply call session_id($_COOKIE['PHPSESSID']); right BEFORE the first call to Zend_Session::start() if there is a valid cookie.

As a bonus, doing so prevents the creation of sessions with wrong session ID at the first place.

fredericgboutin-yapla commented 1 year ago

Since explicitly sending an invalid PHPSESSIDmakes Zend create 2 sessions (but only retaining one), I realize that an attacker could use this flaw to prematurely close active sessions, depending on the type of session storage in used - i .e. one curl request in my example momentarily occupy 2 sessions on the storage.

For example, when using a Redis setup or any FIFO session storage.

fredericgboutin-yapla commented 1 year ago

Fun fact, After reviewing PHP's internal code to understand how the SID constant was created and why / how its value diverged from session_id() I realized that PHP already tries to cleanup "dangerous characters" in the session ID - https://github.com/php/php-src/blob/PHP-7.0.33/ext/session/session.c#L1684 -

This logic doesn't do very much to help here, unfortunately, but it's nice to know.

fredericgboutin-yapla commented 1 year ago

Another fun fact, An empty string is in fact a valid session id. When I set PHPSESSID to an empty value, a session is created. Zend, which relies on session_id() call to determine if a session ID is set or not, cannot differentiate a session with an empty string ID of no session at all and therefore does not check the session ID.

session_id() returns the session id for the current session or the empty string ("") if there is no current session (no current session id exists). - https://www.php.net/manual/en/function.session-id.php

Long story short, if a website somehow ends up applying an empty string to PHPSESSID then every users in the same scenario are suddenly sharing the very same session...

falkenhawk commented 1 year ago

wow you keep on finding interesting stuff! Seems like Zend_Session v1 has really serious issues 😅

fredericgboutin-yapla commented 1 year ago

Yes. But eh! At some point one has to consider other layers of technology to find solutions. For example, we are exploring AWS WAF with custom rules tailored towards PHPSESSID cookie so we reject this malicious traffic automatically before it reaches the code.

fredericgboutin-yapla commented 1 year ago

In our situation the origin of the PHPSESSID manipulations stemmed from an actor using a bot called nessus.

In regard to using a WAF, AWS has what they call a "Core rule set" which, among other things, targets "bad bots".

Activating this set of rules would "mostly" prevent the situation described in this issue from happening.

fredericgboutin-yapla commented 1 year ago

Another piece of wisdom I would like to share and add to this issue.

There is a vulnerability called "session fixation" that is/was super easy to pull in PHP. An attacker sets the PHPSESSID cookie in the victim's browser with a pre-defined value, the victim then unknowingly uses that value when navigating the web site, login, making purchase, etc. The attacker is then able to access the victim's user session remotely through a shared usage of the PHPSESSID cookie.

See,

There are multiple ways to mitigate this issue. To my knowledge, I'm not sure Zf1 does or can do it but nonetheless one can simply set session.use_strict_mode in his config file to mitigate this vulnerability (in theory).

resources.session.use_strict_mode = 1

So, long story short, the workaround that I proposed - https://github.com/zf1s/zf1/issues/165#issuecomment-1429935843 - is not a good idea since it seems to interfere with good session management.