Problem/Motivation
When requesting an image through an image style URL (e.g. .../files/styles/my-style/public/my-image.jpg
), but the original image file (.../files/my-image.jpg
) does not exist, it would make more sense for Drupal to return a Page Not Found (404), instead of an Access Denied error (403).
Reasons:
1. When requesting .../files/my-image.jpg
(i.e. without the image style portion), Drupal returns a 404.
2. In certain server configurations, 404's are cached for some time, but 403's aren't. In this case, a DDoS attack could be made to the server by simply requesting many images that don't exist.
Proposed resolution
Quoting @catch in #32
There are several scenarios. The first value in parenthesis is the current behaviour, the second is what the patch changes it to (if there's a change).
1. Valid itok + derivative on disk (200)
2. Invalid/no itok + derivative on disk + source image (200)
3. Invalid/no itok + derivative on disk + no source image (200)
4. Valid itok + source image (200)
5. Valid itok + no source image (404)
6. Invalid itok + source image (403) (404) - we check the itok before checking file_exists() and return early
7. Invalid itok + no source image (403) (404)
Remaining tasks
Review / improve existing patches.
User interface changes
Drupal will now return a 404 (Page Not Found) instead of a 403 (Access Denied) when an itok is invalid or an itok is invalid and the source image doesn't exist.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#45 | interdiff.txt | 1 KB | anavarre |
#45 | 2211429-45.patch | 5.18 KB | anavarre |
#41 | 2211429-41.patch | 5.13 KB | anavarre |
#36 | update-image-error-code-2211429-36.patch | 5.08 KB | LoMo |
#26 | update-image-error-code-2211429-26.patch | 4.81 KB | royal121 |
Comments
Comment #1
cambraca CreditAttribution: cambraca commentedComment #4
cambraca CreditAttribution: cambraca commentedOf course I forgot to update the .test file. Here is an updated patch.
Comment #5
cashwilliams CreditAttribution: cashwilliams commentedThis was causing major performance issues for a very large site we launched. An external app vender had stripped the query parameters from image requests, thus causing a huge amount of 403s. These were all hitting origin and made up for 70% of the origin traffic.
I reviewed the thread which introduced this feature from the security patch, and from what I can tell a 403 was chosen because a menu access was refactored from returning false. My guess is since the original solution using menu access would return a 403, the same value was kept in the refactoring.
Does anyone have any reasons this should not be a 404?
Comment #7
cambraca CreditAttribution: cambraca commentedWhy would this test fail now if it passed before?
Comment #9
David_Rothstein CreditAttribution: David_Rothstein commentedI would guess the rationale for a 403 is that your request to the server is trying to do something (generate an image) but the system is choosing not to allow you to do it (e.g. because you have provided an invalid token)...?
In any case, is this issue valid for Drupal 8 also? If so, it would need to go there first, then backported.
Comment #10
cashwilliams CreditAttribution: cashwilliams commentedI think we should follow the path set by the just committed #927138: Fail image generation with 404 instead of 500, when source file is missing
Comment #11
cambraca CreditAttribution: cambraca commentedShould we mark this as duplicate then? I'm not too familiar with the process, I couldn't tell if that other issue's code is already in the 7.x dev branch, if so then this issue is no longer relevant and should be closed. Right?
Comment #12
cashwilliams CreditAttribution: cashwilliams commentedNo, it's separate. It addresses the actual image missing, where as this addresses the wrong token.
Comment #13
Dane Powell CreditAttribution: Dane Powell at Acquia commentedI confirmed this issue still exists in Drupal 8. Oddly, the test case appears to already test for a 404- I don't know how it was passing in the first place.
Here's a patch against 8.0.x-dev, functionally identical to the 7.x version in #4.
Comment #18
Dane Powell CreditAttribution: Dane Powell at Acquia commentedUmm... I have no idea why tests are repeatedly failing. Each time it's with a different error- none of them seem related to the patch.
Edit: tests are passing now.
Comment #19
mgiffordComment #20
Truptti CreditAttribution: Truptti at Axelerant commentedThe patch 'drupal-2211429-13.patch' in comment #13 is not applied and gives error.
Attaching snapshot for referenc
Comment #21
mgiffordAgreed @Truptti - a lot can change in 9 months. You up for trying to re-roll this patch?
Comment #22
ajalan065 CreditAttribution: ajalan065 commentedRerolled the patch, and it worked for me..Rest on the Test Bot
Comment #23
ajalan065 CreditAttribution: ajalan065 commentedComment #24
anavarreComment #25
heykarthikwithuOn applying patch, i am getting warnings
warning: core/modules/image/src/Controller/ImageStyleDownloadController.php has type 100644, expected 100755
warning: core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php has type 100644, expected 100755
Comment #26
royal121 CreditAttribution: royal121 commentedPatch attached. Please review.
Comment #29
basvredelingDid a quick test of #26. Seems to work as intended.
Comment #30
akashkrishnan01 CreditAttribution: akashkrishnan01 at Google Summer of Code commentedPatch #26 has been tested and working fine
Comment #31
LoMo CreditAttribution: LoMo as a volunteer commentedAlso (manually) tested #26 and can confirm that it seems to work fine. :-) Since automated tests also appear to be in place, look correct (and pass), I think this can be marked RTBC. It would be nice to see this committed before it needs another re-roll. ;-)
Comment #32
catchDiscussed this with @alexpott a bit, we disagreed on the solution somewhat, so trying to document my take on the 404 vs. 403 here. Alex's point of view was that we should 403 if the file could be generated but we're refusing, and 404 when it's not there (this is not our current behaviour when we have a bad itok and also a missing file).
There are several scenarios. The first value in parenthesis is the current behaviour, the second is what the patch changes it to (if there's a change).
1. Valid itok + derivative on disk (200)
2. Invalid/no itok + derivative on disk + source image (200)
3. Invalid/no itok + derivative on disk + no source image (200)
4. Valid itok + source image (200)
5. Valid itok + no source image (404)
6. Invalid itok + source image (403) (404) - we check the itok before checking file_exists() and return early
7. Invalid itok + no source image (403) (404)
For me, given that any other request with a correct itok will result in a valid image on disk that's then accessible, we should not treat the itok check as an access check as such, but just determining whether something (the generated file) is going to be returned by that location or not, which only needs to happen when it's not already there.
The only reason to stick to a 403 semantically here would be if we weren't using on-disk derivatives at all but were access checking the files each time, but that's not what the itok is for - it's only a protection against DDOS.
Given that links with and without the itok are fairly common, the existence of the file on disk may or may not be determined by the request at all - it's simply there or it isn't, and if it isn't only then does the itok matter. So a 404 seems fine.
Comment #33
alexpottGiven #32 I think that returning a 404 is reasonable.
Comment #34
alexpottI think some commentary why we return a 404 here for invalid itok's. Something like at this point we don't know if the source file exists on the server and in order to minimise disk activity we don't bother to check. Because we already know that we're going to return an error response and we chose a 404 because it is cacheable. Or something like that. Or borrow some of the words from #32
Comment #35
LoMo CreditAttribution: LoMo as a volunteer commentedOkay. I'll get a patch together with the needed commentary for review. I think this is a good way to avoid regression (even more appropriate than just updating the tests to our current behavior). I'll include a reference to this issue.
Comment #36
LoMo CreditAttribution: LoMo as a volunteer commentedHopefully this fits the bill and we can just get this committed after 3 years. ;-)
Comment #37
akashkrishnan01 CreditAttribution: akashkrishnan01 at Google Summer of Code commentedComment #38
LoMo CreditAttribution: LoMo as a volunteer commented@akashkrishnan What?
Comment #41
anavarreRerolled against 8.6.x as it no longer applied.
Comment #42
anavarreUpdated the issue summary and wrote an initial draft for a change record as I think this would be helpful to advertise for this change when it lands.
Comment #43
mcdruidIn the change record, I'd suggest:
...should instead be:
(I think the same text would be changed in both the before and after section.)
I'd perhaps also change "load balancer" for something like "caching reverse proxy" or just "caching proxy".
Comment #44
anavarreThat makes sense! Diff here https://www.drupal.org/node/2962688/revisions/view/10931428/10931971
Comment #45
anavarreQuickly discussed the issue with @mcdruid and he suggested an improved comment and few other CR changes.
Core committers: please make sure to credit him, thanks!
Comment #46
mcdruidI think the patch looks good, tests pass, and the CR makes sense. RTBC!
Comment #47
Wim Leers👍
Comment #48
mcdruidAdding "Security improvements" tag, as this is about enhancing resilience to (D)DoS.
Comment #49
alexpottCrediting @catch, @cashwilliams and @mcdruid as per comments and reviews.
Removed @see on commit because git history gives you this. The @see to an issue nid is pointless.
Backported to 8.5.x since this has caused issues on real sites.
Comment #52
anavarreThanks! Published the CR.