It's marginally "unsafe"
It was marginally "unsafe" before the addition of
scp -T in OpenSSH 8.0 and it still is marginally "unsafe" when using
Like other answers have stated, claims that scp is unsafe come from the client's previous inability to verify that it's receiving what it requested. The reason why scp couldn't verify is because the request is made using shell code running in the server's environment (with the server's state).
For example, let's say that the server's shell is set to zsh with extended glob, and I have my project directories set as zsh dynamic named directories for quick access. If I want to get a file from Rails project foo, I could get it with:
scp server:~foo/config/database.yml .
zsh on my server expands
If I want to get the server's 10 newest regular files, I could do:
scp server:'*(.oc[1,10])' .
If the server has plain bash, I could do:
scp server:'$(ls -t | head -10)' .
If I wanted to get the files that contained a pattern foo in their content, I could do:
scp server:'$(grep -l foo *)' .
There's just no way for the scp client to verify these kinds of requests that use shell code. However, it is a concern that it enables a malicious server to respond to:
scp -r server:foo/ ~
.ssh/authorized_keys. In the eyes of
foo/ is server shell code that will evaluate to who knows what.
-T Disable strict filename checking. By default when copying files from a remote host to a local directory scp checks that the received filenames match those requested on the command-line to prevent the remote end from sending unexpected or unwanted files. Because of differences in how various operating systems and shells interpret filename wildcards, these checks may cause wanted files to be rejected. This option disables these checks at the expense of fully trusting that the server will not send unexpected filenames.
To summarize, the previous default behavior of
scp was moved to
-T, and now the default is to check what's being received, using the provided argument as a simple pattern, when one's not doing a recursive copy. I believe it recognizes simple globs and escapes (looking at the source, it uses fnmatch(3) for the matching, with flags set to 0). In other words, it behaves more predictably when you're not looking to take advantage of its unique ability to specify files by shell code.
So now, if you don't use
-T and you don't use
-r, the only way to overwrite stuff like
.bashrc is to ask for it explicitly. If you want to use shell code, you'd have to use
-T at the expense of trusting the server:
scp -T server:'*(.oc[1,10])' .
So now you get the safety in the simple, non-recursive cases without completely giving up the unique ability that makes
scp more useful than other utilities like
Can scp be replaced by sftp?
sftp can't specify the files it wants with shell code. It only accepts actual file paths. It's more limited that way. If you only needed scp for simple filepaths, then yes. However, if you want the added power of being able to specify the files from a simple command with server-side shell code (albeit at the expense of having to trust your server), then I think your only choice is
Why the quotes around "unsafe"?
It was unsafe, but only when you wouldn't trust the server to not be compromised. In my case, and perhaps for the majority of people, we use scp with servers that we completely trust in other ways anyways. In particular, I use it to login from my laptop to my desktop or vice versa. If we can't trust our own devices, then what can we trust? That's why I think calling it just plain unsafe is hyperbolic. It's not comparable to how we call telnet unsafe, for instance.
I still appreciate the change to requiring
-T, though. It is safer. Also, it's certainly true that for those people that don't need the features enabled by
-T, there really isn't much advantage in using scp over sftp or rsync.