OpenSSH 是 SSH 协议的一个实现。一般通过 scp 或 sftp 用于远程登录、备份、远程文件传输等功能。SSH能够完美保障两个网络或系统间数据传输的保密性和完整性。尽管如此,它最大的优势是使用公匙加密来进行服务器验证。时不时会出现关于 OpenSSH 零日漏洞的传言。本文将描述如何设置你的 Linux 或类 Unix 系统以提高 sshd 的安全性。
OpenSSH 默认设置
1、 基于公匙的登录
OpenSSH 服务支持各种验证方式。推荐使用公匙加密验证。首先,使用以下 ssh-keygen命令在本地电脑上创建密匙对:
1024 位或低于它的 DSA 和 RSA 加密是很弱的,请不要使用。当考虑 ssh 客户端向后兼容性的时候,请使用 RSA密匙代替 ECDSA 密匙。所有的 ssh 密钥要么使用 ED25519 ,要么使用 RSA,不要使用其它类型。
$
ssh
-
keygen
-
t
key_type
-
b
bits
-
C
"comment"
示例:
$
ssh
-
keygen
-
t
ed25519
-
C
"Login to production cluster at xyz corp"
或
$
ssh
-
keygen
-
t
rsa
-
b
4096
-
f
~/
.
ssh
/
id_rsa_aws_
$(
date
+%
Y
-%
m
-%
d
)
-
C
"AWS key for abc corp clients"
下一步,使用 ssh-copy-id 命令安装公匙:
$
ssh
-
copy
-
id
-
i
/
path
/
to
/
public
-
key
-
file
user
@
host
或
$
ssh
-
copy
-
id
user
@
remote
-
server
-
ip
-
or
-
dns
-
name
示例:
$
ssh
-
copy
-
id
vivek
@
rhel7
-
aws
-
server
提示输入用户名和密码的时候,确认基于 ssh 公匙的登录是否工作:
$
ssh
vivek
@
rhel7
-
aws
-
server
2、 禁用 root 用户登录
禁用 root 用户登录前,确认普通用户可以以 root 身份登录。例如,允许用户 vivek 使用 sudo 命令以 root 身份登录。
在 Debian/Ubuntu 系统中如何将用户 vivek 添加到 sudo 组中
允许 sudo 组中的用户执行任何命令。 将用户 vivek 添加到 sudo 组中:
$
sudo adduser vivek
sudo
使用 id 命令 验证用户组。
$
id
vivek
在 CentOS/RHEL 系统中如何将用户 vivek 添加到 sudo 组中
在 CentOS/RHEL 和 Fedora 系统中允许 wheel 组中的用户执行所有的命令。使用 usermod 命令将用户 vivek 添加到 wheel 组中:
$
sudo
usermod
-
aG wheel
vivek
$
id
vivek
测试 sudo 权限并禁用 ssh root 登录
测试并确保用户 vivek 可以以 root 身份登录执行以下命令:
$
sudo
-
i
$
sudo
/
etc
/
init
.
d
/
sshd
status
$
sudo systemctl status
httpd
添加以下内容到 sshd_config 文件中来禁用 root 登录:
PermitRootLogin no
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM
no
3、 禁用密码登录
所有的密码登录都应该禁用,仅留下公匙登录。添加以下内容到 sshd_config 文件中:
AuthenticationMethods publickey
PubkeyAuthentication
yes
CentOS 6.x/RHEL 6.x 系统中老版本的 sshd 用户可以使用以下设置:
PubkeyAuthentication
yes
4、 限制用户的 ssh 访问
默认状态下,所有的系统用户都可以使用密码或公匙登录。但是有些时候需要为 FTP 或者 email 服务创建 UNIX/Linux 用户。然而,这些用户也可以使用 ssh 登录系统。他们将获得访问系统工具的完整权限,包括编译器和诸如 Perl、Python(可以打开网络端口干很多疯狂的事情)等的脚本语言。通过添加以下内容到 sshd_config 文件中来仅允许用户 root、vivek 和 jerry 通过 SSH 登录系统:
AllowUsers vivek
jerry
当然,你也可以添加以下内容到 sshd_config 文件中来达到仅拒绝一部分用户通过 SSH 登录系统的效果。
DenyUsers root saroj anjali
foo
你也可以通过配置 Linux PAM 来禁用或允许用户通过 sshd 登录。也可以允许或禁止一个用户组列表通过 ssh 登录系统。
5、 禁用空密码
你需要明确禁止空密码账户远程登录系统,更新 sshd_config 文件的以下内容:
PermitEmptyPasswords
no
6、 为 ssh 用户或者密匙使用强密码
为密匙使用强密码和短语的重要性再怎么强调都不过分。暴力破解可以起作用就是因为用户使用了基于字典的密码。你可以强制用户避开字典密码并使用约翰的开膛手工具来检测弱密码。以下是一个随机密码生成器(放到你的 ~/.bashrc 下):
genpasswd
()
{
local
l
=
$
1
[
"$l"
==
""
]
&&
l
=
20
tr
-
dc
A
-
Za
-
z0
-
9_
< /
dev
/
urandom
|
head
-
c
${
l
}
|
xargs
运行:
genpasswd
16
输出:
uw8CnDVMwC6vOKgW
7、 为 SSH 的 22端口配置防火墙
你需要更新 iptables/ufw/firewall-cmd 或 pf 防火墙配置来为 ssh 的 TCP 端口 22 配置防火墙。一般来说,OpenSSH 服务应该仅允许本地或者其他的远端地址访问。
Netfilter(Iptables) 配置
更新 /etc/sysconfig/iptables (Redhat 和其派生系统特有文件) 实现仅接受来自于 192.168.1.0/24 和 202.54.1.5/29 的连接,输入:
-
A
RH
-
Firewall
-
1
-
INPUT
-
s
192.168.1.0
/
24
-
m
state
--
state
NEW
-
p
tcp
--
dport
22
-
j
ACCEPT
-
A
RH
-
Firewall
-
1
-
INPUT
-
s
202.54.1.5
/
29
-
m
state
--
state
NEW
-
p
tcp
--
dport
22
-
j
ACCEPT
如果同时使用 IPv6 的话,可以编辑 /etc/sysconfig/ip6tables (Redhat 和其派生系统特有文件),输入:
-
A
RH
-
Firewall
-
1
-
INPUT
-
s
ipv6network
::/
ipv6mask
-
m
tcp
-
p
tcp
--
dport
22
-
j
ACCEPT
将 ipv6network::/ipv6mask 替换为实际的 IPv6 网段。
Debian/Ubuntu Linux 下的 UFW
UFW 是 Uncomplicated FireWall 的首字母缩写,主要用来管理 Linux 防火墙,目的是提供一种用户友好的界面。输入以下命令使得系统仅允许网段 202.54.1.5/29 接入端口 22:
$
sudo ufw allow
from
202.54.1.5
/
29
to
any
port
22
*BSD PF 防火墙配置
如果使用 PF 防火墙 /etc/pf.conf 配置如下:
pass
in
on
$
ext_if
inet
proto
tcp
from
{
192.168.1.0
/
24
,
202.54.1.5
/
29
}
to
$
ssh_server_ip port ssh
flags
S
/
SA synproxy
state
8、 修改 SSH 端口和绑定 IP
ssh 默认监听系统中所有可用的网卡。修改并绑定 ssh 端口有助于避免暴力脚本的连接(许多暴力脚本只尝试端口 22)。更新文件 sshd_config 的以下内容来绑定端口 300 到 IP 192.168.1.5 和 202.54.1.5:
Port
300
ListenAddress
192.168.1.5
ListenAddress
202.54.1.5
当需要接受动态广域网地址的连接时,使用主动脚本是个不错的选择,比如 fail2ban 或 denyhosts。
9、 使用 TCP wrappers (可选的)
TCP wrapper 是一个基于主机的访问控制系统,用来过滤来自互联网的网络访问。OpenSSH 支持 TCP wrappers。只需要更新文件 /etc/hosts.allow 中的以下内容就可以使得 SSH 只接受来自于 192.168.1.2 和 172.16.23.12 的连接:
sshd
:
192.168.1.2
172.16.23.12
10、 阻止 SSH 破解或暴力攻击
暴力破解是一种在单一或者分布式网络中使用大量(用户名和密码的)组合来尝试连接一个加密系统的方法。可以使用以下软件来应对暴力攻击:
-
DenyHosts 是一个基于 Python SSH 安全工具。该工具通过监控授权日志中的非法登录日志并封禁原始 IP 的方式来应对暴力攻击。
-
Fail2ban 是另一个类似的用来预防针对 SSH 攻击的工具。
-
sshguard 是一个使用 pf 来预防针对 SSH 和其他服务攻击的工具。
-
security/sshblock 阻止滥用 SSH 尝试登录。
-
IPQ BDB filter 可以看做是 fail2ban 的一个简化版。
11、 限制 TCP 端口 22 的传入速率(可选的)
netfilter 和 pf 都提供速率限制选项可以对端口 22 的传入速率进行简单的限制。
Iptables 示例
以下脚本将会阻止 60 秒内尝试登录 5 次以上的客户端的连入。
#!/bin/bash
inet_if
=
eth1
ssh_port
=
22
$
IPT
-
I
INPUT
-
p
tcp
--
dport
${
ssh_port
}
-
i
${
inet_if
}
-
m
state
--
state
NEW
-
m
recent
--
set
$
IPT
-
I
INPUT
-
p
tcp
--
dport
${
ssh_port
}
-
i
${
inet_if
}
-
m
state
--
state
NEW
-
m
recent
--
update
--
seconds
60
--
hitcount
5
在你的 iptables 脚本中调用以上脚本。其他配置选项:
$
IPT
-
A
INPUT
-
i
${
inet_if
}
-
p
tcp
--
dport
${
ssh_port
}
-
m
state
--
state
NEW
-
m
limit
--
limit
3
/
min
--
limit
-
burst
3
-
j
ACCEPT
$
IPT
-
A
INPUT
-
i
${
inet_if
}
-
p
tcp
--
dport
${
ssh_port
}
-
m
state
--
state
ESTABLISHED
-
j
ACCEPT
$
IPT
-
A
OUTPUT
-
o
${
inet_if
}
-
p
tcp
--
sport
${
ssh_port
}
-
m
state
--
state
ESTABLISHED
-
j
ACCEPT
# another one line example
# $IPT -A INPUT -i ${inet_if} -m state --state NEW,ESTABLISHED,RELATED -p tcp --dport 22 -m limit --limit 5/minute --limit-burst 5-j ACCEPT
其他细节参见 iptables 用户手册。
*BSD PF 示例
以下脚本将限制每个客户端的连入数量为 20,并且 5 秒内的连接不超过 15 个。如果客户端触发此规则,则将其加入 abusive_ips 表并限制该客户端连入。最后 flush 关键词杀死所有触发规则的客户端的连接。
sshd_server_ip
=
"202.54.1.5"
table
<
abusive_ips
>
persist
block
in
quick
from
<
abusive_ips
>
pass
in
on
$
ext_if proto tcp
to
$
sshd_server_ip port ssh
flags
S
/
SA keep state
(
max
-
src
-
conn
20
,
max
-
src
-
conn
-
rate
15
/
5
,
overload
<
abusive_ips
>
flush
)
12、 使用端口敲门(可选的)
端口敲门是通过在一组预先指定的封闭端口上生成连接尝试,以便从外部打开防火墙上的端口的方法。一旦指定的端口连接顺序被触发,防火墙规则就被动态修改以允许发送连接的主机连入指定的端口。以下是一个使用 iptables 实现的端口敲门的示例:
$
IPT
-
N
stage1
$
IPT
-
A
stage1
-
m
recent
--
remove
--
name
knock
$
IPT
-
A
stage1
-
p
tcp
--
dport
3456
-
m
recent
--
set
--
name
knock2
$
IPT
-
N
stage2
$
IPT
-
A
stage2
-
m
recent
--
remove
--
name
knock2
$
IPT
-
A
stage2
-
p
tcp
--
dport
2345
-
m
recent
--
set
--
name
heaven
$
IPT
-
N
door
$
IPT
-
A
door
-
m
recent
--
rcheck
--
seconds
5
--
name
knock2
-
j
stage2
$
IPT
-
A
door
-
m
recent
--
rcheck
--