Wireshark抓包深度解析SSH协议:从握手到加密通信全流程 1. 项目概述从协议到数据包一次彻底的SSH通信透视搞网络或者做运维的朋友对SSHSecure Shell肯定不陌生。我们每天都在用ssh userhost登录服务器传输文件执行命令觉得它安全、可靠像个黑盒子一样只管用。但你真的不好奇在你敲下回车键到出现命令行提示符这短短一两秒里你的电脑和远端服务器到底“悄悄”说了些什么吗这些对话是如何在不可信的网络中建立起一个加密隧道的今天我们就亲手把这个黑盒子打开用最经典的网络分析工具Wireshark像看“聊天记录”一样把SSH从握手到加密通信的整个流程从头到尾抓包分析一遍。这不仅仅是一个理论练习。当你遇到SSH连接缓慢、连接失败、或者某些高级功能如端口转发、X11转发不工作时光看客户端的错误信息往往是隔靴搔痒。直接分析数据包你能看到握手在哪一步卡住了算法协商是否成功甚至能发现中间是否存在意想不到的网络干扰。对于开发者理解SSH的协议细节有助于你更好地集成SSH库如OpenSSH的libssh对于安全爱好者这是理解现代加密网络协议运作方式的绝佳案例。本文假设你已有基本的命令行和网络概念我们会从零开始带你完成一次完整的SSH连接抓包与深度解析。2. SSH协议核心架构与工作流程拆解在打开Wireshark之前我们必须先搞清楚SSH协议到底是怎么设计的。它不是简单地把你的密码加密一下发过去而是一个分层、有状态的复杂协议族。理解这个框架你看数据包时才能知道每一帧属于哪个阶段在干什么。2.1 SSH协议的分层模型SSH协议栈大致分为三层从下到上分别是传输层协议、用户认证协议和连接协议。你可以把它想象成寄一封机密信件传输层负责找到安全的邮路并建立信任认证层核实寄信人的身份连接层则在确认身份后在安全的邮路上开设多个“小窗口”信道来传递不同的实际内容如shell命令、文件流。传输层协议这是所有通信的基础。它的核心任务是在客户端和服务器之间建立一个加密的、完整性保护的、可选的压缩数据通道。在连接开始时双方会进行“算法协商”决定使用哪种密钥交换算法如Diffie-Hellman、对称加密算法如AES-GCM、消息认证码算法如HMAC-SHA2以及压缩算法。协商完成后双方会利用密钥交换算法在不安全的网络上共同生成一个只有双方知道的“会话密钥”。此后所有通信都使用这个会话密钥派生的密钥进行加密和认证。这一步确保了后续所有通信的机密性和防篡改性。用户认证协议在安全的传输层建立之后客户端需要向服务器证明“我是谁”。SSH支持多种认证方式最常见的是密码认证直接发送加密后的密码。公钥认证客户端使用自己的私钥对一段会话数据进行签名服务器用预先配置好的对应公钥进行验证。这是我们常说的“SSH免密登录”的基础。键盘交互认证、GSSAPI等。认证层是独立的它运行在已加密的传输层之上所以即使使用密码认证密码本身也是在加密通道中传输的避免了明文泄露。连接协议认证成功后连接协议将在已建立的安全隧道内运行。它允许客户端打开多个逻辑“信道”每个信道用于不同的目的。最常见的信道类型有session信道用于启动一个远程shell交互式命令行或执行单个命令。direct-tcpip信道用于本地端口转发-L参数。forwarded-tcpip信道用于远程端口转发-R参数。x11信道用于X11图形界面转发。 连接协议管理着这些信道的创建、数据流传输、窗口大小调整以及最终的关闭。2.2 一次完整SSH连接的生命周期结合以上三层一次标准的SSH登录流程可以分解为以下几个清晰的阶段这也是我们待会在Wireshark中要逐一寻找和验证的TCP三次握手SSH默认运行在TCP的22号端口上因此一切始于一个标准的TCP连接建立。协议版本交换客户端和服务器互相发送标识字符串例如SSH-2.0-OpenSSH_8.9p1。双方必须都支持SSH-2.0版本才能继续。如果服务器只支持SSH-1.x已不安全连接会在此终止。算法协商密钥交换初始化双方交换各自支持的算法列表KEX_INIT。列表包含密钥交换算法、加密算法、MAC算法、压缩算法等。服务器会从交集里选出双方都支持的第一组算法。密钥交换与认证根据协商的算法如curve25519-sha256执行密钥交换生成共享的会话密钥。随后双方交换“新密钥”消息确认启用新的加密密钥和MAC密钥。从此后续数据包除了个别消息都被加密Wireshark看到的是“Application Data”。服务请求客户端发送一个SSH_MSG_SERVICE_REQUEST消息请求进入“用户认证”服务。用户认证进行密码或公钥等认证流程。如果启用公钥认证通常会先尝试公钥失败后再 fallback 到密码。连接协议与信道建立认证成功后客户端请求打开一个session信道。启动Shell或命令在session信道中客户端请求启动一个伪终端pty并启动用户shell如/bin/bash。交互式会话用户的按键输入和服务器的输出通过加密的信道进行传输。会话结束用户退出shell信道关闭TCP连接终止。注意从第4步密钥交换完成后Wireshark捕获的SSH数据包负载payload绝大部分都是加密的显示为“Encrypted packet”。我们无法直接解密内容除非拥有会话密钥。但Wireshark可以通过导入服务器私钥来解密部分交换过程对于较新版本的OpenSSH默认算法可能不支持。不过即使看不到具体内容通过分析数据包的时序、大小、方向以及未加密的协议头信息我们依然能诊断出大量问题。3. 实验环境搭建与Wireshark抓包配置理论说得再多不如动手抓包。我们先来搭建一个最小化的实验环境确保抓到的包干净、易于分析。3.1 实验环境准备为了在可控的环境下分析我建议在本地使用虚拟机或容器技术。这里我使用docker-compose快速创建一个包含SSH客户端和服务器的微型网络。# docker-compose.yml version: 3.8 services: ssh-server: image: linuxserver/openssh-server:latest container_name: test-ssh-server environment: - PUID1000 - PGID1000 - TZEtc/UTC - PUBLIC_KEYyour_public_key_here # 可选用于免密登录 - USER_NAMEtestuser - PASSWORD_ACCESStrue # 启用密码登录方便实验 - USER_PASSWORDtestpass123 ports: - 2222:22 # 将容器的22端口映射到主机的2222端口 volumes: - ./server-config:/config restart: unless-stopped networks: - test-net ssh-client: image: alpine:latest container_name: test-ssh-client command: tail -f /dev/null # 保持容器运行以便我们进入执行命令 networks: - test-net networks: test-net: driver: bridge将上述docker-compose.yml中的your_public_key_here替换成你的公钥如~/.ssh/id_rsa.pub的内容如果只想用密码登录可以暂时删掉PUBLIC_KEY这一行。在终端中运行docker-compose up -d启动环境。运行docker exec -it test-ssh-client /bin/sh进入客户端容器。在容器内你需要安装SSH客户端apk add openssh-client。现在你的主机运行Wireshark的地方的localhost:2222就对应着SSH服务器。客户端容器和服务器容器在同一个test-net网络中。3.2 Wireshark抓包技巧与过滤器设置在主机上打开Wireshark。选择正确的网卡至关重要。如果你使用Docker Desktop默认创建虚拟网卡选择名为DockerNAT或br-开头的桥接网络接口。如果使用原生Docker可能是docker0。一个更稳妥的方法是使用Wireshark的捕获过滤器或显示过滤器限定我们关心的流量。捕获过滤器Capture Filter在开始抓包前设置用于减少捕获的数据量。针对本次实验可以设置为host 172.20.0.3假设服务器容器的IP是172.20.0.3。你可以通过docker inspect test-ssh-server | grep IPAddress查看其IP。 或者更宽泛一点port 22捕获所有22端口的流量。显示过滤器Display Filter抓包后使用用于在已捕获的数据中快速定位。最常用的SSH相关过滤器有ssh显示所有SSH协议报文。tcp.port 2222显示所有涉及主机2222端口的TCP包因为我们将容器22端口映射到了主机2222。ip.addr 172.20.0.2 ip.addr 172.20.0.3显示客户端和服务器之间的所有IP流量。结合使用tcp.port 2222 ssh可以精准定位我们实验的SSH流量。开始抓包在Wireshark中设置好捕获过滤器或留空后期用显示过滤器点击开始捕获。在终端中从客户端容器发起连接ssh -p 2222 testuserssh-server。当提示输入密码时输入testpass123。登录成功后执行一个简单命令如ls然后输入exit退出。回到Wireshark停止捕获。你现在应该能看到一系列TCP和SSH协议的数据包。实操心得第一次抓包时建议不要设置太严格的捕获过滤器以免漏掉关键的前置流量如DNS查询如果使用主机名。可以先进行一轮“探索性”抓包用显示过滤器tcp.port2222观察整个会话确认服务器IP和完整流程后再针对性地设置捕获过滤器进行第二轮精细抓包。4. 逐层深度解析Wireshark中的SSH握手与交互现在我们面对Wireshark中捕获到的一列数据包来逐一解读每个阶段。我会以一个典型的、使用密码认证的SSH-2.0连接为例进行说明。4.1 TCP连接建立与协议版本交换首先找到最开始的三个包它们应该是TCP的SYN,SYN-ACK,ACK即三次握手目标端口是22或你映射的2222。这标志着物理连接的建立。紧接着你会看到第一个SSH协议包。在Info列通常会显示Client: Protocol (SSH-2.0-xxxx)。点击这个包在下方详情面板中展开Secure Shell Layer。TCP层可以看到源端口一个随机高位端口和目标端口22。SSH层Protocol: SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6。这是客户端向服务器宣告自己支持的协议版本。 下一个包应该是服务器的回复Server: Protocol (SSH-2.0-xxxx)。双方版本兼容握手进入下一阶段。4.2 算法协商KEX_INIT详解这是未加密阶段最复杂、信息量最大的一步。客户端和服务器会各发送一个SSH_MSG_KEXINIT数据包。在Wireshark中它们通常被标识为Key Exchange Init。点击客户端的KEX_INIT包在详情面板中找到Key Exchange Init并展开。你会看到一个长长的列表包含多个字段kex_algorithms密钥交换算法列表如curve25519-sha256, ecdh-sha2-nistp256, diffie-hellman-group14-sha1, ...。客户端按偏好顺序排列。server_host_key_algorithms服务器主机密钥算法列表如rsa-sha2-512, rsa-sha2-256, ecdsa-sha2-nistp256, ssh-ed25519, ...。用于后续验证服务器身份。encryption_algorithms_client_to_server/..._server_to_client加密算法列表如chacha20-poly1305openssh.com, aes128-ctr, aes192-ctr, aes256-ctr。mac_algorithms_client_to_server/..._server_to_client消息认证码算法列表。compression_algorithms_client_to_server/..._server_to_client压缩算法列表。languages_client_to_server/..._server_to_client语言列表通常为空。服务器的KEX_INIT包结构完全相同也列出了自己支持的算法。协商的规则是服务器从客户端和自己列表的交集中为每个类别选择第一个即客户端优先列表中服务器也支持的算法。在Wireshark中查看紧随其后的SSH_MSG_KEX_ECDH_INIT如果协商的是ECDH算法或类似的包结合两个KEX_INIT包的内容你就可以推断出最终协商确定的算法组合。例如如果客户端列表第一个是curve25519-sha256且服务器也支持它那么密钥交换算法就是它。4.3 密钥交换与加密通道建立协商完成后双方开始执行具体的密钥交换。以curve25519-sha256为例客户端发送SSH_MSG_KEX_ECDH_INIT包含一个临时公钥。服务器回复SSH_MSG_KEX_ECDH_REPLY这个包至关重要它包含服务器的临时公钥。服务器的主机公钥用于身份验证及对其的签名。客户端利用自己的临时私钥和服务器的临时公钥可以计算出共享密钥会话密钥的种子。服务器亦然。双方各自计算生成相同的会话密钥。然后客户端发送SSH_MSG_NEWKEYS表示“我这边准备切换到新密钥了”。服务器也回复一个SSH_MSG_NEWKEYS。从这里开始一个重要的分水岭出现了在Wireshark中SSH_MSG_NEWKEYS之后的数据包其SSH协议层会显示为Encrypted packet (xxx)详情面板中不再能解析出具体的SSH消息类型因为负载已经被加密了。注意事项SSH_MSG_KEX_ECDH_REPLY中服务器的主机密钥是验证服务器身份、防止中间人攻击的关键。客户端首次连接时会记录此密钥保存在~/.ssh/known_hosts中后续连接会进行比对。如果密钥变更会发出著名的“WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!”警告。4.4 用户认证阶段分析加密层内虽然数据包已加密但我们仍能从包的大小、时序和方向推断出认证阶段。通常在NEWKEYS交换后客户端会发送一个较小的加密包这是SSH_MSG_SERVICE_REQUEST请求ssh-userauth服务。服务器回复一个小的加密包同意服务请求。接着客户端会发送一个稍大的加密包里面包含了认证方法请求。如果是密码认证你会看到客户端连续发送几个大小相似的加密包可能包含用户名、密码请求交互。认证成功后服务器会回复一个特定的成功包然后可能紧跟着一个请求打开信道的包。如何区分观察数据包长度和序列。在交互式密码认证中你可能会看到客户端-服务器方向有几个长度几乎相同的包输入密码的每个字符不SSH通常是整行发送然后服务器回复一个很小的包认证成功。如果是公钥认证客户端的第一个认证包通常会大一些因为包含了公钥和签名。4.5 连接协议与信道数据流认证通过后客户端会请求打开一个session信道。这同样发生在加密通道内Wireshark无法直接解析。但我们可以通过观察后续的流量模式来识别交互开始当你在SSH会话中键入命令时每次按键或整行命令会产生一个小的客户端-服务器加密包。命令输出服务器执行命令后返回的输出会产生一个或一系列如果输出长服务器-客户端的加密包这些包通常比输入包大得多。流量特征一个典型的ls -la命令会先有一个小的输入包紧接着是多个大小不等的输出包。使用ping命令会产生周期性的大小固定的输出包。当你输入exit退出时会看到最后几个加密包交换然后客户端或服务器发起TCP的FIN包来关闭连接。5. 高级抓包技巧与实战问题诊断仅仅看懂一次成功连接还不够Wireshark的真正威力在于诊断故障。下面我们模拟几种常见问题并学习如何通过抓包定位根因。5.1 解密SSH数据包局限性分析是的Wireshark在特定条件下可以解密SSH流量但这通常需要RSA密钥交换算法和服务器私钥。对于现代默认的curve25519-sha256或ecdh-sha2-nistp256等ECDH算法Wireshark目前截至v4.2无法解密因为它们使用了临时密钥会话密钥与服务器的长期私钥无关。对于支持解密的场景如老旧的diffie-hellman-group1-sha1或rsa密钥交换操作如下获取服务器的私钥如/etc/ssh/ssh_host_rsa_key。警告私钥极其敏感仅用于测试环境在Wireshark中进入编辑 - 首选项 - Protocols - SSH。在RSA keys list中点击Edit添加服务器的IP地址、端口22、协议ssh和私钥文件路径。重新载入抓包文件或开始新的捕获Wireshark会自动尝试解密。如果解密成功你会看到原本的Encrypted packet变成了可读的SSH消息如SSH_MSG_USERAUTH_REQUEST,SSH_MSG_CHANNEL_DATA等甚至能看到password字段或命令行输入输出如果是未加密的TELNET或早期版本SSH本身是加密的但解密后可以看到这些。但在生产环境或使用现代加密算法时不要依赖此功能。5.2 常见连接故障的抓包诊断案例案例一SSH连接超时或拒绝连接现象ssh命令卡住最终返回Connection timed out或Connection refused。抓包分析Connection timed out抓包显示客户端发送了TCPSYN包但没有收到服务器的SYN-ACK回复。这说明TCP连接无法建立。可能原因防火墙阻断、服务器未监听22端口、网络路由问题。检查服务器netstat -tlnp | grep :22以及中间网络设备的规则。Connection refused抓包显示客户端发送了TCPSYN包服务器回复了RST, ACK包。这说明TCP端口可达但该端口上没有进程监听。可能原因SSH服务未启动或监听在其他端口。案例二协议版本不匹配现象连接立即断开错误信息提及协议版本。抓包分析查看最初的协议交换包。如果服务器回复的版本是SSH-1.99-...表示它支持SSH-1.5和2.0但客户端可能配置为只允许SSH-2.0-o Protocol2。如果服务器只回复SSH-1.5-...而客户端不支持1.5服务器可能会直接关闭TCP连接。在Wireshark中你会在版本交换后很快看到TCPFIN包。案例三算法协商失败现象错误信息包含no matching key exchange method,no matching cipher, 或no matching MAC。抓包分析这是分析KEX_INIT包的绝佳场景。仔细对比客户端和服务器发送的KEX_INIT包中的算法列表。你会发现在某个类别如encryption_algorithms中客户端和服务器提供的列表完全没有交集。服务器因此无法选择算法会发送SSH_MSG_DISCONNECT消息并断开连接。在Wireshark中你可以在服务器发送的最后一个SSH明文包中看到Disconnect Reason Code。案例四认证反复失败现象密码明明正确却一直提示Permission denied。抓包分析在无法解密的情况下观察认证阶段的加密包序列。正常的密码认证在用户输入密码后客户端会发送一个加密包服务器回复一个较小的包认证成功或一个稍大的包认证失败可能包含提示信息。如果发现客户端在发送一个包后服务器回复的包大小每次都相似且紧接着客户端又发送一个包形成循环这可能是键盘交互认证keyboard-interactive在多次质询或者sshd配置了如pam模块的复杂认证流程。查看服务器端的日志/var/log/auth.log或/var/log/secure与抓包时间戳对照是更直接的诊断方法。5.3 性能问题分析与优化启示抓包也能帮助分析SSH连接慢的问题。TCP层分析在Wireshark的Statistics - TCP Stream Graphs中查看Time-Sequence Graph。如果看到很多重传Retransmission或零窗口Zero Window说明网络存在丢包或拥塞或者接收方处理不过来。这会导致SSH连接卡顿。算法选择在KEX_INIT阶段如果客户端和服务器的首选算法不一致可能会选择到一个计算开销较大的算法如较老的DH group。虽然协商过程很快但对于需要频繁建立新连接如Git的场景优化算法列表顺序可能有细微改善。可以通过客户端的~/.ssh/config或服务器的/etc/ssh/sshd_config中的KexAlgorithms,Ciphers,MACs指令来调整。压缩如果传输大量文本数据如cat一个大日志文件启用压缩-C参数可以在Wireshark中观察到数据包体积明显变小。但压缩会增加CPU开销需权衡。6. 扩展分析SSH端口转发与X11转发的抓包观察SSH的强大不止于远程Shell其端口转发和X11转发功能也经常使用。它们的抓包表现有何不同6.1 本地端口转发-L流量分析使用命令ssh -L 8080:internal-server:80 usergateway-server建立本地转发。抓包设置在运行SSH客户端的主机上对localhost或127.0.0.1的8080端口进行抓包。需要使用Wireshark的loopback接口捕获功能或者使用rawcap等工具。流量特征当你访问http://localhost:8080时Wireshark会看到从你的浏览器到127.0.0.1:8080的TCP连接。与此同时在SSH客户端与gateway-server的22端口连接上你会看到相应的加密流量激增。这些加密包中承载了你浏览器发出的HTTP请求和服务器返回的HTTP响应。关键点本地转发中本地端口8080到SSH客户端的连接是明文的可在本地抓包看到HTTP协议而SSH客户端到网关服务器的隧道内是加密的SSH流量。网关服务器解密后再以其身份向internal-server:80发起明文连接。6.2 X11转发-X流量分析使用命令ssh -X userserver登录然后运行一个图形程序如xeyes。协议X11转发本质是SSH在加密隧道内创建了一个x11信道将远程的X11协议流量转发到本地的X服务器。抓包观察在SSH连接的数据流中你只会看到加密的Encrypted packet。但是如果你在本机抓取与localhost:6010或类似的DISPLAY指向的端口的通信可能会看到X11协议数据。然而现代OpenSSH默认使用X11 SECURITY扩展并实施“cookie”认证并且X11流量本身也可能在SSH隧道内被加密和压缩因此在本地回环端口上抓到的可能只是握手或认证数据而非真正的图形指令。主要的图形数据流仍然被封装在SSH加密包中。实操心得分析转发流量时最重要的是厘清信任边界。本地端口转发中你的本地应用与SSH客户端之间的连接是信任域内的可能是明文而SSH隧道穿越公网的部分一定是加密的。诊断转发问题时应在多个点抓包如本地环回、SSH客户端出口以确定问题发生在隧道内部还是外部。通过这样一次从理论到实践、从成功流程到故障排查的完整抓包分析SSH对你而言不再是一个神秘的黑盒。你不仅明白了它如何构建安全通道更掌握了在出现问题时如何利用像Wireshark这样的工具从网络数据包的层面寻找线索、验证猜想。这种技能是深入理解任何网络协议、构建稳健系统不可或缺的。下次再遇到棘手的SSH问题别光看日志试着抓个包看看数据包从不撒谎。