Skip to content

Commit

Permalink
Sanitize DB path traversal (#1179)
Browse files Browse the repository at this point in the history
* test: check that make_editor_session rejects funky paths

* fix: validate path in make_editor_session

* test: check that untrusted_to_os_path prevents traversal to parent

* fix[untrusted_to_os_path]: prevent traversal to parent directories
  • Loading branch information
dairiki committed Feb 27, 2024
1 parent 4b57312 commit 7393d87
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 2 deletions.
13 changes: 13 additions & 0 deletions lektor/editor.py
Expand Up @@ -16,8 +16,10 @@
from lektor.constants import PRIMARY_ALT
from lektor.metaformat import serialize
from lektor.utils import atomic_open
from lektor.utils import cleanup_path
from lektor.utils import increment_filename
from lektor.utils import is_valid_id
from lektor.utils import parse_path
from lektor.utils import secure_filename


Expand All @@ -33,8 +35,19 @@ class BadDelete(BadEdit):
pass


def _is_valid_path(path: str) -> bool:
split_path = path.strip("/").split("/")
if split_path == [""]:
split_path = []
return parse_path(path) == split_path


def make_editor_session(pad, path, is_attachment=None, alt=PRIMARY_ALT, datamodel=None):
"""Creates an editor session for the given path object."""
if not _is_valid_path(path):
raise BadEdit("Invalid path")
path = cleanup_path(path)

if alt != PRIMARY_ALT and not pad.db.config.is_valid_alternative(alt):
raise BadEdit("Attempted to edit an invalid alternative (%s)" % alt)

Expand Down
5 changes: 3 additions & 2 deletions lektor/utils.py
Expand Up @@ -149,10 +149,11 @@ def is_path_child_of(a, b, strict=True):


def untrusted_to_os_path(path):
path = path.strip("/").replace("/", os.path.sep)
if not isinstance(path, str):
path = path.decode(fs_enc, "replace")
return path
clean_path = cleanup_path(path)
assert clean_path.startswith("/")
return clean_path[1:].replace("/", os.path.sep)


def is_path(path):
Expand Down
1 change: 1 addition & 0 deletions tests/test_editor.py
Expand Up @@ -43,6 +43,7 @@ def test_make_editor_session(pad, path, kwargs, expect):
"conflicting",
marks=pytest.mark.xfail(reason="buglet that should be fixed"),
),
("/../../templates", {}, "Invalid path"),
],
)
def test_make_editor_session_raises_bad_edit(pad, path, kwargs, expect):
Expand Down
16 changes: 16 additions & 0 deletions tests/test_utils.py
@@ -1,3 +1,4 @@
import os
import warnings
from contextlib import contextmanager
from dataclasses import dataclass
Expand All @@ -16,6 +17,7 @@
from lektor.utils import secure_url
from lektor.utils import slugify
from lektor.utils import unique_everseen
from lektor.utils import untrusted_to_os_path
from lektor.utils import Url


Expand Down Expand Up @@ -336,3 +338,17 @@ def _warning_line(warning: warnings.WarningMessage) -> str:
"""Get the text of the line for which warning was issued."""
with open(warning.filename, encoding="utf-8") as fp:
return next(islice(fp, warning.lineno - 1, None), None)


@pytest.mark.parametrize(
"db_path, expected",
[
("a/b", "a/b"),
("/a/b", "a/b"),
("a/b/", "a/b"),
("/../../a", "a"),
],
)
def test_untrusted_to_os_path(db_path, expected):
os_path = untrusted_to_os_path(db_path)
assert os_path.split(os.sep) == expected.split("/")

0 comments on commit 7393d87

Please sign in to comment.