
Tóm tắt
Ngày 21/10/2026, pipeline release của LiteLLM xuất bản litellm==1.107.0 độc hại lên PyPI, Docker Hub và GHCR. Gói này được ký và phát hành qua đúng quy trình chính thức, nên mọi kiểm tra chữ ký đều hợp lệ.
Gốc rễ không phải một lỗ hổng phần mềm. Đó là một khóa quản trị bị commit nhầm vào repo công khai ngày 27/05/2026 và không bao giờ được thu hồi. Hơn bốn tháng sau, kẻ tấn công dùng chính khóa đó để lấy token GitHub CLI của một org owner, rồi đi tiếp cho tới khi ký được một bản release.
Điểm nguy hiểm nhất của payload: các file được bundle thực thi tự động khi Python khởi động, kể cả khi ứng dụng không hề import litellm.
Hành động ưu tiên:
- Nếu từng cài
litellm==1.107.0hoặc kéo imagev1.107.0,latest,main-latest,main-stabletrong ngày 21/10: coi toàn bộ credential trên máy đó là đã lộ, và lưu ý file khởi động nằm ngoài thư mục ứng dụng, nên gỡ gói thôi là chưa sạch. - Rà soát mọi khóa từng bị commit nhầm rồi "ghi nhận là cần thu hồi". Sự cố này tồn tại được chỉ vì một việc duy nhất không ai làm nốt.
File đã rò rỉ trông như thế nào
File là harnesses/opencode/opencode.json trong repo công khai LiteLLM-Labs/lite-harness, một file cấu hình cho harness chạy agent.
Nhìn vào cấu trúc, có bốn điều đáng rút ra ngay, không cần biết giá trị khóa là gì:
- Cùng một khóa dạng
sk-…được hardcode ở bốn chỗ khác nhau trong cùng một file: trong headerAuthorization: Bearercủa ba MCP server remote trỏ tới gateway nội bộ, và trong trườngapiKeycủa provider. Một khóa dùng chung cho nhiều mục đích nghĩa là thu hồi nó sẽ làm hỏng nhiều thứ cùng lúc, và đó chính là loại ma sát khiến người ta trì hoãn việc thu hồi. - Một khóa API của dịch vụ sandbox bên thứ ba cũng nằm nguyên trong biến môi trường.
- Các placeholder kiểu
sk-dev-master-key-change-mecho endpoint LAP chạy ởlocalhost. Nói cách khác, đây là file cấu hình phát triển, có cả giá trị mẫu lẫn giá trị thật trộn lẫn. - Đường dẫn tuyệt đối trên máy của một lập trình viên cũng lộ ra trong phần
command.
Bản thân file không có gì tinh vi. Nó là thứ xảy ra hàng ngày ở mọi tổ chức: một config dev được commit vì tiện, trong đó có một giá trị thật lẫn giữa các giá trị mẫu.
Bốn tháng im lặng
Theo RCA của LiteLLM, sai sót được phát hiện rất nhanh. Khoảng ba giờ sau, một commit tiếp theo đã xóa file, và các khóa bị lộ được ghi nhận là cần thu hồi.
Nhưng việc thu hồi không bao giờ được thực hiện. LiteLLM gọi thẳng đây là lỗi con người: "nobody followed through on revoking the administrative key".
Thêm một chi tiết quan trọng: commit gốc vẫn nằm trong lịch sử công khai của repo cho tới tận 22/10, tức là một ngày sau khi sự cố nổ ra. Xóa file ở commit sau không xóa được nội dung khỏi lịch sử Git.
Ghi chú nguồn: The Hacker News viết rằng commit đã bị "force-pushed over". RCA chính thức của LiteLLM nói khác: một commit tiếp theo xóa file, còn commit gốc vẫn ở trong lịch sử công khai. Bài này theo RCA.
Khóa vẫn hợp lệ sau hơn bốn tháng.
Chuỗi bảy bước
Toàn bộ mốc giờ dưới đây theo UTC, lấy từ timeline chi tiết trong RCA.
Bước 1 — Khóa cũ đổi lấy token GitHub (10/10, 21:13–21:28)
Từ IP 69.48.229.143, kẻ tấn công gọi API của LiteLLM Agent Platform bằng khóa quản trị đã commit nhầm. Đây là một dự án thử nghiệm nội bộ, dùng để các cloud agent tự host mở pull request lên GitHub.
Lúc 21:27:10, request GET /api/v1/managed_agents/agents?limit=100 trả về cấu hình của mọi managed agent. Trong đó có GitHub CLI OAuth token của một trong hai org owner.
Điểm mấu chốt: loại token bị lấy
Đây là chi tiết đáng dừng lại lâu nhất trong cả bài.
Token mà Agent Platform được cấu hình không phải là Personal Access Token có scope giới hạn theo repo và theo khả năng. Đó là GitHub CLI OAuth token gắn với tài khoản org owner. Theo đúng mô tả của LiteLLM, loại token này:
- Không có scope
- Nằm sau 2FA, tức là đã vượt qua bước xác thực hai yếu tố
- Không chịu luồng re-auth "super-user" của GitHub cho các thao tác nhạy cảm ở cấp tổ chức hoặc repo
Từ thời điểm này, kẻ tấn công có quyền owner trên toàn bộ tổ chức GitHub.
Bước 2–3 — CI của chính nạn nhân trở thành kênh exfiltration (10/10 và 12/10)
Kẻ tấn công không cần công cụ gì mới. Họ dùng chính GitHub Actions.
Mô hình lặp lại, được Socket mô tả chi tiết:
- Tạo một nhánh tạm tên
ci-tmp-<hex>từ một commit hợp lệ gần đây để push trông vô hại - Push một commit thêm file
.github/workflows/cache.yml, với subject cũng vô hại: "ci: bump cache config" - Workflow cấu hình
on: [push]nên tự chạy ngay - Xóa nhánh
Kết quả:
| Thời điểm | Nhánh | Repo | Thu được |
|---|---|---|---|
| 22:11:45 | ci-tmp-a351cc |
BerriAI/litellm |
6 secret, trong đó GH_TOKEN là token GitHub CLI của maintainer thứ hai |
| 22:12:09 | ci-tmp-be33e9 |
BerriAI/litellm-auto |
RELEASE_PLEASE_TOKEN, token GitHub CLI của maintainer thứ ba |
Cả hai run record bị xóa trong vòng ba phút, lần lượt lúc 22:14:14 và 22:14:31.
Ngày 12/10, đợt thứ hai mở rộng sang nhiều private repo khác. Theo phân tích của Socket, phạm vi thu thập gồm token GitHub và API key của Linear, deployment LiteLLM, Together AI, định danh và khóa ký của một GitHub App; đợt sau còn lấy thêm key của Anthropic, OpenAI, AWS Bedrock, Tavily, tenant quét secret GitGuardian, cùng Playwright storage state, thông tin kết nối PostgreSQL và endpoint Redis kèm mật khẩu.
Dữ liệu được gửi tới hai endpoint webhook[.]site khác nhau.
Bước 4 — Trỏ pipeline release sang một fork (21/10, 10:08–11:08)
Workflow release nằm trong private repo BerriAI/project-releaser, và chúng hardcode owner nguồn là BerriAI, nên không có input nào cho phép trỏ build sang một fork. Muốn build từ fork, kẻ tấn công phải sửa chính workflow.
Họ viết commit 9cf97e9 ("ci: release from a fork source repository"), thêm một input source_owner mặc định là BerriAI nhưng chấp nhận bất kỳ tổ chức nào.
Rồi diễn ra đúng năm giây đáng chú ý nhất của toàn bộ sự cố:
10:08:29 Push commit bang token cua maintainer thu hai
-> BI CHAN boi branch protection
(yeu cau pull request, 2 check pass, approval cua codeowner)
10:08:34 Push lai bang token cua org owner
-> THANH CONG, override branch protection
Lúc 10:46:39, staging workflow được dispatch với source trỏ về Berri-AI/litellm, một fork nằm trong tổ chức lookalike Berri-AI (chú ý dấu gạch nối), tại commit 20535d8f.
Một chi tiết khiến việc phát hiện khó hơn: commit 20535d8f chỉ tồn tại trên fork của kẻ tấn công, nhưng vẫn mở được qua URL của repo gốc, vì GitHub phục vụ commit của fork thông qua repo mà nó được fork từ đó.
Staging chạy xong lúc 11:08:09, production pipeline khởi động lúc 11:08:53.
Bước 5 — Cổng production được duyệt bằng token đánh cắp (11:11–11:46)
Lúc 11:11:18, cổng môi trường production được phê duyệt bằng token của maintainer thứ hai, từ IP 199.91.221.39. Pipeline ký và phát hành v1.107.0 được build từ commit trên fork.
- 11:39:51 – 11:40:13: bảy wheel lên PyPI
- 11:46:56: image Docker Hub và GHCR
Bước 6–7 — Dọn dấu vết (11:53–12:24)
- 11:53:49 và 11:54:01: token của maintainer thứ ba xóa run record của production rồi staging, kéo theo toàn bộ log workflow
- 12:09:38: commit revert
285fbdc5được push bằng token owner, lại override branch protection - 12:24:52:
mainbị force-push vềd687b69, tức commit trước khi có thay đổi, vượt luôn cả cơ chế chặn force-push
Sau bước này, nhìn vào main không thấy cả thay đổi lẫn bản revert. Tuy nhiên cả hai commit vẫn truy xuất được bằng SHA, và audit log của tổ chức có ghi lại từng lần override.
Vì sao quy trình phê duyệt không cứu được
Đây là phần mà mọi đội DevOps nên đọc kỹ.
Trong vận hành bình thường, việc build và publish một release đòi hỏi:
- Được khởi động bởi một kỹ sư được ủy quyền
- Được phê duyệt bởi một tài khoản đặc biệt gắn với một thiết bị vật lý đặt tại văn phòng
Đó là một quy trình tốt. Nhưng theo RCA, hạn chế này vừa được nới lỏng trong chính tuần đó để phục vụ nhu cầu tạm thời là phát hành thường xuyên hơn và backport bản vá, cho phép hai kỹ sư bất kỳ kích hoạt và phê duyệt một release.
Kẻ tấn công dùng đúng hai token khác nhau cho hai vai đó.
LiteLLM nêu thêm một quan sát sắc sảo: với token của org owner trong tay, kẻ tấn công hoàn toàn có thể tự nới lỏng hoặc gỡ bỏ hạn chế đó. Họ không làm. LiteLLM cho rằng lý do là tránh tạo ra các bản ghi audit log ồn ào.
Payload trong gói 1.107.0
Theo Microsoft Security Research và các báo cáo khác, các file đi kèm bản 1.107.0 được thiết kế để thực thi tự động khi Python khởi động, kể cả khi ứng dụng chưa bao giờ import LiteLLM.
Hai nhánh thực thi:
1. Module thu thập credential, nhắm vào:
- Công cụ phát triển, ứng dụng AI, môi trường cloud và trình duyệt
- Thông tin hệ thống, file SSH, credential và token của Git và GitLab
- Biến môi trường
- Token của npm, PyPI, pip, Yarn, Maven, Gradle, Composer, RubyGems, Cargo
Dữ liệu được nén rồi POST tới litellm[.]dev/healthcheck. Domain này được đăng ký ngày 13/10, tức tám ngày trước khi release được phát hành.
2. Backdoor hướng Unix, chạy như một tiến trình nền, kéo và thực thi mã tùy ý từ kẻ tấn công mỗi một giờ.
Cảnh báo quan trọng nhất cho khâu xử lý, trích từ phân tích của Microsoft: vì các file khởi động nằm ngoài thư mục ứng dụng của LiteLLM, việc chỉ xóa file ứng dụng có thể để sót lại các thành phần khởi động độc hại.
Nhận định
Không có lỗ hổng phần mềm nào trong chuỗi này. Mọi mắt xích đều là một quyết định vận hành bình thường: commit nhầm file config, quên thu hồi, cấu hình token tiện tay, nới quy trình để chạy nhanh hơn. Đây là lý do khiến sự cố này đáng đọc hơn hầu hết các báo cáo CVE: không có gì để vá.
Token GitHub CLI là loại credential bị đánh giá thấp một cách nguy hiểm. Nhiều đội coi "đã qua 2FA" là đủ an toàn. Nhưng chính đặc tính đó khiến nó nguy hiểm: không scope, đã vượt 2FA, và không bị luồng re-auth cho thao tác nhạy cảm chặn lại. Một PAT có scope theo repo đặt ở cùng vị trí đó sẽ khiến bước 1 không dẫn tới đâu.
Việc nới lỏng quy trình "tạm thời" rơi đúng vào tuần bị tấn công. Rất đáng để nhìn thẳng: quy trình yêu cầu phê duyệt từ thiết bị vật lý tại văn phòng là một biện pháp mạnh, và nó bị nới ra vì một lý do hoàn toàn hợp lý là cần đẩy bản vá nhanh. Đây là dạng rủi ro mà không checklist bảo mật nào bắt được, vì quyết định nới lỏng luôn trông hợp lý tại thời điểm ra quyết định.
Kẻ tấn công hiểu rõ audit log. Họ chia công việc cho ba danh tính khác nhau, dùng nhánh tạm sống vài phút, xóa run record, và chỉ override branch protection khi thực sự bắt buộc. Việc họ không tự gỡ hạn chế phê duyệt dù có đủ quyền cho thấy ưu tiên là giảm tín hiệu, không phải giảm số bước.
Chi tiết đáng lo nhất trong danh sách secret bị lấy là GITGUARDIAN_API_KEY. Như Socket chỉ ra, đây là credential của chính nền tảng quét secret, cho kẻ tấn công khả năng nhìn thấy và có thể can thiệp vào đúng những cảnh báo lẽ ra sẽ phát hiện hoạt động này. Cùng nhóm đó là E2E_LINEAR_STORAGE_STATE_B64, một storage state của Playwright, cho phép truy cập tài khoản Linear mà không cần mật khẩu và không cần vượt MFA.
Phát hiện đến từ cộng đồng, không từ hệ thống giám sát. Lidor Machluf (Upwind Security) công bố issue xác định bản release bị xâm nhập lúc 12:06:41, tức khoảng 20 phút sau khi gói lên PyPI. LiteLLM bắt đầu ứng cứu lúc 13:02. Tổng thời gian artifact độc hại sống trên registry là khoảng hai giờ.
Liên hệ Việt Nam
- LiteLLM đang được dùng rộng rãi trong các đội AI nội bộ tại Việt Nam, thường làm gateway cho nhiều model. Đây không phải sự cố đọc để tham khảo mà là sự cố cần kiểm tra trực tiếp: có máy nào trong tổ chức từng cài 1.107.0 trong ngày 21/10 không.
- Thói quen commit file config vào repo rất phổ biến, nhất là với các dự án nội bộ chạy nhanh. Khoảng cách giữa "đã phát hiện và ghi nhận cần thu hồi" với "đã thu hồi xong" là nơi sự cố này sinh ra.
- Token GitHub cá nhân không scope vẫn được dùng nhiều trong CI ở các đội nhỏ, vì tiện và vì PAT có scope hay gây lỗi quyền giữa chừng.
- Máy lập trình viên là nơi tập trung credential. Payload này lấy token của gần như mọi package manager phổ biến, nên một máy dev nhiễm là một đợt xoay credential diện rộng, không phải một lần gỡ phần mềm.
- Pipeline release của chính tổ chức bạn cũng đáng rà lại, cụ thể là ba câu hỏi: workflow release có cho phép chỉ định repo nguồn không, ai có thể override branch protection, và quy trình phê duyệt có đang ở trạng thái "tạm nới lỏng" nào không.
Khuyến nghị
Nếu bạn từng chạy phiên bản bị ảnh hưởng
- Coi mọi credential trên máy hoặc container đó là đã lộ, và xoay chúng: SSH key, token Git và GitLab, token của mọi package manager, API key của nhà cung cấp model, biến môi trường, credential cloud.
- Không chỉ gỡ gói. Các thành phần khởi động nằm ngoài thư mục ứng dụng, nên phải kiểm tra cả cơ chế auto-start của Python trong môi trường đó. Với container, cách sạch nhất là dựng lại từ image đã xác minh.
- Tìm dấu vết backdoor: kết nối ra ngoài theo chu kỳ một giờ, và mọi request tới
litellm[.]dev. - Rà soát log cho khoảng 21/10 trở đi trên các hệ thống mà máy đó có quyền truy cập.
Bài học quy trình cho đội DevOps và bảo mật
- Biến việc thu hồi khóa thành một ticket có người chịu trách nhiệm và có hạn. "Đã ghi nhận là cần thu hồi" không phải trạng thái kết thúc. Nếu tổ chức bạn có danh sách khóa từng bị lộ mà chưa xoay, đó chính là danh sách cần xử lý trong tuần này.
- Xóa file không xóa được lịch sử Git. Khi một secret đã lên repo công khai, phải giả định nó đã bị thu thập, bất kể bạn xóa nhanh thế nào.
- Không bao giờ đặt token không scope vào một dịch vụ tự động. Mọi hệ thống tự động cần một credential có scope hẹp nhất có thể, giới hạn theo repo và theo hành động, và tốt nhất là có thời hạn ngắn.
- Khóa chặt nguồn build của pipeline release. Nếu workflow release cho phép chỉ định repo nguồn qua input, đó là một lỗ hổng thiết kế. Nếu nó hardcode, hãy bảo vệ chính file workflow bằng codeowner và không cho ai override.
- Rà soát ai có thể override branch protection và force-push. Trong sự cố này, quyền override của org owner là thứ biến mọi biện pháp còn lại thành hình thức. Cân nhắc bật enforce admin cho các nhánh quan trọng.
- Coi việc nới lỏng quy trình phê duyệt là một thay đổi có rủi ro, cần ghi nhận, có thời hạn và có cảnh báo khi hết hạn mà chưa khôi phục. Nới "tạm thời" rồi quên khôi phục là mô hình lặp lại ở rất nhiều sự cố.
- Giám sát hành vi bất thường trong CI: nhánh sống rất ngắn theo mẫu tên cố định, workflow mới xuất hiện rồi biến mất, run record bị xóa, và secret được đọc bởi workflow không liên quan tới chức năng của nó.
IOC
Theo mục Indicators trong RCA của LiteLLM, bổ sung từ phân tích của Socket qua The Hacker News.
Địa chỉ IP
69.48.229.143 # goi API Agent Platform ngay 10/10
199.91.221.39 # push vao BerriAI/project-releaser va phe duyet cong production ngay 21/10
Hạ tầng exfiltration
webhook[.]site/65a42464-3cfa-4b02-a18c-17a6d7b86f37
webhook[.]site/4da03473-a3e7-4dc2-a087-9c564b4208e1
litellm[.]dev/healthcheck # dich den cua payload, domain dang ky 13/10/2026
Tổ chức và commit trên GitHub
Berri-AI # to chuc lookalike (chu y dau gach noi), chua fork dung lam nguon build
20535d8f # commit tren fork ma ban release duoc build tu do
9cf97e9 # commit them input source_owner vao workflow release
285fbdc5 # commit revert, da bi force-push khoi main
d687b69 # commit ma main bi force-push ve
Nhánh tạm dùng để exfiltration
ci-tmp-a351cc # BerriAI/litellm
ci-tmp-be33e9 # BerriAI/litellm-auto
ci-tmp-4d8517 # dot thu hai, theo Socket
Dấu hiệu trong repo
File: .github/workflows/cache.yml (khong thuoc ve repo)
Trigger: on: [push]
Subject: "ci: bump cache config"
Mau: nhanh ci-tmp-<hex> tao tu mot commit hop le, chay workflow, roi bi xoa
Artifact bị ảnh hưởng
PyPI: litellm==1.107.0 (7 wheel, phat hanh 11:39:51-11:40:13 UTC ngay 21/10)
Docker Hub: v1.107.0, latest, main-latest, main-stable
GHCR: cac tag tuong ung
Cua so song: khoang 2 gio truoc khi bi go
Tài liệu tham khảo
- LiteLLM (Oliver Jensen, Mateo Wang) — The attack chain behind the v1.107.0 LiteLLM release (23/10/2026): https://docs.litellm.ai/blog/security-incident-oct-21-2026-attack-chain
- The Hacker News — Leaked LiteLLM Master Key Let Attackers Steal GitHub Tokens and Hijack Release Pipeline: https://thehackernews.com/2026/10/leaked-litellm-master-key-let-attackers.html
- LiteLLM-Labs/lite-harness — file cấu hình bị commit nhầm: https://github.com/LiteLLM-Labs/lite-harness/blob/e10b14a/harnesses/opencode/opencode.json
- Socket (Karlo Zanki) — phân tích các workflow độc hại dùng để exfiltration
- Microsoft Security Research — phân tích hành vi của gói litellm 1.107.0
- Lidor Machluf (Upwind Security) — issue #47826 xác định bản release bị xâm nhập
Comments
Join the discussionNo comments yet. Be the first to comment.





