我想设置一个 SSH 服务器,作为客户端和“真实”SSH 服务器之间的代理。我通过利用 . 指向一个脚本来实现这一点,ForceCommand
该sshd_config
脚本ForceCommand
在转发会话之前会执行一些额外的操作,使用类似于以下行的内容:
ssh $theRealSSHServer $SSH_ORIGINAL_COMMAND
$SSH_ORIGINAL_COMMAND
是因为我需要通过 SSH 执行命令。为了保护我的代理服务器免受恶意用户的攻击,我计划在转发会话之前执行以下操作:
if echo "$SSH_ORIGINAL_COMMAND" | grep "[^a-z|A-Z|0-9|[:space:]|_|-]" ; then
echo "Illegal command: $SSH_ORIGINAL_COMMAND"
exit 1
fi
即我只允许一组有限的字符,这应该使得无法插入任何将在代理服务器上本地执行的命令(如; rm -rf /
)。这对可能执行的命令施加了相当大的限制,但比使用字典的静态方法要好。我真的不想使用带有受信任命令的字典,这似乎是解决这个问题的推荐方法,http://at.magma-soft.at/sw/blog/posts/The_Only_Way_For_SSH_Forced_Commands/。
我的方法“安全”吗?如果不安全,是否可以修复,或者从安全角度来看这种方法是不可接受的?
答案1
ssh $theRealSSHServer $SSH_ORIGINAL_COMMAND
该命令虽然有些缺陷,但不容易允许命令注入。
即使不加引号,包含 的变量; rm -rf whatever
也不会执行rm
。 控制运算符(如 );
在变量扩展发生之前就被识别。 当变量扩展时,命令行的逻辑已经固定,其他;
无法更改它;它只是一个文字;
。 这同样适用于像 这样的代码片段$(rm …)
在变量扩展时弹出。
未加引号的变量仍会经历变量扩展(显然)、单词拆分和文件名生成。请参阅这。您可以轻松干扰诚实用户在目标服务器上执行的操作。示例:
- 用户调用
ssh proxy 'echo "I need double spaces"'
。他们在响应中获得单个空格。 - 用户调用
ssh proxy 'echo some/location/*'
.*
被展开在代理上并将结果作为参数传递给目标服务器echo
。用户将看不到他们想要的内容。 - 他们不仅看不到他们想要的东西,还可能在目标服务器上触发不必要的操作。想象一下,扩展将得到他们
some/location/; rm -rf whatever
(合法的名字)。这将是解释在目标服务器上。whatever
将被删除(如果允许)。注意rm
可以采用多个操作数;因此,如果代理上有更多匹配项出现在 之后some/location/; rm -rf whatever
,则目标服务器上的对应项将被删除(如果允许)。
恶意用户可以利用这一点。例如:
- 用户调用
ssh proxy 'echo /etc/*'
。这几乎就像ls /etc/
在代理上执行一样。那么/boot/
,/lib/modules/
和子目录呢?获得的信息可能有助于他们准备攻击。 - 用户可以将选项传递给
ssh
您的脚本调用。例如,如果他们输入,ssh proxy -- '-o HostName=another_server -o User=impostor -p 22'
那么他们将启动与另一台服务器的连接,而不管$theRealSSHServer
您的脚本中是否包含以下内容(比较这个答案)。他们还有更多可以(滥用)的选项。双引号$SSH_ORIGINAL_COMMAND
将使大多数此类尝试失败。真正的对策是双划线。
脚本中的命令应该是
ssh -- "$theRealSSHServer" "$SSH_ORIGINAL_COMMAND"
现在您不需要清理原始命令,除非您可能想为目标服务器执行此操作。在本地(在代理上),这是安全的。
脚本的其他部分可能仍会以允许执行(部分)变量的方式使用该变量。您的工作是确保它们不会这样做。示例:
该变量提供命令的第一个字。
(明显的)
SSH_ORIGINAL_COMMAND="echo foo" $SSH_ORIGINAL_COMMAND SSH_ORIGINAL_COMMAND="date" "$SSH_ORIGINAL_COMMAND"
(不太明显)
SSH_ORIGINAL_COMMAND="date" cmmnd="" $cmmnd "$SSH_ORIGINAL_COMMAND"
请注意,所有变量的严格双引号将阻止这种情况。
扩展的内容在代理上进行解释。(它应该被解释,但仅在目标服务器上进行)。
(明显的)
SSH_ORIGINAL_COMMAND='date; echo foo' eval "$SSH_ORIGINAL_COMMAND"
(不太明显)
SSH_ORIGINAL_COMMAND="foo'; date'" sh -c "printf '%s\n' '$SSH_ORIGINAL_COMMAND'"
比较这。安全的方式是
sh -c 'printf "%s\n" "$1"' sh "$SSH_ORIGINAL_COMMAND"
。
如果您的脚本不执行这样的操作,那么双引号$SSH_ORIGINAL_COMMAND
将不会在代理服务器上注入代码。
注意,用户可以在调用中指定-L
、-t
或 等选项。选项不会影响与目标服务器的连接。至少覆盖/ 、请求 tty 或不请求 tty,具体取决于用户是否在代理上获得 tty:-o
ssh
-t
-T
if [ -t 0 ]; then t=-t; else t=-T; fi
ssh "$t" -- "$theRealSSHServer" "$SSH_ORIGINAL_COMMAND"
如果您的脚本只是转发连接(而不是做“额外的事情”),那么最好请求用户使用ProxyJump
。
如果您的目标是抑制目标服务器上的某些命令,那么您可能应该在那里应用一些限制,而不是在代理上应用(设置适当的权限,使用某种受限的 shell,...)。