本記事は、NTTコミュニケーションズ Advent Calendar 2019 15日目の記事です。
昨日は @Mahito さんの記事、 保育園にChaos Engineeringを提案した話 でした。
はじめに
先日公開されたNTTコミュニケーションズの開発者ブログの記事にもあったように、NTTコミュニケーションズグループではグループ社員を対象としたセキュリティコンテスト「ComCTF」を開催しています。
私は決勝で出題した「Pentest」という問題を作問しました。
Pentest(ペンテスト)は、ペネトレーションテストと呼ばれるセキュリティテストの略称で、明確な意図を持った攻撃者にその目的が達成されてしまうかを検証します。 1
この問題は仮想の企業ネットワークに侵入し、複数のサーバの脆弱性を悪用、最終的に重要データが保存されているサーバから情報を入手できるかを問う問題で、まさに攻撃者の気持ちになって重要なデータを入手するという目的が達成可能かどうかを検証してもらう、ペネトレーションテストをしてもらう問題でした。
今回、この問題の基盤を作るにあたり、Dockerを使ってペネトレーションテストのトレーニング環境を構築できる Naumachia と呼ばれるOSSを使用しました。
この記事では、Naumachiaの概要と構築方法、この基盤を使ったペネトレーションテストのトレーニング環境構築について紹介します。
Naumachia とは
Naumachiaは、Dockerを使ってクローズドネットワークと脆弱なサーバを構築できるOSSです。
私がこのOSSを知ったきっかけは、Texas A&M University が主催する TAMUctf 19 と呼ばれるCTFです。NetworkPentest
というジャンルの問題の基盤にこの Naumachia が使用されています。
なお、このCTFの問題はGitHubで公開されているので興味がある方は見てみてください。
Naumachia には、以下の機能が実装されています。
- ユーザごとにトレーニング用のDockerコンテナとネットワークを作成、管理
- トレーニング環境を他のユーザの環境と分離
- OpenVPNを使ったトレーニング環境ネットワークへのL2レベルの接続の提供
これにより、以下のようなインターネットからVPNの接続情報を持つユーザのみアクセス可能な専用のトレーニング環境を構築できます。
例えば、Drupalの任意コード実行の脆弱性(CVE-2018-7600)を使ってシステムに侵入できるか試すような問題を作ろうとした場合、インターネットからアクセスできる問題サーバを作ろうとすると、インターネット上の脆弱性のスキャンに引っかかり、最悪サーバが踏み台にされる可能性もあります。
Naumachia を使えば、インターネットからはVPNの接続情報を持つユーザのみ問題に挑戦できるので、そのようなリスクなく作問できます。
また、L2レベルでのアクセスも提供してくれるので、ARPスプーフィングにような同一LAN内で行われる攻撃手法を試すような問題も作ることができます。
詳しい機能や仕組みは、Naumachia のREADME に書いてあるので、こちらを読むと良いと思います。
Naumachia の構築
ここからは Naumachia の構築手順を紹介します。
動作環境
READMEには、
Obtain a Linux server (tested on Ubuntu 16.04 and 18.04)
と書いてあるので、使うOSは Ubuntu 18.04 がベストでしょう。
しかし、今回のコンテストでは諸事情ありCentOS 7を使ったので、CentOS 7 で検証した構築手順を書いておきます。構築手順を検証したOSの情報は以下のとおりです。
# uname -a
Linux localhost.localdomain 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
Naumachia のインストール準備
Naumachiaを構築するには、docker
, docker-compose
, Python3
, pip3
が必要となるので、これらをまずインストールする必要がある。
その後、GitHubにあるNaumachiaのリポジトリからソースコードをCloneし、requirements.txt
に書かれているPython3のライブラリをインストールする。
dockerのインストール
# yum install -y yum-utils device-mapper-persistent-data lvm2
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# yum install -y docker-ce docker-ce-cli containerd.io
# systemctl start docker
# systemctl enable docker
docker-composeのインストール
# curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
Python3.6、pip3のインストール
# yum install -y https://centos7.iuscommunity.org/ius-release.rpm
# yum install python36u python36u-libs python36u-devel python36u-pip
GitHubからソースコードをCloneする
# git clone https://github.com/nategraf/Naumachia.git
Python3のライブラリをインストール
# cat requirements.txt
jinja2==2.10.1
PyYAML==4.2b4
requests==2.21.0
nose2==0.8.0
pytest==4.5.0
hypothesis==4.23.5
# pip3 install -r requirements.txt
Naumachia のセットアップ
トレーニング用のDockerコンテナとネットワークの準備
Naumachiaでユーザに対して提供するトレーニング用のDockerコンテナとネットワークは、 docker-compose.yml
で定義します。
Naumachia起動後、OpenVPNでユーザが接続してくると、このdocker-compose.yml
をもとに自動でdocker-composeが実行されトレーニングの環境が構築されます。
今回の説明では、Naumachiaの問題集(nategraf/Naumachia-challenges) から、example
というチャレンジを動かしてみます。
問題集のGitHubのリポジトリをCloneし、Naumachiaディレクトリ内の challenges
ディレクトリに example
チャレンジに必要なファイルをコピーします。
# git clone https://github.com/nategraf/Naumachia-challenges
# mkdir Naumachia/challenges
# cp -r Naumachia-challenges/example Naumachia/challenges
ちなみに、example
の docker-compose.yml
は以下のとおりです。
bob
と alice
という2つのコンテナと、 default
という1つのネットワークが作成されるのがわかります。
version: '2.4'
# The file defines the configuration for simple Nauachia challenge where a
# sucessful man-in-the-middle (MTIM) attack (such as ARP poisoning) provides a
# solution
# If you are unfamiliar with docker-compose this might be helpful:
# * https://docs.docker.com/compose/
# * https://docs.docker.com/compose/compose-file/
#
# But the gist is that the services block below specifies two containers, which
# act as parties in a vulnerable communication
services:
bob:
build: ./bob
image: naumachia/example.bob
environment:
- CTF_FLAG=fOOBaR
restart: unless-stopped
networks:
default:
ipv4_address: 172.30.0.2
alice:
build: ./alice
image: naumachia/example.alice
depends_on:
- bob
environment:
- CTF_FLAG=fOOBaR
restart: unless-stopped
networks:
default:
ipv4_address: 172.30.0.3
networks:
default:
driver: l2bridge
ipam:
driver: static
config:
- subnet: 172.30.0.0/28
カスタマイズされたDocker libnetwork Driverのインストール・起動
example チャレンジの docker-compose.yml
の networks
で指定されているdriverを見ると、 l2bridge
、 static
という通常とは異なるdriverが指定されているのがわかると思います。
上記のNaumachiaのチャレンジでは、すべてのユーザに同じ環境を提供、安全なトレーニング環境を構築するために、カスタマイズされたDocker libnetowrk driverを使用しています。
https://github.com/nategraf/l2bridge-driver
https://github.com/nategraf/static-ipam-driver
これを使うことで、デフォルトのDocker libnetowrk driverではできない以下のことが可能となります。
- 重複したIPサブネットの許可
- コンテナネットワークからインターネットへのアクセス禁止
ここでUbuntuやDebianであれば、サービスとしてDriverをインストールする方法が紹介されてますが、今回はCentOSであったため以下のようなスクリプトを作成し、無理やりDriverのプログラムを動かしました。(sysv.sh をRedHat系のOS向けに書き直す余裕はなかった…
# Download the static-ipam driver to usr/local/bin
if [ ! -e /usr/local/bin/l2bridge ]; then
echo "[!] l2bridge driver is not installed"
echo "[+] Download the l2bridge driver to usr/local/bin"
curl -L https://github.com/nategraf/l2bridge-driver/releases/latest/download/l2bridge-driver.linux.amd64 -o /usr/local/bin/l2bridge
chmod +x /usr/local/bin/l2bridge
else
echo "[*] l2bridge driver is installed"
fi
# Download the static-ipam driver to usr/local/bin
if [ ! -e /usr/local/bin/static-ipam ]; then
echo "[!] static-ipam driver is not installed"
echo "[+] Download the static-ipam driver to usr/local/bin"
curl -L https://github.com/nategraf/static-ipam-driver/releases/latest/download/static-ipam-driver.linux.amd64 -o /usr/local/bin/static-ipam
chmod +x /usr/local/bin/static-ipam
else
echo "[*] static-ipam driver is installed"
fi
# Activate the service
echo "[+] Startup the servicies"
if [ ! -e /run/docker/plugins/l2bridge.sock ]; then
nohup /usr/local/bin/l2bridge > /dev/null 2>&1 &
echo "[*] Done: l2bridge"
else
echo "[!] Started l2bridge driver"
fi
if [ ! -e /run/docker/plugins/static.sock ]; then
nohup /usr/local/bin/static-ipam > /dev/null 2>&1 &
echo "[*] Done: static-ipam"
else
echo "[!] Started static-ipam driver"
fi
sleep 0.5
# Verify that it is running
echo "[+] Verify that it is running"
echo ""
echo "[*] stat /run/docker/plugins/l2bridge.sock"
stat /run/docker/plugins/l2bridge.sock
# File: /run/docker/plugins/l2bridge.sock
# Size: 0 Blocks: 0 IO Block: 4096 socket
# ...
echo ""
echo "[*] stat /run/docker/plugins/static.sock"
stat /run/docker/plugins/static.sock
# File: /run/docker/plugins/static.sock
# Size: 0 Blocks: 0 IO Block: 4096 socket
# ...
echo ""
echo "[*] Complete!!"
なお、シャットダウンするとDriverのプログラムは停止し、再起動時に立ち上がらないので、再起動時には必ずこれを実行する必要があります。
bridgeを通るパケットのフィルタリング無効
bridgeを通るパケットがフィルタ対象になっているとうまく動かないことがあるようなので、 disable-bridge-nf-iptables.sh
を実行します。
echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 0 > /proc/sys/net/bridge/bridge-nf-call-ip6tables
config.yml の修正
config.example.yml
を config.yml
にコピーして一部を書き換えます。
書き換えるのは、 challenges
の部分。
変更する点は以下のとおり。
-
files:
に、「トレーニング用のDockerコンテナとネットワークの準備」で作ったdocker-compose.yml
ファイルの場所を書く -
commonname:
にサーバのアドレス(ドメイン、IPアドレス)を書く
# [required] Configurations for each challenge
challenges:
# [required] An indiviual challenge config. The key is the challenge name
# This should be a valid unix filename and preferably short
example:
# [default: 1194] The exposed external port for this challenges OpenVPN server
port: 2000
# [default: [{challenge name}/docker-compose.yml] ] The compose files to which define this challenge
# Paths should be relative to the challenges directory
files:
- example/docker-compose.yml
# [default: {challenge name}.{domain}] The commonname used for the OpenVPN's certificates
# This should be the domain name or ip that directs to this challenge
commonname: 192.168.91.130
# [default: None] If set, the OpenVPN management interface will be opened on localhost and the given port
openvpn_management_port: null
# [default: None] If set, the OpenVPN server will inform the client what IPv4 address and mask to apply to their tap0 interface
ifconfig_push: 172.30.0.14/28
Naumachiaのビルド
configure.py
を実行すると、config.yml
に書かれている内容をもとにNaumachiaをbuildします。
これにより、Naumachiaの docker-compose.yml
やOpenVPNの鍵や証明書、設定のファイルが自動で生成されます。
# ./configure.py
[INFO] Using config from /root/Naumachia/config.yml
[INFO] Using easyrsa installation at /root/Naumachia/tools/EasyRSA-v3.0.6/easyrsa
[INFO] Rendered /root/Naumachia/docker-compose.yml from /root/Naumachia/templates/docker-compose.yml.j2
[INFO] Configuring 'example'
[INFO] Created new openvpn config directory /root/Naumachia/openvpn/config/example
[INFO] Initializing public key infrastructure (PKI)
[INFO] Building certificiate authority (CA)
[INFO] Generating Diffie-Hellman (DH) parameters
[INFO] Building server certificiate
[INFO] Generating certificate revocation list (CRL)
[INFO] Rendered /root/Naumachia/openvpn/config/example/ovpn_env.sh from /root/Naumachia/templates/ovpn_env.sh.j2
[INFO] Rendered /root/Naumachia/openvpn/config/example/openvpn.conf from /root/Naumachia/templates/openvpn.conf.j2
また、競技用のコンテナもbuildしておきます。
# docker-compose -f ./challenges/example/docker-compose.yml build
競技環境の実行
ここまでの作業を行うと、 docker-compose.yml
が自動で生成されているはずなので、buildしてupします。
# docker-compose build
# docker-compose up -d
この状態で docker ps -a
で立ち上がってるコンテナを見てみると、以下のようなコンテナが立ち上がっているはずです。
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dd9e858277bd naumachia/manager "python -m app" 27 seconds ago Up 25 seconds build_manager_1
f80057d9dc2e naumachia/openvpn "/scripts/naumachia-…" 27 seconds ago Up 25 seconds 0.0.0.0:2000->1194/udp build_openvpn-example_1
86fc3709d4e3 redis:alpine "docker-entrypoint.s…" 27 seconds ago Up 26 seconds build_redis_1
a0f45e1f292a naumachia/registrar "gunicorn -c python:…" 27 seconds ago Up 26 seconds 0.0.0.0:3960->3960/tcp build_registrar_1
9d1ef7902351 alpine "/bin/true" 27 seconds ago Exited (0) 27 seconds ago build_bootstrapper_1
ユーザへ配布するOpenVPN設定ファイルの生成
ユーザがOpenVPNサーバに接続し、トレーニング環境にアクセスするためには設定ファイルが必要です。
これもNaumachiaが自動で生成してくれます。
生成する方法には、以下の2つの方法があります。
- registrar CLIのPythonスクリプトを使用する
- registrar serverのREST APIを使用する
-
3960/tcp
で待ち受けてるコンテナがそう - 認証がないので外部に公開するときは注意
-
今回はregistrar CLIのPythonスクリプトを使って、設定ファイルを作成、取得します。
registrar-cliを以下のように実行すると、OpenVPNの鍵、サーバ証明書、認証局の証明書を含んだOpenVPNの設定ファイルが作成できるので、これをユーザに配ります。
# ./registrar-cli example add user1
# ./registrar-cli example get user1 > user1.ovpn
# cat user1.ovpn
client
nobind
dev tap
remote-cert-tls server
float
explicit-exit-notify
remote 192.168.91.130 2000 udp
<key>
-----BEGIN PRIVATE KEY-----
(省略)
-----END PRIVATE KEY-----
</key>
<cert>
-----BEGIN CERTIFICATE-----
(省略)
-----END CERTIFICATE-----
</cert>
<ca>
-----BEGIN CERTIFICATE-----
(省略)
-----END CERTIFICATE-----
</ca>
key-direction 1
cipher AES-256-CBC
auth SHA256
comp-lzo
構築したトレーニング環境で遊んでみる
それでは、構築したトレーニング環境にアクセスして遊んでみましょう。
検証に使用する環境
今回はユーザ側はデフォルトでOpenVPNのクライアントとペネトレーションテスト用のツールがインストールされている Kali Linux
を使用します。
# grep VERSION /etc/os-release
VERSION="2018.1"
VERSION_ID="2018.1"
OpenVPNでトレーニング環境へ接続
生成したOpenVPNの設定ファイルを使って、Naumachia上のトレーニング環境にアクセスします。
Initialization Sequence Completed
と出れば成功です!
# openvpn user1.ovpn
Sun Dec 15 06:33:45 2019 OpenVPN 2.4.5 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Mar 4 2018
Sun Dec 15 06:33:45 2019 library versions: OpenSSL 1.1.0h 27 Mar 2018, LZO 2.08
Sun Dec 15 06:33:45 2019 TCP/UDP: Preserving recently used remote address: [AF_INET]192.168.91.130:2000
Sun Dec 15 06:33:45 2019 UDP link local: (not bound)
Sun Dec 15 06:33:45 2019 UDP link remote: [AF_INET]192.168.91.130:2000
Sun Dec 15 06:33:45 2019 [192.168.91.130] Peer Connection Initiated with [AF_INET]192.168.91.130:2000
Sun Dec 15 06:33:46 2019 Options error: Unrecognized option or missing or extra parameter(s) in [PUSH-OPTIONS]:1: dhcp-renew (2.4.5)
Sun Dec 15 06:33:46 2019 TUN/TAP device tap0 opened
Sun Dec 15 06:33:46 2019 do_ifconfig, tt->did_ifconfig_ipv6_setup=0
Sun Dec 15 06:33:46 2019 /sbin/ip link set dev tap0 up mtu 1500
Sun Dec 15 06:33:46 2019 /sbin/ip addr add dev tap0 172.30.0.14/28 broadcast 172.30.0.15
Sun Dec 15 06:33:46 2019 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
Sun Dec 15 06:33:46 2019 Initialization Sequence Completed
ifconfigでインターフェースの状態を見てみると、tap0
というインターフェースが作成され、172.30.0.14
というIPアドレスが割り当てられていると思います。
# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.91.129 netmask 255.255.255.0 broadcast 192.168.91.255
inet6 fe80::20c:29ff:fe18:a0c8 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:18:a0:c8 txqueuelen 1000 (Ethernet)
RX packets 14781 bytes 9483880 (9.0 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6484 bytes 645921 (630.7 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 31612 bytes 10003030 (9.5 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 31612 bytes 10003030 (9.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
tap0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.30.0.14 netmask 255.255.255.240 broadcast 172.30.0.15
inet6 fe80::c0d8:eeff:fe38:d79b prefixlen 64 scopeid 0x20<link>
ether c2:d8:ee:38:d7:9b txqueuelen 100 (Ethernet)
RX packets 16 bytes 1272 (1.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 21 bytes 1622 (1.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
このとき、NaumachiaのサーバでDockerコンテナとネットワークの状態を見ると、新たに user1_example_
というプレフィックスがついたコンテナとネットワークが作成されているはずです。
これがユーザ専用のトレーニング用のコンテナとネットワークです。ユーザが増えると、コンテナとネットワークも増えていきます。
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
17c4ef2ccbb9 naumachia/example.alice "python /app/alice.py" About a minute ago Up About a minute user1_example_alice_1
ff271a01eba9 naumachia/example.bob "python /app/bob.py" About a minute ago Up About a minute user1_example_bob_1
dd9e858277bd naumachia/manager "python -m app" 32 minutes ago Up 32 minutes build_manager_1
f80057d9dc2e naumachia/openvpn "/scripts/naumachia-…" 32 minutes ago Up 32 minutes 0.0.0.0:2000->1194/udp build_openvpn-example_1
86fc3709d4e3 redis:alpine "docker-entrypoint.s…" 32 minutes ago Up 32 minutes build_redis_1
a0f45e1f292a naumachia/registrar "gunicorn -c python:…" 32 minutes ago Up 32 minutes 0.0.0.0:3960->3960/tcp build_registrar_1
9d1ef7902351 alpine "/bin/true" 32 minutes ago Exited (0) 32 minutes ago build_bootstrapper_1
# docker network ls
NETWORK ID NAME DRIVER SCOPE
743f747a01b3 bridge bridge local
7017ddd37ba8 build_default bridge local
dce5de7a2fa2 build_internal bridge local
de7c1746cc32 host host local
6dc0c89a9ccf none null local
b1649b2f2e93 user1_example_default l2bridge local
ARPスプーフィングを試してみる
この問題は example の docker-compose.yml に書かれているとおり、ARPスプーフィングのようなMITM(中間者攻撃)を行う問題です。
The file defines the configuration for simple Nauachia challenge where a sucessful man-in-the-middle (MTIM) attack (such as ARP poisoning) provides a solution
今回は 172.30.0.2
と 172.30.0.3
のIPアドレスを持つ2台の端末がいるので、この2台が行っている通信をARPスプーフィングして盗聴することを試みます。
ARPスプーフィングの仕組みや具体的な手法についてはここでは詳しくは説明しませんが、成功すると以下のようにパケットキャプチャすることで、 172.30.0.2
と 172.30.0.3
の2つのホスト間の通信が見えてしまいます。
# tcpdump -i tap0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tap0, link-type EN10MB (Ethernet), capture size 262144 bytes
06:40:47.791591 ARP, Reply 172.30.0.2 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:48.042999 ARP, Reply 172.30.0.3 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:48.696193 IP 172.30.0.3.55672 > 172.30.0.2.5005: UDP, length 30
06:40:49.792320 ARP, Reply 172.30.0.2 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:50.044301 ARP, Reply 172.30.0.3 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:51.700769 IP 172.30.0.3.55672 > 172.30.0.2.5005: UDP, length 30
06:40:51.793616 ARP, Reply 172.30.0.2 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:52.044971 ARP, Reply 172.30.0.3 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:53.794367 ARP, Reply 172.30.0.2 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:54.045958 ARP, Reply 172.30.0.3 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:54.705584 IP 172.30.0.3.55672 > 172.30.0.2.5005: UDP, length 30
06:40:55.795642 ARP, Reply 172.30.0.2 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
06:40:56.047136 ARP, Reply 172.30.0.3 is-at 3e:d6:f2:ca:92:81 (oui Unknown), length 28
L2で接続されている同じネットワークにいる場合、このようなリスクがあることを認識しなければいけません。
おわりに
この記事では、NaumachiaというOSSを使ったペネトレーションテストのトレーニング環境の構築について紹介しました。
ペネトレーションテストは実際に攻撃を行うことなので、それをトレーニングすることは攻撃者を養成したいのか?と思われる方がいるかもしれませんが、そうではありません。
近年のサイバー攻撃は高度になってきており、防御側の視点だけでは守りきれないことが増えてきています。
このような攻撃から守るためには、実際の攻撃手法を知り、それに合わせた効果的な防御手法を考えることが重要です。
トレーニングで手を動かして実際の攻撃手法を試すことで攻撃手法への理解も進み、より効率のよい防御ができる人材を育成できるのでないかと考えています。
明日は @nyakuo さんの担当となります。
それでは良いお年を!
-
ペネトレーションテストについて by 脆弱性診断士スキルマッププロジェクト ( https://github.com/ueno1000/about_PenetrationTest ) ↩