-
Notifications
You must be signed in to change notification settings - Fork 7.9k
BUG-10807: session_regenerate_id fails to check results of validateId… #10813
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
base: master
Are you sure you want to change the base?
Conversation
I'm not sure if I raised this correctly, but could this fix also be applied to PHP 8.0 as this is the version available for oraclelinux 9 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! This looks almost perfect. Test case looks good and reliable too.
Just some nits about the naming.
I'm not sure if I raised this correctly, but could this fix also be applied to PHP 8.0 as this is the version available for oraclelinux 9
Almost ;) In general, bugfixes should go to the lowest bug-supported branch they apply to. In this case it's 8.1. So you should rebase your PR to that branch.
Normally this PR wouldn't be applied to 8.0 since that release is security-only, and this bug does not look like a critical bug as far as I can see.
ext/session/tests/bug10807.phpt
Outdated
@@ -0,0 +1,69 @@ | |||
--TEST-- | |||
Bug #10807 (session_regenerate_id with custom handler and use_strict_mode generates three new session ids) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this is a bit confusing at first... But "Bug #XXXXX" is for the old bugs.php.net bug IDs. For GitHub we use "GH-XXXXX". So in this case you should change Bug #10807
to GH-10807
.
Similarly, you should name your file gh10807
instead of bug10807
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes made. Might I suggest that the docs at https://qa.php.net/write-test.php are updated to handle this.
Re rebasing to the 8.1 branch, my fork doesn't have this branch and this has exceeded my github knowledge. Are you able to take this over for now?
Requesting a review from George because this was changed recently. (499fbcd) |
Note that until I added a validateId function to my custom session handler, I could not get session_regenerate_id to work at all ie the hidden validateId function caused me a world of pain until I happened to discover the session.use_strict_mode + custom validateId function |
Additionally, the fix by @cmb69 seems incorrect to be in that that loop will always call create_sid, ignore the validateId response and just use the last value from create_sid anyway |
However, I can look at adding a test case for invoking create_session_id during an active session as well |
…ailing and non failing validation
I added a test case for session_create_id for the failing and non failing validation during an active session. |
I wonder if I misinterpreted the documentation. From: https://www.php.net/manual/en/sessionupdatetimestamphandlerinterface.validateid.php
So the old code might've actually been correct. We're supposed to return false to indicate that no session already exists. |
Oh god, this part of ext/session again... let me try to recall what the semantics are because they are very weird and unintuitive. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to blame our docs and the stupid naming of this method for the mistake.
However, the change and the bug don't seem to be valid due to the misconception of what this method is meant to do.
This method must not be used to check the "shape" of an ID or if it satisfies what ever arbitrary requirements, those need to be done in create_sid()
and throw an Exception if it doesn't (or just regenerate one that does). The only purpose of the validateID()
method is to check if the ID given exists within the data storage. A possible better name would have been validateDataStorageExistenceOfId()
or isIdInUseInDataStorage()
(or even some other more descriptive name) to prevent this misconception.
@@ -2295,7 +2295,7 @@ PHP_FUNCTION(session_regenerate_id) | |||
if ((!PS(mod_user_implemented) && PS(mod)->s_validate_sid) || !Z_ISUNDEF(PS(mod_user_names).ps_validate_sid)) { | |||
int limit = 3; | |||
/* Try to generate non-existing ID */ | |||
while (limit-- && PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == SUCCESS) { | |||
while (limit-- && PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == FAILURE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is definitely incorrect.
I know the method name is terrible, but validateId()
MUST only return true
if a session with this ID already exists.
Symfony seems to have hit that mistake too and fixed it: symfony/symfony#47843
@@ -2362,7 +2362,7 @@ PHP_FUNCTION(session_create_id) | |||
break; | |||
} else { | |||
/* Detect collision and retry */ | |||
if (PS(mod)->s_validate_sid(&PS(mod_data), new_id) == SUCCESS) { | |||
if (PS(mod)->s_validate_sid(&PS(mod_data), new_id) == FAILURE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, this change is also incorrect. As this allows you to create two sessions with the same ID, which is a massive issue.
public function validateId(string $id) : bool { | ||
|
||
if ($this->fail_validation > 0 && intval($id)< $this->fail_validation) { | ||
return FALSE; | ||
} | ||
|
||
return TRUE; | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic of this is nonsensical.
Validations about the "shape" of the ID must happen in the create_sid()
method.
The purpose of the validateId()
method is only to validate that the given ID exists in the data storage.
Please read the Limitation and Compatibility sections of the RFC that introduced those features, which describes the behaviour of this method: https://wiki.php.net/rfc/strict_sessions#limitation
The rationale as to why uninitialized sessions should be discarded is explained in the RFC here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, my validateId contents above are not a real world scenario, it is there to confirm that my change did not break the scenario where validateId returns false
Sorry about the confusion @bgardner-noggin, we should probably clarify the docs indeed... And thank you @Girgias for digging into this. |
No worries, I think the fact that this is confusing is something to be concerned about, especially as this is related to a security feature. Might dedicate some time to actually propose an RFC fixing the whole user session handler stuff... |
This still seems broken. It seems you have confused my test case above which was ensuring that if validateId returns FALSE, that create_sid is reinvoked. My actual bug is that validateId is returning TRUE, ie the session token is valid, but PHP internals don't care and calls create_sid + validateSid multiple times In my real world code, after a user successfully authenticates, I am invoking session_regenerate_id to prevent session fixation attacks. This leads to my original scenario create_sid() <-- returns a new securely random generated token and inserts into the database surely create_sid should only be reinvoked if validateId returns false? |
So I've continued to stare at the code, trying to dig through this mess and understand it. I still believe your changed are incorrect because the test for bug 79413 is broken (as seen by the total failure of CI). The issue you are having is with the collision detections, because your implementation of That's why the validate method fails for them, as the ID is not yet in a created state. So... according to the current implementation, Now the question is should |
I didn't see the 79413 failure during my make test run. I'll take a look at this tomorrow. What you say above make sense, except it is different to what you said previously where you stated
As well as what the docs of validateId state
Now if validateId is actually meant to be Return True if the id is generated by us, but has no associated storage yet then it makes sense, but the naming of the function and docs make me think otherwise. |
Return True if the id is generated by us, but has no associated storage yet This statement cannot be true though, because validateId is invoked during the course of a normal start_session, ie not creating a new session id or regenerating an id, but through the normal course of using an existing session id which is in storage already. |
I don't see how what I said contradicts my other comments. That's still it's only purpose.
Naming and docs are completely irrelevant in PHP. However, I never implied that
I said the expectation is that However, I don't think the expectation that Just a side question, but it seems you've basically rolled out a custom session mechanism already. Why rely on ext/session and on |
So if I understand this correctly if I have a custom save handler
As to why I was using the session extension, this is because it provides other useful things, like managing the session cookie However, the docs are not clear and I think i will end up using a completely custom model for this and other reasons, which is disappointing. Eg another problem I can see from looking at the source code is that if I have a custom save handler that implements the SessionIDInterface, then I would expect session_create_id() would always call my custom handler, yet I can see that this is not the case when the session is not active and it will fallback to the internal PHP function https://github.com/php/php-src/blob/master/ext/session/session.c#L2357
|
This is my understanding, yes. But I do think the semantics of
Right, thanks for the explanation. Yeah, the docs are in a sad state of affairs for that section.
Yeah, this does look problematic indeed... Thanks for your time anyway. |
session_generate_id should only invoke create_sid while the validateID result is failing