fix(13): CR-02 apply PurePosixPath.name guard in WebDAV upload_file and rename to prevent path traversal
This commit is contained in:
@@ -41,7 +41,7 @@ import io
|
|||||||
import uuid
|
import uuid
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path, PurePosixPath
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from webdav3.client import Client
|
from webdav3.client import Client
|
||||||
@@ -505,13 +505,20 @@ class WebDAVBackend(StorageBackend, MutableCloudResourceAdapter):
|
|||||||
|
|
||||||
{'kind': 'updated', 'reason': 'renamed', 'provider_item_id': str, 'name': str}
|
{'kind': 'updated', 'reason': 'renamed', 'provider_item_id': str, 'name': str}
|
||||||
"""
|
"""
|
||||||
# Compute new path: same parent directory, new name
|
# T-13-04 path-traversal guard: strip all directory components from the
|
||||||
|
# caller-supplied new_name. A value like "../../evil" would otherwise
|
||||||
|
# escape the item's parent directory when the new path is computed.
|
||||||
|
safe_new_name = PurePosixPath(new_name).name
|
||||||
|
if not safe_new_name or safe_new_name in (".", ".."):
|
||||||
|
safe_new_name = new_name # caller-validated; keep original on empty result
|
||||||
|
|
||||||
|
# Compute new path: same parent directory, safe new name
|
||||||
parts = provider_item_id.rstrip("/").rsplit("/", 1)
|
parts = provider_item_id.rstrip("/").rsplit("/", 1)
|
||||||
if len(parts) == 2:
|
if len(parts) == 2:
|
||||||
parent_path = parts[0]
|
parent_path = parts[0]
|
||||||
new_path = f"{parent_path}/{new_name}"
|
new_path = f"{parent_path}/{safe_new_name}"
|
||||||
else:
|
else:
|
||||||
new_path = new_name
|
new_path = safe_new_name
|
||||||
|
|
||||||
def _move_rename() -> dict:
|
def _move_rename() -> dict:
|
||||||
try:
|
try:
|
||||||
@@ -520,7 +527,7 @@ class WebDAVBackend(StorageBackend, MutableCloudResourceAdapter):
|
|||||||
"kind": MUT_KIND_UPDATED,
|
"kind": MUT_KIND_UPDATED,
|
||||||
"reason": MUT_REASON_RENAMED,
|
"reason": MUT_REASON_RENAMED,
|
||||||
"provider_item_id": new_path,
|
"provider_item_id": new_path,
|
||||||
"name": new_name,
|
"name": safe_new_name,
|
||||||
}
|
}
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return self._normalize_error(exc)
|
return self._normalize_error(exc)
|
||||||
@@ -627,10 +634,18 @@ class WebDAVBackend(StorageBackend, MutableCloudResourceAdapter):
|
|||||||
|
|
||||||
{'kind': 'uploaded', 'reason': 'created', 'provider_item_id': str, 'name': str, 'parent_ref': str | None, 'size': int}
|
{'kind': 'uploaded', 'reason': 'created', 'provider_item_id': str, 'name': str, 'parent_ref': str | None, 'size': int}
|
||||||
"""
|
"""
|
||||||
|
# T-13-04 path-traversal guard: strip all directory components from the
|
||||||
|
# caller-supplied filename so a value like "../../evil" cannot escape
|
||||||
|
# the intended parent_ref. PurePosixPath.name returns only the final
|
||||||
|
# segment. Fall back to "upload" for empty or dot-only results.
|
||||||
|
safe_filename = PurePosixPath(filename).name
|
||||||
|
if not safe_filename or safe_filename in (".", ".."):
|
||||||
|
safe_filename = "upload"
|
||||||
|
|
||||||
if parent_ref:
|
if parent_ref:
|
||||||
object_path = f"{parent_ref.rstrip('/')}/{filename}"
|
object_path = f"{parent_ref.rstrip('/')}/{safe_filename}"
|
||||||
else:
|
else:
|
||||||
object_path = filename
|
object_path = safe_filename
|
||||||
|
|
||||||
def _upload() -> dict:
|
def _upload() -> dict:
|
||||||
try:
|
try:
|
||||||
@@ -640,7 +655,7 @@ class WebDAVBackend(StorageBackend, MutableCloudResourceAdapter):
|
|||||||
"kind": MUT_KIND_UPLOADED,
|
"kind": MUT_KIND_UPLOADED,
|
||||||
"reason": MUT_REASON_CREATED,
|
"reason": MUT_REASON_CREATED,
|
||||||
"provider_item_id": object_path,
|
"provider_item_id": object_path,
|
||||||
"name": filename,
|
"name": safe_filename,
|
||||||
"parent_ref": parent_ref,
|
"parent_ref": parent_ref,
|
||||||
"size": len(content),
|
"size": len(content),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user