我想用ssh在远程主机上下载一个url,我用的是exec((,它正在工作:
const cmd = `mkdir -p /home/username/test; wget --no-check-certificate -q -U "" -c "${url}" -O /home/username/test/img.jpg`;
const out = execSync(`ssh -o ConnectTimeout=8 -o StrictHostKeyChecking=no -p 2356 username@${ip} '${cmd}'`);
但是以这种方式使用url
变量是很常见的,这个变量的值来自用户输入,所以我在stackoverflow上发现了一些帖子,说我需要使用spawn
:
const url = 'https://example.com/image.jpg';
const out = spawnSync('ssh', [
'-o', 'ConnectTimeout=8',
'-o', 'StrictHostKeyChecking=no',
'-p', '2356',
`username@${ip}`,
`mkdir -p /home/username/test; wget --no-check-certificate -q -U "" -c "${url}" -O /home/username/test/img.jpg`,
]);
如果const url = 'https://example.com/image.jpg"; echo 5; "';
、echo
将被执行,有人能告诉我如何以安全的方式执行这些代码吗?
有两个方面。首先,您认为execSync
不安全是正确的。引用文件:
永远不要将未初始化的用户输入传递到此函数。任何包含外壳元字符的输入都可以用于触发任意命令执行。
解决方案是使用execSpawn
,例如,正如您在相关答案中指出的那样。
但是,在您的示例中,您正在调用ssh
并向其传递一个文本,该文本将由远程系统上的shell执行。正因为如此,正如您在示例中所展示的那样,您仍然容易受到攻击。但请注意,它不再是与NodeJ相关的漏洞,而是ssh
和shell上的漏洞。
为了减轻攻击,我建议将注意力集中在远程服务器上。您可以从服务器提供一个定义明确的API,而不是通过ssh向它发送命令(它应该信任并在shell中执行(。在HTTP接口中,您可以接受输入并进行适当的验证(而不是简化信任(。优点是您不需要处理外壳的微妙之处。
如果您被迫使用ssh,您可以验证URL,并且只有在它安全的情况下,才能将其转发到服务器。从安全角度来看,这仍然不理想。首先,远程服务器需要完全信任您(通常最好避免这种情况,而是尽可能在本地进行验证(。其次,验证本身并不简单。您需要决定字符串是否看起来像URL(例如,使用new URL(url)
(,但更困难的方面是确保没有漏洞利用。我没有一个具体的例子,但我会谨慎地假设所有通过URL解析器的字符串都可以安全地在shell环境中执行。
总之,如果可能的话,在这种情况下通过传递shell命令(由攻击者控制的输入数据(来避免ssh
。相反,更喜欢普通的API,如HTTP接口(或其他文本或二进制协议(。如果不可能,请在发送数据之前尽量对其进行消毒。也许你事先就知道URL的样子(例如,允许的主机名列表、允许的路径等(。但要意识到,你可能会忽略一些隐藏的例子,永远不要低估攻击者的创造力。