导语:对于个人使用,Rsync和SCP都没问题,不过对于任何使用不信任来源或者用户提供的路径/文件名时,切记要使用STFP或者使用rsync -s。除非路径在作为命令行参数再次转义前得到充分的转义,否则不要相信任何来自非信任用户输入的SCP命令。
我最近正在研究Java文件传输,碰到了一些有趣的问题。这些问题是我在看到一篇博客中的几个示例代码中发现的。这篇文章描述了用Java执行SCP命令的系统,使用的是流行的JSch库。当我通读整个代码之后,我很快就发现了他们代码中的一些问题。
// exec 'scp -f rfile' remotely String command = "scp -f " + from; Channel channel = session.openChannel("exec"); ((ChannelExec) channel).setCommand(command); // get I/O streams for remote scp OutputStream out = channel.getOutputStream(); InputStream in = channel.getInputStream(); channel.connect();
如果你之前看到过如何安全的将参数传递为命令行,那你就会知道示例代码中的这种传递方式是不安全的。第二行添加任意字符串到命令结尾,而没有进行任何形式的清理,这种场景毫无疑问是会导致sql注入的。
背景信息
在深入挖掘这个漏洞之前,有一些重要的背景信息我们需要了解一下:
这条命令在服务端运行。SCP客户端是通过SSH登录到远程服务器(上面代码中有执行通道)来在服务器端执行SCP命令的。
由于运行在服务端,我并没有找到Java中好用的库来转义命令。我查了很多资料,大部分都是推荐使用Java的Process或者runtime类,然而这些只适用于本地运行的进程,在服务端是无法运行的。
发现1
在上面的示例代码中,命令应该是像下面这样的:
scp -f /some/user/provided/path
不过,由于路径在任何地方都没有进行转义或限制,所以路径也可以很轻易地被修改为下面这样:
# path = '/; touch /tmp/foo' scp -f /; touch /tmp/foo
这样一来,就会先执行scp -f命令,然后再执行touch /tmp/foo命令。进一步研究之后,我发现这段代码是直接从JSch的源码中复制粘贴过来的。这就表明这段示例代码也存在相同的漏洞。我向这个项目的维护者报告了这个问题,我想他们要么修复这个问题,要么可能不搭理我,不过情况并不是这样的。
发现2
在我报告了JSch中这个漏洞的第二天,我收到了一个回复。当我在研究我的初步报告时,该项目的维护者注意到OpenSSH的SCP命令和Rsync也存在同样的问题。比如执行如下命令:
scp /tmp/foo [email protected]:/tmp/bar\;touch\ /tmp/foo
这将会在远程服务器中执行touch /tmp/foo命令,即使参数在客户端执行了正确的转义也没用。这是因为在应用程序层面对此进行转义并不会导致在远程运行也进行转义。对于上面的例子,远程主机上的服务器会执行scp -f /tmp/bar; touch /tmp/foo命令。
Rsync的问题也类似,在向他们报告了这个漏洞之后,他们向我介绍了一个标志(-s/–protect-args),这个标志能防止漏洞利用。
发现3
围绕这个问题,我跟OpenSSH的维护人员进行了沟通交流,他们说了几个关键点:
· 这个问题众所周知,但没有得到重视,也没有良好的文档记录。
· SCP这个协议已经“彻底被玩坏了”。
· 基于SFTP作为替代品的SCP2,从未实施。
· 更改此问题就会破坏兼容性。
· 由于支持多种shell,他们认为在这种情况下,转义shell参数是不可能的。
作为一名大量使用SCP命令的开发者,得知SCP命令是无法修复的让我感觉到非常惊讶。我觉得他们有必要提供关于此问题的官方文档,并告诉用户更好的选择(比如SFTP或者Rsync),因为我看到的库和代码片段中没有考虑到这一点。
总结
对于个人使用,Rsync和SCP都没问题,不过对于任何使用不信任来源或者用户提供的路径/文件名时,切记要使用STFP或者使用rsync -s。除非路径在作为命令行参数再次转义前得到充分的转义,否则不要相信任何来自非信任用户输入的SCP命令。