从CTF实战看软件供应链攻击:漏洞利用链与安全加固

📅 2026/7/4 11:57:27
从CTF实战看软件供应链攻击:漏洞利用链与安全加固
1. 项目概述一次经典的供应链攻击实战复盘几年前我在研究CTF竞赛中的高级攻击手法时DEFCON CTF 2019 Quals中Tamper-Evident Village的“Secure Key Storage”挑战给我留下了深刻印象。这不仅仅是一道CTF题目它几乎完整复现了一个现代软件供应链攻击的关键环节——从初始立足点到构建服务器渗透再到最终在合法软件分发渠道中植入后门。整个场景围绕一个虚构公司Lunarfire及其产品Wuphf聊天应用展开攻击者需要利用一系列漏洞最终实现在官方发布的软件更新中植入恶意代码。这个挑战的精妙之处在于它迫使你像真正的红队一样思考如何在不触发警报的情况下篡改一个具有完整签名验证机制的软件发布流程。今天我就来详细拆解这道题目的漏洞原理、利用链构建以及其中那些教科书上不会写的实战技巧。2. 环境与目标架构深度解析2.1 挑战场景与初始立足点题目模拟的场景是攻击者已经通过社会工程学如钓鱼邮件初步渗透进了目标组织Lunarfire的内部网络并控制了一台开发人员的机器。在这台机器上我们发现了能够SSH连接到核心构建服务器的密钥。这个构建服务器是整个软件供应链的“心脏”它承担着代码编译、打包、签名和分发的全部职责。服务器上主要运行着三个关键服务Gitea一个自托管的Git服务托管着Wuphf聊天应用的源代码仓库。AppVeyor一个持续集成/持续部署CI/CD服务配置为监控Gitea仓库的变更一旦有新的提交就自动拉取代码、执行构建脚本并生成最终的可执行文件。静态文件Web服务器用于对外分发已签名的Wuphf客户端安装包及其对应的校验和文件SHA256SUMS和数字签名SHA256SUMS.sig。我们的目标很明确在不具备构建服务器root权限的初始状态下篡改最终分发给客户的软件并使其通过官方的签名验证。这听起来像是一个“不可能任务”因为签名私钥理应被严格保护。2.2 安全假设与攻击面分析在开始动手之前我们必须理解题目设定的安全边界也就是通常认为“安全”的环节在哪里以及我们可能从何处突破。第一道防线代码仓库访问控制。理论上只有受信任的开发者才能向Gitea仓库推送代码。我们初始拥有的dev用户SSH密钥可能只提供了对构建服务器的系统访问而非对Gitea的写权限。第二道防线CI/CD服务凭证。即使代码被恶意修改CI/CD服务AppVeyor的执行账户也应该具有最小权限并且其配置和密钥应被妥善保管。第三道防线也是最关键的一道数字签名。最终生成的软件包会使用GPG私钥进行签名。私钥的密码应该只有极少数人知道并且CI/CD流程在签名时需要使用这个密码。如果拿不到签名密钥或密码我们生成的恶意软件将无法通过客户端的验证。攻击面因此变得清晰我们需要横向移动首先获得对Gitea仓库的写权限然后控制或影响CI/CD流程最后解决签名问题。整个链条环环相扣缺一不可。3. 漏洞挖掘与横向移动实战3.1 从系统用户到CI/CD凭证窃取通过SSH以dev用户登录构建服务器后我们首先进行基本的权限枚举。执行id命令发现dev用户属于appveyor组。这是一个非常重要的信号意味着我们可以读取AppVeyor服务用户相关的某些文件。注意在Linux系统中检查当前用户所属的组id命令是信息收集的第一步往往能发现意想不到的权限继承路径。接下来我们开始搜索AppVeyor的配置和数据库文件。通常这类服务会在/etc、/opt或/var目录下存放配置文件。经过一番查找我们定位到了几个关键文件/etc/opt/appveyor/server/appsettings.jsonAppVeyor的主配置文件其中包含了数据库连接字符串等敏感信息。/var/opt/appveyor/server/appveyor-server.dbAppVeyor的SQLite数据库文件里面存储了用户信息包括密码哈希、项目配置、构建历史等。由于dev用户属于appveyor组我们成功读取了这些文件。数据库文件是突破口。我们使用sqlite3命令行工具将其下载到本地进行分析。# 在攻击者机器上操作假设已将数据库文件scp到本地 sqlite3 appveyor-server.db .tables SELECT * FROM users;在users表中我们找到了一个用户rhendrickslunarfire.com及其对应的密码哈希字段。直接破解这个哈希例如用john或hashcat尝试未果说明密码可能足够强。然而数据库中还有其他看似加密的字段比如构建任务的配置信息。这里题目埋下了一个关键点AppVeyor在某些版本中会将敏感配置如访问令牌、GPG密码以加密形式存储在数据库中而解密密钥可能与系统或某个特定用户密钥关联。3.2 巧妙的凭证“替换”攻击直接破解哈希和加密数据行不通我们换一种思路既然我们拥有整个数据库文件能否“伪造”一个我们已知密码的用户然后替换进去从而登录到AppVeyor的管理界面搭建本地环境我们在自己的攻击机上快速部署一个相同版本的AppVeyor服务利用Docker或按照官方文档安装。这是一个非常实用的红队技巧——在可控环境中复刻目标服务用于分析、测试攻击载荷或像本例一样生成凭证。创建已知用户在本地AppVeyor中创建一个新用户并设置一个我们知道的简单密码例如Password123!。提取哈希查看本地数据库中新用户的密码哈希和盐salt值。AppVeyor的密码哈希算法通常是PBKDF2。实施替换用文本编辑器或编程脚本如Python的sqlite3库将目标appveyor-server.db中rhendrickslunarfire.com的密码哈希和盐字段替换成我们本地生成的、对应Password123!的哈希和盐。覆盖与登录将修改后的数据库文件上传回目标服务器的原路径需要appveyor组权限我们具备。然后访问构建服务器的AppVeyor Web界面通常端口为8080使用rhendrickslunarfire.com和密码Password123!登录。成功登录后我们获得了AppVeyor的管理控制台。在这里我们可以查看所有项目的构建配置appveyor.yml或UI中的设置。最关键的是在某个项目的环境变量或安全配置中我们找到了明文存储的GPG私钥的密码这很可能是因为配置人员图方便将密码直接存放在了CI/CD的环境变量中而不是使用更安全的密钥管理器。实操心得在CI/CD配置中寻找明文密码、API密钥、访问令牌是渗透测试的黄金步骤。许多开发团队会错误地将敏感信息直接写入构建脚本或环境变量。工具如truffleHog或git-secrets可以帮助自动化发现但手动审查关键配置永远不可替代。3.3 获取代码仓库写权限有了GPG密码我们尝试使用它来登录Gitea服务。果然密码复用发生了rhendrickslunarfire.com这个邮箱账户和刚才得到的密码成功登录了Gitea。现在我们拥有了对wuphf源代码仓库的写权限。供应链攻击的大门已经打开了一半——我们可以向官方代码库注入恶意代码了。4. 权限提升与构建流程控制4.1 利用Docker挂载实现Root权限逃逸虽然我们已经可以修改代码并触发构建但构建过程是在CI/CD的上下文中完成的我们仍然不是服务器上的root用户。没有root权限我们无法自由监控网络、无法随意替换最终分发目录的文件调试和后续攻击会非常低效。我们需要将权限提升至root。审查Gitea仓库中的构建脚本通常是ci.sh或.appveyor.yml我们发现了一段关键的Docker命令docker run -v /home/appveyor/.cache/electron-builder/node-modules:/project/node_modules ... /bin/bash -c yarn config set ... yarn install yarn build这条命令的含义是在容器内运行构建命令并将宿主机的目录/home/appveyor/.cache/electron-builder/node-modules挂载到容器内的/project/node_modules。目的是缓存node_modules加速后续构建。漏洞就出在这里Docker容器内的命令默认以root用户运行。当yarn install等命令在容器内执行时它下载和创建的文件在/project/node_modules目录下所有权是root。由于这个目录是挂载的宿主目录导致宿主机上的/home/appveyor/.cache/electron-builder/node-modules目录下的文件其所有者也会变成root。更重要的是我们作为dev用户属于appveyor组对这个缓存目录有写权限。我们可以修改构建脚本在Docker执行的命令序列中插入一条创建Setuid二进制文件的指令# 在构建脚本的docker run命令中在原有的yarn命令前后插入 /bin/bash -c cp /bin/bash /project/node_modules/.root_bash chmod 4755 /project/node_modules/.root_bash; yarn config set ... yarn install yarn build这条命令做了两件事cp /bin/bash /project/node_modules/.root_bash将容器内的/bin/bash复制到挂载的目录在宿主机上路径为/home/appveyor/.cache/electron-builder/node-modules/.root_bash。chmod 4755 /project/node_modules/.root_bash为这个bash副本设置Setuid位4755中的4。Setuid位意味着任何用户执行此文件时都将以文件所有者root的权限运行。我们将修改后的脚本提交到Gitea仓库触发CI/CD构建。构建完成后我们在宿主机上检查缓存目录ls -la /home/appveyor/.cache/electron-builder/node-modules/.root_bash如果看到文件所有者为root并且权限是-rwsr-xr-x注意s位说明成功了。现在我们以dev用户身份执行它/home/appveyor/.cache/electron-builder/node-modules/.root_bash -p-p参数用于告诉bash保留提升的权限。执行后我们便获得了一个root shell随后我们可以将SSH公钥添加到/root/.ssh/authorized_keys实现持久的root访问。注意事项这种利用方式成功的关键在于appveyor用户或其所属组对缓存目录有写权限并且Docker容器内以root运行。在实际环境中防范此类攻击需要遵循最小权限原则CI/CD运行用户不应有对敏感目录的写权限考虑使用无root容器rootless Docker或用户命名空间映射定期审计挂载目录的权限和所有权变化。4.2 掌控软件分发目录与签名流程获得root权限后我们便能访问整个系统。我们定位到软件最终的分发目录/opt/lunarfire/dist。这里存放着所有版本的Wuphf客户端安装包如wuphf-win-1.7.2.exe以及对应的SHA256SUMS和SHA256SUMS.sig文件。通过查看构建日志或逆向构建脚本我们还原出官方的签名流程构建完成后所有新生成的安装包被放入/opt/lunarfire/dist。运行命令生成所有文件的SHA256校验和sha256sum * SHA256SUMS。使用GPG私钥和密码对SHA256SUMS文件进行签名gpg --detach-sign -o SHA256SUMS.sig SHA256SUMS。密码通过某种方式如环境变量、文件传递给gpg命令。由于我们已经从AppVeyor配置中拿到了GPG密码并且以root身份可以访问AppVeyor用户的GPG密钥环通常在~/.gnupg/我们完全能够复现这个签名过程。我们可以编写一个自动化脚本#!/bin/bash # sign_update.sh DIST_DIR/opt/lunarfire/dist GPG_PASS从AppVeyor配置中获取的密码 cd $DIST_DIR # 生成校验和 sha256sum wuphf-* SHA256SUMS # 使用密码进行签名这里使用--pinentry-mode loopback避免交互 echo $GPG_PASS | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --detach-sign -o SHA256SUMS.sig SHA256SUMS现在我们拥有了篡改软件并使其通过验证的全部能力root权限可以任意替换安装包、签名密钥和密码可以生成合法的签名。5. 完整的供应链攻击链构建与利用5.1 理解客户端的更新机制为了发起有效的攻击我们还需要知道客户端是如何更新的。我们使用tcpdump在构建服务器上监控网络流量tcpdump -i any port 80 -nn -s0 -vv我们发现了一个规律的HTTP GET请求来自某个固定的IP模拟的客户端机器请求路径是/dist/SHA256SUMS。客户端的行为逻辑变得清晰定期例如每5分钟下载SHA256SUMS文件。使用内置的GPG公钥验证SHA256SUMS.sig签名的有效性。如果签名有效则计算本地已安装软件例如wuphf-win-1.7.2.exe的SHA256值与SHA256SUMS文件中的记录比对。如果不匹配则认为有更新便下载新的安装包并执行。这里有一个至关重要的细节客户端只认特定的文件名版本如1.7.2而不是简单地下载版本号最高的文件。这意味着如果我们发布一个wuphf-win-1.7.3.exe客户端会直接忽略它。我们必须替换掉现有的wuphf-win-1.7.2.exe文件。5.2 实施恶意软件替换与签名攻击流程最终成型制作后门我们准备一个恶意的wuphf-win-1.7.2.exe。在CTF中这可能是一个能回连到我们监听服务器的反向shell在真实攻击中这可能是功能完整的远控木马。文件替换以root身份将恶意exe文件上传到构建服务器并覆盖/opt/lunarfire/dist/wuphf-win-1.7.2.exe。重新生成校验和与签名运行我们编写的sign_update.sh脚本。这会基于当前目录下所有文件包括我们刚放进去的恶意文件生成新的SHA256SUMS并用合法的私钥签名生成新的SHA256SUMS.sig。等待与捕获在攻击机上启动网络监听如nc -lvnp 4444。由于客户端定期检查更新它会很快发现SHA256SUMS文件中wuphf-win-1.7.2.exe的哈希值变了。在验证签名有效后客户端便会下载并执行我们的恶意程序从而与我们建立连接。5.3 实战中遇到的“意外”与调试在实际解题过程中我们遇到了一个意想不到的障碍在成功接收到几次回调后客户端突然停止下载我们的恶意软件了。tcpdump显示不再有来自客户端IP的请求。我们最初的判断是恶意软件被客户端的防护软件如Windows Defender拦截并导致了系统异常。但与题目维护者沟通后他们确认服务端是正常的。这迫使我们必须进行更深层次的排查。由于我们拥有root权限可以进行更广泛的调查。我们检查了服务器本身的Web服务日志发现没有开启排除了服务器问题。我们开始怀疑是网络或客户端解析问题。最终通过多方面的测试和与维护者的协作发现问题是出在客户端的DNS配置上。模拟客户端的机器通过域名如downloads.lunarfire.dev来访问构建服务器。而这个域名配置了两个A记录DNS轮询或负载均衡其中一个指向我们的构建服务器另一个指向了一个陈旧或错误的IP地址。在一段时间后客户端的DNS解析可能切换到了那个错误的IP导致请求无法到达我们的服务器。实操心得在红队行动或CTF中当一切技术手段都看似正确但攻击却不生效时一定要拓宽排查范围将基础设施问题如DNS、网络策略、负载均衡、防火墙规则纳入考虑。拥有root权限提供的全方位可见性如tcpdump在此类调试中是无价的。此外与活动组织方保持清晰、专业的沟通有时能快速定位那些非技术性的“环境问题”。6. 漏洞根源与安全加固建议回顾整个攻击链多个环节的失守共同导致了灾难性的后果过宽的权限设置dev用户被加入到appveyor组使其能够访问CI/CD服务的敏感数据和文件这是横向移动的起点。凭证管理与复用CI/CD中存储明文密码GPG私钥密码以明文形式存储在AppVeyor的环境变量中。密码复用同一个密码被用于Gitea和AppVeyor两个系统导致一处泄露全线崩溃。不安全的容器使用容器内root运行Docker容器内进程以root身份执行是权限提升的直接原因。危险的目录挂载将宿主机目录挂载到容器内且该目录权限控制不当容器内root写入导致宿主机文件属主变为root结合Setuid机制完成了权限逃逸。供应链安全机制缺失更新验证机制僵化客户端仅通过哈希对比判断更新而非版本号且对同名文件替换没有额外防护使得“降级攻击”或“替换攻击”成为可能。签名密钥保护不足虽然使用了签名但签名密钥的密码未能妥善保管使得整个签名体系形同虚设。针对性的加固措施应包括实施最小权限原则严格限制用户和组的权限CI/CD运行用户不应有对构建目录之外文件的写权限。加强密钥与凭证管理使用专业的密钥管理服务如HashiCorp Vault、AWS KMS来存储签名密钥密码和API令牌CI/CD流程通过临时令牌动态获取。严禁密码复用。安全配置容器使用非root用户运行容器docker run --user或启用用户命名空间映射。避免将宿主机敏感目录挂载到容器如需缓存应仔细审查挂载点的权限。完善软件供应链安全实施代码签名并且将签名密钥存储在硬件安全模块HSM中。客户端更新逻辑应加入版本号检查、证书钉扎Certificate Pinning或使用TUFThe Update Framework等专门框架来防御中间人攻击和回滚攻击。对构建环境进行严格隔离和固化确保构建过程的可复现性。全面的日志记录与监控记录所有对代码仓库的推送、CI/CD构建活动、对分发服务器的文件修改以及签名操作。设置告警机制对异常活动如非工作时间的构建、签名操作进行实时告警。这道CTF题目如同一场浓缩的实战演习它清晰地展示了一条从外部渗透到内部横向移动最终篡改软件供应链的完整攻击路径。每个环节的漏洞都源于现实中常见的安全疏忽。对于防御者而言它是一份极佳的检查清单对于攻击者它则展示了如何将多个看似微小的弱点串联成一条致命的攻击链。在安全的世界里链条的强度取决于它最薄弱的一环而供应链的安全需要守护好每一个环节。