sftp-server - lock user in working directory (or prevent folder navigation)
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
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.
True. But this will be a read only sftp so unable to write on it.
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.
With Go 1.24 os.Root may help.
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
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.
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.
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.Filepathalways 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.
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
directory junction link
Is that the same as a symbolic link? I think
os.Rootis 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.
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.