For secure (HTTPS) sites, Drupal 7 now stores the session identifier in the sessions.ssid column rather than sessions.sid. However, the sessions table still defines the sid column as the primary key.
When a user logs in on the secure site, the session is created with an empty string sid. No additional logins are possible until this session logs out, because merging in another row into the sessions table with empty string sid results in a duplicate primary key and the merge fails.
A possible fix is attached: always store the session identifier in the sid column, whether or not it is a secure session.
Comment | File | Size | Author |
---|---|---|---|
#156 | 575280-156-empty-session-id.patch | 5.63 KB | chx |
#155 | 575280-155-empty-session-id.patch | 6.54 KB | carlos8f |
#153 | 575280-empty-session-id-test-NO-FIX.patch | 3.83 KB | carlos8f |
#153 | 575280-empty-session-id-test.patch | 6.93 KB | carlos8f |
#147 | 575280-key_1.patch | 2.7 KB | carlos8f |
Comments
Comment #1
Heine CreditAttribution: Heine commentedAdditional information from Mark (send to the security team, but can & should be fixed publicly):
I found that running Drupal 7 on a HTTPS URL and logging in results in a session being saved to the sessions table with an empty string session identifier. Once this happens, if the site is also available on a HTTP URL, then anyone can pass an empty string session cookie to the HTTP URL e.g. curl --cookie SESS96efe38290907de812869e22ea7c7c62= http://... and make any request with the privileges of that initial HTTPS login.
Comment #2
Damien Tournoud CreditAttribution: Damien Tournoud commentedI suggest we replace isset($_COOKIE[$insecure_session_name]) by !empty($_COOKIE[$insecure_session_name]) in _drupal_session_read(). The true bug is _drupal_session_read() now accept empty session ids ;)
Comment #3
Damien Tournoud CreditAttribution: Damien Tournoud commentedComment #4
mfbIsn't the issue just that we are *writing* an empty session id? Drupal 6 would also happily read an empty session id if it were there.
Comment #5
mfbBy the way, I ran the test suite on my HTTPS instance, and found that the patch reduces the number of failures. But even with this patch I still had some failures in the session tests, which happened when the test framework seemed to mysteriously redirect from HTTPS to HTTP at certain times. This seems like an unrelated issue but thought I'd mention it in case anyone else is testing on HTTPS.
Comment #6
mfbOK I am also bundling into this patch a fix for issue noted in #5. Tests now pass in HTTPS.
Comment #7
Dries CreditAttribution: Dries commentedGiven that this is security related, some extra code comments would be nice.
Comment #8
Damien Tournoud CreditAttribution: Damien Tournoud commentedWe don't want the session to be accessible by HTTP when generated from HTTPS, so I'm not convinced the fix makes sense. The real bug is accepting an empty session id.
Comment #9
mfbI added some code comments.
I thought about this issue, but I couldn't think of how it would be a problem since the secure session cookie is always marked as "secure only." By saving it to the sid column, it *could* be used via HTTP but it won't be, unless someone manually hacked their cookie jar.
Comment #10
mfbOK I missed something serious when thinking about this before, but I since played around with Firecookie on my HTTP+HTTPS sandbox site...
We should ignore the $_COOKIE[$insecure_session_name] on an HTTPS site by default. The reason is that the insecure cookie is, as it says "insecure". And could thus be set (fixated) by a man-in-the-middle on the HTTP site. The contents of this cookie would then be saved into the session table when a user logged in via HTTPS, and then could be used by the man-in-the-middle to gain authenticated access. This is a type of scenario that only allowing logins to the HTTPS site should be able to protect against.
We need to wrap all the insecure_session functionality in checks for the 'https' variable, which ironically gives you access to this less secure functionality.
Comment #11
mfbOK, this is a little better for me. I'm now prevented from attacking my HTTPS login via HTTP man-in-the-middle (unless this 'https' variable is enabled, which hasn't been part of my testing scenario so far).
Comment #12
Damien Tournoud CreditAttribution: Damien Tournoud commentedNot sure what you mean. We don't do that. When the user logs in on the HTTPS site, his session will get regenerated. We only use the "insecure" cookie to maintain session data from anonymous (browsing the HTTP site) to authenticated (on both the HTTP and HTTPS site).
Comment #13
chx CreditAttribution: chx commentedHm, wait. If you are running solely on HTTPS why would the new session code kick in?
Comment #14
mfbMy comment in #10 and #11 is talking about security in terms of what HTTPS is designed to protect against, e.g. altering contents of HTTP responses, man-in-the-middle attacks etc. The scenario is a site that has both HTTP and HTTPS sites available. The user and user/login pages redirect to HTTPS so all authenticated access is made via HTTPS.
This line of code:
$fields['sid'] = $_COOKIE[$insecure_session_name];
runs during an HTTPS request, and allows a cookie that was set on the insecure site to be saved as a valid session identifier. The problem is if a hijacker on the network intercepts a user's HTTP response, and sets their own cookie, and the same user later logs in on the HTTPS site, the hijhacker's cookie would be sent to the HTTPS site and saved into the session table, without being regenerated. It's a type of session fixation attack that I'd like to be able to protect against when I require all authenticated access to use HTTPS.Comment #15
chx CreditAttribution: chx commentedWhen a user logs in on the secure site, the session is created with an empty string sid. <= again wait a second. I think I fought a bit that this can not happen, you never get an empty sid?
Comment #16
mfbAre you actually testing on a HTTPS instance? If so it should be quite clear that sid = '', just look in the db after logging in :)
Or just look at the code. in _drupal_session_write(), sid is undefined if $is_https unless isset($_COOKIE[$insecure_session_name]). And default value of sid is '' according to system.install
Comment #18
mfbre-test
Comment #20
mfbRe-roll.
Comment #21
mfbI added tests for HTTPS session handling without the 'https' variable being enabled (i.e. default Drupal configuration).
In this patch I also experimented with some refactoring of _drupal_session_write(): I put both sid and ssid in the db_merge key array to make it more explicit that we are treating both sid and ssid as keys.
Comment #23
mfbOops, forgot that a mock HTTPS request causes a redirect from HTTP to HTTPS. Tweaked session_test_drupal_goto_alter() to ensure that the redirect is altered when HTTPS request has been mocked, which can be determined from a new global variable $is_https_mock
Comment #24
mfbBy the way, I just noticed that drupal_http_request() timeout option does not work for HTTPS URLs because apparently stream_set_timeout() does not work for SSL connections? See http://www.php.net/manual/en/function.stream-set-timeout.php#93298 Anyone know a work around for this?
This causes the Drupal HTTP request test to fail when running on HTTPS environment.
Comment #25
chx CreditAttribution: chx commentedWhat's not clear from this issue and took a long chat session to clarify: the bug appears if you have a mixed HTTP-HTTPS site and the API is off.
db_merge('sessions')->key($key)->fields($fields)->execute();
needs to multiple lines like the original.$GLOBALS['is_https_mock']
GLOBALS is unnecessary as this code is already in the global scope. Wow, array_fill_keys that's new, first time I see this function, handy.// Alter the redirect when using a mock HTTPS request. this needs to be // Alter the redirect when using a mock HTTPS request and not when on real HTTPS because...
If you need to skip a test because of a PHP bug when the testing environment is on HTTPS, please do it. If there is no bugs.php.net report then please create one and link to it.
And thanks a ton for the great work on this.
Comment #26
mfbThis should resolve everything in #25
Comment #27
dwwI just saw this issue, and I'm happy to report that it doesn't conflict at all with #607008: Fix bugs in https support and force using https for authorize.php if available -- neither in code nor in functionality. Yay. ;) Tested authorize.php in the mixed http/https case with this applied, all is still well. I read over the patch -- great work on the tests! All look sane and reasonable. Patch fixes an important bug, code is clean and happy. RTBC!
Comment #28
webchickWait. What? That makes no sense. :) If secure pages is /enabled/, don't I want to use the /secure/ session?
Could you please clarify this comment?
This code looks really odd. Could we just drupalGet('user/login') instead?
Then just as a general note, there is a ton of really useful discussion here that we should make sure is reflected in either the code comments or the tests. I'm not sure the importance of this fix is properly communicated, yet.
I'm on crack. Are you, too?
Comment #29
mfbOne would think :( This so-called 'https' or "secure pages" setting is not required for HTTPS support. What it does is force you to use both secure HTTPS and insecure HTTP sessions. *Not* enabling this setting is intrinsically *more* secure because it allows a site to have only secure HTTPS authenticated sessions (which prevents session hijacking by someone able to snarf network traffic, man-in-the-middle attacks, etc.)
In this patch I am really just trying to fix a critical bug. If I took a more holistic approach I'd probably rewrite the HTTPS support to have multiple variables. The 'https' variable triggers multiple bits of logic throughout core, some of which may be desired and some of which may not be desired depending on the business logic for each site. For example I might want to be able to force certain URLs to redirect from HTTP to HTTPS or vice versa, but not force secure authenticated sessions to use both secure and insecure session cookies.
I can add more comments in the test. This code is more-or-less copied from the existing HTTPS tests, which create a mock HTTPS form submission by modifying the form action to route through a https.php file.
Comment #30
chx CreditAttribution: chx commentedif you have a https only site you need no api, it Just Works. Or it worked ;) anyways, it's kinda obvious that this API is for mixed HTTP-HTTPS because a HTTPS site is slow. Very very very slow. Unless you have a hardware SSL accelerator which costs mucho money.
Comment #31
mfbAdded some additional comments to address #28. Also, fixed one additional test failure in DrupalHTTPRequestTestCase when the test environment is using HTTPS.
Comment #32
chx CreditAttribution: chx commentedBack to the green queue :)
Comment #33
webchickCool, thank you!
Committed to HEAD.
Comment #34
deekayen CreditAttribution: deekayen commentedAfter this issue was committed, my PIFR 2 client threw 3 exceptions on SessionHttpsTestCase.
http://testing.drupal.org/pifr-2.x/pifr/test/16137
I'll leave the client failure up for a couple hours. Screenshot of specifics attached as well.
Comment #36
mfbI can't reproduce these notices so hopefully someone else can fix or give me more info. E.g. which calls to drupal_http_request() are throwing the notices? Apparently the response code, status message and data are all missing, i.e. there is no response at all.
Comment #37
deekayen CreditAttribution: deekayen commentedI'll mark this fixed again. I think I have an idea about what might be going on.
Comment #38
deekayen CreditAttribution: deekayen commentedThe tests fail if a non-https service is running on port 443, like a tor bridge. I guess that's probably not a worthy reason for re-opening this issue.
Comment #43
deekayen CreditAttribution: deekayen commentedNone of the automated test clients are setup for testing https. Should they be?
Comment #44
webchick@deekayen: That was a spammer re-posting mfb's comment from #5. Blocked now.
Comment #46
deekayen CreditAttribution: deekayen commentedI wasn't responding to the spammer. I had a non-https service running on port 443, which the test tried to use and failed, so that suggests to me that there's something that the tests could check on 443 if it was working properly. Is it better that the test bots have SSL available for tests, or better that the tests can verify that SSL doesn't exist, or a combo of both (which would lead to inconsistent failures)?
Comment #47
mfbCan you clarify? I don't know why the tests would try to use port 443, unless the test environment itself is on port 443.
Comment #48
webchickThis discussion is probably better moved to the infrastructure queue or PIFT or wherever people talk about testing bot set-ups. :)
Comment #53
mfbUhg, this horrible bug is back, at least in my test site :(
Unfortunately I never got a chance to review #813492: HTTPS sessions use an invalid merge query, which rolled back part of this patch and appears to have re-introduced the bug.
The problem is that we are saving rows to the sessions table with empty string values for sid or ssid. And then we are selecting these rows and finding a valid session when someone passes in a session cookie with an empty string value.
Steps to reproduce:
Comment #54
mfbBTW, with the empty but valid session ID, I get Notice: A session had already been started - ignoring session_start() in drupal_session_start() (line 272 of session.inc). This is because drupal_session_started() returns
$session_started && session_id()
, and session_id() is the empty string so it returns FALSE even though the session was in fact started.Comment #55
mfbHere's what I have so far. First of all sessions.sid and sessions.ssid should not have a default value. Then we need to set values for both columns in the db_merge().
Also, changed the column descriptions to reflect that session IDs are no longer generated by PHP's session API but by Drupal's session handlers.
This still needs tests.
Comment #57
Anonymous (not verified) CreditAttribution: Anonymous commentedsubscribe. nice catch, looking into it.
Comment #58
mfbHere's another patch. Also fix up some db schema updates.
#55 stored the insecure session ID in both sid and ssid columns, but secure session IDs should be secure only. In this version I store an empty string as the secure session ID (otherwise we'd have to generate a secure session ID, which takes time) but also add some logic in _drupal_session_read() to prevent empty strings from actually being selected as valid secure session IDs.
Comment #59
mfbComment #61
catchThe edits to system_update_7057() should be in a new update handler, we need to support sites that have already installed beta1 now.
Comment #62
mfbOK. Also might have found the problem with all the upgrade path errors.
Comment #63
mfbThis version just removes the last hunk, it shouldn't be needed.
I see now how session tests passed in #813492: HTTPS sessions use an invalid merge query:
Modifying the test, pretty sneaky.. ;)
Comment #64
dwwDo we need a comment at the assertion in the test pointing to this issue nid saying "don't change me"? :/
Glad you tracked this down again, mfb... thanks!
Comment #65
mfbAdded another test: login to the insecure site, and verify that the resulting empty secure session ID saved to the sessions table cannot be used to access the secure site.
Added a new file for mocking HTTP requests so the test works on both HTTP and HTTPS test environments.
Comment #66
mfbAdd another test: login to the secure site, and verify that an empty session ID cannot be used to access the non-secure site. Like the above test, this test fails without the patch.
Comment #67
mfbTweak comments and add some missing doxygen
Comment #68
chx CreditAttribution: chx commentedThe patch is good but had a chance of a notice in the new http.php. Added a !empty to it.
Comment #69
mfbThanks. and, this just changes a comment from "http" to "HTTP"
Comment #70
grendzy CreditAttribution: grendzy commented$conf['https'] should be documented in default.settings.php, yes?
[EDIT] - this was a cross-post, I hadn't intended to change the status. Still, I'm a bit confused by $conf['https'] - that seems like a weird choice considering what it does. It seems more like $conf['enable_https_mixed_sessions'] or something. As it stands we have this:
http://drupal.org/https-information : For better security, leave $conf['https'] at the default value (FALSE) and only use HTTPS for authenticated sessions.
So we're supposed to turn 'https' off to turn it on?
Comment #71
mfbTechnically speaking, this bug has nothing to do with $conf['https'] -- I have only been testing a default install, without enabling the poorly named $conf['https'] setting.
I totally agree, $conf['enable_https_mixed_sessions'] is a much better name. Maybe $conf['session_mixed_https'] to match the existing $conf['session_write_interval'].
I haven't done much testing of $conf['https'] mainly because I don't really understand the security model. With HTTPS-only authenticated sessions it's pretty straightforward -- attackers shouldn't be able to read or tamper with or replay any authenticated requests or responses; authenticated session keys and all data sent to and from the site should be safe. With mixed HTTPS it's another story.
Comment #72
grendzy CreditAttribution: grendzy commentedcool, thanks - I'm working on the port of securepages.module so I'll do some more research later on $conf['https'].
Setting the status back per #68.
Comment #73
grendzy CreditAttribution: grendzy commenteddoh.
Comment #74
mfbBTW I made some updates to https://drupal.org/https-information. Would be great if someone could add info on proper setup for $conf['https'] -- Presumably needs contrib modules like Secure Pages to prevent insecure sessions from accessing certain pages, forms, etc.
Comment #75
carlos8f CreditAttribution: carlos8f commentedExtremely scary security bug! So glad to see we've noticed this regression now and not after 7.0 release :P
Comment #76
bleen CreditAttribution: bleen commentedThis is the identical patch from #69 with two whitespace issue removed.
Comment #77
Damien Tournoud CreditAttribution: Damien Tournoud commentedThis (from both http.php and https.php) rely on the fact that it is difficult from the client side to forge the User Agent header. Are we sure that's always the case? For example what about XMLHttpRequest and HTTP requests from Flash content?
Comment #78
carlos8f CreditAttribution: carlos8f commentedI think http://api.drupal.org/api/function/drupal_valid_test_ua/7 is the proper way to validate the request, unfortunately it requires DRUPAL_ROOT and REQUEST_TIME to be pre-defined. Maybe we can have http.php and https.php have private versions of drupal_valid_test_ua() that use variables instead of constants to generate the HMAC.
Comment #79
chx CreditAttribution: chx commentedIt does not rely on that. If you forge your user agent to start with "simpletest" then the check in bootstrap will kick in. That's what we ensure here. The actual UA check is in bootstrap.
Comment #80
carlos8f CreditAttribution: carlos8f commentedBy the way, "forging" the User-Agent string is trivial. Anyone can set that to include the word "simpletest". Checking for that word alone (and not comparing the HMAC) in the User-Agent is not really enforcing anything.
Comment #81
carlos8f CreditAttribution: carlos8f commentedCross-posted the status, but it looks like there is a problem with the User-Agent check.
In http.php we are checking if the User-Agent contains the string "simpletest", but in bootstrap.inc we check if the User-Agent starts with "simpletest" and then has digits and a semicolon, and only validates then. If you use "something simpletest" as the User-Agent to http(s).php, it passes the initial check, and the bootstrap.inc check is ignored. That would allow http(s).php to be used to make non-test-db requests and fool the server into thinking it is in one mode or the other.
Comment #82
chx CreditAttribution: chx commentedWorking on it.
Comment #83
carlos8f CreditAttribution: carlos8f commentedNow using the exact same preg_match() as bootstrap.inc, this should prevent any HTTP/S spoofing attacks.
Comment #84
chx CreditAttribution: chx commentedHere we go. This will make sure it can't be broken again.
Comment #85
carlos8f CreditAttribution: carlos8f commented#84 is OK for me. There are several places in core that can be cleaned up by this new function, we can leave that to a follow-up though. install.core.inc uses a similar strpos() on the User-Agent, but in that instance it is pointless since we call drupal_boostrap() directly after.
Comment #87
chx CreditAttribution: chx commented#84: 575280-empty-session-id.patch queued for re-testing.
Comment #88
carlos8f CreditAttribution: carlos8f commentedgood
Comment #89
webchickSummary of IRC discussion:
I don't understand the point of this, then. Having two functions that are kinda-sorta validating the HTTP user agent strikes me as practically begging for further security problems. Let's move that logic into drupal_valid_test_ua() and have drupal_valid_test_ua() return the SimpleTest prefix.
Comment #90
chx CreditAttribution: chx commentedSure thing, I can do that. I have found the weirdest bug ever: drupal_get_bootstrap_phase messes up bootstrap if called before there was a DRUPAL_BOOTSTRAP_FULL phase which we have not found 'cos the only way to get there is by including bootstrap.inc from somethign that's not index.php. But this patch enhances the ability of Drupal to work with other systems.
Comment #91
carlos8f CreditAttribution: carlos8f commentedI looked at the patch for a while comparing the changes vs. #76 and it seems correct. At the same time it improves the worth of drupal_valid_test_ua(), too. Very nice.
Comment #93
aspilicious CreditAttribution: aspilicious commented#90: 575280-empty-session-id.patch queued for re-testing.
Comment #94
mfbHrm there's nothing preventing Drupal from running the full page load thru these URLs, just use a Firefox plugin to change your user agent to "simpletest123;" and you can load https://localhost/d7/modules/simpletest/tests/http.php?q=user or http://localhost/d7/modules/simpletest/tests/https.php?q=user (when will Drupal will move itself out of the document root...)
Comment #95
carlos8f CreditAttribution: carlos8f commented@mfb actually you're right. We removed the 403 exit in #90, so we still have the same security issue with HTTP/S spoofing. I still think #83 is an easy way to fix the problem, just use consistent preg_match() in http.php, the hmac will get checked in bootstrap.inc and we'd be denied access. #90 would just treat it as a normal request, which would allow the spoof.
Comment #96
chx CreditAttribution: chx commentedThat's not a spoofing issue however. The invalid UA is disregarded. The fact that it loads proves this -- if it would use the simpletest prefix it would not find the tables.
Comment #97
mfbWell IMO we should figure out some way to ensure that only the sites' own tests can load http.php and https.php. and this should happen before database initialization, in case there is some pre-database page cache delivering pages. Otherwise it sure feels like a backdoor to the site (granted I haven't thought of a specific exploit but it's Sunday and I'm supposed to be doing other things now :)
Comment #98
carlos8f CreditAttribution: carlos8f commentedIn HEAD we check for a possible spoof in _drupal_bootstrap_database(), for our purposes here that's good enough.
#90 has the same flaw as #76 it seems, just using a different spoof header ("simpletest123;" instead of "simpletest blah"). I'm re-uploading #83, and here's the difference between #76 and this:
The change means you can't spoof using "foo simpletest". If you try "simpletest123;" and get through this check, the hmac is checked in bootstrap.inc and the request dies with access denied. I think that is sufficient to address this side issue and get the larger picture (critical sid vulnerability) fixed.
Comment #99
mfbOK I am now getting 403. A real edge case, but I believe page cache could still be accessible, e.g. for $conf['page_cache_without_database'] then the page cache could deliver what it thinks is a HTTPS request via HTTP before bootstrapping the db.
Comment #100
carlos8f CreditAttribution: carlos8f commentedYeah, no worry for cached responses; checking in the bootstrap database phase is fine. If the response is cached then a spoof would be unable to accomplish anything, in theory.
Comment #101
mfbYes, the only thing that could be accomplished would be loading a cached page. A request for http://localhost/d7/modules/simpletest/tests/https.php?q=node will trigger a cache_get for the page cache of https://localhost/d7/index.php?q=node This could be a work-around for any logic that certain pages only be delivered via HTTPS. Let's say you use SSL client authentication to control access on corporate intranet, or you are serving content to users in a country where the content is illegal, but part of the site is also available via HTTP. Like I said a real edge case, does anyone use SSL to verify the client on part of their site.. But seemed worth bringing up.
Comment #102
chx CreditAttribution: chx commentedWell the solution then is to call drupal_valid_test_ua() twice once immediately after configuration is loaded second when the prefix is needed. Can you guys beat that? I have added static caching to the function so it's very fast.
Edit: it is possible that this check completely obsoletes the one in http.php and https.php which would be quite great.
Comment #103
carlos8f CreditAttribution: carlos8f commentedI don't know, I still think all we need is to change 2 lines to fix the problem (#98). Anything more than that is catering too much to this edge-case (alternative entry point which modifies server variables, a rare occurrence).
#102 looks like voodoo, calling drupal_valid_test_ua() at the end of bootstrap configuration, the function returns a simpletest prefix now (which is not documented) and that is not used until later... where it is pulled from the static... and $http_deny = TRUE by default, that is not obvious either... plus the static is broken for normal page loads: static is only set if the preg_match() passes. I am generally unhappy with this patch. It overly complicated and caters to edge-case.
Comment #104
chx CreditAttribution: chx commentedMy poor patch throws a notice but it's SO early that noone evah finds it. Also the check is now completely unneeded in http.php, https.php and install.core.inc because it's all neatly wrapped in the function. That actually simplifies and hardens Drupal.
Comment #105
chx CreditAttribution: chx commentedSo to recap, the second security hole we discovered and fixed in the recent patches:
The first sechole, just to make this post a complete summary (copied from mfb):
Comment #106
carlos8f CreditAttribution: carlos8f commentedDocs say $deny, but function sig says $http_deny.
I don't understand what this is saying. Maybe it can be written more clearly.
Other than those two things, this patch looks OK. It moves the spoof check earlier into the boot configuration phase, which prevents "attackers" from peeking at cached pages that they might not normally be able to see through HTTP. This is a pre-existing, totally unrelated issue to the sid flaw and beyond the scope of this issue IMO, but since we have a working patch we might as well go with it. Fix the docs and I think we are done here.
Comment #107
chx CreditAttribution: chx commentedFixed the docs as asked. Given that the session fix is good since #69 now carlos8f said we are good to go, I set to RTBC.
Comment #109
Pls CreditAttribution: Pls commented#107: 575280-empty-session-id.patch queued for re-testing.
Comment #111
carlos8f CreditAttribution: carlos8f commentedLatest patch fails because chx tried to sneak this in. We had been talking about it on IRC and decided it was a useless check. This prevents simpletest from ever using install.php. However, it's next to impossible to forge a legitimate simpletest header, and as long as we check the HMAC (which booting configuration now does) I think we can remove the lines in simpletest.test that checks for a 403.
Powered by Dreditor.
Comment #112
mfbor, if you want to leave the test in, change it to use an invalid test UA so bootstrap configuration returns the 403:
$this->drupalGet($base_url . '/install.php', array('external' => TRUE), array('User-Agent', 'simpletest123;'));
Comment #113
carlos8f CreditAttribution: carlos8f commented@mfb, I think that works (overridding CURLOPT_USERAGENT with CURLOPT_HTTPHEADER), but the header should be one string, like
User-Agent: simpletest123;
.Comment #114
chx CreditAttribution: chx commentedI would think that you want to use additionalCurlOptions. I did. The patch has failures yet I post it 'cos I am out of time to work on this now.
Comment #115
carlos8f CreditAttribution: carlos8f commentedThis assertion is not what mfb suggested. We want to make sure install.php returns a 403 if a spoofed simpletest header is present, i.e. "simpletest123;". $this->databasePrefix would produce a valid header and a corresponding 200 OK.
Comment #116
mfbalso that would set curl options after the request we want to tweak (which then affect later requests). this should get tests passing:
$this->drupalGet($base_url . '/install.php', array('external' => TRUE), array('User-Agent: simpletest123;'));
Comment #117
chx CreditAttribution: chx commented$this->databasePrefix definitely would not produce a legit header because it lacks timestamp and HMAC. And passing in User-Agent: leads to a conflicting situation (a preset cURL option vs this HTTP header), the variable I used is designed to override any CURL options set.
Comment #118
carlos8f CreditAttribution: carlos8f commentedOK, in any case we need to make the assertion more clear. It says "Cannot access install.php with a "simpletest" user-agent header." Let's change that to "invalid simpletest user-agent header", and perhaps use simpletest123; instead of $this->databasePrefix. I tested out passing the User-Agent string directly in the drupalGet(), it should work to override CURLOPT_USERAGENT temporarily.
Additionally, I think we should add assertions to test for a 403 when trying to use a spoofed simpletest header on index.php, http.php, and https.php. Then we could group all these 403 assertions into a new test method, and use $this->additionalCurlOptions.
Comment #119
carlos8f CreditAttribution: carlos8f commentedI'm working on cleaning this up a bit.
Comment #120
KeithH CreditAttribution: KeithH commentedAs far as I'm concerned, once the overlay bug and Dashboard module bug are closed along with this, that then should mean that Drupal seven will be ready for an RC release.
If I can help by testing patches as long as your patches and such have to deal with using a screen reading program or keyboared alone in Drupal7-I can test them out and let you folks know.
Overlay patch loks fine as far as I'm concerned.
I can't wait until all the issues reach zero and an RC build is released.
I think judging on the number of various bugs I've seen is that the more time spent on these the longer Drupal Seven will not be released. I'd like to see this happen ASAP.
Although I don't have the resources at the moment to fix any outstanding issues myself, I can apply various patches and such.
But like I said-if the patch isn't a screen reader specific type of patch as that would have to deal with using the keyboard and such, then I can't really test for anything.
Let's get an RC out soon, hopefully!
I believe that modules and bugs can always be easily addresed during/after an RC, and even after the oficial release.
Please see comment 182 (I think) on the overlay module screen reader chriticle issue and you'll see my opinion on what we all need to do together as a community to get Drupal into an RC release, and finally the next stable. :)
Let's make Drupal the best we can make it be for all users!
Comment #121
carlos8f CreditAttribution: carlos8f commentedIt turns out we can't remove the user agent check in http(s).php, like #104 states. That's because if you have a user agent that looks nothing like simpletest, it is ignored as a simpletest spoof and you can happily make requests to http(s).php using your browser (I was able to verify that).
This patch has a slightly different implementation than @chx's, but I tried to keep the goal the same. It locks down http(s).php so you can never make fake requests to it. To do that, I re-arranged some things in http(s).php and have developed a sneaky way to extract the hash salt from settings.php when we're running the tests.
As a side, a 403 is no longer returned for requests to index.php when the user agent looks like 'simpletest123', it's simply treated as a normal request to index.php.
Let's see how the tests do.
Comment #123
carlos8f CreditAttribution: carlos8f commentedI learned why
if (!$this->inCURL())
is necessary in simpletest.test.Comment #124
carlos8f CreditAttribution: carlos8f commentedFixed a problem with the static in drupal_valid_test_ua().
Comment #125
carlos8f CreditAttribution: carlos8f commentedEven better, instead of "forging" the header with "simpletest123;", let's use an actual header and slightly modify the HMAC. That ensures the HMAC is actually getting validated.
Comment #126
carlos8f CreditAttribution: carlos8f commentedImproved the test again, now we check for a 200 with our generated $test_ua, then change the HMAC and test for 403.
Comment #127
mfbI don't think you can/should try to parse PHP with preg_match.. what about people modifying the formatting, commenting it out, etc? How about bootstrap configuration so salt is available, validate the test UA and then bootstrap full.
BTW, chx said something about finding a bootstrap bug, not sure if fix for that is included in this.
Comment #128
carlos8f CreditAttribution: carlos8f commentedYes, that sounds like a better idea. I will include @chx's bootstrap bugfix in there too, although I'm not sure if it affects my patch.
Comment #129
carlos8f CreditAttribution: carlos8f commentedMuch better.
Comment #130
carlos8f CreditAttribution: carlos8f commentedOn second thought, the bootstrap bugfix doesn't affect the patch, so I left it out and let's open a separate issue if necessary.
Comment #131
mfbHmm looks like you changed my patch to bootstrap config and then do the mock HTTPS/HTTP thing. I would have assumed that doesn't work since some drupal variables wouldn't be initialized as expected.
Comment #132
carlos8f CreditAttribution: carlos8f commentedDoh. I think that it works because we override $_SERVER['HTTPS'] etc. afterwards anyway, but still, we need to simulate that PHP started out with these server variables to begin with.
Comment #133
carlos8f CreditAttribution: carlos8f commented@mfb, sorry, for some reason I totally missed that you posted a patch in #127. Looks like #132 is basically the same though, other than the capitalization of 'HTTPS' in comments.
Comment #134
chx CreditAttribution: chx commentedNo, no, no! We are not exposing the salt like this. Working on it.Nevermind: i am stupid.Comment #135
chx CreditAttribution: chx commentedSorry for the noise. mfb's patch was good http://drupal.org/node/575280#comment-3663234 here linking the patch http://drupal.org/files/issues/575280-empty-session-id_21.patch itself and carlos8f said that he missed the patch and basically implemented the safe and interdiff concurs. So while the latest two green patches are largely the same the comments in mfb's is better. Let's get this sucker in!
Comment #136
Dries CreditAttribution: Dries commentedReviewed and studdied, and committed to CVS HEAD. Thanks.
Comment #138
lafitz CreditAttribution: lafitz commentedsession.patch queued for re-testing.
Comment #139
carlos8f CreditAttribution: carlos8f commentedThis issue is already fixed.
Comment #140
mfbor is it. I might have screwed up the update function -- don't we need to recreate the primary key for postgres? It would be bad if postgres was left hanging without a primary key here!
Comment #141
carlos8f CreditAttribution: carlos8f commentedI've never used Postgres.... why does the primary key need to be dropped and re-added?
Also... is it possible that after this update runs, there are still rows in {sessions} where sid = '', ssid = '...' that still pose a security problem if someone uses a blank sid in their cookie? We do
if ($sid) {
in $is_https mode, but not otherwise. Seems like we should always set $user to drupal_anonymous_user() if we have a blank $sid.Another minor thing, in _drupal_session_read() it's possible if $is_https and !$sid, $user is undefined and might trigger a PHP notice.
Comment #142
grendzy CreditAttribution: grendzy commentedThe last commit seems to create duplicate entries in the session table when $conf['https'] = TRUE.
Steps to reproduce:
- install HEAD, set $conf['https'] = TRUE.
- Log in via HTTPS
- post a comment via HTTPS.
- post another comment via HTTP.
The session table now looks like this:
As a result, you get really strange behavior, such as drupal_get_messages() failing to retrieve messages that are stuck in the second row.
Comment #143
carlos8f CreditAttribution: carlos8f commented.
Comment #144
mfbDoes this patch fix the issue in #142?
Re: #141 see the docs at http://api.drupal.org/api/drupal/includes--database--database.inc/functi...
The global $user declaration in _drupal_session_read() sets $user to NULL if it doesn't exist so there shouldn't be any chance of a notice.
Comment #145
EvanDonovan CreditAttribution: EvanDonovan commentedCross-post.
Comment #146
grendzy CreditAttribution: grendzy commentedYes, thanks! The latest patch fixes the issue I was seeing with duplicate sessions. I've noticed 2 more issues with https, though it's unclear if they are related to this patch:
If an anonymous session is being written because of drupal_set_message(), and it's an https request, $_COOKIE is empty and this test fails:
if (isset($_COOKIE[$insecure_session_name])) {
As a result the insecure cookie never gets sent, and we end up with the following session:
Second issue, $user->timestamp appears not to be defined for the anonymous user; leading to PHP notices in some of my tests.
if ($is_changed || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180))
If a clearer demonstration of what I'm seeing would be helpful, just checkout the 7.x branch of securepages and run the tests (you'll also need the patch in #961508: Dual http/https session cookies interfere with drupal_valid_token()).
Comment #147
carlos8f CreditAttribution: carlos8f commentedWe still haven't fixed the real problem: as Damien points out we are accepting an empty session ID. We've fixed this for $is_https, but not otherwise.
This is a reroll of #144 which returns an anonymous user if empty($sid). @mfb's changes to the update function kill sessions with blank IDs, but sites may have already run that update function and I think we should fix it on the session handler level as well.
Comment #148
chx CreditAttribution: chx commentedWell, this is good.
Comment #149
webchickWe need tests for this.
Comment #150
webchickComment #151
carlos8f CreditAttribution: carlos8f commentedokay.
Comment #152
webchickCome on IRC! :D
Comment #153
carlos8f CreditAttribution: carlos8f commentedThe bot should fail the first patch, since it does not include the fix. I moved the fix to drupal_session_initialize(), and simply check !empty($_COOKIE[$session_name]) instead of isset(), so now we don't even session_start() if a blank session ID is passed.
This does not include a test for #142, so it still needs work.
Comment #155
carlos8f CreditAttribution: carlos8f commentedOK, here is a straight isset() to !empty() change, let's see how the bot likes this one.
Comment #156
chx CreditAttribution: chx commentedWhile I was on the fence with the previous patch (because it changed session writes) I really like this one. I have deleted one assert from the test however -- there is no need to assert the success of a database update. We have update tests for that.
Comment #157
webchickCommitted to HEAD! Thanks!