-
Notifications
You must be signed in to change notification settings - Fork 881
feat: block file transfers for security #13501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
94a4067
9915cde
abd97df
00e0a8a
6cec719
fa0010d
dbaa763
94926bd
d92c070
47ee9b0
4e45761
d852417
96ca9ae
19fe5cf
e3a9dba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,8 +52,16 @@ const ( | |
// MagicProcessCmdlineJetBrains is a string in a process's command line that | ||
// uniquely identifies it as JetBrains software. | ||
MagicProcessCmdlineJetBrains = "idea.vendor.name=JetBrains" | ||
|
||
// BlockedFileTransferErrorCode indicates that SSH server restricted the raw command from performing | ||
// the file transfer. | ||
BlockedFileTransferErrorCode = 65 // Error code: host not allowed to connect | ||
BlockedFileTransferErrorMessage = "File transfer has been disabled." | ||
) | ||
|
||
// BlockedFileTransferCommands contains a list of restricted file transfer commands. | ||
var BlockedFileTransferCommands = []string{"nc", "rsync", "scp", "sftp"} | ||
|
||
// Config sets configuration parameters for the agent SSH server. | ||
type Config struct { | ||
// MaxTimeout sets the absolute connection timeout, none if empty. If set to | ||
|
@@ -74,6 +82,8 @@ type Config struct { | |
// X11SocketDir is the directory where X11 sockets are created. Default is | ||
// /tmp/.X11-unix. | ||
X11SocketDir string | ||
// BlockFileTransfer restricts use of file transfer applications. | ||
BlockFileTransfer bool | ||
} | ||
|
||
type Server struct { | ||
|
@@ -272,6 +282,18 @@ func (s *Server) sessionHandler(session ssh.Session) { | |
extraEnv = append(extraEnv, fmt.Sprintf("DISPLAY=:%d.0", x11.ScreenNumber)) | ||
} | ||
|
||
if s.fileTransferBlocked(session) { | ||
s.logger.Warn(ctx, "file transfer blocked", slog.F("session_subsystem", session.Subsystem()), slog.F("raw_command", session.RawCommand())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we run the risk here of logging a potentially large amount of text here? Aside: this feels like a good use-case for an audit-log. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as #13501 (comment)? Yeah, the problem here is that it is logged on the agent side in |
||
|
||
if session.Subsystem() == "" { // sftp does not expect error, otherwise it fails with "package too long" | ||
mtojek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Response format: <status_code><message body>\n | ||
errorMessage := fmt.Sprintf("\x02%s\n", BlockedFileTransferErrorMessage) | ||
_, _ = session.Write([]byte(errorMessage)) | ||
} | ||
_ = session.Exit(BlockedFileTransferErrorCode) | ||
return | ||
} | ||
|
||
switch ss := session.Subsystem(); ss { | ||
case "": | ||
case "sftp": | ||
|
@@ -322,6 +344,37 @@ func (s *Server) sessionHandler(session ssh.Session) { | |
_ = session.Exit(0) | ||
} | ||
|
||
// fileTransferBlocked method checks if the file transfer commands should be blocked. | ||
// | ||
// Warning: consider this mechanism as "Do not trespass" sign, as a violator can still ssh to the host, | ||
// smuggle the `scp` binary, or just manually send files outside with `curl` or `ftp`. | ||
// If a user needs a more sophisticated and battle-proof solution, consider full endpoint security. | ||
func (s *Server) fileTransferBlocked(session ssh.Session) bool { | ||
if !s.config.BlockFileTransfer { | ||
return false // file transfers are permitted | ||
} | ||
// File transfers are restricted. | ||
|
||
if session.Subsystem() == "sftp" { | ||
return true | ||
} | ||
|
||
cmd := session.Command() | ||
if len(cmd) == 0 { | ||
return false // no command? | ||
} | ||
|
||
c := cmd[0] | ||
c = filepath.Base(c) // in case the binary is absolute path, /usr/sbin/scp | ||
|
||
for _, cmd := range BlockedFileTransferCommands { | ||
if cmd == c { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (s *Server) sessionStart(logger slog.Logger, session ssh.Session, extraEnv []string) (retErr error) { | ||
ctx := session.Context() | ||
env := append(session.Environ(), extraEnv...) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
out of scope: we may want to eventually have a way to surface this to admins as they may be interested in this sort of thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a new metric? maybe consider this as a follow-up option. I don't know if an admin wants to review notifications from users accidentally trying to use
scp
.