sftp icon indicating copy to clipboard operation
sftp copied to clipboard

sftp-server - lock user in working directory (or prevent folder navigation)

Open pestevao opened this issue 1 year ago • 11 comments

Hi,

I'm using this example https://github.com/pkg/sftp/blob/master/examples/go-sftp-server/main.go to make some tests on a internal sftp-server for data syncronization on multiple servers.

I was abble to define some working directories per user, checking username and defined WithServerWorkingDirectory using serverOptions.

But the users can navigate to other folders using cd .. or cd /directory.

Some chance to lock users in the defined working directories? I was thinking of compare the output for the current sftp folder and compare it with the working directory and throw some error to the sftp client but can find a way to do it.

Thank you, Pedro

pestevao avatar Nov 28 '24 16:11 pestevao

There is no way to guarantee that a user cannot get out of their working directory. You need to use a chroot to do that.

The problem is that one can always do ln -s / root in a directory that you have write permission to, and voila, you can access the whole drive through a ${PWD}/root/ filename prefix, that will pass any such test you can think of to check a prefix on the filename.

puellanivis avatar Nov 28 '24 23:11 puellanivis

True. But this will be a read only sftp so unable to write on it.

pestevao avatar Dec 02 '24 15:12 pestevao

I would recommend either implementing a RequestSever or from the new dev-v2 branch implementing the ServerHandler and you can then lock out accesses without a specific prefix.

puellanivis avatar Dec 03 '24 12:12 puellanivis

With Go 1.24 os.Root may help.

drakkan avatar Dec 03 '24 12:12 drakkan

I recently had a similar requirement. And I successfully done it by using sftp.RequestServer. I also tried the os.Root API in Go 1.24, but some operations still don’t have corresponding APIs, so path issues (Rename, Chmod, Chtimes) still need to be handled manually. ps. It seems that Go 1.25 has added the corresponding APIs, but I haven’t tried them yet.

Additionally, during testing on the Windows platform, I discovered an issue: When using the os.Root API, directory junction won't work, even if the target does not escape, it still reports the error: "path escapes from parent". I’m still investigating whether this is a bug or expected behavior.

here is the code (and repo): https://github.com/cs8425/go-sftpd/blob/main/sftpd_handler.go

cs8425 avatar Aug 15 '25 11:08 cs8425

When using the os.Root API, directory junction won't work, even if the target does not escape, it still reports the error: "path escapes from parent".

I had the same issue. It seems like the r.Filepath always has a / prefixed even if the user didn't send one. I am not sure if this is intended or not, but it would be nice to have an option to receive the user's requested Filepath verbatim.

To work around it I am manually stripping the prefixed / and any .. or similar upward traversal paths. This doesn't feel like a nice solution.

handyvim avatar Oct 22 '25 12:10 handyvim

So, I think the prefix is coming from here: https://github.com/pkg/sftp/blob/v1.13.9/request-server.go#L346

Part of the problem is that while SFTP naturally inherits the concept of a current working directory from being a POSIX process, and there is no way to actually change that directory remotely from the client. So, while files can technically be accessed through relative path components like .. and ., it’s generally safest to access everything through absolute paths.

You might want to check the dev-v2 branch. It’s basically complete rewrite, and a whole new API, but I think it’s a little less “cooked” inputs, that is, it decodes the encoding/ssh/filexfer.StatPacket and passes that directly into the Stat receiver method of the ServerHandler interface. Getting more use out of this dev branch, and we could get it to a level that we can release a v2.

puellanivis avatar Oct 22 '25 22:10 puellanivis

When using the os.Root API, directory junction won't work, even if the target does not escape, it still reports the error: "path escapes from parent".

I had the same issue. It seems like the r.Filepath always has a / prefixed even if the user didn't send one. I am not sure if this is intended or not, but it would be nice to have an option to receive the user's requested Filepath verbatim.

To work around it I am manually stripping the prefixed / and any .. or similar upward traversal paths. This doesn't feel like a nice solution.

yes, I also add . too: https://github.com/cs8425/go-sftpd/blob/main/sftpd_handler.go#L41-L48

but the issue is that using the os.Root API on windows platform, if root set to C:/test (map to /) and there is a directory junction link from C:/test/latest to C:/test/001 (/latest => /001), access anything in /latest (eg: /latest/build.zip) will always getting "path escapes from parent" error. Accessing /latest/build.zip (C:/test/latest/build.zip) should equivalent to access /001/build.zip (C:/test/001/build.zip), and without any error.

cs8425 avatar Oct 23 '25 11:10 cs8425

directory junction link

Is that the same as a symbolic link? I think os.Root is deliberately designed to error on an absolute symlink.

Methods on Root will follow symbolic links, but symbolic links may not reference a location outside the root. Symbolic links must not be absolute. https://pkg.go.dev/os#Root

handyvim avatar Oct 23 '25 15:10 handyvim

directory junction link

Is that the same as a symbolic link? I think os.Root is deliberately designed to error on an absolute symlink.

for directory, yes, almost same as a symbolic link on linux. for file, no, it only work for directory.

Methods on Root will follow symbolic links, but symbolic links may not reference a location outside the root. Symbolic links must not be absolute. https://pkg.go.dev/os#Root

oh, maybe this is the answer.... I had try using DeviceIoControl to create a directory junction before, it reject to write a relative path, only accept absolute path, even mklink /J just only creat a directory junction with absolute path. Not sure if there are some different method to do with relative path version of directory junction.

cs8425 avatar Oct 23 '25 16:10 cs8425

Because of the way directory junctions work (as a reparse point for a given path) they can only ever work via full absolute paths.

You will need to use mklink /d to create a symbolic link that is more compatible with POSIX semantics, and allows for relative path symbolic links in Windows.

puellanivis avatar Oct 24 '25 07:10 puellanivis