Skip to content

Commit

Permalink
Merge pull request from GHSA-83pv-qr33-2vcf
Browse files Browse the repository at this point in the history
* fix(GHSA-83pv-qr33-2vcf): do not allow stray file access

* docs(GHSA-83pv-qr33-2vcf): prepare changelog for release

* fix: use `os.path.{common_path,norm_path}`

* docs: add cve

---------

Co-authored-by: Peter Schutt <[email protected]>
Co-authored-by: Janek Nouvertné <[email protected]>
  • Loading branch information
3 people authored May 6, 2024
1 parent e8997c8 commit a07b79b
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 14 deletions.
6 changes: 6 additions & 0 deletions docs/release-notes/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
1.x Changelog
=============

1.51.15
-------

* Fix a security issue ([CVE-2024-32982](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-32982) where static files could allow users the ability to escape the configured static files directory
and read arbitrary files on the filesystem.

1.51.11
-------

Expand Down
21 changes: 14 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@
name = "starlite"
version = "1.51.15"
description = "Performant, light and flexible ASGI API Framework"
authors = ["Na'aman Hirschfeld <[email protected]>"]
authors = [
"Janek Nouvertné <[email protected]>",
"Cody Fincher <[email protected]>",
"Peter Schutt <[email protected]>",
"Jacob Coffee <[email protected]>",
"Na'aman Hirschfeld <[email protected]>",
]
maintainers = [
"Cody Fincher <[email protected]>",
"Janek Nouvertné <[email protected]",
"Jacob Coffee <[email protected]",
"Peter Schutt <[email protected]>",
"Visakh Unnikrishnan <[email protected]>",
"Alc <[email protected]>"
"Litestar Developers <[email protected]>",
"Cody Fincher <[email protected]>",
"Jacob Coffee <[email protected]>",
"Janek Nouvertné <[email protected]>",
"Peter Schutt <[email protected]>",
"Visakh Unnikrishnan <[email protected]>",
"Alc <[email protected]>"
]
license = "MIT"
readme = "README.md"
Expand Down
16 changes: 9 additions & 7 deletions starlite/static_files/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from os.path import commonpath, join
import os.path
from typing import TYPE_CHECKING, Literal, Sequence, Tuple, Union

from starlite.enums import ScopeType
Expand Down Expand Up @@ -35,7 +35,7 @@ def __init__(
``attachment`` or ``inline``
"""
self.adapter = FileSystemAdapter(file_system)
self.directories = directories
self.directories = [os.path.normpath(d) for d in directories]
self.is_html_mode = is_html_mode
self.send_as_attachment = send_as_attachment

Expand All @@ -54,9 +54,11 @@ async def get_fs_info(
"""
for directory in directories:
try:
joined_path = join(directory, file_path) # noqa: PL118
file_info = await self.adapter.info(joined_path)
if file_info and commonpath([str(directory), file_info["name"], joined_path]) == str(directory):
joined_path = os.path.join(directory, file_path)
normalized_file_path = os.path.normpath(joined_path)
if os.path.commonpath([directory, normalized_file_path]) == str(directory) and (
file_info := await self.adapter.info(joined_path)
):
return joined_path, file_info
except FileNotFoundError:
continue
Expand All @@ -78,7 +80,7 @@ async def __call__(self, scope: "Scope", receive: "Receive", send: "Send") -> No

split_path = scope["path"].split("/")
filename = split_path[-1]
joined_path = join(*split_path) # noqa: PL118
joined_path = os.path.join(*split_path) # noqa: PL118
resolved_path, fs_info = await self.get_fs_info(directories=self.directories, file_path=joined_path)
content_disposition_type: Literal["inline", "attachment"] = (
"attachment" if self.send_as_attachment else "inline"
Expand All @@ -87,7 +89,7 @@ async def __call__(self, scope: "Scope", receive: "Receive", send: "Send") -> No
if self.is_html_mode and fs_info and fs_info["type"] == "directory":
filename = "index.html"
resolved_path, fs_info = await self.get_fs_info(
directories=self.directories, file_path=join(resolved_path or joined_path, filename)
directories=self.directories, file_path=os.path.join(resolved_path or joined_path, filename)
)

if fs_info and fs_info["type"] == "file":
Expand Down
13 changes: 13 additions & 0 deletions tests/static_files/test_file_serving_resolution.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import gzip
import mimetypes
from pathlib import Path
from typing import TYPE_CHECKING

import brotli
Expand All @@ -8,6 +9,7 @@

from starlite import MediaType, get
from starlite.config import StaticFilesConfig
from starlite.static_files import StaticFiles
from starlite.status_codes import HTTP_200_OK
from starlite.testing import create_test_client
from starlite.utils.file import BaseLocalFileSystem
Expand Down Expand Up @@ -194,3 +196,14 @@ def test_static_files_content_disposition(tmpdir: "Path", send_as_attachment: bo
response = client.get("/static/static_part/static/test.txt")
assert response.status_code == HTTP_200_OK
assert response.headers["content-disposition"].startswith(disposition)


async def test_staticfiles_get_fs_info_no_access_to_non_static_directory(tmp_path: Path,) -> None:
assets = tmp_path / "assets"
assets.mkdir()
index = tmp_path / "index.html"
index.write_text("content", "utf-8")
static_files = StaticFiles(is_html_mode=False, directories=["static"], file_system=BaseLocalFileSystem())
path, info = await static_files.get_fs_info([assets], "../index.html")
assert path is None
assert info is None

0 comments on commit a07b79b

Please sign in to comment.