SSH是传统的telnet的一个替代,它不但是一个很好的交互工具,而且可以用来完成其它复杂的工作,以及和其它应用程序协同工作。本文将介绍一些SSH高级应用。
简介
SSH除了能够执行平常的交互操作外,还可以用来自动地执行一些脚本和自动任务,为这些任务提供优秀的安全保护。不过要做到这些有一个最大的障碍,就是客户的认证:在无人输入的情况下,如何获得用户的认证信息(用户名和密码)?这里,我们将讨论如何实现ssh的非交互认证。
注意任何非交互认证都存在安全问题,即使SSH也不例外,因此一定要慎重考虑。在无人参与的情况下,认证所需的凭证(例如:密码)都要放在主机的某个地方,这样只要攻击者获得了主机的root权限,就可以访问保存在主机上的所有认证凭证。总之,在在选择使用的方法之前,你一定要权衡每种实现方式的优、缺点。选择危害较请的实现方法。如果你不想降低系统的安全性,那就不要使用批量和定时的远程作业。
1.密码认证
这种认证方式的安全系数是最底的。因为为了使用密码认证,你必需把密码潜入到要执行的脚本或者放到一个脚本能够读到的文件中。无论你采取什么措施,具有脚本读权限的任何人都会获得密码。因此,我们不建议使用这种技术,相对来说,选择公开密钥认证要安全的多。
2.公开密钥认证
在公开密钥认证中,私钥是客户的认证凭证。因此,脚本需要访问这个密钥,必须保存在脚本能够访问到的地方。保存密钥的地方可以有三个选择,后面我们将会分别讨论:
把加密后的密钥和访问这个密钥的口令保存到文件系统。
把明文(未加密的)私钥保存到文件系统,因此不需要口令的保护。
使用ssh代理管理密钥,这种方式比直接保存到文件系统要安全一些,只是在系统启动时需要人工解密这个密钥。
2.1.在文件系统中保存口令加密过的密钥
这种保存方式是在文件系统中保存加密的密钥和访问这个密钥的口令。不过,我们不建议使用这种方式,因为它并不比直接保存明文密钥更安全,它们的安全性都依赖于对文件系统的保护。而且,如果使用这种方式还比较麻烦。
2.2.保存明文密钥(私钥)
使用这种保存方式不需要密钥的口令。用户可以使用OpenSSH套件的ssh-keygen命令产生新的私钥和维护原由的私钥。在ssh-genkey提示输入私钥的保护口令时,就可以产生一个明文保存的私钥;另外使用ssh-genkey -p命令可以去掉私钥的保护口令。然后,你可以使用ssh命令的-i选项指定私钥文件的位置,或者在客户配置文件中使用IdentityFile关键词指定私钥文件的位置。
明文密钥不太安全的,相当于把密码放到帐户的一个文件中。不适合交互登录的情况下采用这种密钥保存方式,而使用SSH代理管理密钥可以更安全一些。不过,对于自动登录,这种方式还是可以使用的。从安全角度看,以上三种情况:明文密钥、使用口令加密的密钥和密钥的安全性是一样的。但是,有三个理由使我们采用明文密钥的认证方式:
在服务器端,使用公开密钥认证更有利于帐户的管理,尤其是在建立批量作业时。
公开密钥认证不会被恶意服务器窃取认证机密。
从一个程序向SSH提供一个密码比较困难。SSH不是从标准输入获得用户的密码,它是直接从与用户进行交互的控制终端获得密码。为了在应用程序和SSH之间传递密码,你需要使用一个伪终端和SSH进行交互(例如:使用expect等工具)。
明文密钥还是比较让人担心的。攻击者只要突破文件系统的保护,就可以获得他需要的东西。因此,我们建议使用下面的认证方式。
2.3.使用代理(agent)管理密钥
在ssh套件中,ssh-agent程序能够提供更为安全的密钥管理。用户只要运行一个代理(详细用法请参考ssh-agent的手册页)并从一个口令保护的文件中加载需要的密钥,然后就可以使用这个长期运行的代理进行认证了。
在这种情况下,密钥也是以明文保存的,不过不是在磁盘的文件上,而是在内存空间。从实际攻击的角度看,从一个进程的内存空间提取某个数据结构比获得一个文件的访问权要困难的多。
使用这种认证方式,虽然使攻击者难以通过突破文件系统的保护获得密钥,但是仍然存在其它的安全隐患。ssh-agent程序提供了一个UNIX-DOMAIN套接字用于和应用程序之间的通讯,而这个套接字看起来就是一个文件系统的节点。可以读写这个套接字的任何人都能够迫使代理发出认证请求,从而获得密钥的使用权。不过,出现这种情况的危害不是很大,因为攻击者无法通过这种攻击凡是获得用户的私钥,只能在代理运行期间使用私钥。
虽然使用私钥代理可以提高系统的安全性,但是使用这种方式有一些不便,如果系统重启,所有的批量和定时作业都将无法进行,直到有人输入了保护私钥的口令,从文件中把私钥加载到代理中。为了提高系统的安全性,这点代价还是值得的。
在使用私钥代理来提供认证时,还有一点麻烦就是需要使应用程序能够发现私钥代理。SSH客户程序需要SSH_AU_SOCK环境变量来寻找和私钥代理通讯的套接字。当启动一个提供认证服务的私钥代理时,你应该记录下它的输出,以便导出相关环境变量。例如:如果作业是一个shell脚本,你可以把输出保存到一个文件中:
[nixe0n@localhost nixe0n]$ssh-agent |head -2>~/agent-info
[nixe0n@localhost nixe0n]$cat ~/agent-info
SSH_AUTH_SOCK=/tmp/ssh-XXsAV98A/agent.738; export SSH_AUTH_SOCK;
SSH_AGENT_PID=739; export SSH_AGENT_PID;
你可以把需要的密钥加载给代理:
[nixe0n@localhost nixe0n]source ~/agent-info <--从上面产生的文件中导出环境变量
[nixe0n@localhost nixe0n]ssh-add batch-key <--假设你已经使用ssh-keygen命令产生了一对保存在~/batch-key目录下的密钥
Need passphrase for batch-key
Enter passhrase for nixe0n@localhost <--输入私钥保护口令
然后,你可以把上面的脚本嵌入到自己的脚本中,导出需要的环境变量,例如:
#!/bin/bash
# Source the agent-info file to get access to our ssh-agent.
agent = ~/agent-info
if [-r $agent] then
source $agent
else
echo "Can't find or read agent file; exiting."
exit 1
endif
# Now use SSH for something...
ssh -q -o 'BatchMode yes' user@remote-server my-job-command
最后,为了安全,你还需要确认除了这个脚本之外,其他任何用户拥有的进程都不能读写这个UNIX-DOMAIN套接字。如果只有一个用户的进程使用代理,用这个用户的UID运行ssh-agent就可以了(例如:root用户使用su <帐户> ssh-agent)。如果有多个用户的进程需要使用这个代理,就有点麻烦,你需要对通讯套接字的权限做适当地调整,最好是通过用户组的方式来管理。
从上面的例子可以看出,使用代理比使用明文私钥复杂烦琐的多,不过使攻击者很难窃取用户的私钥,安全性也高的多。因此建议在批量和自动作业中采用这种认证方式。
3.信任主机认证
如果对于安全的要求不是很高,对于批量作业可以使用信任主机的认证方式。在这种情况下,认证的凭证是操作系统中运行进程的UID。一个攻击者只要控制运行进程的用户UID,就可以模仿你的行为,使远程SSH主机相信是你的请求。如果他获得一个客户主机的root权限,就可以以任何的UID运行进程。这里的关键是获得客户主机的密码,一旦他登录到受信任的客户主机,远程的SSH主机就会把他当作合法的用户。
信任主机在上面讨论过的所有认证方式中,是最脆弱的。这种方式会造成系统漏洞的扩散,如果一个攻击者获得了主机H的一个帐户,就可以不费吹灰之力地获得对所有信任主机H的主机的访问权。而且,信任主机的配置有很大的局限性,并且很容易出错。
4.批量作业应该注意的问题
除了选择合适的认证方式意外,在编写脚本时你还应该注意以下问题,这些问题对于帮助你加强系统的安全也非常重要。
4.1.尽量减小帐户的权限
为了保证系统的安全,在建立运行脚本的用户时应该遵循最小权限的原则,也就是赋予帐户的权限只要能够保证这个脚本能够运行就可以了,不要给予任何额外的权限。除非必要,否则不要以root的权限运行这个脚本,因为远程的作业会给执行作业的帐户带来很的安全威胁。另外,还要注意文件系统的保护。
4.2.隔离、锁定运行脚本的帐户
建立专门的帐户执行这些脚本。尽量不要使用用户的帐户来执行这些脚本,因为你很难削减这种帐户的某些权限。例如:自动执行的脚本几乎不需要交互的登录,也不需要和系统进行交互的shell。我们只是需要这些帐户的UID来执行这些自动执行的脚本。实际上,系统中已经预先设定了一些系统帐户用于执行某些特殊的服务:例如:cron、nobody等,这些帐户不能让用户登录,应该处于锁定状态。
4.3.使用专用的密钥
在使用公开密钥进行认证时,自动执行的脚本尽量使用专用的密钥,不要和交互登录共享密钥。这样,如果出于安全的原因你需要修改密码,你可以最大限度地减小修改密码造成的影响。而且,为了最大限度地加强系统的安全,最好每个任务使用各自的密钥。除此之外,还要在认证配置文件中使用命令选项对密钥做最大程度的限制。这些限制包括:使用命令选项限制只在运行所需的远程命令时,使用这个密钥;使用from选项限制只有特定的客户主机才能使用这个密钥。另外,如果你不想看到脚本的输出信息,还要加入以下的选项:
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
这些选项能够增加密钥被窃取的难度。
不过,如果使用信任主机认证方式,却不能使用以上的限制。在这种情况下,最好能够为这个帐户指定一个专门的shell,能够限制可以运行的命令,因为sshd需要使用目标帐户的shell来运行用户端的任何命令。在UNIX系统中有一个标准的工具,叫做erstricted shell,可以简称为rsh,不过要注意这个rsh和Berkeley r-系列的远程命令没有任何的关系。
4.4.有用的ssh选项
ssh有很多的命令选项,在批量作业中执行ssh命令时,最好使用如下的选项:
ssh -q -o 'BatchMode yes'
其中,-q选项可以阻止ssh输出各种警告信息,如果你使用管道把ssh的输出作为另一个命令的输入,就非常有必要使用这个选项。否则,ssh警告信息就会被解释成远程程序的输出,使本地的程序很难处理。
简介
SSH除了能够执行平常的交互操作外,还可以用来自动地执行一些脚本和自动任务,为这些任务提供优秀的安全保护。不过要做到这些有一个最大的障碍,就是客户的认证:在无人输入的情况下,如何获得用户的认证信息(用户名和密码)?这里,我们将讨论如何实现ssh的非交互认证。
注意任何非交互认证都存在安全问题,即使SSH也不例外,因此一定要慎重考虑。在无人参与的情况下,认证所需的凭证(例如:密码)都要放在主机的某个地方,这样只要攻击者获得了主机的root权限,就可以访问保存在主机上的所有认证凭证。总之,在在选择使用的方法之前,你一定要权衡每种实现方式的优、缺点。选择危害较请的实现方法。如果你不想降低系统的安全性,那就不要使用批量和定时的远程作业。
1.密码认证
这种认证方式的安全系数是最底的。因为为了使用密码认证,你必需把密码潜入到要执行的脚本或者放到一个脚本能够读到的文件中。无论你采取什么措施,具有脚本读权限的任何人都会获得密码。因此,我们不建议使用这种技术,相对来说,选择公开密钥认证要安全的多。
2.公开密钥认证
在公开密钥认证中,私钥是客户的认证凭证。因此,脚本需要访问这个密钥,必须保存在脚本能够访问到的地方。保存密钥的地方可以有三个选择,后面我们将会分别讨论:
把加密后的密钥和访问这个密钥的口令保存到文件系统。
把明文(未加密的)私钥保存到文件系统,因此不需要口令的保护。
使用ssh代理管理密钥,这种方式比直接保存到文件系统要安全一些,只是在系统启动时需要人工解密这个密钥。
2.1.在文件系统中保存口令加密过的密钥
这种保存方式是在文件系统中保存加密的密钥和访问这个密钥的口令。不过,我们不建议使用这种方式,因为它并不比直接保存明文密钥更安全,它们的安全性都依赖于对文件系统的保护。而且,如果使用这种方式还比较麻烦。
2.2.保存明文密钥(私钥)
使用这种保存方式不需要密钥的口令。用户可以使用OpenSSH套件的ssh-keygen命令产生新的私钥和维护原由的私钥。在ssh-genkey提示输入私钥的保护口令时,就可以产生一个明文保存的私钥;另外使用ssh-genkey -p命令可以去掉私钥的保护口令。然后,你可以使用ssh命令的-i选项指定私钥文件的位置,或者在客户配置文件中使用IdentityFile关键词指定私钥文件的位置。
明文密钥不太安全的,相当于把密码放到帐户的一个文件中。不适合交互登录的情况下采用这种密钥保存方式,而使用SSH代理管理密钥可以更安全一些。不过,对于自动登录,这种方式还是可以使用的。从安全角度看,以上三种情况:明文密钥、使用口令加密的密钥和密钥的安全性是一样的。但是,有三个理由使我们采用明文密钥的认证方式:
在服务器端,使用公开密钥认证更有利于帐户的管理,尤其是在建立批量作业时。
公开密钥认证不会被恶意服务器窃取认证机密。
从一个程序向SSH提供一个密码比较困难。SSH不是从标准输入获得用户的密码,它是直接从与用户进行交互的控制终端获得密码。为了在应用程序和SSH之间传递密码,你需要使用一个伪终端和SSH进行交互(例如:使用expect等工具)。
明文密钥还是比较让人担心的。攻击者只要突破文件系统的保护,就可以获得他需要的东西。因此,我们建议使用下面的认证方式。
2.3.使用代理(agent)管理密钥
在ssh套件中,ssh-agent程序能够提供更为安全的密钥管理。用户只要运行一个代理(详细用法请参考ssh-agent的手册页)并从一个口令保护的文件中加载需要的密钥,然后就可以使用这个长期运行的代理进行认证了。
在这种情况下,密钥也是以明文保存的,不过不是在磁盘的文件上,而是在内存空间。从实际攻击的角度看,从一个进程的内存空间提取某个数据结构比获得一个文件的访问权要困难的多。
使用这种认证方式,虽然使攻击者难以通过突破文件系统的保护获得密钥,但是仍然存在其它的安全隐患。ssh-agent程序提供了一个UNIX-DOMAIN套接字用于和应用程序之间的通讯,而这个套接字看起来就是一个文件系统的节点。可以读写这个套接字的任何人都能够迫使代理发出认证请求,从而获得密钥的使用权。不过,出现这种情况的危害不是很大,因为攻击者无法通过这种攻击凡是获得用户的私钥,只能在代理运行期间使用私钥。
虽然使用私钥代理可以提高系统的安全性,但是使用这种方式有一些不便,如果系统重启,所有的批量和定时作业都将无法进行,直到有人输入了保护私钥的口令,从文件中把私钥加载到代理中。为了提高系统的安全性,这点代价还是值得的。
在使用私钥代理来提供认证时,还有一点麻烦就是需要使应用程序能够发现私钥代理。SSH客户程序需要SSH_AU_SOCK环境变量来寻找和私钥代理通讯的套接字。当启动一个提供认证服务的私钥代理时,你应该记录下它的输出,以便导出相关环境变量。例如:如果作业是一个shell脚本,你可以把输出保存到一个文件中:
[nixe0n@localhost nixe0n]$ssh-agent |head -2>~/agent-info
[nixe0n@localhost nixe0n]$cat ~/agent-info
SSH_AUTH_SOCK=/tmp/ssh-XXsAV98A/agent.738; export SSH_AUTH_SOCK;
SSH_AGENT_PID=739; export SSH_AGENT_PID;
你可以把需要的密钥加载给代理:
[nixe0n@localhost nixe0n]source ~/agent-info <--从上面产生的文件中导出环境变量
[nixe0n@localhost nixe0n]ssh-add batch-key <--假设你已经使用ssh-keygen命令产生了一对保存在~/batch-key目录下的密钥
Need passphrase for batch-key
Enter passhrase for nixe0n@localhost <--输入私钥保护口令
然后,你可以把上面的脚本嵌入到自己的脚本中,导出需要的环境变量,例如:
#!/bin/bash
# Source the agent-info file to get access to our ssh-agent.
agent = ~/agent-info
if [-r $agent] then
source $agent
else
echo "Can't find or read agent file; exiting."
exit 1
endif
# Now use SSH for something...
ssh -q -o 'BatchMode yes' user@remote-server my-job-command
最后,为了安全,你还需要确认除了这个脚本之外,其他任何用户拥有的进程都不能读写这个UNIX-DOMAIN套接字。如果只有一个用户的进程使用代理,用这个用户的UID运行ssh-agent就可以了(例如:root用户使用su <帐户> ssh-agent)。如果有多个用户的进程需要使用这个代理,就有点麻烦,你需要对通讯套接字的权限做适当地调整,最好是通过用户组的方式来管理。
从上面的例子可以看出,使用代理比使用明文私钥复杂烦琐的多,不过使攻击者很难窃取用户的私钥,安全性也高的多。因此建议在批量和自动作业中采用这种认证方式。
3.信任主机认证
如果对于安全的要求不是很高,对于批量作业可以使用信任主机的认证方式。在这种情况下,认证的凭证是操作系统中运行进程的UID。一个攻击者只要控制运行进程的用户UID,就可以模仿你的行为,使远程SSH主机相信是你的请求。如果他获得一个客户主机的root权限,就可以以任何的UID运行进程。这里的关键是获得客户主机的密码,一旦他登录到受信任的客户主机,远程的SSH主机就会把他当作合法的用户。
信任主机在上面讨论过的所有认证方式中,是最脆弱的。这种方式会造成系统漏洞的扩散,如果一个攻击者获得了主机H的一个帐户,就可以不费吹灰之力地获得对所有信任主机H的主机的访问权。而且,信任主机的配置有很大的局限性,并且很容易出错。
4.批量作业应该注意的问题
除了选择合适的认证方式意外,在编写脚本时你还应该注意以下问题,这些问题对于帮助你加强系统的安全也非常重要。
4.1.尽量减小帐户的权限
为了保证系统的安全,在建立运行脚本的用户时应该遵循最小权限的原则,也就是赋予帐户的权限只要能够保证这个脚本能够运行就可以了,不要给予任何额外的权限。除非必要,否则不要以root的权限运行这个脚本,因为远程的作业会给执行作业的帐户带来很的安全威胁。另外,还要注意文件系统的保护。
4.2.隔离、锁定运行脚本的帐户
建立专门的帐户执行这些脚本。尽量不要使用用户的帐户来执行这些脚本,因为你很难削减这种帐户的某些权限。例如:自动执行的脚本几乎不需要交互的登录,也不需要和系统进行交互的shell。我们只是需要这些帐户的UID来执行这些自动执行的脚本。实际上,系统中已经预先设定了一些系统帐户用于执行某些特殊的服务:例如:cron、nobody等,这些帐户不能让用户登录,应该处于锁定状态。
4.3.使用专用的密钥
在使用公开密钥进行认证时,自动执行的脚本尽量使用专用的密钥,不要和交互登录共享密钥。这样,如果出于安全的原因你需要修改密码,你可以最大限度地减小修改密码造成的影响。而且,为了最大限度地加强系统的安全,最好每个任务使用各自的密钥。除此之外,还要在认证配置文件中使用命令选项对密钥做最大程度的限制。这些限制包括:使用命令选项限制只在运行所需的远程命令时,使用这个密钥;使用from选项限制只有特定的客户主机才能使用这个密钥。另外,如果你不想看到脚本的输出信息,还要加入以下的选项:
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
这些选项能够增加密钥被窃取的难度。
不过,如果使用信任主机认证方式,却不能使用以上的限制。在这种情况下,最好能够为这个帐户指定一个专门的shell,能够限制可以运行的命令,因为sshd需要使用目标帐户的shell来运行用户端的任何命令。在UNIX系统中有一个标准的工具,叫做erstricted shell,可以简称为rsh,不过要注意这个rsh和Berkeley r-系列的远程命令没有任何的关系。
4.4.有用的ssh选项
ssh有很多的命令选项,在批量作业中执行ssh命令时,最好使用如下的选项:
ssh -q -o 'BatchMode yes'
其中,-q选项可以阻止ssh输出各种警告信息,如果你使用管道把ssh的输出作为另一个命令的输入,就非常有必要使用这个选项。否则,ssh警告信息就会被解释成远程程序的输出,使本地的程序很难处理。