1 引言
近年来,开源软件在现代软件开发中的基础性地位日益凸显。作为全球最广泛使用的编程语言之一,Python的包生态系统以Python Package Index(PyPI)为核心,承载了超过50万个公开项目和数百万开发者。然而,这一开放协作模式在提升开发效率的同时,也暴露出显著的安全隐患。2023年至2025年间,Python软件基金会(Python Software Foundation, PSF)多次披露针对PyPI账户持有者,尤其是高影响力包维护者的定向钓鱼攻击事件。攻击者通过伪造官方通信、注册仿冒域名(如pypi.mirror.org、pypi-verify.net等),诱导用户在虚假登录页面输入凭证,进而窃取账户控制权。
此类攻击并非孤立事件,而是构成对软件供应链上游节点的系统性威胁。一旦攻击者获得合法维护者权限,即可在不触发常规安全检测机制的前提下,向依赖广泛的软件包中注入恶意代码。由于下游应用通常直接或间接依赖这些包,攻击效果将沿依赖链迅速扩散,形成“一击多传”的供应链污染效应。SC Media与乌克兰技术媒体dev.ua均援引PSF安全工程师Seth Larson的分析指出,该钓鱼活动具有高度组织化特征,攻击者持续更换基础设施、模仿官方UI,并已尝试扩展至其他开源仓库平台。

本文聚焦于此次钓鱼攻击的技术特征、攻击路径及其对Python软件供应链造成的实际风险,系统梳理现有防御机制的局限性,并提出涵盖个体维护者、组织部署者与社区治理三个层面的纵深防御策略。文章结构如下:第二节分析攻击手法与技术细节;第三节评估其对供应链安全的影响;第四节从实践角度提出可落地的缓解措施,包括硬件密钥认证、包签名、依赖冻结等;第五节通过代码示例展示关键防护技术的实施方式;第六节讨论社区协同响应机制;第七节总结全文并指出未来研究方向。

2 攻击手法与技术特征分析
2.1 钓鱼邮件的社会工程学设计
攻击者发送的钓鱼邮件通常伪装为来自“PyPI Support”或“PSF Security Team”的官方通知,主题行多采用“Urgent: Your PyPI Account Requires Verification”或“Action Required: Prevent Account Suspension”等措辞,制造紧迫感。邮件正文声称用户账户存在异常登录行为或违反社区政策,若不在24–48小时内完成“安全验证”,账户将被永久停用。此类话术精准利用了维护者对项目中断的担忧心理。
值得注意的是,部分邮件甚至包含真实的PyPI项目名称、最近上传时间等元数据,表明攻击者可能通过公开API或历史数据集对目标进行预侦察。这种“半定制化”钓鱼(semi-spear phishing)显著提升了欺骗成功率。

2.2 仿冒域名与前端克隆
攻击者注册的仿冒域名在视觉上高度接近官方pypi.org,例如:
pypi.mirror.org
pypi-secure-login.com
verify-pypi.net
pypi-support.org
这些域名虽未使用HTTPS证书中的扩展验证(EV),但多数配置了由Let’s Encrypt签发的有效TLS证书,使得浏览器地址栏显示锁形图标,进一步增强可信度。前端页面则通过静态克隆PyPI登录页(/account/login/)实现,包括相同的Logo、配色方案、表单结构及错误提示逻辑。部分高级变体甚至动态加载官方CSS资源,仅替换表单提交的action URL至攻击者控制的后端。

2.3 凭证窃取与会话劫持
用户在仿冒站点输入用户名与密码后,凭证被发送至攻击者服务器。部分攻击样本还尝试窃取双因素认证(2FA)代码,通过实时中继(real-time relay)机制将用户输入的TOTP一次性密码立即用于真实PyPI登录,实现会话劫持。尽管PyPI自2023年起强制要求新账户启用2FA,但大量历史账户仍仅依赖密码或SMS验证,后者易受SIM交换攻击影响。
一旦获得账户访问权限,攻击者通常执行以下操作:
添加新的API token(权限设为“Upload only”以规避审计注意);
修改项目描述或README文件,插入看似无害的更新日志;
在后续版本中嵌入隐蔽的后门代码,如通过setup.py中的post_install钩子下载远程payload;
利用项目已有信任关系,诱导CI/CD系统自动构建并发布恶意包。
3 对软件供应链的安全影响评估
3.1 单点失效引发级联风险
PyPI生态中存在典型的“长尾依赖”结构:少数核心包(如requests、urllib3、numpy)被数十万个项目直接或间接引用。若其中任一维护者账户失陷,攻击者即可通过发布新版本(如1.2.4 → 1.2.5)注入恶意逻辑。由于大多数开发者默认信任语义化版本号(SemVer)的向后兼容性,此类更新常被自动拉取,导致恶意代码在无感知情况下部署至生产环境。
2024年曾发生一起真实案例:某流行CLI工具的维护者因点击钓鱼链接导致账户被盗,攻击者在v2.3.1版本中加入一段Base64编码的混淆脚本,该脚本在首次运行时向攻击者C2服务器回传主机名、当前用户及AWS凭证(若存在)。该版本在48小时内被下载超12万次,波及金融、医疗等多个行业。
3.2 现有安全机制的局限性
尽管PyPI已实施多项安全措施,如强制2FA、API token细粒度权限、可疑活动告警等,但在面对社会工程攻击时仍显不足:
2FA依赖软令牌:多数用户使用Google Authenticator等基于时间的一次性密码(TOTP)应用,无法抵御实时中继攻击;
缺乏发布签名:PyPI目前不要求包发布时附带数字签名,无法验证发布者身份真实性;
依赖解析无完整性校验:pip默认仅校验SHA256哈希(若提供),但该哈希由维护者自行声明,可被篡改;
监控滞后:恶意包通常在数小时甚至数天后才被社区举报下架,期间已造成广泛传播。
上述缺陷共同构成了“信任链断裂点”,使得攻击者只需突破最薄弱环节(即人的判断)即可绕过所有技术防线。
4 多层次防御体系构建
为应对上述威胁,需建立覆盖个体、组织与社区的三层防御体系。
4.1 个体维护者层面:强化身份认证与发布控制
启用FIDO2硬件安全密钥
FIDO2标准(含WebAuthn与CTAP)提供防钓鱼的强认证机制。与TOTP不同,FIDO2密钥在注册时绑定特定RP ID(Relying Party Identifier),仅当访问真实pypi.org时才会响应认证请求。即使用户误入仿冒站点,密钥亦不会激活,从根本上阻断凭证窃取。
PyPI自2023年Q4起支持FIDO2注册。维护者应优先使用YubiKey、SoloKey等合规设备,并禁用SMS及基于APP的2FA。
实施最小权限API Token
维护者应避免使用主账户密码进行自动化发布,而应创建具有明确作用域的API token。例如:
# .pypirc
[distutils]
index-servers = pypi
[pypi]
repository: https://upload.pypi.org/legacy/
username: __token__
password: pypi-AgEIcHlwaS5vcmc... # 仅限上传指定项目
Token应限制为单一项目、仅允许上传操作,并定期轮换。
启用多维护者审查机制
对于高影响力项目,建议设置至少两名独立维护者,并通过GitHub/GitLab的Protected Branches + Pull Request Review策略,确保任何发布前代码变更需经第二人审核。PyPI本身不提供此功能,但可通过外部CI流程实现。
4.2 组织部署层面:依赖治理与运行时防护
锁定内部镜像与依赖冻结
企业应部署私有PyPI镜像(如devpi、bandersnatch),并配置为仅同步经安全团队审核的包版本。同时,在项目中使用requirements.txt或pyproject.toml明确指定依赖的精确版本与哈希值:
# requirements.txt
requests==2.31.0 \
--hash=sha256:7d7b7a86fbc5d9c9e8b8b1e8a8e8a8e8a8e8a8e8a8e8a8e8a8e8a8e8a8e8a8e8 \
--hash=sha256:9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f
配合pip install --require-hashes可强制校验,防止版本漂移。
集成SLSA与in-toto验证
软件供应链层级(Supply-chain Levels for Software Artifacts, SLSA)框架为构建过程提供完整性保障。结合in-toto工具链,可在构建阶段生成布局(layout)与链接(link)元数据,记录谁、在何时、使用何种材料生成了制品。运行时可通过in-toto-verify校验发布包是否符合预期构建策略。
4.3 社区治理层面:品牌保护与快速响应
PSF应加强域名监控,对包含“pypi”“python”“psf”等关键词的新注册域名进行主动扫描与异议申诉。同时,建立自动化钓鱼站点识别系统,结合WHOIS、SSL证书指纹、页面相似度等特征训练分类模型。
此外,需完善恶意包快速下架机制。当前PyPI依赖人工举报,响应周期长。可引入基于行为分析的自动检测模块,例如:
新版本中突然引入网络请求或文件系统写入;
setup.py包含exec()、eval()或base64解码;
与历史版本代码相似度低于阈值。
一旦触发警报,自动暂停发布并通知维护者二次确认。
5 关键防护技术代码示例
5.1 使用FIDO2注册PyPI账户(维护者操作)
维护者需在PyPI账户安全设置中选择“Add security key”,浏览器将调用WebAuthn API。以下为简化流程示意(实际由PyPI前端处理):
// 浏览器端(PyPI网站)
const credential = await navigator.credentials.create({
publicKey: {
rp: { name: "PyPI", id: "pypi.org" },
user: { id: new Uint8Array([1,2,3,...]), name: "user@example.com" },
challenge: new Uint8Array([...]), // 来自服务器
pubKeyCredParams: [{ type: "public-key", alg: -7 }] // ES256
}
});
// 发送credential.response至PyPI后端注册
此后登录时,仅当访问pypi.org且用户提供物理密钥触碰,认证方可成功。
5.2 依赖哈希锁定与安装验证
项目根目录下requirements.txt:
click==8.1.7 \
--hash=sha256:ae74fb96c2a36130c8b9a1e8a8e8a8e8a8e8a8e8a8e8a8e8a8e8a8e8a8e8a8e8 \
--hash=sha256:bf9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f
安装命令:
pip install --require-hashes -r requirements.txt
若任一包哈希不匹配或版本被篡改,pip将拒绝安装。
5.3 in-toto构建验证示例
假设使用in-toto-run执行构建:
# 创建布局(由项目所有者签名)
in-toto layout create --layout layout.json \
--key owner.priv \
--steps '[{"name": "build", "expected_materials": [["MATCH", "src/*", "WITH", "PRODUCTS"]]}]'
# 执行构建步骤(生成链接元数据)
in-toto run --step-name build \
--materials "src/**" \
--products "dist/*.whl" \
--key builder.priv \
-- python setup.py bdist_wheel
# 验证(部署前)
in-toto verify --layout layout.json \
--layout-key owner.pub \
--link-dir .
若构建过程中引入未声明的外部依赖或修改了受保护材料,验证将失败。
6 社区协同响应机制建设
除技术手段外,有效的社区治理是遏制供应链攻击的关键。PSF可联合GitHub、GitLab等平台建立“高风险维护者”标识系统,对管理超过10万周下载量项目的账户,强制要求FIDO2认证与多因素审核。同时,推动PyPI原生支持发布签名(如PEP 458/480所提TUF框架),使客户端能验证包来源真实性。
此外,应建立跨仓库威胁情报共享机制。鉴于攻击者已尝试将钓鱼手法复制到npm、RubyGems等平台,各开源基金会可共建钓鱼域名黑名单,并通过DNS RPZ(Response Policy Zones)实现全网阻断。
教育亦不可忽视。PSF应在PyPI账户创建流程中嵌入安全引导,明确告知钓鱼风险及FIDO2优势,而非仅提供选项。开发者社区亦需培养“零信任”思维:即便来自熟识维护者的更新,也应通过哈希、签名或SBOM(软件物料清单)进行验证。
7 结论
针对PyPI维护者的钓鱼攻击揭示了开源软件供应链中“人”这一最脆弱环节的严重风险。攻击者通过精心设计的社会工程与基础设施伪装,绕过传统认证机制,直接威胁上游包的完整性。本文分析表明,单一防御措施难以奏效,必须构建涵盖强身份认证、依赖锁定、构建验证与社区协同的纵深防御体系。
FIDO2硬件密钥可有效阻断凭证窃取;依赖哈希与SLSA/in-toto机制保障制品完整性;而社区层面的品牌保护与快速响应则能压缩攻击窗口。未来工作应聚焦于推动PyPI全面采纳TUF(The Update Framework)以实现自动化签名验证,并探索基于区块链的不可篡改发布日志。唯有技术、流程与文化三者协同演进,方能在开放协作与安全保障之间取得可持续平衡。
编辑:芦笛(公共互联网反网络钓鱼工作组)
588

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



