Opened 18 months ago

Last modified 8 months ago

#27590 new Cleanup/optimization

Prevent public access of staticfiles manifest

Reported by: David Sanders Owned by: nobody
Component: contrib.staticfiles Version: 1.10
Severity: Normal Keywords:
Cc: Kevin Christopher Henry Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

A standard Django deploy has all staticfiles accessible to all users. This is understandable, if undesirable. By itself this is not a huge problem since those on the public Internet don't know the filenames of all of the files a deployment has, and fuskering the entire possible namespace isn't feasible and is also detectable.

However, deployments that make use of ManifestStaticFilesStorage will most likely expose a master list of all static files to anyone who wants to look. It's not a huge security risk because you shouldn't be depending on security through obscurity, but there's certainly a leg up given when there's a master list of all files. Due to the way ManifestStaticFilesStorage is setup, the manifest ends up in the directory of publicly served files. If the files are stored locally this can be fixed by blacklisting the file from webserver access and only letting Django itself read the file off the local filesystem. This is the approach I've taken once I discovered the issue - I have a server deployment running Apache serving files on the local filesystem, but have CloudFront in front of that which fetches from Apache if the cache misses. I've since blacklisted the staticfiles manifest and invalidated any cached copies in CloudFront.

Here's what I consider the risks of having a publicly exposed staticfiles manifest:

  • Easily find trade secrets in JavaScript files meant to be used only internally by staff users
  • Find hardcoded secrets in internal files - anything in the static tree gets listed here, even pre-processed files like coffee or less if the developers use django-compressor
  • Find potential attack vectors by finding normally unlisted files that are exploitable which could be used to form URLs in phishing emails
  • Possible novel way to fingerprint Django versions using the easy master list of files, could be used to quickly identify potentially vulnerable Django servers

All that said, I don't have a great solution to the problem that Django itself could implement. Currently Django writes the manifest to the staticfiles root so it's always going to be readable unless you take extra steps. The real stickler is deployments that use something like S3BotoStorage which in effect needs Django to be able to access the manifest remotely. My understanding of that setup (I don't use it) would be that on load Django is going to read the manifest from S3, so it needs to be accessible over the web by default. Further steps could be taken to make it only accessible to Django itself, but that requires user action.

Potential solutions:

  • Encrypt the manifest on disk, decrypt on load into memory - loses human readability for debugging purposes but hides it from prying eyes by default
  • Fast-track ticket #26029 to make staticfiles storage configuration allow passing options to storage - use options to change manifest path somewhere non-public or configure a secret header to use with S3 to only give Django access to the file.

On a related note, this discovery has made me extra paranoid about the exposure of internal files meant for staff only and now I'm looking at a way to formalize restricted access to the files. With the exposure of the staticfiles manifest it's clear much of the business logic we use (in JavaScript under admin) is by default visible to the Web if you know the URL.

Change History (3)

comment:1 Changed 18 months ago by Tim Graham

Triage Stage: UnreviewedAccepted

Florian says,

I think we should just bite the dust and add a setting which the static manifest gets written to (directly, without involving storage). Moving this file out of STATIC_ROOT is the only sensible solution I see. S3BotoStorage should redirect the manifest to a different (secure) bucket if needed. Maybe we can make it easier to detect that this file is "sensitive".

comment:2 Changed 8 months ago by Kevin Christopher Henry

At #28764 I reached a similar conclusion for very different reasons.

I don't see much point in making the location configurable, though. It seems to me that the file should just be stored to some standard location within the codebase. The obvious analogy here would be to makemigrations, which does the same thing. As I argue in the other ticket, this is a configuration file that affects Django's behavior, is tied to a specific commit, and has nothing to do with the actual serving of static files. Storing it in the codebase would solve David's issues above, would solve the correctness and performance issues I mentioned, and would do so for all users without any new settings. Are there advantages to storing the file in an external location that I've overlooked?

comment:3 Changed 8 months ago by Kevin Christopher Henry

Cc: Kevin Christopher Henry added
Note: See TracTickets for help on using tickets.
Back to Top