說真的,Pivot 這個詞我第一次看到的時候完全不知道在說什麼。讀了後還是覺得好模糊
但HTB Academy 的模組練習,沒辦法公開步驟,HTB retired lab 我也沒有找到特別針對這個觀念的練習靶機。
跳板這個觀念沒有一點實作,又很難想像><
加上先前我沒建過 Docker,想趁著這次鐵人讓自己升級一下xdd所以今天會自己建 Docker 環境來練習 SSH 的三種 Port Forwarding,讓大家可以跟著做。
那我們就開始吧!
等等看 SSH 三種轉發時,會搭配實作,先把環境準備好~~~
Windows 的話,步驟是這樣:
1. 先裝 WSL2(Docker Desktop 需要它)
打開 PowerShell(以系統管理員執行),貼:
wsl --install

裝完重開機。
2. 安裝 Docker Desktop
到 Docker Desktop ,依自己的電腦作業系統,下載對應的Docker版本,裝完後會顯示installation succeeded。
3. 驗證
打開 PowerShell:
docker --version
ssh -V

兩個都有版本號,Docker的安裝就就緒了(Windows 10/11 內建 SSH)。
開啟Docker Desktop
出現錯誤,依指示在PowerShell(系統管理員)貼:
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
,然後按Enter
跑完會問我們要不要重開機,選 Y 重開。
重開機後打開 Docker Desktop,等左下角變綠色
我們今天要建的 Docker Compose 會幫我們build三個容器,模擬「跳板機 + 內網服務」,讓我們練習

在任意資料夾建兩個檔案:
docker-compose.yml — 整個 Lab 的「設計圖」。它告訴 Docker:要建哪些容器、每個容器用什麼 image、開哪些 port、怎麼連網。也就是我們的lab環境~~~

檔案內容:
services:
# 跳板機
# 我們會 SSH 連到這台,再透過它去存取內網服務
jump:
build: .
container_name: jump-server
hostname: jump-server
ports:
- "2222:2222"
networks:
- lab-net
# 內網 Web 服務(模擬防火牆後的服務)
# 故意不對外開 port,只能從 lab-net 內部連
internal-web:
image: nginx:alpine
container_name: internal-web
hostname: internal-web
expose:
- "80"
networks:
- lab-net
# 內網 Redis 服務(模擬防火牆後的服務)
internal-redis:
image: redis:alpine
container_name: internal-redis
hostname: internal-redis
expose:
- "6379"
networks:
- lab-net
# 把三台都接上同一個虛擬網路 `lab-net`,讓它們彼此能互通
networks:
lab-net:
driver: bridge
裡面定義了三個 service(= 三個容器):
jump:跳板機。寫 build: . 代表「去當前資料夾找 Dockerfile 自己 build」,因為 Docker Hub 上沒有現成的 image 符合我們的需求(要有 SSH server + 開啟 Port Forwarding)。對外開 port 2222 讓我們 SSH 連進來internal-web:用現成的 nginx:alpine image(Docker Hub 直接拉),不對外開 port,只有同網段的容器連得到internal-redis:用現成的 redis image,也只有內網的容器連得到為什麼
internal-web和internal-redis用expose而不是ports?
ports會把容器的 port 映射到我們的電腦上,讓我們可以透過localhost連線。
expose則不會將 port 發布到 Windows 主機上,但同一個 Docker Network 裡的容器,仍然可以直接透過服務名稱和 port 互相連線。所以這個 Lab 裡,Windows 電腦不能直接透過
localhost:80存取 Nginx,但jump-server可以透過internal-web:80連到它。這是我們刻意設計的網路隔離情境,讓我們可以練習如何透過 SSH Port Forwarding 存取原本無法直接連線的服務 ><
同個資料夾建立Dockerfile:
Dockerfile — 跳板機的「組裝說明書」。因為沒有現成的 image 剛好符合我們的需求(要有 SSH server、要開 Port Forwarding),所以自己寫一個。
檔案內容:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y openssh-server && \
mkdir /run/sshd && \
echo 'AllowTcpForwarding yes' >> /etc/ssh/sshd_config && \
echo 'GatewayPorts yes' >> /etc/ssh/sshd_config && \
useradd -m -s /bin/bash labuser && \
echo 'labuser:labpass' | chpasswd
EXPOSE 2222
CMD ["/usr/sbin/sshd", "-D", "-p", "2222"]
FROM ubuntu:22.04:以 Ubuntu 為基底apt-get install -y openssh-server:裝 SSH serverAllowTcpForwarding yes:允許 SSH Port Forwarding(預設是關的,不開的話沒辦法實作)GatewayPorts yes:讓 -R 開的 port 不只跳板機自己能連,同網段的其他容器也有機會連進來useradd + chpasswd:建立帳號 labuser,密碼設成 labpass
EXPOSE 2222 + CMD:讓 SSH 跑在 port 2222為什麼要自己寫 Dockerfile?因為我們需要 SSH server 開啟
AllowTcpForwarding,這樣後面的 Port Forwarding 才能正常運作~
建好後,在檔案的資料夾啟動Powershell,下
docker compose up -d --build
,按Enter
三個容器跑起來後,我們的跳板機資訊:
127.0.0.1
2222
labuser
labpass


internal-web 和 internal-redis 只在 Docker 的 lab-net 裡面,我們的電腦直接連不到(為了模擬「內網隔離」><
| 滲透測試環境 | 我們的Docker環境 |
|---|---|
| 攻擊機(我們的 Kali) | 我們的 Windows 電腦 |
| 跳板機(已拿下的 DMZ 機器) | jump-server(port 2222) |
| 內網服務(MySQL、Web Server) | internal-web:80 / internal-redis:6379 |
| 網段隔離(防火牆、路由規則) | Docker Network lab-net |
我們可以試試直接 curl http://internal-web
失敗,因為我們的電腦根本不在internal-web的網路裡。
但 jump-server 跟它們在同一個 lab-net,所以連得到。這就是 Pivoting 的起點:我們連不到,但跳板機連得到。Pivoting 就是利用已拿下的機器當跳板,穿越網段,連到原本連不到的地方。
Port Forwarding 就是:我連不到你,但跳板機連得到,所以我請跳板機幫我轉送流量。
用語解析(站在「打 SSH 指令的人」的角度):
情境: 跳板機的內網有一個服務(Nginx Web Server),但這個服務沒有對外開放,我們想從自己的電腦直接存取它。
ssh -L [本機port]:[目標主機]:[目標port] [user@跳板機]

跳板機後面有一個 Nginx(internal-web:80),我們的電腦連不到它,但跳板機在同一個 Docker Network 裡面連得到。
Step 1:建立通道
ssh -L 8080:internal-web:80 -p 2222 labuser@127.0.0.1
-p 2222 是指定 SSH 要連到的 port。因為跳板機的 SSH 開在 2222 而不是預設的 22,所以要用 -p 告訴 SSH「不要連 22,去連 2222」。internal-web:80。
流量:
我們的電腦 :8080 → SSH 隧道 → jump-server → internal-web:80
Step 2:測試(在另開一個終端)
curl http://127.0.0.1:8080

直接在瀏覽器,搜尋http://localhost:8080
看到 Nginx 的 HTML 歡迎頁面!!!代表 Local Port Forwarding 成功了!
我們在本機打 curl http://127.0.0.1:8080,就等於連到了 Docker 內網裡的 Nginx——這台機器我們本來是完全連不到的。
Step 3:驗證通道存在
(我是Windows作業系統呦!!!)
# PowerShell
netstat -ano | findstr 8080
-a — 顯示所有連線-n — 用數字顯示(不反查 DNS)-o — 顯示 PID(Process編號),Windows 用這個取代 Linux 的 -p
findstr 8080 — Windows 版的 grep,篩選含有 8080 的行# PowerShell
tasklist | findstr 24592 #24592是前一步看到的process編號
tasklist — 列出 Windows 上所有正在跑的程式| — pipe,把輸出丟給下一個指令findstr 24592 — 篩選含有 PID 24592 的那一行這樣就能知道佔24592 port的是哪個程式
-L 使用時機: 想操作某個特定的、已知port的遠端服務
情境: 我想透過跳板機存取不同的內網服務,但不想每換一個目標,就重新建立一條 -L 隧道。
這時候就可以考慮 Dynamic Port Forwarding(-D)。
它會在我們的電腦上建立一個 SOCKS Proxy。之後,只要應用程式支援 SOCKS,或能透過適當的代理工具轉送流量,就可以經由這個入口連線到不同的目標。
不過-D 本身不會主動幫我們掃描內網;它負責的是提供動態轉送的通道,實際的連線或掃描仍然要由其他工具執行><
ssh -D [本機SOCKS port] [user@跳板機]

# 第一個 Terminal:建立 SOCKS proxy
ssh -D 1080 -p 2222 labuser@127.0.0.1
這行指令會在我們的電腦起一個 SOCKS5 proxy(開在 1080 port 上)。之後只要把程式設定成透過這個 proxy 連線,流量就會經過 SSH 隧道,由跳板機代為轉送到內網—不用事先指定要連哪個服務。
# 第二個 Terminal :透過 proxy 連內網 Web(curl 本身支援 SOCKS)
curl -x socks5h://127.0.0.1:1080 http://internal-web:80
curl — 命令列的 HTTP 請求工具-x socks5h://127.0.0.1:1080 — -x 是 --proxy 的簡寫,用來指定 curl 要透過哪個代理伺服器發送請求。我們這指定使用 SOCKS5 代理,位於本機(127.0.0.1)的 1080 埠。socks5h 中的 h 表示 DNS 解析也交給代理端處理(而非本機解析),因為本機可能解析不到 internal-web。http://internal-web:80— 目標網址,指向一台主機名為 internal-web 的 HTTP 服務(port 80)。
有回傳Welcome to Nginx 頁面,成功哩!!!
跟 Lab 1 比較:-L :
internal-web:80)-D 不用先知道目標—我們決定要連哪個服務的時候,流量才動態轉過去。
大部分工具(nmap、gobuster)不像 curl 支援 SOCKS,需要搭配 Proxychains 才能操作><我們明天來談談!
情境: 我們電腦上有個服務(例如 Web Server、攻擊工具下載站),但內網的機器連不到我們(網段不通)。透過 -R,我們可以把這個服務「推」到跳板機上,讓內網那邊也能存取。
ssh -R [遠端port]:[本機主機]:[本機port] [user@跳板機]

第一個終端機: 先在我們的電腦起一個 Web server
python -m http.server 8080

第二個終端機: 建立反向隧道,把我們的 8080 推到跳板機的 9090
ssh -R 9090:127.0.0.1:8080 -p 2222 labuser@127.0.0.1

這行指令的意思是:跳板機上開一個 9090 port,任何連到這個連接埠的流量,都會透過 SSH 隧道轉回我們電腦的 8080。
跳板機裡沒有 curl,安裝:
docker exec jump-server apt-get update -qq && docker exec jump-server apt-get install -y -qq curl
從跳板機測(另一個cmd)
docker exec jump-server curl http://127.0.0.1:9090
docker exec jump-server= 進去跳板機容器裡面執行指令curl http://127.0.0.1:9090= 在跳板機裡連自己的 9090 port因為我們剛剛用
-R把 9090 接回我們的電腦 8080,所以跳板機連自己的 9090,流量其實會跑回我們的 Python Web Server。

看到檔案列表就代表成功了!
-R 使用時機: 想讓內網碰得到我們的東西——運送攻擊工具、架接收站,甚至在條件允許時接 reverse shell。
記得把容器關掉,不然容器們會一直在我們的背景跑,佔我們電腦的記憶體和 CPU QQ
docker compose down

說真的,Pivoting 這個觀念我之前一直覺得很玄。
有練習需要一長串 SSH 指令,把web打回本機瀏覽器,那些 -L、-D 的參數,雖然有研究過,但還是霧颯颯~最後都是直接複製貼上使用
今天強迫自己用Docker建立練習port forwarding靶機,終於比較有感覺了xddd
可能是透過自己建練習環境,了解環境的設計、容器的設計、網路的設計,讓我在實作port forwarding時,相應思考環境的設計,更加有感!!
明天我們還會看看一些跳板相關的概念與工具~明天見啦!