Ecosyste.ms: Advisories
An open API service providing security vulnerability metadata for many open source software ecosystems.
Security Advisories: GSA_kwCzR0hTQS12NWd3LW13N2YtODRweM4AAzXu
Starlette has Path Traversal vulnerability in StaticFiles
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()
:
https://github.com/encode/starlette/blob/4bab981d9e870f6cee1bd4cd59b87ddaf355b2dc/starlette/staticfiles.py#L172-L174
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.
Permalink: https://github.com/advisories/GHSA-v5gw-mw7f-84pxJSON: https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12NWd3LW13N2YtODRweM4AAzXu
Source: GitHub Advisory Database
Origin: Unspecified
Severity: Low
Classification: General
Published: 12 months ago
Updated: 8 months ago
CVSS Score: 3.7
CVSS vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
Identifiers: GHSA-v5gw-mw7f-84px, CVE-2023-29159
References:
- https://github.com/encode/starlette/security/advisories/GHSA-v5gw-mw7f-84px
- https://github.com/encode/starlette/commit/1797de464124b090f10cf570441e8292936d63e3
- https://github.com/encode/starlette/blob/4bab981d9e870f6cee1bd4cd59b87ddaf355b2dc/starlette/staticfiles.py#L172-L174
- https://github.com/encode/starlette/releases/tag/0.27.0
- https://nvd.nist.gov/vuln/detail/CVE-2023-29159
- https://github.com/pypa/advisory-database/tree/main/vulns/starlette/PYSEC-2023-83.yaml
- https://jvn.jp/en/jp/JVN95981715/
- https://github.com/advisories/GHSA-v5gw-mw7f-84px
Blast Radius: 16.6
Affected Packages
pypi:starlette
Dependent packages: 518Dependent repositories: 29,789
Downloads: 27,895,288 last month
Affected Version Ranges: >= 0.13.5, < 0.27.0
Fixed in: 0.27.0
All affected versions: 0.13.5, 0.13.6, 0.13.7, 0.13.8, 0.14.0, 0.14.1, 0.14.2, 0.15.0, 0.16.0, 0.17.0, 0.17.1, 0.18.0, 0.19.0, 0.19.1, 0.20.0, 0.20.1, 0.20.2, 0.20.3, 0.20.4, 0.21.0, 0.22.0, 0.23.0, 0.23.1, 0.24.0, 0.25.0, 0.26.0, 0.26.1
All unaffected versions: 0.1.0, 0.1.1, 0.1.2, 0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.7, 0.1.8, 0.1.9, 0.1.10, 0.1.11, 0.1.12, 0.1.13, 0.1.14, 0.1.15, 0.1.16, 0.1.17, 0.2.0, 0.2.1, 0.2.2, 0.2.3, 0.3.0, 0.3.1, 0.3.2, 0.3.3, 0.3.4, 0.3.5, 0.3.6, 0.3.7, 0.4.0, 0.4.1, 0.4.2, 0.5.0, 0.5.1, 0.5.2, 0.5.3, 0.5.4, 0.5.5, 0.6.0, 0.6.1, 0.6.2, 0.6.3, 0.7.0, 0.7.1, 0.7.2, 0.7.3, 0.7.4, 0.8.0, 0.8.1, 0.8.2, 0.8.3, 0.8.4, 0.8.5, 0.8.6, 0.8.7, 0.8.8, 0.9.0, 0.9.1, 0.9.2, 0.9.3, 0.9.4, 0.9.5, 0.9.6, 0.9.7, 0.9.8, 0.9.9, 0.9.10, 0.9.11, 0.10.0, 0.10.1, 0.10.2, 0.10.3, 0.10.4, 0.10.5, 0.10.6, 0.10.7, 0.11.0, 0.11.1, 0.11.2, 0.11.3, 0.11.4, 0.12.0, 0.12.1, 0.12.2, 0.12.3, 0.12.4, 0.12.5, 0.12.6, 0.12.7, 0.12.8, 0.12.9, 0.12.10, 0.12.11, 0.12.12, 0.12.13, 0.13.0, 0.13.1, 0.13.2, 0.13.3, 0.13.4, 0.27.0, 0.28.0, 0.29.0, 0.30.0, 0.31.0, 0.31.1, 0.32.0, 0.33.0, 0.34.0, 0.35.0, 0.35.1, 0.36.0, 0.36.1, 0.36.2, 0.36.3, 0.37.0, 0.37.1, 0.37.2