内网-SSH中的安全 | 从SSH协议看身份验证底层原理
前置关键词:SSH 客户端/服务器,Linux/Unix 系统的用户账户,TCP/IP,Socket。
本文撰于 2022 年 9 月,若相关内容有更新请依照引用链接内的内容为准。
本文介绍了 SSH 协议在验证用户身份进程中的完结细节,想帮助读者更加深入的了解 SSH 客户端与服务器的工作进程。也因而,本文可能不适用于辅导 SSH 服务器或客户端的装备。
本文在编撰中参阅了下列内容。
SSH 架构 RFC 4251 The Secure Shell (SSH) Protocol Architecture
SSH 传输层协议 RFC 4253 The Secure Shell (SSH) Transport Layer Protocol
SSH 身份验证协议 RFC 4252 The Secure Shell (SSH) Authentication Protocol
SSH 衔接协议 RFC 4254 The Secure Shell (SSH) Connection Protocol
SSH 交互式身份验证 RFC 4256 Generic Message Exchange Authentication for the Secure Shell Protocol (SSH)
SSH 协议的结构
SSH 协议的根本结构
树立一个 SSH 衔接,将会经过下面几个进程。
(明文通讯)树立 TCP 衔接
(明文通讯)洽谈 SSH 协议版别 (本质上是彼此发送包含版别号的字符串)
服务器将自己的 SSH 协议版别发送到客户端,格局为:SSH-protoversion(版别号)-softwareversion(自定义) SP(空格一个,可选) comments(注释,可选) CR(回车) LF(换行)
客户端将自己的 SSH 协议版别发送到服务器,格局为:SSH-protoversion(版别号)-softwareversion(自定义) SP(空格一个,可选) comments(注释,可选) CR(回车符) LF(换行符)
(明文通讯)洽谈密钥
服务器发送公钥
客户端和服务器彼此发送支撑的相关加密算法列表、MAC 算法列表
运用 D-H 算法 生成尔后通讯中运用的对称加密密钥(会话密钥)
(密文通讯)身份认证
(密文通讯)正式进入运用 (如打开 Shell、SFTP、端口转发)
从上面的流程中能够看出,身份验证进程实际上发生在 SSH 衔接树立之后,尔后客户端与服务器之间传输的暗码、密钥、信息都现已受到 SSH 协议生成的「会话密钥」加密。
关于「安全」
SSH 的全称是「Secure Shell」,安全 Shell。其间的「安全」不是指运用 SSH 就能进入一个肯定的安全国际,而是包含了传输安全和身份安全。
假设,我正在打电话给身在公司的朋友,问询一些机要信息。此刻进犯者就能够剪断我屋外的电话线,分别接上两个电话听筒对听筒放在一同。我和我的朋友都不会注意到问题,而进犯者能够从中得知咱们之间传输的信息。这是网络中存在的中间人进犯。
SSH 防止了这个问题,客户端与服务器之间的数据传输经过了上面的进程而加密(SSH 传输层协议 RFC 4253)。这个加密是无关于用户的账号暗码的,在事前就完结。无论是网络中的交换机仍是跳板机都无法直接获取加密前的明文。SSH 保护了传输时的安全。
SSH 也相同供给了验证用户身份的办法,客户端能够将用户的暗码发送至服务器,以此让服务器确认用户的身份,只答应被授权的拜访衔接到服务器。
SSH 的安全不是肯定的。比如进犯者拿起剪断的电话线(中间人进犯),内网他依旧能够经过客户端与服务器之间的通讯内容推测出这是 SSH 衔接。SSH 协议并不能确保衔接不被侦测到特征。
身份验证办法
暗码(Password)
运用暗码登录是常用且较为快捷的认证办法。在装备文件 /etc/ssh/sshd_config 中添加 PasswordAuthentication yes 以敞开暗码登录。
运用 ssh 指令时提示输入暗码
一般也认为运用暗码登录是一种较为软弱的认证办法。这不是说运用暗码会形成传输时的不安全,而是对暗码本身保存的忧虑。一般的用户暗码都是十位或数十位字符,可能被记载与纸上或记事本中。即便不是如此,有意义的暗码也容易遭到社会工程进犯而泄露。
在 /etc/ssh/sshd_config 文件中有时会设置 PermitRootLogin prohibit-password ,要求系统管理用户 root 不得运用暗码登录。
根据 RFC 中的描绘,用户认证进程是在衔接握手之后的。此刻客户端与服务器之间现已树立起了加密的衔接。双方都会运用握手时交换好的密钥加密一切传输内容。后文中的 SSH 数据样式都是被加密传输的。
登录时,登录恳求由客户端建议。一个名称为 SSH_MSG_USERAUTH_REQUEST 的音讯从客户端宣布,包含了登录的用户名与暗码。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string user name
C: string service name
C: string “password”
C: boolean FALSE
C: string plaintext password in ISO-10646 UTF-8 encoding [RFC3629]
S: byte SSH_MSG_USERAUTH_SUCCESS
若登录认证失败,服务器将回复 SSH_MSG_USERAUTH_FAILURE。
根本上,这样的音讯往复就完结了平时常见的登录进程。
除此之外,在 RFC 规范中还有一个用于服务器呼应暗码登录恳求的音讯。
一般,服务器会成功或失败地呼应此音讯。可是,假如暗码已过期,服务器应经过 SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 呼应来指示这一点。在任何状况下,服务器都不得答应运用过期暗码进行身份验证。
后文介绍的 keyboard-interactive 登录办法也能够做出暗码过期提示。
公钥私钥(Publickey)
在 RFC 规范中,公钥验证办法是仅有有必要完结的验证办法(The only REQUIRED authentication)。一切完结都有必要(MUST, RFC2119)支撑这种办法。在 /etc/ssh/sshd_config 文件中运用 PubkeyAuthentication yes 敞开公钥验证办法。
此验证办法需要用户先准备一个非对称加密的密钥对,将公钥保存至 SSH 服务器的 ~/.ssh/authorized_key 文件中。客户端登录时,在本地用私钥加密某个信息,并将成果发送给服务器,服务器将经过公钥验证收到的密文是否来自指定的用户。
私钥一般以加密的办法存储在客户主机上,用户有必要在生成签名之前供给一个口令(passphrase)。 即便不是这样,签名操作也涉及一些贵重的核算。 为了防止不必要的处理和用户互动,供给以下信息来查询运用 “公钥 “办法的认证是否能够承受。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string user name in ISO-10646 UTF-8 encoding [RFC3629]
C: string service name in US-ASCII
C: string “publickey”
C: boolean FALSE
C: string public key algorithm name
C: string public key blob
任何公钥算法都能够被供给给认证运用,假如恳求中的算法不被服务器支撑,它有必要直接拒绝该恳求。
服务器有必要以 SSH_MSG_USERAUTH_FAILURE 或以下办法回应该音讯。
S: byte SSH_MSG_USERAUTH_PK_OK
S: string public key algorithm name from the request
S: string public key blob from the request
之后,客户端会运用私钥加密一个音讯(音讯的构成办法参见 RFC 4252),将成果发送给服务器。下面音讯中的 signature 即为加密运算后的内容。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string user name
C: string service name
C: string “publickey”
C: boolean TRUE
C: string public key algorithm name
C: string public key to be used for authentication
C: string signature
运用公钥办法登录的长处是,密钥对根本不可能被写在纸上(密钥是很长的随机文本,社会工程进犯中只能经过更困难的间接办法盗取这么长的内容);在网络上传输、保存的一般是密钥对的公钥文件,而非私钥文件。
更加清楚明了的优点是,私钥文件是保存在客户端的核算机上的。运用 SSH 指令时就无需再反复输入暗码。因而网络上很多教程运用此办法作为免暗码登录的办法。与此同时,由于需要预先将公钥放在服务器上(一般是经过网络上传),其也确实不便于装备。
交互式(keyboard-interactive)
在 RFC 文档中,这个验证办法被视作是前述计划的一种扩展。答应 SSH 客户端和服务器在获取身份验证信息时进行一些交互。如要启用此办法需在 /etc/ssh/sshd_config 文件中添加 ChallengeResponseAuthentication yes 。一般状况下,这个验证形式会与系统内的 PAM 模块一同启用。以此来支撑谷歌验证器(多因素验证),或其他内部身份校验模块。
NextSSH 在衔接需要交互式验证的服务器时的提示
运用交互式验证能够答应用户输入更多的信息,获得更多的提示内容。
从 RFC 中来看,此验证形式也是从客户端建议身份验证恳求开端。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string user name (ISO-10646 UTF-8, as defined in [RFC-3629])
C: string service name (US-ASCII)
C: string “keyboard-interactive” (US-ASCII)
C: string language tag (as defined in [RFC-3066])
C: string submethods (ISO-10646 UTF-8)
当服务器得知客户端准备运用 keyboard-interactive 为验证办法后,服务器会向客户端宣布用户信息恳求。在这个来自服务器的恳求中,服务器将供给提示文本(instruction, prompt)并且为每一个字段(或者称为问询)符号一个序号(num-prompts)。
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string name (ISO-10646 UTF-8)
S: string instruction (ISO-10646 UTF-8)
S: string language tag (as defined in [RFC-3066])
S: int num-prompts
S: string prompt[1] (ISO-10646 UTF-8)
S: boolean echo[1]
S: …
S: string prompt[num-prompts] (ISO-10646 UTF-8)
S: boolean echo[num-prompts]
收到来自 SSH 服务器的恳求后,客户端即可开端向用户展示界面获取信息。在运用 ssh 指令时,一般会在终端内等待用户输入,具有 GUI 的软件将会展示提示界面。
运用 ssh 指令时提示输入质询信息
当用户完结输入后,客户端即可发送用户信息呼应。
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int num-responses
C: string response[1] (ISO-10646 UTF-8)
C: …
C: string response[num-responses] (ISO-10646 UTF-8)
这样的进程(服务器恳求-用户输入-客户端呼应)可能会重复多次。例如用户输入的暗码过错,或者服务器根据状况恳求了更多的信息。
下面是来自 RFC 文档中的,客户端和服务器之间的两个交换比如。 第一个比如是用需处理的 Token 进行验证的比如。这是一种其他认证办法无法完结的办法。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string “user23”
C: string “ssh-userauth”
C: string “keyboard-interactive”
C: string “”
C: string “”
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string “CRYPTOCard Authentication”
S: string “The challenge is ’14315716’”
S: string “en-US”
S: int 1
S: string “Response: ”
S: boolean TRUE
[Client prompts user for password]
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int 1
C: string “6d757575”
S: byte SSH_MSG_USERAUTH_SUCCESS
第二个比如是一个规范的暗码认证。但在此比如中,用户的暗码现已过期。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string “user23”
C: string “ssh-userauth”
C: string “keyboard-interactive”
C: string “en-US”
C: string “”
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string “Password Authentication”
S: string “”
S: string “en-US”
S: int 1
S: string “Password: ”
S: boolean FALSE
[Client prompts user for password]
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int 1
C: string “password”
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string “Password Expired”
S: string “Your password has expired.”
S: string “en-US”
S: int 2
S: string “Enter new password: ”
S: boolean FALSE
S: string “Enter it again: ”
S: boolean FALSE
[Client prompts user for new password]
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int 2
C: string “newpass”
C: string “newpass”
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string “Password changed”
S: string “Password successfully changed for user23.”
S: string “en-US”
S: int 0
[Client displays message to user]
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int 0
S: byte SSH_MSG_USERAUTH_SUCCESS