Problem/Motivation
Note that while this issue is a security flaw, it only exists in 8.5.x so it is not exploitable in any released version of Drupal.
#2824851: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior recently updated the access checking for PATCH requests to entities. Interdiff #203 in that issue introduced the possibility of a timing attack.
We allow providing fields in a PATCH request, even if the user is not allowed to edit those fields, if the values match the originals. However, if the user does not even have access to view that field, we always deny the PATCH request, as otherwise the user could just try out different values until they find one which doesn't deny access at which point they know the field value, so they have in essence "viewed" the field. The following code takes care of this:
if ($original_field->equals($received_field) && $original_field->access('view')) {
return FALSE;
}
The problem is the order of the checks. If the field values do not match we never reach the access check, so there will be a difference in response time depending on whether the field value is the current one or not.
Proposed resolution
Simply flipping those conditions will make sure that both conditions are always evaluated.
Comment | File | Size | Author |
---|---|---|---|
#2 | 2934520-2.patch | 834 bytes | tstoeckler |
#2 | 2934520-access-handler-hack--do-not-test.patch | 905 bytes | tstoeckler |
Comments
Comment #2
tstoecklerI'm not sure if there is any way to test this automatically, but I did check that this is actually an issue.
To play along at home on a clean Drupal (without any patches) enable Node, HAL and Basic Auth modules and create a node with a given title. Now apply the "2934520-access-handler-hack--do-not-test.patch" patch and clear caches. Now send two patch requests to the node: one with the original title and one with a different title. The first request will take something like 20 seconds (because both edit and view access of the field are checked) and the second one will take something like 10 seconds (because only edit access is checked).
Here's my terminal for easy copy-paste and verification:
Now apply the "2934520-2.patch" patch and repeat the two requests. This time they will both take around 20 seconds.
Comment #3
larowlanI think this qualifies as critical because we can't release 8.5 without it.
Comment #4
BerdirMakes sense, but having a reliable difference between the two cases seems pretty hard to achieve, we are talking about PHP after all and I did a few tries without the sleep and the at least on my laptop, Didn't do an actual empirical test, but I've seen values that vary as much as 124ms and 146ms with the same title string. So you wouldn't have to just guess the right value, you'd have to do it enough times to have a statistically relevant output. Maybe if the access logic would be very complex but that's not usually the case on a field.
Did that because I'm wondering about the security impact of this. If it would be a security issue, even just in 8.5.x, it would be a release-blocking critical, but you created it as a normal issue. That or a reason to revert that other issue, as we did before, although this is by far less complex to change than the previous problem.
Ha, actually crossposted with @larowlan :)
Comment #5
tstoecklerYes - although I'm not a security expert, so take this with a fair amount of salt - I agree that it will be near impossible to exploit this under any normal circumstances. It may become exploitable with crazier access checks, though. A sleep() is obviously stupid, but if someone is asking some remote service over HTTP or is accidentally causing a router rebuild or something bizarre like that it may become noticeable.
The far-fetched-ness of this together with the fact that I would reeeeally like to avoid a revert of the mentioned issue is the reason why I didn't mark this critical from the start.
In any case, even if it is far-fetched, it really doesn't hurt in any way, i.e. it doesn't increase code complexity or anything, so, I do think we should get this in.
Comment #6
BerdirAgreed, was just waiting for the tests to come back green :)
PS: why u no sleep?!
Comment #7
larowlanCommitted 2971ea9 and pushed to 8.5.x
thanks
Comment #9
tstoecklerThanks for the quick commit! Not sure what happened, but somehow the status didn't stick.
@Berdir: Aren't we in the same timezone... !? ;-)
Comment #10
Wim LeersI didn't even get a chance to review this!
Wow. I suspect there are many of these timing attack surfaces in Drupal then. But the difference is that usually, lots and lots of PHP needs to execute, which is not the case for a REST
PATCH
request, where you can even send a single field to be modified.IOW: timing attacks weren't really a problem for Drupal in the past, but they are for API requests made to Drupal. Great catch, @tstoeckler! 😮 👏
Huge mitigating factor: the Symfony routing and access checking is so slow (~30 ms) that it prevents accurate time measurements that'd allow the line of code in question to even be accurately measured… This is why @tstoeckler had to artificially inflate certain computations in #2 to be able to actually attack. is true though — with crazy complex access checks (perhaps some that talk to some external service), it would be possible to deduce the current value!
👏
👌
Comment #11
cburschkaNice one! Timing-based leakage certainly seems to be on people's minds this week. :D
And yeah, since the entity access check can be hooked into, it does seem possible for a contrib module to make this attack feasible just by being slow. Maybe not critical, but certainly good to harden against.
Comment #12
almaudoh CreditAttribution: almaudoh commentedToday I learned something. Thanks @tstoeckler.