SSH 协议基本原理及 wireshark 抓包分析

11,918

一、SSH协议简介

我们经常会使用ssh username@hostIp命令登陆我们的linux服务器,如下图所示:

我们也明白这是使用了SSH协议进行登陆,但我们想知道的是,为什么可以使用SSH协议进行登陆,而且为什么使用SSH就是安全的,其背后的原理是什么?下面我们就一起来探讨下这几个话题。

当然啦!如果现在你手头上有相关公网可访问的云主机,那么请你登陆你的云主机,然后执行grep sshd.*Failed /var/log/secure命令看看,或许你会惊讶的发现有很多输出日志,你就会明白到底有多少人想尝试登陆你的主机了。

简单地说,SSH协议是建立在不安全的网络之上的进行远程安全登陆的协议。它是一个协议族,其中有三个子协议,分别是:

  • 1、传输层协议[SSH-TRANS]:提供服务器验证、完整性和保密性功能,建立在传统的TCP/IP协议之上。
  • 2、验证协议[SSH-USERAUTH]:向服务器验证客户端用户,有基于用户名密码和公钥两种验证方式,建立在传输层协议[SSH-TRANS]之上。
  • 3、连接协议[SSH-CONNECT]:将加密隧道复用为若干逻辑信道。它建立在验证协议之上。

这里不对SSH协议做更加详细的介绍,我们可以自行百度一下。下面的写作思路将先通过wireshire抓包分析SSH协议上述三个流程,最后将提出整个学习过程中小编遇到的问题进行探讨。

二、SSH协议握手过程分析

在接着下面的内容之前,这里有几个概念非常重要!

  • 1、会话密钥 key:key是通过客户端和服务器之间通过诸如D-H算法协商出来的。
  • 2、公钥 pub key:pub key成为服务器主机密钥server_host_key,用于SSH-TRANS传输协议进行服务器验证,说白了就是客户端去验证服务器用的

SSH协议握手过程大致流程如下图所示:

下面是小编通过wireshire工具抓的数据包,让我们分别一步步进行分析:

  • 1、TCP三次握手建立连接

我们都知道,TCP协议有个叫三次握手的过程,从上面图中可以看出序号(647-649)即是TCP连接建立过程。

NO. 描述 seq Win ACK 解释
647 第一次握手 0 8192 seq = 0表示客户端当前的TCP包序列号
648 第二次握手 0 14600 1 seq = 0,表示服务器端当前的TCP包序列号
ack = 1(客户端seq + 1),表示对客户端第 seq = 0 的TCP包进行应答
649 第三次握手 1 65536 1 seq = 1,表示客户端端当前的TCP包序列号
ack = 1(服务器seq + 1),表示对服务器端第 seq = 0 的TCP包进行应答
  • 2、SSH版本协议交换

上图序号(647-649)即是SSH版本协议交换过程。

NO. 描述 解释
650 协议版本协商 服务器将自己的SSH协议版本发送到客户端,格式为:SSH-protoversion(版本号)-softwareversion(自定义) SP(空格一个,可选) comments(注释,可选) CR(回车) LF(换行)
651 协议版本协商 客户端将自己的SSH协议版本发送到服务器,格式为:SSH-protoversion(版本号)-softwareversion(自定义) SP(空格一个,可选) comments(注释,可选) CR(回车符) LF(换行符)

这一步其实没什么高大上的内容,就是发送一个格式为SSH-protoversion-softwareversion SP comments CR LF的字节流而已。

  • 3、密钥协商key

上图序号(652-677)即是SSH版本协议交换过程。

密钥协商过程从客户端和服务器相互发出Key Exchange Init请求开始,主要是告诉对方自己支持的相关加密算法列表、MAC算法列表等。

最后协商成功之后,将会生成一个对称加密会话密钥key以及一个会话ID,在这里要特别强调,这个是对称加密密钥key,不要和公钥相混淆了,公钥和密钥在上面开头已经着重强调两者的区别了,公钥是给客户端去验证服务器用的。

在这一步中,公钥会从服务器传送到客户端:

而会话密钥是通过D-H算法计算出来的,不会在网络上传输,其破解的难度取决于离散对数的破解难度,一般不会被破解的,有兴趣的可以自行了解该算法原理。

下面我将贴出Key Exchange Init发送的请求包数据分析

NO. 描述 解释
1 kex_algorithms 密钥交换算法,里边即包含我们使用的D-H算法,用于生成会话密钥
2 server_host_key_algorithms 服务器主机密钥算法,可以采用 ssh-rsa,ssh-dss,ecdsa-sha2-nistp256,有公钥和私钥的说法,公钥即我们上面讲到的pub key,对于公钥私钥的概念,可以参见understanding public key private key concepts
3 encryption_algorithms_client_to_server 对称加密算法,常用的有aes128-cbc,3des-cbc
4 mac_algorithms_client_to_server MAC算法,主要用于保证数据完整性
5 compression_algorithms_client_to_server 压缩算法
  • 4、认证阶段

上图序号(678-680)即是SSH版本认证阶段。

  • 1、基于账号和口令的验证方式

    客户端将自己的用户名 + 密码用上面生成的会话密钥key进行加密之后传送到服务器端进行验证,服务器端验证通过,则响应成功,否则在进行有限次(推荐是20次)重新认证。至于服务器是怎么验证的,是否结合了会话ID小编也不清楚,网上众说纷纭。
    注意,用户名和密码是采用上面密钥协商阶段生成的会话密钥key进行加密的,包括后面的连接会话阶段所传送的数据都是,不要认为是采用服务器的pub key加密的

  • 2、基于公钥和私钥的验证方式

    这种方式也称为免密登陆。简单地说,就是客户端自己生成公钥私钥(通常采用ssh-keygen程序生成),然后将公钥以某种方式(通常是手动添加)保存到服务器~/.ssh/authorized_keys文件中,以后服务器都会接受客户端传过来的经过会话密钥加密过的公钥,然后解密得到公钥之后和本地authorized_keys配置的公钥是否相等,如果是,则允许登陆。

如果你配过github或者gitlab的公钥,其实第二种方式认证方式很好理解,因为github它们也是采用SSH协议进行代码克隆的,没有配置公钥好像是不允许克隆的。

三、相关问题

  • 1、密钥协商阶段安全吗?有没有中间人攻击的情况!

就我的理解,第一次总是不安全的。为什么呢?上面说到协商过程中,服务器会将自己的公钥server_host_key_algorithms发送给客户端,但是客户端无法保证它拿到的公钥就是目标服务器所发出来的,很可能有个中间人拦截了你的请求,然后中间人发了另外一个公钥给到你客户端,这就不安全了!这也很好解释了为什么我们第一次登陆的时候,Shell终端总是会出现这个提示的原因:

这也是将确认权留给客户端自己去判断的一种策略。相反,如果想要更加安全,那么我们可以采用第二种认证方式进行登陆。由于提示的是经过MD5之后的公钥,那么我们怎么判断这个值是有效的呢?请看下面第3个疑问。

  • 2、传输协议协商出来的会话密钥和会话ID到底有什么作用?

会话密钥:对称加密算法的密钥,用于对通信数据进行加解密,会话ID有点像WEB中的Session,就是用来表示每一个会话的,同时在认证阶段也起判断是否同一会话有效的作用。

  • 3、既然密码验证登陆,那么客户端第一次登陆的时候如何验证服务器公钥的正确性?

请你告诉小编吧!小编苦于找不到答案!

四、其他

下面是相关资料文档:

我们可能会问了,既然有了SSH协议文档,那么假如我们想要照着文档写一个实现出来,那么应该怎么去入手呢?其实SSH中的传输协议[SSH-TARNS]是基于TCP协议的,因此我们可以从创建一个最基本Java Socket套接字开始,逐步实现SSH的传输协议、认证协议以及连接协议。如果你对实现过程感兴趣,可以研究下ganymed-ssh2-build209.jar中的源码,结合SSH协议RFC文档,你就会发现,其实SSH协议中的协商和认证过程,其实都是通过Socket输入流、输出流的形式实现的。小编自己撸到协商阶段果断放弃,原因是不懂得算法太多了!