Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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 this issue Feb 13, 2023 · 8 comments

Comments

@fredericgboutin-yapla
Copy link
Contributor

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:

  • Thread.Sleep(4*1000);
  • C:/winnt/win.ini
  • ${T(java.lang.System).properties}

Etc.

Upon analysis I stumbled on this old issue zendframework/zend-session#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
Copy link
Contributor Author

fredericgboutin-yapla commented Feb 14, 2023

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
Copy link
Contributor Author

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
Copy link
Contributor Author

fredericgboutin-yapla commented Feb 15, 2023

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
Copy link
Contributor Author

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
Copy link
Member

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

@fredericgboutin-yapla
Copy link
Contributor Author

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
Copy link
Contributor Author

fredericgboutin-yapla commented Feb 21, 2023

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
Copy link
Contributor Author

fredericgboutin-yapla commented May 24, 2023

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 - #165 (comment) - is not a good idea since it seems to interfere with good session management.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants