安全外壳协议(Secure Shell Protocol,简称SSH)是一种加密的网络传输协议,属于应用层协议。OpenSSH 是最流行的 SSH 实现,它是大量操作系统的默认组件

OpenSSH 套件由以下工具组成:

  • 远程操作使用:ssh, scp 和 sftp
  • 密钥管理:ssh-add, ssh-keysign, ssh-keyscan 和 ssh-keygen
  • 服务端: sshd, sftp-server 和 ssh-agent

使用 SSH 连接服务器

1. 客户端创建公私钥对

密钥类型选择 ed25519 椭圆曲线,它生成的公私钥都要比 RSA 更短,具有较高的安全性和性能

# - t 类型
# Ubuntu 22.04 默认:RSA 3072;Mac OS 默认:ED25519 256 # - a KDF (Key Derivation Function) 的迭代次数 默认:16 ,防止暴力破解
# - b 创建 key 的 bit,不同类型不同
# - C 备注,可以备注上创建年月,定期更换私钥
ssh-keygen -a 256 -t ed25519 -C "Brandon+2025-01@MacBook"
# 可以手动指定路径和密码,也可以一路回车

~/.ssh 下会生成公私钥对

.
├── [ 411] id_ed25519
├── [ 98] id_ed25519.pub # 私钥需要妥善保管,避免暴露
cat id_ed25519 -----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCRtC9cJJBFwvVsp4vV058ci8lSHNrf2qcx8W+umtK7OwAAAKArJx9PKycf...
-----END OPENSSH PRIVATE KEY----- # .pub 结尾为公钥
cat id_ed25519.pub ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJG0L1wkkEXC9Wyni9XTnxyLIt/zHxb66a0rs7 Brandon+2025-01@MacBook

2. 在服务器上添加公钥

将上面客户端生成的公钥 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJG0L1wkkEXC9Wyni9XTnxyLIt/zHxb66a0rs7 Brandon+2025-01@MacBook

加入到服务端 ~/.ssh/authorized_keys,每个私钥占据一行

也可以使用 `ssh-copy-id

ssh-copy-id -i ~/.ssh/id_ed25519.pub ubuntu@192.168.64.6

3. 客户端 ssh 连接

# 登录的用户名@目标主机的 IP 地址
ssh root@192.168.16.13 # 首次连接某个服务器会提示
The authenticity of host '192.168.16.13 (192.168.16.13)' can't be established.
ED25519 key fingerprint is SHA256:QawKK4qYtzv/WyymFO64Yby5oxo9bVYZu0TQRvLZsL8.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

这是需要要求你通过 服务端公钥指纹 来验证服务端的身份,避免中间人攻击。在确认指纹后输入 yes 完成连接,ssh 会将公钥信息写入到 ~/.ssh/known_hosts 中。后续连接如果服务端指纹变更,就说明可能出现了中间人攻击,ssh 会在连接时提示指纹不一致

获取服务端公钥指纹

服务端 /etc/ssh/ 下有多对公私钥:

├── [1.3K]  ssh_host_dsa_key
├── [ 609] ssh_host_dsa_key.pub
├── [ 513] ssh_host_ecdsa_key
├── [ 181] ssh_host_ecdsa_key.pub
├── [ 411] ssh_host_ed25519_key
├── [ 101] ssh_host_ed25519_key.pub
├── [2.5K] ssh_host_rsa_key
├── [ 573] ssh_host_rsa_key.pub

ed25519 为例,查看公钥指纹:

# -l:列出密钥的指纹
# -f:后面跟要查看的文件路径
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
256 SHA256:WykOLKFPEwaC42OM8B5EgFBS5RlgV4qvXxkCIxPE6h4 root@VM-12-5-ubuntu (ED25519)

如果无法登陆服务器,可以直接获取,相对来说还是找管理员索要指纹更加安全

ssh-keyscan -t ed25519 -p 22 192.168.16.13 2>/dev/null | ssh-keygen -E sha256 -lf -
256 SHA256:WykOLKFPEwaC42OM8B5EgFBS5RlgV4qvXxkCIxPE6h4 root@VM-12-5-ubuntu (ED25519)

关于 ssh 还有一个常见用法,就是测试 GitHub 连通性

# 测试 GitHub 连接
# -T: 禁用伪终端分配 ssh -T git@github.com # 回应:
# Hi someone! You've successfully authenticated, but GitHub does not provide shell access.

连接过程

加密连接最重要的就是解决 密钥传递 的问题,如果传递密钥时密钥被窃取,那么后续的加密将毫无意义

为了避免传递时泄露密钥,ssh 采用了 非对称加密算法,即公私钥的这种方式。服务器和客户端在 确认对方的公钥是安全的 之后,就可以通过对方的公钥加密数据,对方通过私钥解密数据,实现安全地传输数据。客户端与服务器连接的具体过程:

  1. 建立 TCP 连接
  2. 协商 SSH 版本:SSH1 或 SSH2
  3. 协商使用的 算法:加密、密钥交换、消息认证码等
  4. 用协商好的密钥交换算法(例如 Elliptic Curve Diffie-Hellman)生成共享密钥,通过它进行回话的对称加密5
  5. 客户端验证服务器身份: 为了防止中间人攻击,比较服务器的公钥指纹或密钥与客户端已知的主机是否一致, 不一致或者首次连接会提示用户验证服务器的公钥指纹
  6. 服务器验证用户身份: 服务器验证客户端的身份
  7. 会话建立: 身份验证成功后,客户端和服务器之间建立加密的会话

Elliptic Curve Diffie-Hellman

双方分别生成一对临时 公私钥对,将自己的 临时公钥 发送给对方,双方都可以通过 对方临时公钥 + 自己临时私钥 生成 共享密钥,这个共享密钥双方相同。共享密钥 作为 KDF 的输入,生成多个密钥,用于加密、完整性校验

交换哈希 (Exchange Hash):为了确保密钥交换过程没有被篡改,SSH 会计算一个交换哈希值,服务器会对交换哈希进行签名(这里用的是服务器私钥,即 /etc/ssh/ 下的私钥)。哈希值包含了密钥交换过程中交换的所有数据,包括客户端和服务器版本、算法协商结果、密钥交换参数等

ssh 连接过程的数据包:

从流程可以看出,ssh 虽然可以保证会话传输安全,但是无法保证对方身份的真实性,所以在连接时要 确认公钥指纹

常见问题

回话保持

删除 authorized_keys 中的 key,该 key 已打开的会话不会被断开

可以通过 pkill 终止特定用户的 SSH 会话:

pkill -u $username sshd

权限导致登陆失败

man ssh 中:

~/.ssh/: the recommended permissions are read/write/execute for the user, and not accessible by others.

~/.ssh/authorized_keys: the recommended permissions are read/write for the user, and not accessible by others.

保证 ~/.ssh authorized_keys 只有 所有者 可以访问修改

如果我们修改权限:chmod 666 authorized_keys,这样登陆会失败:

shell failed: ssh failed to authenticate: 'Access denied for 'publickey'. Authentication that can continue: publickey'

在 sshd 的日志中也可以看到原因:

tail -f /var/log/auth.log

2025-01-17T11:36:41.734631+08:00 iptables sshd[1294]: Authentication refused: bad ownership or modes for file /home/ubuntu/.ssh/authorized_keys

.ssh 子目录中的 config

通过 config 简化 ssh 连接

Host aliyun
HostName 192.168.1.100
User tom
IdentityFile ~/.ssh/id_rsa
Port 22
# ssh $Host 快速连接
ssh aliyun

安全防护

查看连接

# who 查看当前登录用户的方法。它会显示用户名、终端、登录时间以及远程主机(如果是 SSH 连接)
who ubuntu pts/0 2025-01-20 17:43 (192.168.64.1)
# w 命令提供更详细的信息,包括当前登录用户、他们正在运行的进程、系统负载等。
w
19:44:32 up 14:48, 2 users, load average: 0.06, 0.04, 0.00 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
ubuntu 192.168.64.1 17:43 3days 0.00s 0.05s sshd: ubuntu [priv]
ubuntu 192.168.64.1 17:43 3days 0.00s ? sshd: ubuntu [priv]
# last 命令显示最近登录的用户列表,包括用户名、终端、登录时间、远程主机和登出时间
last ubuntu pts/0 192.168.64.1 Mon Jan 20 17:43 still logged in
ubuntu pts/0 192.168.64.1 Fri Jan 17 11:33 - 11:47 (00:13)
reboot system boot 6.8.0-51-generic Fri Jan 17 11:33 still running
reboot system boot 6.8.0-51-generic Thu Jan 9 16:58 - 17:11 (00:13)
ubuntu pts/0 192.168.64.1 Thu Jan 9 16:57 - 16:58 (00:00)
# lastlog 命令显示每个用户最后一次登录的时间
lastlog Username Port From Latest
root **Never logged in**
daemon **Never logged in**
bin **Never logged in**
sys **Never logged in**
ubuntu pts/0 192.168.64.1 Mon Jan 20 17:43:58 +0800 2025

安全策略

pkill 会话

ps ajfx

   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1 1621867 1621867 1621867 ? -1 Ss 0 0:49 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 sta
1621867 2226258 2226258 2226258 ? -1 Ss 0 0:00 \_ sshd: root@pts/5
2226258 2226414 2226414 2226414 pts/5 2227480 Ss 0 0:00 \_ bash --rcfile /dev/fd/63
2226414 2227480 2227480 2226414 pts/5 2227480 R+ 0 0:00 \_ ps ajfx

通过 SID 可以 kill 掉整个 session,如果 pikill tmux 等软件创建的 session,这些 session 的进程还是会继续运行

# -e: 显示进程将被终止
# -s: 指定一个或多个会话 ID
# pkill -e -s $SID
pkill -e -s 2226258

排查用户和密钥

查看非 root 可登录的账号

awk -F: 'BEGIN {OFS=":"} $3 != 0 && $7 !~ /\/bin\/false|\/nologin/ {print $1}' /etc/passwd

查看允许 ssh 登陆的公钥:

cat ~/.ssh/authorized_keys

禁用密码登陆

grep PasswordAuthentication /etc/ssh/sshd_config

注意:这里是 sshd_config,而不是少了 dssh_config

如果 /etc/ssh/sshd_config.d 中的某个文件中定义了某个配置选项,那么它会覆盖 /etc/ssh/sshd_config 文件中相同的配置

例如在 /etc/ssh/sshd_config.d/50-cloud-init.conf 中配置了 PasswordAuthentication yes,那么 /etc/ssh/sshd_config 中修改 PasswordAuthentication 的值都会被覆盖,即密码登陆一直有效,修改为 PasswordAuthentication no 后重新 sshd:sudo systemctl restart sshd,就会提示只允许密钥登陆:

ssh root@192.168.64.6

root@192.168.64.6: Permission denied (publickey).

如果没有关闭密码登陆,就可能会被暴力破解,查询不同用户被尝试登陆的次数:

grep "Failed password" /var/log/auth.log|perl -e 'while($_=<>){ /for(.*?)from/; print "$1\n";}'|sort|uniq -c|sort -nr
   5611  root
2013 invalid user hadoop
1975 invalid user inspur
1973 invalid user cloud
285 invalid user roo
101 invalid user test
84 invalid user user
72 invalid user admin
68 invalid user oracle
61 invalid user postgres
61 invalid user git
48 invalid user ubuntu
31 invalid user test1
28 invalid user amandabackup
24 invalid user pcpqa
22 invalid user ftpuser
21 invalid user test2
21 invalid user chenly

查询登陆成功的 IP

grep "Accepted" /var/log/auth.log | awk '{print $11}' | sort | uniq

其他安全配置可以参考:

Linux 应急响应手册 v1.9 - NOP Team

如何配置安全的 SSH 服务

SSH安全加固指南

登陆告警

在用户登陆时向钉钉发送告警,参考自:VPS 安全加固之用户登录后向 telegram 发送登录信息

cd /etc/profile.d/
vim ssh-login-alerm.sh
#!/bin/bash

# 钉钉机器人 Webhook URL
dingtalk_webhook="https://oapi.dingtalk.com/robot/send?access_token=$token" # 获取系统信息,并进行转义以避免 JSON 解析错误
message=$(hostname && TZ=UTC-8 date '+%Y-%m-%d %H:%M:%S' && who && w | awk 'BEGIN{OFS="\t"}{print $1,$8}' | sed 's/\\/\\\\/g;s/\n/\\n/g;s/"/\\"/g') # 使用 curl 发送消息到钉钉机器人
curl -s -X POST \
-H "Content-Type: application/json" \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"ssh: ${message}\"}}" \
"${dingtalk_webhook}" || echo "Error sending DingTalk message." #可选:添加日志记录
echo "$(date '+%Y-%m-%d %H:%M:%S') - SSH login detected. Message sent to DingTalk (or error occurred)." >> /var/log/ssh_login_dingtalk.log
# 权限设置为 555 ,这样登陆任何用户都会执行告警
chmod 555 /etc/profile.d/ssh-login-alerm.sh

登陆后钉钉会收到一条消息:

ssh: localhost
2025-01-23 22:17:16
root pts/0 2025-01-23 22:17 (192.168.64.5)
22:17:16 load
USER WHAT
root bash

参考资料

Secure Shell - wiki

OpenSSH - wiki

SSH-Keygen的更安全用法

It’s 2023. You Should Be Using an Ed25519 SSH Key (And Other Current Best Practices)

验证远程主机SSH指纹

什么是SSH?

图解SSH原理

SSH协议解析及wireshark抓包分析

SSH协议握手核心过程

重拾 SSH:从基础到安全加固的更多相关文章

  1. 重拾C语言基础知识

    从实习到工作两年多的时间了,虽然感觉学到了很多知识,但是事实上却将立足之本的基础知识给忘了个精光.也许跟自己没有出去找工作有关,没有好好的将C语言的基础牢牢掌握. 从现在开始吧!好好的重温基础,做一名 ...

  2. 重拾Java Web应用的基础体系结构

    目录 一.背景 二.Web应用 2.1 HTML 2.2 HTTP 2.3 URL 2.4 Servlet 2.4.1 编写第一个Servlet程序 2.5 JSP 2.6 容器 2.7 URL映射到 ...

  3. CSS魔法堂:重拾Border之——更广阔的遐想

    前言  当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...

  4. CSS魔法堂:重拾Border之——图片作边框

    前言  当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...

  5. [linux]重拾linux

    起因 因为想重拾起linux,同时需要用docker起几个镜像,用来学习网络知识.本来想直接去阿里云上买,后来一想自己机器上,起一个linux是个不错的选择,毕竟不花钱! 还可以用来做本地测试,学习使 ...

  6. 重拾linux

    重拾linux 起因 因为想重拾起linux,同时需要用docker起几个镜像,用来学习网络知识.本来想直接去阿里云上买,后来一想自己机器上,起一个linux是个不错的选择,毕竟不花钱! 还可以用来做 ...

  7. 重拾算法之复杂度分析(大O表示法)

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  8. 快速重拾 Tmux

    Tmux 是一个 Linux (Mac OS也支持)下的终端复用器,相较于 Screen 更为强大,但快捷键和操作逻辑也更复杂,一段时间不用,就很容易忘记相关的命令和快捷键.本文旨在通过一个简单的场景 ...

  9. CSS魔法堂:重拾Border之——不仅仅是圆角

    前言  当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...

  10. CSS魔法堂:重拾Border之——解构Border

    前言  当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...

随机推荐

  1. Air780E之TCP应用,你了解吗?

    ​ 一.TCP简介 TCP(TransmissionControlProtocol,传输控制协议)是一种面向连接的.可靠的.基于字节流的传输层通信协议.它主要用于在不可靠的网络环境中提供稳定的数据传输 ...

  2. Air780E量产binpkg文件如何获取

    ​ 今天我们学习Air780E量产binpkg文件如何获取: 一.背景 最近luatos开发客户增多,客户在量产烧录的时候需要binpkg文件,但是有些客户不知道binpkg文件是什么,在哪里获取,是 ...

  3. Slate文档编辑器-WrapNode数据结构与操作变换

    Slate文档编辑器-WrapNode数据结构与操作变换 在之前我们聊到了一些关于slate富文本引擎的基本概念,并且对基于slate实现文档编辑器的一些插件化能力设计.类型拓展.具体方案等作了探讨, ...

  4. 命运的X

    命运的X cjx 生成函数强. 思路 首先,设 \(f_i\) 为添加第 \(i\) 项后满足条件的概率,\(g_i\) 任意添加至第 \(i\) 项的概率. 我们要求的答案: \[ans=\sum_ ...

  5. 没有MAC电脑,如何申请苹果开发证书、上架APP Store?

    [引言] 使用uni-app进行跨平台APP开发时,苹果ios平台最终还是要通过APP Store渠道发布,调试时uni-app基座也必须使用开发者证书签名后才能安装.对于使用MAC电脑的开发者,倒也 ...

  6. ElementUI Select单选切换多选无法清除历史数据的解决方案

    背景: 有一个tab切换,每一个tab下都有一个 下拉框,只是一个是多选一个是单选,问题是当切换tab标签的时候,下拉框的样式不会被清空. 解决方案: 只需要在 el-select 上加一个 key ...

  7. 如何使用建造者模式(Builder Pattern)创建不可变类

    本文由 ImportNew - 唐小娟 翻译自 Journaldev.如需转载本文,请先参见文章末尾处的转载要求. ImportNew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的 Java ...

  8. SharpZipLib解压.tar.gz

    上一篇文章我们说了如何用SharpZipLib来压缩.tar.gz,这篇文章我们来看看如何解压. 直接上代码: using ICSharpCode.SharpZipLib.GZip; using IC ...

  9. Blazor 组件库 BootstrapBlazor 中Markdown组件介绍

    组件介绍 Markdown组件是tui.editor的封装,所以所有内容均基于tui.editor. 默认状态下样子如下所示: 其代码如下: <Markdown Language="@ ...

  10. Mybatis【12】-- Mybatis多条件怎么查询?

    很多时候,我们需要传入多个参数给sql语句接收,但是如果这些参数整体不是一个对象,那么我们应该怎么做呢?这里有两种解决方案,仅供参考. 1.将多个参数封装成为Map 测试接口,我们传入一个Map,里面 ...