🚪

SadServers解説 Easy No.4 「ノックしてください」

に公開

https://ja.wikipedia.org/wiki/台北市

問題概要

問題URL

https://sadservers.com/scenario/taipei

シナリオ

ノックしてください

問題詳細

80番ポートで待ち受けているWebサーバーが Port Knocking で保護されています。curl localhost が実行できるように、ノックするポートを特定しましょう。

今回は、Port Knockingの保護は、単一のポートに対して SYN パケットを送ることで解除することができます。複数ポートを特定の順番でノックする必要はありません。

解決判定

curl localhost がメッセージを返します。そのメッセージに改行を含めて md5sum を計算した結果はfe474f8e1c29e9f412ed3b726369ab65 になります。

問題解決の方針

【表示する】

まず現象を観察して切り分けましょう。通信が80番ポートに届かないのか、届いているが応答できていないのかで、対応方針が変わります。

問題通り80番ポートに通信が届いているがアクセスできないことがわかったら、設定ファイルから直接読み取れるかどうか試してみます。それが難しければスクリプトで片っ端からポートノックする(根性の)方針に切り替えましょう。

具体的な解決の段取りを表示する
  1. curl localhost の内容と、80番ポートまわりの状態を確認して、Webサーバー停止なのか到達制御なのかを切り分ける。
  2. 設定ファイルや利用可能なツールを確認し、対象ポートを直接特定できるかを試す。
  3. 直接特定できない場合は、単一ポートを順にノックするスクリプトを作成・実行する。
  4. 当たりのポートが見つかったら、curl localhost がメッセージを返すことを確認し、さらに md5sum の値も確認する。

ヒント

オリジナルヒント

ヒント1

念のため、「80番ポートに届いていない」のか、「届いているがWebサーバーがうまく応答していない」のかを確認しましょう。

まずは curl の失敗内容を見て、次に待ち受けポートを確認します。80番ポートで何かが待ち受けているのに curl が失敗するなら、単純な Web サーバー停止だけでは説明しにくくなります。

実行コマンド

curl は現在の症状を確認するために使います。ss は待ち受け中のTCPポートを見るために使います。

$ curl -v localhost
*   Trying 127.0.0.1:80...
* connect to 127.0.0.1 port 80 failed: Connection refused
* Failed to connect to localhost port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to localhost port 80: Connection refused
$ ss -natu
Netid      State          Recv-Q      Send-Q                             Local Address:Port                  Peer Address:Port      Process      
udp        UNCONN         0           0                                      127.0.0.1:323                        0.0.0.0:*                      
udp        UNCONN         0           0                                        0.0.0.0:68                         0.0.0.0:*                      
udp        UNCONN         0           0                [fe80::46d:6bff:feba:34e7]%ens5:546                           [::]:*                      
udp        UNCONN         0           0                                          [::1]:323                           [::]:*                      
tcp        LISTEN         0           128                                      0.0.0.0:22                         0.0.0.0:*                      
tcp        LISTEN         0           511                                      0.0.0.0:80                         0.0.0.0:*                      
tcp        LISTEN         0           128                                         [::]:22                            [::]:*                      
tcp        LISTEN         0           4096                                           *:6767                             *:*                      
tcp        LISTEN         0           511                                         [::]:80                            [::]:*                      
tcp        LISTEN         0           4096                                           *:8080                             *:*                      
tcp        TIME-WAIT      0           0                            [::ffff:10.1.12.79]:8080           [::ffff:10.1.0.123]:58324                  
...

ss では80番ポートが待ち受けていますが、curl では Connection refused になっています。

ヒント2

次は、どのポートをノックすればよいかを直接知れないかを試してみます。

実行コマンド
$ ps aux | grep knock
root         632  0.0  0.2   8504  1216 ?        Ss   05:26   0:00 /usr/sbin/knockd -i lo
admin        760  0.0  0.1   5264   640 pts/1    S<+  05:28   0:00 grep knock
$ find /etc -name knockd.conf
find: ‘/etc/sudoers.d’: Permission denied
/etc/knockd.conf
find: ‘/etc/ssl/private’: Permission denied
$ less /etc/knockd.conf
: can't read /etc/knockd.conf: Permission denied

今回は /etc/knockd.conf を読む権限がありませんでした。設定から読み取るのは諦め、実際に単一ポートを順に試す方針に切り替えます。

ヒント3

knock コマンドで0番から65535番まで順にノックします。手作業では現実的ではないので、自動でひとつずつポートをノックしそのたびに curl localhost が通るかを確認するスクリプトを作成します。

実行コマンド

1000ポートごとに進捗を表示しながら、全ポートを順にノックし、curl localhost が通ったらその場で止めるスクリプトを作成・実行します。

$ vi port-knock.sh
$ chmod +x port-knock.sh
$ cat port-knock.sh
#!/bin/bash

for port in $(seq 0 65535)
do
  if [ $((port % 1000)) -eq 0 ]
  then
    echo "trying $port ~ $((port + 1000))"
  fi

  knock localhost "$port"

  if curl localhost >/dev/null 2>&1
  then
    echo "FOUND_PORT=$port"
    exit 0
  fi
done

echo "not found"

$ ./port-knock.sh
trying 0 ~ 1000
trying 1000 ~ 2000
trying 2000 ~ 3000
trying 3000 ~ 4000
trying 4000 ~ 5000
trying 5000 ~ 6000
trying 6000 ~ 7000
trying 7000 ~ 8000
FOUND_PORT=7007

今回の環境では、ノック対象ポートは 7007 だったようです。

今回のスクリプトでは、 curl localhost の標準出力を2>&1でリダイレクトし、成否だけを見ています。失敗時の表示を毎回出さないようにして、探索結果を読みやすくするためです。

ヒント4

正しいポートが分かったら、curl localhost が通るようになったか、curl localhostのハッシュ値がfe474f8e1c29e9f412ed3b726369ab65と一致するかを確認します。

実行コマンド

応答とハッシュ値を確認します。

$ curl localhost
Who is there?$ echo $(curl localhost) | md5sum
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    13  100    13    0     0   3250      0 --:--:-- --:--:-- --:--:--  3250
fe474f8e1c29e9f412ed3b726369ab65  -

Who is there? が返るので、7007へのノックで80番ポートに到達できることは確認できています。
また、ハッシュ値もfe474f8e1c29e9f412ed3b726369ab65になりました。

SadServers公式ヒント(翻訳)

ヒント1

knock ユーティリティを使えます。たとえば3000番ポートをノックするなら knock localhost 3000 です。netcat (nc) と nmap も使えます。ただし、nmap には root 権限が必要なオプションがあり、今回は使えません。

ヒント2

全ポートを順番にノックする BASH スクリプトを書くこともできます。

ヒント3

おそらく最も速いのは、nmap で全ポートを対象に調べる方法です。たとえば nmap -p- localhost のように実行します。

参考

ヒント3のスクリプトで使った >/dev/null 2>&1 は、コマンドの標準出力と標準エラー出力をどちらも捨てる書き方です。>/dev/null で標準出力を捨て、2>&1 でエラー出力をそこへ合流させています。コマンドの成否だけをスクリプト内で判定したい場面でよく使います。

 
「いきなり問題を解き始めても調べるばかりになってしまう…」 「やりたいことが分かっても、コマンドが分からない…」 という方は、下記の記事でLinuxのコマンドを復習してから、SadServersの問題に取り掛かってみてはいかがでしょうか。
https://zenn.dev/comf_nakamura/articles/linux_command

 

問題一覧はこちら

https://zenn.dev/comf_nakamura/articles/sadservers_sitemap

Discussion