LiteLLM投毒事件:从一个Pod查到旧副本

简介: 本文是一份K8s生产环境安全核验操作指南:基于已有RBAC权限,通过kubectl+jq逐层追溯Pod声明、运行时镜像ID、控制器(RS/Deployment)及生命周期事件,聚焦LiteLLM漏洞通告时间窗内的制品关联验证,不越权、不调试、不执行,全程只读API,强调证据链完整性与权限边界。(239字)

这篇直接从已有K8s部署开始:确认集群,列出Pod,逐容器核对声明和状态,再沿owner UID追旧ReplicaSet。下文所有输出均为“结构示意/非真实输出”,不是云上实测。只从可信管理终端读API,使用已有RBAC权限;不exec、debug、读取kubectl logs、运行Python或pip,不拉取镜像、不重启或滚动发布。没有权限就停止交给已有权限的管理员。

1.确认context,发现目标Pod

先查看已有上下文,再填入管理员已核实的命名空间与标签选择器。标签来自当前部署配置,不能猜一个名称当全部范围。

kubectl config get-contexts
CTX='REPLACE_CONFIRMED_CONTEXT'
NS='REPLACE_VERIFIED_NAMESPACE'
SEL='REPLACE_VERIFIED_LABEL_SELECTOR'
kubectl --context "$CTX" get pods -n "$NS" -l "$SEL" -o wide

示意输出:

NAME             READY  STATUS       RESTARTS  AGE  NODE
litellm-7c8-a1    1/1    Running      0         2d   worker-a
litellm-6b9-b2    0/1    Terminating  1         2d   worker-b

保留两行,终止中的旧副本也查。No resources found先核NS和SEL;确实无匹配时,没有POD变量,不执行第2—5步中依赖POD的命令。如果已有发布记录给出Deployment UID,可直接用第4步最后一条查询仍保留的RS;否则停止,向管理员补历史部署记录,不写“未使用”。Forbidden停止该请求,不用集群管理员权限试探;context不符先改本文CTX变量,不执行全局切换。

若仅怀疑选择器写错,可在已授权且已确认的命名空间内去掉筛选看标签,再由发布配置确认SEL;不要扩大到所有命名空间:

kubectl --context "$CTX" get pods -n "$NS" --show-labels

示意输出末列可能是app=litellm,track=stable。看到标签只能帮助定位,不能自行断言它覆盖全部历史批次;NS也不确定则停止,先核发布配置。

从输出复制一个Pod名进入第2步;第一步每个Pod都要重复第2—5步,不能只检查Running的最新一行。

2.列三类容器,先知道应该查几个

需要本地已有jq。下面从声明输出类型、容器名、镜像引用,不输出环境变量或Secret。

POD='REPLACE_POD_NAME_FROM_STEP_1'
kubectl --context "$CTX" get pod "$POD" -n "$NS" -o json |
jq -r '
  .spec as $s |
  ["initContainers","containers","ephemeralContainers"][] as $k |
  ($s[$k] // [])[] |
  [$k,.name,.image] | @tsv'

示意输出:

initContainers       prepare  example/prepare:42
containers           proxy    ghcr.io/berriai/litellm:main-stable
ephemeralContainers  diag     example/diag:3

这是待核容器名单,不是已执行证明。下一步按“类别+名称”逐个对照状态;初始化与临时容器不能漏。某类没有行,只表示当前声明没有该类对象。若Pod已NotFound,记录它已删除,停止该Pod后续查询,按第5步补历史;不要拿同名新Pod续填。

示例有三个容器,就需要三条对应状态或明确缺口。若镜像是业务方自建名称,仍追该构建批次的依赖记录;只看名称没有litellm不够。配置写了固定tag也只固定一个名字,不能代替当时取得的制品摘要。

3.取状态imageID,缺状态就保留缺口

kubectl --context "$CTX" get pod "$POD" -n "$NS" -o json |
jq -r '
  ["initContainerStatuses","containerStatuses",
   "ephemeralContainerStatuses"][] as $k |
  (.status[$k] // [])[] |
  [$k,.name,.image,(.imageID // "—"),
   ((.state | keys[0]) // "—")] | @tsv'

示意输出,摘要仅为缩写:

initContainerStatuses       prepare  example/prepare:42  containerd://sha256:aaaa…  terminated
containerStatuses           proxy    ghcr.io/berriai/litellm:main-stable  containerd://sha256:bbbb…  running
ephemeralContainerStatuses  diag     example/diag:3      —                          waiting

prepare虽已结束仍要核;proxy的镜像标识待关联制品;diag没有imageID,不能判未运行过。若第2步有容器而这里没有对应行,标“状态未取得”,不可从名单中删掉。状态image与声明也可能不同,保留原值,不用一个覆盖另一个。

如果看到waiting,下一步只读等待原因,不尝试重新拉取:

kubectl --context "$CTX" get pod "$POD" -n "$NS" -o json |
jq -r '
  ((.status.initContainerStatuses // []) +
   (.status.containerStatuses // []) +
   (.status.ephemeralContainerStatuses // [])) |
  .[] | select(.state.waiting != null) |
  [.name,(.state.waiting.reason // "—")] | @tsv'

示意输出为diag ImagePullBackOff,说明当前等待分支;不是历史从未执行。若无等待行,回到已取得的状态逐条核对,不追加调试容器。

继续读取选定Pod节点与平台;尚未调度时明确停止节点查询:

if ! NODE=$(kubectl --context "$CTX" get pod "$POD" -n "$NS" \
  -o jsonpath='{.spec.nodeName}'); then
  printf '%s\n' '节点字段查询失败:停止,保留错误类别'
elif [ -n "$NODE" ]; then
  kubectl --context "$CTX" get node "$NODE" \
    -o custom-columns='NODE:.metadata.name,ARCH:.status.nodeInfo.architecture,RUNTIME:.status.nodeInfo.containerRuntimeVersion'
else
  printf '%s\n' '尚无节点:平台和运行时待取得'
fi

示意输出:

NODE      ARCH   RUNTIME
worker-a  amd64  containerd://2.x

节点Forbidden只记平台未知,不能由其它节点代填。imageID的含义依运行时与后端确认,不直接等于公告index;缺失、不匹配、不同架构都不能直接排除风险。需要同仓库、同摘要对象或已建立父子映射才比较,不下载恶意制品补映射。

到这里按三类结果继续:tag命中公告名单,保留声明与状态,继续第4—5步追批次和运行时间,不能只看今天tag;digest已核为同仓库同对象或可关联子对象,命中受影响制品就停止现场验证、交接响应,不匹配仍核历史;旧Pod或RS已删,不再对失效名称重复查询,转第5步的历史补证分支。自建tag则补CI中的wheel和基础镜像输入,不能拿业务镜像ID直接排除。

4.沿owner UID找到控制器,别只看名字

kubectl --context "$CTX" get pod "$POD" -n "$NS" -o json |
jq -r '.metadata.ownerReferences[]? |
  select(.controller == true) | [.kind,.name,.uid] | @tsv'

示意输出:

ReplicaSet  litellm-7c8  rs-uid-7

若为ReplicaSet,取其名称继续;其它控制器按输出类型交给对应负责人追溯,不能硬套RS。空结果说明未取得控制器关联,停止自动串联。

RS='REPLACE_REPLICASET_NAME_FROM_STEP_4'
kubectl --context "$CTX" get replicaset "$RS" -n "$NS" -o json |
jq -r '
  (["SELF",.metadata.name,.metadata.uid] | @tsv),
  (.metadata.ownerReferences[]? | select(.controller == true) |
   [.kind,.name,.uid] | @tsv)'

示意输出:

SELF        litellm-7c8  rs-uid-7
Deployment  litellm      deploy-uid-1

SELF UID必须与上条owner UID一致。不同就停止关联:同名对象可能重建;NotFound表示RS已删除,按第5步补记录。确认Deployment owner后,把它的UID代入下条,列仍保留的新旧RS:

DEPLOY_UID='REPLACE_DEPLOYMENT_UID_FROM_PREVIOUS_OUTPUT'
kubectl --context "$CTX" get replicasets -n "$NS" -o json |
jq -r --arg uid "$DEPLOY_UID" '
  .items[] |
  select(any(.metadata.ownerReferences[]?;
    .controller == true and .uid == $uid)) |
  [.metadata.name,.metadata.uid,.metadata.creationTimestamp,
   (.spec.replicas | tostring),
   ([((.spec.template.spec.initContainers // []) +
      (.spec.template.spec.containers // []))[] |
      .name + "=" + .image] | join(","))] | @tsv'

示意输出:

litellm-7c8  rs-uid-7  2026-10-21T12:00:00Z  2  proxy=example/litellm:42
litellm-6b9  rs-uid-6  2026-10-21T11:50:00Z  0  proxy=example/litellm:41

最后一列是模板中的初始化和普通容器引用,仍不是实际运行证据。旧RS副本0只说明当前期望数量,不能证明它未运行过;用旧引用接着查对应构建与发布批次。结果为空或Forbidden,停止“历史完整”的判断;旧RS也可能已被清理。

示例的新旧RS都关联同一个Deployment UID,才能归到同一控制器历史;名字相近但owner UID不同的对象不能合并。若发现旧模板仍用浮动tag,保留原引用,向该批次补当时解析摘要,不拿今天同名tag的结果覆盖。

5.看终止记录,再对回事件

kubectl --context "$CTX" get pod "$POD" -n "$NS" -o json |
jq -r '
  .metadata.uid as $uid |
  ["initContainerStatuses","containerStatuses",
   "ephemeralContainerStatuses"][] as $k |
  (.status[$k] // [])[] |
  [$uid,$k,.name,
   (.state.running.startedAt // .state.terminated.startedAt // "—"),
   (.state.terminated.finishedAt // "—"),
   (.lastState.terminated.finishedAt // "—")] | @tsv'

示意输出:

pod-uid-1  containerStatuses  proxy  2026-10-21T13:11:00Z  —  2026-10-21T13:10:00Z

后三列分别为当前状态启动、当前终止、上次终止时间;空值不能补“从未退出”。lastState不是完整重启史。Pod已删时,当前API无法补回它:向管理员取得当时保留的审计、发布或集中采集记录,按Pod UID关联;没有记录就止于“历史证据缺失”,不以当前新Pod证明旧Pod安全。记录中可能有秘密,仅保留镜像、UID、时间等必要字段。

LiteLLM通告的北京时间窗口均在10月21日:PyPI七个1.107.0 wheel为19:39:51—21:23;Hub四tag为19:46:56—22:20;GHCR同tag自19:46:56起、至21:51已不可用。四tag为v1.107.0、latest、main-latest、main-stable。Hub受影响index为:

sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e

GHCR的database与non_root变体须分别核摘要。Pod创建与RS创建都不是拉取时间;窗口内取得的制品可以晚些才执行。确认历史制品或相关wheel命中,就停止现场试跑,交接事件响应;当前tag干净、Pod Ready不能排除过去暴露。凭据处理由所有者确认范围,本文不自动删证据或轮换。

示例当前启动13:11Z即北京时间21:11,在Hub窗口内;上一条终止13:10Z也不能补出更早每次执行。应把时间、Pod UID、容器名和已核摘要对象一并关联。只有运行时间没有制品映射时,保留待核行;有确切关联再升级为受影响处置,不能把两种结论混在一起。

evidence-map.png

依据:LiteLLM 10月21日官方通告、Kubernetes官方容器状态与owner、镜像及Pod生命周期资料;核验日期2026年10月23日。调查仍在进行,示例没有在真实集群运行。

相关文章
|
22天前
|
人工智能 自然语言处理 运维
提升转化率必看:适合电商的智能客服产品推荐及实测对比
电商用户咨询时购买意愿最强,但传统客服响应慢、答不深、办不了,导致转化流失。阿里云瓴羊Quick Service以AI Agent为核心,直连订单物流系统,实现售前导购、售后闭环(如改地址、退款),将“应答工具”升级为“办事员工”。实测显示:人工介入率降45%+,退换货缩至90秒,大促响应达3秒,转化率显著提升。
|
云安全 安全 网络安全
|
23天前
|
缓存 网络安全 Docker
eMudhra证书事件:核对Compose挂载的CA文件与应用配置
eMudhra证书事件揭示容器中CA信任链风险:镜像更新CA后,若Compose只读挂载宿主机旧证书目录,应用仍会使用过期文件。本文详解挂载路径、环境变量(SSL_CERT_FILE/DIR、REQUESTS_CA_BUNDLE)与实际读取差异,提供只读检查、单文件挂载及摘要核验方法,强调“镜像CA、挂载CA、运行时CA”三者需明确区分。(239字)
|
1月前
|
弹性计算 Linux Docker
2026年10月Docker镜像源清单:ECS旧入口与公共候选
本文梳理2023年10月主流容器镜像加速入口现状:阿里云、百度旧加速器已停更或终止服务;腾讯、华为云内网地址限特定环境;毫秒镜像(docker.1ms.run)与DaoCloud为可用公共候选。强调需按“环境适配→显式拉取→摘要校验”三步验证,不可盲目替换配置。(239字)
2026年10月Docker镜像源清单:ECS旧入口与公共候选
|
3月前
|
弹性计算 运维 Kubernetes
2026年8月ECS与containerd可用镜像源清单
本文整理了2026年8月适用于ECS、Kubernetes及containerd节点的9个国内镜像源清单,涵盖Docker Hub、GHCR、K8s、Quay、MCR等主流Registry,并完成端点与manifest级验证。强调需在真实worker节点实测,而非运维机模拟;提供containerd配置、逐源验证及ImagePullBackOff排查方法。(239字)
|
6月前
|
Kubernetes 网络协议 异构计算
云上 K8s GPU 节点 ImagePullBackOff 排查记录
本文记录云上K8s(v1.36)GPU节点模型评测Job因`ImagePullBackOff`卡在Pending的排查过程,聚焦containerd镜像拉取失败根因,涵盖crictl验证、日志分析、DNS/镜像源配置检查,并强调分层排障:先运行时,再资源调度与设备挂载。
|
5月前
|
存储 弹性计算 数据库
ECS 上 Immich 相册服务升级与恢复排查记录
本次在ECS复现Immich升级后恢复检查:执行`docker compose pull && up -d`后,容器虽运行、Web可访问,但相册索引与缩略图任务未及时恢复。重点排查镜像拉取、DB迁移、挂载路径及权限,强调升级前务必固定版本、备份配置与存储状态。(239字)
|
6月前
|
Kubernetes Perl 容器
K8s Ingress 502 排查:先看 Service、Endpoints 和 readinessProbe
本文详解K8s中Ingress返回502的系统化排查思路:不盲目修改Ingress,而是按Pod→Service→Endpoints→readinessProbe→Ingress逐层验证。重点检查Pod就绪状态、Service selector与targetPort匹配性、Endpoints是否为空、健康探针配置合理性等核心环节,直击502根本原因——上游服务未就绪。(239字)
|
6月前
|
Kubernetes 测试技术 Docker
Jellyfin 上 K8s 前,先把镜像、PVC 和权限排一遍
本文记录Jellyfin从Docker Compose迁移至K8s测试环境的典型问题排查:镜像拉取慢、PVC未绑定致媒体目录为空、容器用户权限不足、路径配置错误等。强调K8s中“宿主机→PV→PVC→volumeMount→容器路径”的分层模型,须逐层验证,避免误判。
|
6月前
|
应用服务中间件 文件存储 nginx
测试 NAS 容器服务恢复时的 Docker Compose 排查记录
节后恢复NAS测试环境,Jellyfin、PhotoPrism、Home Assistant容器页面异常。本文记录分层排查过程:从磁盘空间、镜像拉取、Compose状态,到挂载路径、端口连通性及Nginx反向代理配置,精准定位三处问题并修复。(239字)