SSH中的安全 | 从SSH协议看身份验证底层原理

zhshch · · 571 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

> 原文链接:[SSH中的安全 | 从SSH协议看身份验证底层原理](https://codemutex.com/blog/20220909-SSH%E4%B8%AD%E7%9A%84%E5%AE%89%E5%85%A8-%E4%BB%8ESSH%E5%8D%8F%E8%AE%AE%E7%9C%8B%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86) > > 推广:[NextSSH 简洁直观的 SSH 客户端 ](https://codemutex.com/)[https://codemutex.com/](https://codemutex.com/) ## 前言 前置关键词:SSH 客户端/服务器,Linux/Unix 系统的用户账户,TCP/IP,Socket。 本文撰于 2022 年 9 月,若相关内容有更新请按照引用链接内的内容为准。 本文介绍了 SSH 协议在验证用户身份过程中的实现细节,想帮助读者更加深入的了解 SSH 客户端与服务器的工作过程。也因此,本文可能不适用于指导 SSH 服务器或客户端的配置。 本文在撰写中参考了下列内容。 * SSH 架构 [RFC 4251](http://www.rfc-editor.org/info/rfc4251) The Secure Shell (SSH) Protocol Architecture * SSH 传输层协议 [RFC 4253](http://www.rfc-editor.org/info/rfc4253) The Secure Shell (SSH) Transport Layer Protocol * SSH 身份验证协议 [RFC 4252](http://www.rfc-editor.org/info/rfc4252) The Secure Shell (SSH) Authentication Protocol * SSH 连接协议 [RFC 4254](http://www.rfc-editor.org/info/rfc4254) The Secure Shell (SSH) Connection Protocol * SSH 交互式身份验证 [RFC 4256](http://www.rfc-editor.org/info/rfc4256) Generic Message Exchange Authentication for the Secure Shell Protocol (SSH) ### SSH 协议的结构 ![SSH 协议的基本框架](https://bu.dusays.com/2022/09/07/6317fb8a3beda.png "SSH 协议的基本框架") 建立一个 SSH 连接,将会经过下面几个过程。 * (明文通信)**建立 TCP 连接** * (明文通信)**协商 SSH 协议版本**(本质上是相互发送包含版本号的字符串) * 服务器将自己的 SSH 协议版本发送到客户端,格式为:`SSH-protoversion(版本号)-softwareversion(自定义) SP(空格一个,可选) comments(注释,可选) CR(回车) LF(换行)` * 客户端将自己的 SSH 协议版本发送到服务器,格式为:`SSH-protoversion(版本号)-softwareversion(自定义) SP(空格一个,可选) comments(注释,可选) CR(回车符) LF(换行符)` * (明文通信)**协商密钥** * 服务器发送公钥 * 客户端和服务器相互发送支持的相关加密算法列表、MAC 算法列表 * 使用 [D-H 算法](https://zh.wikipedia.org/zh-cn/%E8%BF%AA%E8%8F%B2-%E8%B5%AB%E7%88%BE%E6%9B%BC%E5%AF%86%E9%91%B0%E4%BA%A4%E6%8F%9B) 生成此后通讯中使用的[对称加密密钥](https://zh.wikipedia.org/wiki/%E5%B0%8D%E7%A8%B1%E5%AF%86%E9%91%B0%E5%8A%A0%E5%AF%86)(会话密钥) * (密文通信)**身份认证** * (密文通信)**正式进入应用**(如打开 Shell、SFTP、端口转发) 从上面的流程中可以看出,身份验证过程实际上发生在 SSH 连接建立之后,此后客户端与服务器之间传输的密码、密钥、信息都已经受到 SSH 协议生成的「会话密钥」加密。 ## 关于「安全」 SSH 的全称是「Secure Shell」,安全 Shell。其中的「安全」不是指使用 SSH 就能进入一个绝对的安全世界,而是包含了传输安全和身份安全。 假设,我正在打电话给身在公司的朋友,询问一些机要信息。此时攻击者就可以剪断我屋外的电话线,分别接上两个电话听筒对听筒放在一起。我和我的朋友都不会注意到问题,而攻击者可以从中得知我们之间传输的信息。这是网络中存在的[中间人攻击](https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB)。 SSH 避免了这个问题,客户端与服务器之间的数据传输经过了上面的过程而加密(SSH 传输层协议 [RFC 4253](http://www.rfc-editor.org/info/rfc4253))。这个加密是无关于用户的账号密码的,在事前就完成。无论是网络中的交换机还是跳板机都无法直接获取加密前的明文。SSH 保护了传输时的安全。 SSH 也同样提供了验证用户身份的方式,客户端可以将用户的密码发送至服务器,以此让服务器确认用户的身份,只允许被授权的访问连接到服务器。 SSH 的安全不是绝对的。比如攻击者拿起剪断的电话线(中间人攻击),他依旧可以通过客户端与服务器之间的通讯内容推测出这是 SSH 连接。SSH 协议并不能保证连接不被侦测到特征。 ## 身份验证方式 ### 密码(Password) 使用密码登录是常用且较为便捷的认证方式。在配置文件 `/etc/ssh/sshd_config` 中添加 `PasswordAuthentication yes` 以开启密码登录。 ![使用 ssh 命令时提示输入密码](https://bu.dusays.com/2022/09/07/6317fba648bd8.png "使用 ssh 命令时提示输入密码") 通常也认为使用密码登录是一种较为脆弱的认证方式。这不是说使用密码会造成传输时的不安全,而是对密码本身保存的担忧。一般的用户密码都是十位或数十位字符,可能被记录与纸上或记事本中。即使不是如此,有意义的密码也容易遭到社会工程攻击而泄露。 在 `/etc/ssh/sshd_config` 文件中有时会设置 `PermitRootLogin prohibit-password` ,要求系统管理用户 `root` 不得使用密码登录。 根据 RFC 中的描述,用户认证过程是在连接握手之后的。此时客户端与服务器之间已经建立起了加密的连接。双方都会使用握手时交换好的密钥加密所有传输内容。后文中的 SSH 数据样式都是被加密传输的。 登录时,登录请求由客户端发起。一个名称为 `SSH_MSG_USERAUTH_REQUEST` 的消息从客户端发出,包含了登录的用户名与密码。 ```plaintext 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` 开启公钥验证方式。 此验证方式需要用户先准备一个[非对称加密](https://zh.wikipedia.org/wiki/%E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86)的密钥对,将公钥保存至 SSH 服务器的 `~/.ssh/authorized_key` 文件中。客户端登录时,在本地用私钥加密某个信息,并将结果发送给服务器,服务器将通过公钥验证收到的密文是否来自指定的用户。 私钥通常以加密的形式存储在客户主机上,用户必须在生成签名之前提供一个口令(`passphrase`)。 即使不是这样,签名操作也涉及一些昂贵的计算。 为了避免不必要的处理和用户互动,提供以下信息来查询使用 "公钥 "方法的认证是否可以接受。 ```plaintext 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` 或以下方式回应该消息。 ```plaintext 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` 即为加密运算后的内容。 ```plaintext 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](https://zh.wikipedia.org/zh-cn/%E5%8F%AF%E6%8F%92%E6%8B%94%E8%AE%A4%E8%AF%81%E6%A8%A1%E5%9D%97) 模块一同启用。以此来支持谷歌验证器([多因素验证](https://zh.wikipedia.org/wiki/%E5%A4%9A%E9%87%8D%E8%A6%81%E7%B4%A0%E9%A9%97%E8%AD%89)),或其他内部身份校验模块。 ![NextSSH 在连接需要交互式验证的服务器时的提示](https://bu.dusays.com/2022/09/07/6317fba595d9d.png "NextSSH 在连接需要交互式验证的服务器时的提示") 使用交互式验证可以允许用户输入更多的信息,获得更多的提示内容。 从 RFC 中来看,此验证模式也是从客户端发起身份验证请求开始。 ```plaintext 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`)。 ```plaintext 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 命令时提示输入质询信息](https://bu.dusays.com/2022/09/07/6317fba590327.png "使用 ssh 命令时提示输入质询信息") 当用户完成输入后,客户端即可发送用户信息响应。 ```plaintext 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 进行验证的例子。这是一种其他认证方法无法实现的方式。 ```plaintext 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 ``` 第二个例子是一个标准的密码认证。但在此例子中,用户的密码已经过期。 ```plaintext 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 ``` --- 本文转载不得删改,应全文转载并保留原文出处链接。

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

571 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传