ローカルシークレットサービスにおける多層防御

サイバーセキュリティBeginner
オンラインで実践に進む

はじめに

多層防御(Defense in Depth)とは、複数の制御を組み合わせることで、一つのミスが即座に重要な資産の漏洩につながらないようにする考え方です。トークンチェック、ローカルネットワークバインディング、ファイル権限、そしてログ記録は、それぞれ同じサービスの異なる側面を保護します。

この実験では、機密ファイルを読み取る小規模な Python サービスを強化します。一度に一つずつ小さな変更を加え、サービスを実行し、curlでテストを行い、各層が連携して機能していることを確認します。

公開されたシークレットから始める

このステップでは、サービスを調査し、最初の制御を確認します。期待されるトークンがないリクエストは拒否され、トークンがあるリクエストは成功することを確認します。多層防御は、何が既に保護されており、何に別の層が必要かをチェックすることから始まります。

プロジェクトディレクトリに移動します:

cd /home/labex/project

準備されたファイルを確認します:

ls -l

secret_service.py、secret.txt、service-check.py があるはずです。

サービスをバックグラウンドで起動します。末尾の & は、コマンドの入力を続ける間もサービスを実行し続けるようシェルに指示するものです:

python3 secret_service.py > service.log 2>&1 &

後で安全に停止できるように、バックグラウンドプロセスの ID を保存します:

echo $! > service.pid

トークンなしでシークレットの読み取りを試みます:

curl -i http://127.0.0.1:8081/secret

レスポンスに 403 Forbidden が含まれているはずです。次に、必要なトークンヘッダーを送信します:

curl -s -H "X-Access-Token: lab-token-2026" http://127.0.0.1:8081/secret

以下のように表示されるはずです:

Project launch code: ORBIT-742

次に進む前にサービスを停止します:

kill "$(cat service.pid)"

トークン層のチェッカーを実行します:

python3 service-check.py token

期待される結果は以下の通りです:

token: complete

サービスをローカルに限定する

このステップでは、ネットワーク公開層を確認します。ローカルアクセスのみが必要なサービスは、localhost とも呼ばれる 127.0.0.1 にバインドする必要があります。そのアドレスは、同じマシンからの接続のみを受け付けます。

サービスのデフォルトホスト設定を調査します:

grep 'default="127.0.0.1"' secret_service.py

--host オプションの引数定義が表示されるはずです。このデフォルト設定により、python3 secret_service.py を実行するとサービスが localhost で起動します。

サービスを再度起動します:

python3 secret_service.py > service.log 2>&1 &

プロセス ID を保存します:

echo $! > service.pid

ss を使用して、リッスンしている TCP ソケットを調査します。-ltn オプションは、リッスン中、TCP、数値アドレスを意味します:

ss -ltn | grep ':8081'

出力には 127.0.0.1:8081 が表示されるはずであり、これはサービスがローカル専用であることの証拠です:

LISTEN ... 127.0.0.1:8081 ...

サービスを停止します:

kill "$(cat service.pid)"

localhost 層のチェッカーを実行します:

python3 service-check.py localhost

期待される結果は以下の通りです:

localhost: complete

シークレットファイルを制限する

このステップでは、ファイルシステムの権限層を追加します。サービスにバグがあったとしても、シークレットファイルがすべてのローカルアカウントから読み取れるべきではありません。

現在のファイル権限を確認します:

ls -l secret.txt

ファイルは、改善できるように最初は広範な権限で設定されています。chmod 600 を使用して、所有者のみが読み書きできるようにします:

chmod 600 secret.txt

権限を再度確認します:

ls -l secret.txt

権限ビットが -rw------- で始まっているはずです:

-rw------- ... secret.txt

この層のチェッカーを実行します:

python3 service-check.py permissions

期待される結果は以下の通りです:

permissions: complete

拒否されたリクエストをログに記録する

このステップでは、検出層をテストします。不正なリクエストをブロックすることは有用ですが、その拒否を記録することで、誰かが保護されたルートにアクセスしようとした証拠を運用者に提供できます。

このステップをクリーンに開始するために、古いログファイルを削除します:

rm -f access.log

サービスを起動します:

python3 secret_service.py > service.log 2>&1 &

プロセス ID を保存します:

echo $! > service.pid

トークンなしでリクエストを送信します:

curl -s http://127.0.0.1:8081/secret

レスポンスボディは以下のようになるはずです:

forbidden

アクセスログを表示します:

cat access.log

ログには rejected、/secret、および missing_or_bad_token が含まれているはずです。

サービスを停止します:

kill "$(cat service.pid)"

ログ記録層のチェッカーを実行します:

python3 service-check.py logging

期待される結果は以下の通りです:

logging: complete

各層を統合して検証する

このステップでは、多層防御の結果を全体的に検証します。サービスはトークンを要求し、デフォルトでローカルに留まり、所有者のみの権限でシークレットファイルを保護し、拒否されたリクエストをログに記録する必要があります。

フルチェッカーを実行します:

python3 service-check.py all

すべての層が完了しているはずです:

token: complete
localhost: complete
permissions: complete
logging: complete

最後にサービスを起動します:

python3 secret_service.py > service.log 2>&1 &

プロセス ID を保存します:

echo $! > service.pid

許可されたリクエストに対してシークレットが正しく機能することを確認します:

curl -s -H "X-Access-Token: lab-token-2026" http://127.0.0.1:8081/secret

以下のように表示されるはずです:

Project launch code: ORBIT-742

サービスを停止します:

kill "$(cat service.pid)"

この最終チェックは重要です。なぜなら、多層防御は意図された正当な利用を妨げることなく、サービスを保護する必要があるからです。

まとめ

一つの小規模なサービスに対して制御を重ねることで、多層防御を実践しました。トークンの必須化、ローカル専用バインディング、所有者限定のファイル権限、そして拒否されたアクセスのログ記録を検証しました。

重要な教訓は、各層が異なる障害ポイントを保護しつつ、正当なサービス動作は維持されるということです。