This is either a bug or a documentation issue, but it's important we figure it out.
In Drupal 7, the "rule" is that you need to have the PHP module turned on in order to be able to execute arbitrary PHP code. This was especially intended as a result of http://drupal.org/node/224333#php_permissions and related issues. The idea is that if you want to lock down your Drupal installation from arbitrary code execution, you can remove the PHP module from the filesystem, or block it from ever being enabled via custom code. That way, even if someone stumbles across a computer with an administrator logged in, there is a limit to the damage they can do.
However, the Update Manager functionality breaks this assumption. In "normal" operation of the Update Manager, this is certainly by design (as the entire purpose is to let you upload arbitrary code to your server) - however, in normal operation it also asks you for your FTP or SSH credentials right before letting you upload, so that's fine because if someone knows those they can already upload things to your server outside of Drupal.
But for certain server setups (in particular, situations where the webserver user owns the Drupal files - i.e. setups that are popular in shared hosting) the Update Manager does not ever ask you for a server password (this results from #609728: Skip authorize.php step if webroot files are owned by the httpd user). It just lets anyone logged-in with the 'administer software updates' permission upload modules to the site.
We need to decide if it's acceptable to allow that with the PHP module turned off. If it is, we need to document somehow that disabling access to the update manager (which can be done through settings.php) is sometimes necessary as part of any procedure to disallow direct arbitrary code execution (without knowledge of SSH/FTP passwords) on a Drupal site. Right now, I don't believe there is any indication of that.
Comment | File | Size | Author |
---|---|---|---|
#30 | update-manager-932110-30.patch | 1.79 KB | dcam |
#27 | update-manager-932110-27.patch | 1.79 KB | Albert Volkman |
#25 | update-manager-932110-25.patch | 1.79 KB | jurgenhaas |
#23 | update-manager-932110-24.patch | 1.79 KB | Albert Volkman |
#16 | update-manager-932110-16.patch | 1.69 KB | marji |
Comments
Comment #1
David_Rothstein CreditAttribution: David_Rothstein commentedAdding a tag.
Comment #2
EvanDonovan CreditAttribution: EvanDonovan commentedIs the "administer software updates" permission listed as one of the permissions that affect security?
Comment #3
David_Rothstein CreditAttribution: David_Rothstein commentedYes, it is (at least in the admin UI - I guess http://drupal.org/security-advisory-policy hasn't been updated for Drupal 7 yet).
Without that, this would definitely be a more serious bug :)
Comment #4
dwwWe're really afraid of people who leave themselves logged in as super admins and walk away from their machines at a coffee shop or something? Really? ;)
Anyway, if the workflow introduced by #609728: Skip authorize.php step if webroot files are owned by the httpd user is too permissive, we should be able to use authorize.php to ask the admin user to log in as themselves again. That'd satisfy the security concern here, right? Super rough sketch of how this would work:
(Note, this might depend on the API changes from #609772: Impossible to extend the FileTransfer class system in contrib before it actually works).
Instead of update.manager.inc directly creating a FileTransferLocal object in this case, it does special mojo to redirect to authorize.php in such a way that it's not prompting for FTP/SSH credentials, but basically a login form.
- It'd have to not call system_authorized_init(), but instead manually setup $_SESSION['authorize_filetransfer_backends'] and $_SESSION['authorize_operation'] itself.
- It'd setup authorize_filetransfer_backends to use a custom "settings form" and factory callback.
- The factory callback would basically validate the password field, and if it worked, it'd instantiate the right FileTransferLocal object to actually do the operation.
- The update/install would proceed as normal via authorize.php at this point
In fact, this last point is probably an important fix to #609772 in the update case. We actually *want* to run updates from authorize.php so we've got the low bootstrap level and have a slightly cleaner environment to operate in. It doesn't matter when installing new code, but when updating existing code, we don't want all modules bootstrapped. It's certainly evil and dumb to rely on FTP and ask for those credentials (which we don't need) if all the files are owned by the same user. But, we could verify that the user running the operation is a human that knows the password to an account with "administer software updates". Might be a bit of a UX pain in the ass. :( Especially since we never fixed #720452: Allow ability to install multiple modules at once :( Maybe there could be another settings.php killswitch for this behavior? OTOH, if they're using FTP, we ask them for their password each time, so at least it'd be consistent. In a way, that'd actually make it easier to document all this stuff, since the workflow would always be the same.
Anyway, if we actually want this approach, the first step is getting #609772 in, since the whole API for managing this stuff is completely broken in HEAD right now. I have a patch with verified working tests and squeeky-clean API docs over there, it just needs a review other than me before it's RTBC. Once that's done, we can proceed with the plan I outline here.
Meanwhile, it'd be nice to get some UX reviews of this proposed workflow...
Comment #5
Bojhan CreditAttribution: Bojhan commentedThis sounds like a won't fix at this point, I am not sure I fully understand but if it requires logging in again we surely are doing something wrong - asking FTP information should be enough. If the webserver allows things to pass without FTP information, are we to blame or the server configuration?
Comment #6
dww@Bojhan: This would be *instead* of entering your FTP credentials, but at the exact same spot in the workflow. The concern raised here is that due to #609728: Skip authorize.php step if webroot files are owned by the httpd user, on common shared hosting server configurations (where all the files are owned by the user that httpd is running as), if a novice admin leaves themselves logged into the site all the time, and they ever leave their browser unattended, any random person could use the update manager to install malicious PHP code on the site. One assumption we had previously made is that if you completely removed the PHP module, there's no way in Drupal core to execute arbitrary PHP from the web UI. Update manager currently breaks this assumption in many cases.
So, this issue is proposing that in the special case of #609728, when the update manager currently skips authorize.php entirely and *directly* installs/updates new code, we'd still land on authorize.php. The user would technically still be logged in, they'd just be asked to provide their site admin login credentials again (extremely common when installing software updates or new code in other contexts (e.g. Apple's "Software Update").
This way, you'd have to be so foolish as to leave your machine unlocked at a coffee shop *and* tape your Drupal admin password to your laptop for any random user to install malicious PHP code on your site, even without PHP module.
But I'm still torn. My security team hat says "fix this".
My "updates should never happen during a full bootstrap" hat says to fix it. However, most of the theory behind the low bootstrap authorize.php doesn't translate into much practice. The final authorize.php landing page doesn't actually let you recover from failure, restore your site, anything. It's just a last dying breath, potentially full of red errors, telling you "bummer, I just hosed your site, sorry", and the site is already WSOD.
My "updating my site should be easy" hat (the whole premise of the Update manager) says "just document this risk and leave all the code alone."
Comment #7
David_Rothstein CreditAttribution: David_Rothstein commentedI do like the idea of asking for their account password (although implementing it sounds a bit daunting). The UX concerns don't seem like an issue given that it's the exact same thing we make them do for the FTP case.
One relatively simple improvement we might be able to make is that we could cache "this user entered their Drupal password on authorize.php at time X" in the session and only make them type it a second time if it's been more than a few minutes (that's also what operating systems tend to do). That would solve the case of someone installing multiple modules in a row and being annoyed by having to type their Drupal password over and over again.
I don't think that documenting this would be the end of the world either (given that we already have the PHP module in core and this certainly is no bigger of a security risk than that is). However, it would be nice to be able to say that the new Update Manager in D7 is truly secure, in a way that the PHP module isn't.
Comment #8
cweagansAnother approach is to make it so that you could update existing modules without giving the site your FTP or SFTP information, but not install new ones. To clarify:
Situation 1)
All of my drupal files are owned by the same user and group as my web server. I can go update any module without FTP of SFTP information. In order to install a new module, I'd need to supply credentials.
Situation 2)
All of my drupal files have the ownership set in an intelligent manner. I must supply FTP or SFTP information in order to update existing modules, or install new ones.
Comment #9
David_Rothstein CreditAttribution: David_Rothstein commentedHow about we start by updating some of the documentation (the description of this functionality in the settings.php file itself is currently wrong), and then leave this issue open for further changes afterwards?
Something like the attached, perhaps?
Comment #10
dwwYes, that's certainly a good start. The only thing I'd consider adding is a link to http://drupal.org/server-permissions (although that page seems to have been reorganized into a more catch-all place, so that might not be helping much).
Comment #11
Stevel CreditAttribution: Stevel commented-1 for asking the account password to the user here.
Users using an external authentication mechanism (openid, single sign on, ssl authentication etc.) don't have a valid password known to drupal, so it's impossible to check for it.
The comment looks like the best we can do without building a complete authentication abstraction layer.
Comment #12
deviantintegral CreditAttribution: deviantintegral commentedPatch in #9 still applies.
Is http://drupal.org/node/244924 the link it should include and what /server-permissions used to be?
Comment #13
sunAs long as we're talking about a documentation-only change, this is a task.
Comment #14
xjmTagging issues not yet using summary template.
Comment #15
xjmWell, let's get the documentation fix in, at least. (And I think the page in #10 isn't really useful and probably needs a different alias now at that.)
Tagging novice to reroll #9 against the current D8 codebase.
Comment #16
marji CreditAttribution: marji commentedHey guys, I rerolled #9 against the current D8 codebase.
Patch attached.
Comment #17
marji CreditAttribution: marji commentedAdding DDU2012 I tag I omitted in #16 :)
Comment #18
cweagansDDU2012? What's that?
Comment #19
xjmDrupal Down Under. :) They're having a code sprint today.
I think #16 is RTBC, since the handbook documentation is not-so-useful. We can set it back to active for other changes after.
Comment #20
David_Rothstein CreditAttribution: David_Rothstein commentedPer #12, should we link to http://drupal.org/node/244924 instead?
Also:
Although "inherently insecure" is a little strong (the issue is more that it expands the attack surface in the case of other vulnerabilities, rather than being a vulnerability in and of itself), I guess we should keep it, since it's consistent with other code comments elsewhere in Drupal.
Actually, we probably should link to http://drupal.org/node/244924 and word this in a way that explicitly discourages people from changing the file permissions/ownership just to make the Update Manager "work better".... Reading through the comments at http://drupal.org/documentation/install/modules-themes/modules-7, for example, it seems like people have discovered this little trick without understanding the consequences. We probably should be explicitly discouraging it in this code comment.
Comment #21
Bojhan CreditAttribution: Bojhan commentedWhy is this still not in? Its stalled on a code comment? Sometimes I feel our major/critical change basically made all majors into normals.
Comment #22
xjmSince the patch in #16 is for a documentation improvement, let's incorporate David's changes into that improvement. This would be a good novice task.
Comment #23
Albert Volkman CreditAttribution: Albert Volkman commentedPatch updated with @David_Rothstein's suggestions. Unable to interdiff because previous patch no longer applies.
Comment #24
Albert Volkman CreditAttribution: Albert Volkman commentedComment #25
jurgenhaasThe patch from #23 doesn't apply anymore, so I re-rolled and attached it.
Comment #27
Albert Volkman CreditAttribution: Albert Volkman commentedRe-rolled.
Comment #28
jurgenhaasThis is working fine for me.
Note: The patch for D7 will have to be re-rolled as patch #27 won't apply.
Comment #29
webchickLooks good to me, and seems to incorporate the latest feedback.
Committed and pushed to 8.x. Moving to 7.x for backport. Also changing the documentation component, since this is purely a docs fix now.
Comment #30
dcam CreditAttribution: dcam commentedBackported #27 to D7.
Comment #31
jhodgdonLooks like the same fix as was committed to d8, thanks for the backport!
Comment #32
jhodgdonCommitted to 7.x (pure docs).
Comment #33
David_Rothstein CreditAttribution: David_Rothstein commentedI guess we should still keep this open for Drupal 8, though. (Especially in light of people wanting to use #1675260: Implement PHP reading/writing secured against "leaky" script for module upgrades and the like...)
Re #11 and comments before it, I'd love to see a generic "reauthenticate-on-demand" capability in Drupal core, and this would definitely be the first place to use it. I guess that's really the only way to fix the issue at this point.
Comment #34
David_Rothstein CreditAttribution: David_Rothstein commentedNot exactly "Novice", though...
Comment #35
David_Rothstein CreditAttribution: David_Rothstein commentedThis was committed to Drupal 7, but for some reason was reverted later: http://drupalcode.org/project/drupal.git/commitdiff/3b64ddd9630648eb267d...
I assume that was by mistake...
I could fix it myself, but I'll leave it to another core committer to confirm that I'm not going crazy :)
(And don't worry, @webchick, it was your commit but you're certainly not the only one who might have done this. I just left a similar comment on #1722244: drupal_get_destination() is missing a @return, and that one you definitely weren't responsible for :)
Comment #36
webchickErr. Right. No idea what happened there. :P
RE-committed and pushed to 7.x. :P Forreal this time.
Comment #38
David_Rothstein CreditAttribution: David_Rothstein commentedBack to Drupal 8 (per #33)...
Comment #39
YesCT CreditAttribution: YesCT commentedYep. Taking a look at this it looks like there are two concurrent next steps here:
'a generic "reauthenticate-on-demand"' sounds kind of complicated. Who would be a good person to have the right skills to tackle that? maybe we could ping a couple people to see if they are interested. and/or some more detail about what that would entail might be helpful.
Comment #40
jhedstromSeems like the only thing left here for 8.x is to revert the change as per #35?
Comment #41
YesCT CreditAttribution: YesCT commentedComment #57
quietone CreditAttribution: quietone at PreviousNext commentedA patch was committed to 8.x (#29) and to 7.x (#32) for this. The 7.x one was accidentally reverted but then restored (#36). This all points to this being completed.
It appears the only left is to create a followup for #33 and then this can be closed. Adding tag for a followup.