iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
Security

《朝 HTB CPTS 前進:資安新手的 30 天實作筆記》系列 第 27 篇

Day 27— 不再只是複製貼上!用 Docker 搞懂 SSH 的 -L、-D、-R

  • 分享至 

  • xImage
  •  

說真的,Pivot 這個詞我第一次看到的時候完全不知道在說什麼。讀了後還是覺得好模糊

但HTB Academy 的模組練習,沒辦法公開步驟,HTB retired lab 我也沒有找到特別針對這個觀念的練習靶機。

跳板這個觀念沒有一點實作,又很難想像><

加上先前我沒建過 Docker,想趁著這次鐵人讓自己升級一下xdd所以今天會自己建 Docker 環境來練習 SSH 的三種 Port Forwarding,讓大家可以跟著做。

那我們就開始吧!


Docker 安裝

等等看 SSH 三種轉發時,會搭配實作,先把環境準備好~~~

Windows 的話,步驟是這樣:

1. 先裝 WSL2(Docker Desktop 需要它)

打開 PowerShell(以系統管理員執行),貼:

wsl --install

https://ithelp.ithome.com.tw/upload/images/20261011/20184189gVUnIvhXh2.png

裝完重開機。

2. 安裝 Docker Desktop

到 Docker Desktop ,依自己的電腦作業系統,下載對應的Docker版本,裝完後會顯示installation succeeded。
https://ithelp.ithome.com.tw/upload/images/20261011/20184189aIEikPtgff.png

3. 驗證

打開 PowerShell:

docker --version
ssh -V

https://ithelp.ithome.com.tw/upload/images/20261011/20184189lWdAXxqGTA.png

兩個都有版本號,Docker的安裝就就緒了(Windows 10/11 內建 SSH)。

開啟Docker Desktop
https://ithelp.ithome.com.tw/upload/images/20261011/20184189MUYnzYFHWU.png

出現錯誤,依指示在PowerShell(系統管理員)貼:

Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform

,然後按Enter
https://ithelp.ithome.com.tw/upload/images/20261011/201841895mhZLNxcIP.png
跑完會問我們要不要重開機,選 Y 重開。

重開機後打開 Docker Desktop,等左下角變綠色
https://ithelp.ithome.com.tw/upload/images/20261011/20184189mQqUMZ1bMn.png


練習環境

我們今天要建的 Docker Compose 會幫我們build三個容器,模擬「跳板機 + 內網服務」,讓我們練習

https://ithelp.ithome.com.tw/upload/images/20261011/20184189mjAbV2VIEV.png

建立檔案

在任意資料夾建兩個檔案:

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

https://ithelp.ithome.com.tw/upload/images/20261011/20184189PGFPOB2jbX.png
檔案內容:

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:
https://ithelp.ithome.com.tw/upload/images/20261011/201841899ZS0t8fI4c.png

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 server
  • AllowTcpForwarding 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
https://ithelp.ithome.com.tw/upload/images/20261011/20184189AJ4K3MadZl.png

三個容器跑起來後,我們的跳板機資訊:

  • Host: 127.0.0.1
  • Port: 2222
  • User: labuser
  • Password: labpass

https://ithelp.ithome.com.tw/upload/images/20261011/20184189rCqwU3HO7p.png
https://ithelp.ithome.com.tw/upload/images/20261011/20184189XsSgDHTKd4.png

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
https://ithelp.ithome.com.tw/upload/images/20261011/201841896fXJLffAjP.png

失敗,因為我們的電腦根本不在internal-web的網路裡。

但 jump-server 跟它們在同一個 lab-net,所以連得到。這就是 Pivoting 的起點:我們連不到,但跳板機連得到。Pivoting 就是利用已拿下的機器當跳板,穿越網段,連到原本連不到的地方。


SSH 的三種 Port Forwarding

Port Forwarding 就是:我連不到你,但跳板機連得到,所以我請跳板機幫我轉送流量。

用語解析(站在「打 SSH 指令的人」的角度):

  • 本地 = 攻擊機(我們這邊)
  • 遠端 = 跳板機 / 內網那邊

1. Local Port Forwarding(-L)

情境: 跳板機的內網有一個服務(Nginx Web Server),但這個服務沒有對外開放,我們想從自己的電腦直接存取它。

ssh -L [本機port]:[目標主機]:[目標port] [user@跳板機]

🔬 Lab 1:把內網 Nginx 拉到本機

https://ithelp.ithome.com.tw/upload/images/20261011/201841894jh9mQwUIM.png

跳板機後面有一個 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」。
  • 在我們的電腦上開一個 8080 port,任何打進 8080 的流量,都透過 SSH 隧道,讓跳板機幫我們轉送到 internal-web:80。

https://ithelp.ithome.com.tw/upload/images/20261011/20184189amTdCUqG9E.png

流量:

我們的電腦 :8080 → SSH 隧道 → jump-server → internal-web:80

Step 2:測試(在另開一個終端)

curl http://127.0.0.1:8080

https://ithelp.ithome.com.tw/upload/images/20261011/20184189hzqijriDZD.png

直接在瀏覽器,搜尋http://localhost:8080
https://ithelp.ithome.com.tw/upload/images/20261011/20184189nKW5tuy0ue.png

看到 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的是哪個程式
https://ithelp.ithome.com.tw/upload/images/20261011/20184189kv4SIJHdt8.png

-L 使用時機: 想操作某個特定的、已知port的遠端服務


2. Dynamic Port Forwarding(-D)

情境: 我想透過跳板機存取不同的內網服務,但不想每換一個目標,就重新建立一條 -L 隧道。

這時候就可以考慮 Dynamic Port Forwarding(-D)。

它會在我們的電腦上建立一個 SOCKS Proxy。之後,只要應用程式支援 SOCKS,或能透過適當的代理工具轉送流量,就可以經由這個入口連線到不同的目標。

不過-D 本身不會主動幫我們掃描內網;它負責的是提供動態轉送的通道,實際的連線或掃描仍然要由其他工具執行><

ssh -D [本機SOCKS port] [user@跳板機]

🔬 Lab 2:把跳板機變成 SOCKS 代理

https://ithelp.ithome.com.tw/upload/images/20261011/20184189h0HT0enfla.png

# 第一個 Terminal:建立 SOCKS proxy
ssh -D 1080 -p 2222 labuser@127.0.0.1

這行指令會在我們的電腦起一個 SOCKS5 proxy(開在 1080 port 上)。之後只要把程式設定成透過這個 proxy 連線,流量就會經過 SSH 隧道,由跳板機代為轉送到內網—不用事先指定要連哪個服務。
https://ithelp.ithome.com.tw/upload/images/20261011/20184189YYYzWVKdbS.png

# 第二個 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)。

https://ithelp.ithome.com.tw/upload/images/20261011/20184189hpUX4GNJhQ.png
有回傳Welcome to Nginx 頁面,成功哩!!!

跟 Lab 1 比較:
-L :

    1. 使用前提: 要先知道目標(internal-web:80)
    1. 解釋: 把遠端的服務「搬」到我們的 localhost 上,讓我們用 localhost:8080 就能存取原本碰不到的內網服務。

-D 不用先知道目標—我們決定要連哪個服務的時候,流量才動態轉過去。

    1. 使用時機: 想對整個內網做列舉,不鎖定特定服務
    1. 解釋: 我們的電腦上開了一個 SOCKS proxy,但它只是一個「轉送入口」——真正去連目標的是 jump-server。所以我們要告訴 proxy「幫我連到 internal-web」,讓 jump-server 知道要找誰,用 localhost 的話 jump-server 會以為我們要連它。

大部分工具(nmap、gobuster)不像 curl 支援 SOCKS,需要搭配 Proxychains 才能操作><我們明天來談談!


3. Remote Port Forwarding(-R)

情境: 我們電腦上有個服務(例如 Web Server、攻擊工具下載站),但內網的機器連不到我們(網段不通)。透過 -R,我們可以把這個服務「推」到跳板機上,讓內網那邊也能存取。

ssh -R [遠端port]:[本機主機]:[本機port] [user@跳板機]

🔬 Lab 3:把我們的服務推到跳板機上

https://ithelp.ithome.com.tw/upload/images/20261011/20184189uWpnpr6MOp.png

第一個終端機: 先在我們的電腦起一個 Web server

python -m http.server 8080

https://ithelp.ithome.com.tw/upload/images/20261011/20184189hQc748meAc.png

第二個終端機: 建立反向隧道,把我們的 8080 推到跳板機的 9090

ssh -R 9090:127.0.0.1:8080 -p 2222 labuser@127.0.0.1

https://ithelp.ithome.com.tw/upload/images/20261011/20184189ZXkVm1QgUW.png

這行指令的意思是:跳板機上開一個 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。

https://ithelp.ithome.com.tw/upload/images/20261011/20184189OqR9aTuy24.png
看到檔案列表就代表成功了!

-R 使用時機: 想讓內網碰得到我們的東西——運送攻擊工具、架接收站,甚至在條件允許時接 reverse shell。


完成練習

記得把容器關掉,不然容器們會一直在我們的背景跑,佔我們電腦的記憶體和 CPU QQ

docker compose down

https://ithelp.ithome.com.tw/upload/images/20261011/20184189LKXX59Zfwu.png


反思時光 💭

說真的,Pivoting 這個觀念我之前一直覺得很玄。

有練習需要一長串 SSH 指令,把web打回本機瀏覽器,那些 -L、-D 的參數,雖然有研究過,但還是霧颯颯~最後都是直接複製貼上使用

今天強迫自己用Docker建立練習port forwarding靶機,終於比較有感覺了xddd

可能是透過自己建練習環境,了解環境的設計、容器的設計、網路的設計,讓我在實作port forwarding時,相應思考環境的設計,更加有感!!

明天我們還會看看一些跳板相關的概念與工具~明天見啦!


上一篇
Day 26 — 不用提權就拿到 SYSTEM?Jerry 的兩個 Finding 告訴我們什麼!
下一篇
Day 28 — 隧道打通之後呢?用 Proxychains 探測內網、尋找隱藏憑證
系列文
《朝 HTB CPTS 前進:資安新手的 30 天實作筆記》 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言