Skip to content

smbclient: resolve server-provided file names in recursive get - #5237

Open
musthaf-bughunter wants to merge 2 commits into
secdev:masterfrom
musthaf-bughunter:smbclient-getr-path-traversal
Open

musthaf-bughunter wants to merge 2 commits into
secdev:masterfrom
musthaf-bughunter:smbclient-getr-path-traversal

Conversation

@musthaf-bughunter

Copy link
Copy Markdown

Description

Path traversal in smbclient.get -r. In _getr() the file names returned by the server are joined to the local destination without being resolved, so the root not in local.parents check runs on the lexical path and a directory entry named ../evil passes it (PurePath.parents does not collapse ..). A hostile server can make the client write files, or create directories, outside of the directory it downloads into.

Repro: a server whose listing contains ../evil, see

# Discard . and ..
continue
remote = directory / x[0]
local = dest / x[0]
# Word of caution: this check ONLY works because root and local have been
# resolve(). Be careful
. The test added in smbclientserver.uts is the minimal version of it and does not need samba.

Fix: resolve the local path before the containment check, and the root that get() hands to _getr(), as the comment next to the check already requires.

The names returned by the server during `get -r` were joined to the
local destination without being resolved, so the `root in local.parents`
containment check was lexical and a name such as `../evil` could write
outside of the download directory.

AI-Assisted: yes (Claude Fable 5.1)
@gpotter2

Copy link
Copy Markdown
Member

I don't think that this work considering parsepath strips any potential /. Do you have a reproducible against a real "fake" SMB server?

@musthaf-bughunter

Copy link
Copy Markdown
Author

I think parsepath only sees the argument the user gives to get. The names that _getr joins to the local destination come straight out of query_directory (the FileName of each FILE_ID_BOTH_DIR_INFORMATION entry), nothing strips them, and the .. filter only drops the literal "." and ".." entries.

I've replaced the mocked test with one against a real server: a SMB_Server subclass whose listing contains a file named ../evil, with the stock smbclient doing get -r . over the loopback (https://github.com/musthaf-bughunter/scapy/blob/6a1a8ba535ae45a2cddf9cc4e3f34e2798c87d29/test/scapy/layers/smbclientserver.uts#L240-L297). Without the fix, evil is written one level above the download directory with the content the server serves for it; with the fix, get raises FileNotFoundError and nothing is written. It is the same resolve-then-parents check that lookup_file already does on the server side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants