"validate_b2_file_name_as_path" accepts Windows device names and NTFS stream separators
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 50/100
Hướng nghiên cứu
Start with validate_b2_file_name_as_path in b2sdk/_internal/utils/filesystem.py and the containment check in scan/folder.py; the issue includes a reproduction using RawSimulator and Synchronizer.sync_folders. Check how the existing Windows drive-letter guard is scoped, then verify the reserved-name and colon cases in the suggested remediation. Done when Windows sync rejects these names while accepted names still pass.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Description
validate_b2_file_name_as_path (b2sdk/_internal/utils/filesystem.py:54-65) exists to "ensure a B2 file name is safe to interpret as a local path", and it rejects relative path components, absolute paths, and on Windows a drive letter followed by a separator. It does not reject Windows reserved device names, and it does not reject the : that introduces an NTFS alternate data stream. LocalFolder.make_full_path (scan/folder.py:155-175) then joins the name onto the sync root and checks containment with os.path.commonpath, which passes for both shapes because <root>\CON and <root>\foo:bar are lexically inside <root>. On a Windows sync destination that matters: Microsoft lists CON, PRN, AUX, NUL, COM1 to COM9, COM¹, COM², COM³, LPT1 to LPT9, LPT¹, LPT² and LPT³ as reserved in any directory, and states that these names followed immediately by an extension are equivalent, giving NUL.txt and NUL.tar.gz as examples, so a sync writing <root>\COM1.b2.sync.tmp addresses a serial port and <root>\NUL discards the data while the sync reports success, while a name containing : writes an alternate data stream on the sibling file where a directory listing does not show it. Backblaze accepts all of these as object names, its own documentation warns that B2 "may allow names that are not compatible with your disk file system", and RawApi.check_b2_filename (raw_api.py:991) rejects none of them and has a single caller, the upload path at raw_api.py:1059. An attacker needs writeFiles on the bucket the victim syncs from, which is ordinary on a shared or multi-tenant bucket, and the victim needs to sync to a Windows destination; no elevated privilege is involved on either side.
Reproduction
On master @ ffa02b32 with Python 3.10.19, in a container built from a fresh clone at that ref. The reproduction below is on Linux, so it shows the guard accepting the names and the full Synchronizer.sync_folders run carrying every one of them through to the local write; the device and alternate-data-stream semantics themselves are as documented by Microsoft. RawSimulator is the SDK's own public test double, exported from b2sdk.v2 and b2sdk.v3, and stands in for the B2 endpoint only, since all of these names are ones the real service accepts. Build with this Dockerfile, save the script as poc.py, then run docker build -t b2poc . && docker run --rm -u 1001 -v "$PWD/poc.py:/p.py:ro" b2poc python /p.py:
FROM python:3.10.19-slim-bookworm
RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends git >/dev/null
RUN git clone -q https://github.com/Backblaze/b2-sdk-python.git /src \
&& git -C /src checkout -q ffa02b3264bc0e7069ef2c8b25facd9a467e65a6 \
&& pip install --no-cache-dir -q /src
RUN useradd -m -u 1001 victim
import io, os, time
from b2sdk.v3 import (B2Api, B2HttpApiConfig, RawSimulator, StubAccountInfo,
Synchronizer, SyncReport, B2Folder, LocalFolder)
from b2sdk._internal.utils.filesystem import validate_b2_file_name_as_path
NAMES = ['CON', 'AUX', 'COM1', 'LPT1.txt', 'NUL.tar.gz', 'dir/PRN', 'foo:bar']
for n in NAMES:
validate_b2_file_name_as_path(n) # the guard accepts every one of them
print('guard accepted all:', NAMES)
api = B2Api(StubAccountInfo(), api_config=B2HttpApiConfig(_raw_api_class=RawSimulator))
acct, key = api.session.raw_api.create_account()
api.authorize_account(application_key_id=acct, application_key=key, realm='production')
bucket = api.create_bucket('evil', 'allPublic')
for n in NAMES:
bucket.upload_bytes(b'payload', n)
os.makedirs('/tmp/sync', exist_ok=True)
Synchronizer(max_workers=1).sync_folders(source_folder=B2Folder('evil', '', api),
dest_folder=LocalFolder('/tmp/sync'), now_millis=int(time.time() * 1000),
reporter=SyncReport(io.StringIO(), True))
print('reached the local filesystem:', sorted(os.listdir('/tmp/sync')))
guard accepted all: ['CON', 'AUX', 'COM1', 'LPT1.txt', 'NUL.tar.gz', 'dir/PRN', 'foo:bar']
reached the local filesystem: ['AUX', 'COM1', 'CON', 'LPT1.txt', 'NUL.tar.gz', 'dir', 'foo:bar']
Suggested remediation
Reject both shapes in the guard, scoped to Windows exactly as the existing drive-letter check is:
--- a/b2sdk/_internal/utils/filesystem.py
+++ b/b2sdk/_internal/utils/filesystem.py
@@ -28,6 +28,14 @@ RELATIVE_PATH_MATCHER = re.compile(
+ r'([/\\][/\\])|' # abc\/xyz or abc/\xyz or abc//xyz or abc\\xyz
+ r'^(\.)$' # just "."
)
+PATH_SEPARATOR_SPLITTER = re.compile(r'[/\\]')
+# Windows device names are reserved in every directory, with or without an extension, and
+# trailing spaces are stripped before the name is resolved.
+# See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
+RESERVED_DEVICE_NAME_MATCHER = re.compile(
+ r'^(CON|PRN|AUX|NUL|COM[1-9¹²³]|LPT[1-9¹²³])[ ]*(\..*)?[ ]*$',
+ re.IGNORECASE,
+)
@@ -63,3 +71,12 @@ def validate_b2_file_name_as_path(file_name: str) -> None:
if _IS_WINDOWS and DRIVE_MATCHER.search(file_name):
raise ValueError('File names containing Windows drive letters are not supported')
+
+ if _IS_WINDOWS and ':' in file_name:
+ raise ValueError('File names containing a colon are not supported')
+
+ if _IS_WINDOWS and any(
+ RESERVED_DEVICE_NAME_MATCHER.match(part)
+ for part in PATH_SEPARATOR_SPLITTER.split(file_name)
+ ):
+ raise ValueError('File names containing Windows device names are not supported')
Three choices in there are worth explaining. The extension is matched as (\..*)? rather than (\.[^.]*)?, because Microsoft gives NUL.tar.gz as equivalent to NUL, so an extension may itself contain dots. Trailing spaces are absorbed, because Win32 strips them before resolving the name, which makes AUX and COM2 .txt devices too. Every path segment is tested rather than only the last, because a device name is reserved in any position, so dir/PRN has to be rejected as well; the drive-letter check stays ahead of the colon check so C:\file.txt keeps its existing, more specific error message.
Credits
- Ngôn ngữ chính
- Python
- Star
- 214
- Fork
- 68
- Merge trung bình
- 51 phút
- Pull request đã merge (30 ngày)
- 6
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Backblaze/b2-sdk-python
-
Download and copy sync actions print remote file names without escape_control_charsCó thể đã có người làm @dreyfus92 đã nhận 15 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Backblaze/b2-sdk-python#631 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
SqliteAccountInfo profile name validation uses "re.match", so only the first character is checkedCó thể đã có người làm @nazarli-shabnam đã nhận 9 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
Backblaze/b2-sdk-python#629 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Adopt the AI-native SDLC and repository policiesCó thể đã có người làm @goanpeca đã nhận 18 ngày trước. Đang mởenhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 20/100
Backblaze/b2-sdk-python#634 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ci: cli-compat fails — 4 sse/sync integration tests broken in B2_Command_Line_Tool (all environments)Có thể làm lại được @ffumero2003 đã nhận 37 ngày trước và không có pull request nào đang mở. Đang mởbug testing
Backblaze/b2-sdk-python#627 · 2 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 Nửa ngày Mức phù hợp với người mới 62/100
Backblaze/b2-sdk-python#626 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của Backblaze/b2-sdk-python
Issue tương tự
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 72/100
btclib-org/btclib-node#2505 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
open-webui/open-webui#34462 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
gorakhargosh/watchdog#1462 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
openwisp/openwisp-monitoring#889 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
IETF-Hackathon/pqc-certificates#351 ·
Maintainer thường phản hồi trong vòng 2 ngày