AWSでCTFの大会を開催するまでの道のり
CTFの大会を開催するにあたり、色々工夫しなければならない部分がありました。
今後、CTFを開催しようと考えている方の手助けになれば幸いです。
※あくまでも1つの方法として読んで頂けたらと思います。
前提
CTFサーバー構築手順
- AWSでインスタンスを立てる
- ドメイン(サブドメイン)の設定
- インスタンス内でパブリックDNSを取得するスクリプトの用意
- CTFdのインストール
- HTTPS化
- nginxでリバースプロキシを構築
- PHPの設定
- CTFdのセットアップ
AWSでインスタンスを立てる
EC2は規模に応じてスペックを調整します。
今回は小規模で人数にするとせいぜい20人程度なので、そんなにスペックは要りません。
お金に余裕がある場合はスペックを高めにしてもいいかもしれませんが、私はあまり余裕があるとは言えないのである程度絞ります。
今回はt3を利用します。料金表(東京リージョン)はこちら。
※為替レートを指定できます
$1 = 円
スペック | vCPU | メモリ | 料金(Hour) | 料金(Day) | 料金(Week) | 料金(Month) |
---|---|---|---|---|---|---|
t3.nano | 2 | 0.5GB | 0.0068$ 円 |
0.1632$ 円 |
1.1424$ 円 |
5.0592$ 円 |
t3.micro | 2 | 1GB | 0.0136$ 円 |
0.3264$ 円 |
2.2848$ 円 |
10.1184$ 円 |
t3.small | 2 | 2GB | 0.0272$ 円 |
0.6528$ 円 |
4.5696$ 円 |
20.2368$ 円 |
t3.medium | 2 | 4GB | 0.0544$ 円 |
1.3056$ 円 |
9.1392$ 円 |
40.4736$ 円 |
t3.large | 2 | 8GB | 0.1088$ 円 |
2.6112$ 円 |
18.2784$ 円 |
80.9472$ 円 |
t3.xlarge | 4 | 16GB | 0.2176$ 円 |
5.2224$ 円 |
33.5568$ 円 |
161.8944$ 円 |
t3.2xlarge | 8 | 32GB | 0.4352$ 円 |
10.4448$ 円 |
73.1136$ 円 |
323.7888$ 円 |
見ての通り意外と安いです。
気にするべきところはメモリぐらいでしょうか。
ポートを指定して常時動かしておかなければならないような問題や、Web系の問題などが多い場合はメモリに少し余裕を持ったほうがいいでしょう。
今回はsmallにします。
問題が少ない場合はmicroで十分だと思われます。
smallでも1週間あたり500円もかかりません(ドル円で地味に変わります)。
注意点として、この金額はインスタンスが動いている間常に発生します。すなわち、サーバーを構築している期間などにもお金が掛かります。
料金は開催期間+αで計算しましょう。
それでは実際にインスタンスを立てていきます。
スペックは以下の通り。
AMI | Ubuntu Server 18.04 LTS |
---|---|
インスタンスタイプ | t3.micro |
ストレージ | 汎用SSD(gp2) 8GB |
セキュリティグループでは以下のように設定します。
タイプ | プロトコル | ポート範囲 | ソース |
---|---|---|---|
SSH | TCP | 22 | マイIP ***.***.***.*** |
HTTP | TCP | 80 | カスタム 0.0.0.0/0 |
HTTPS | TCP | 443 | カスタム 0.0.0.0/0 |
すべてのTCP | TCP | 0-65535 | カスタム 0.0.0.0/0 |
AWSではインスタンスの中でファイアウォールを設定してもセキュリティグループの設定が優先されるようです。
SSHなどで接続するときはセキュリティグループに自分のIPアドレスが設定されていなければ接続することすらできません。
ソケット通信を行うような問題を出題するときは使用するポートをセキュリティグループにも設定します。
ここではすべてのポートを開放していますが、セキュリティ的には使うポートだけ設定するべきでしょう。
ちゃんと設定しているはずなのに接続できないなどといったときはセキュリティグループの設定を確認してみたらいいかもしれません。
ドメイン(サブドメイン)の設定
AWSのサービスの1つであるRoute53を使ってサブドメインの設定を行います。
お名前ドットコムでドメイン管理している場合は以下のサイトが参考になります。
nori-life.com
難しいことは何もありません。マウスでカチカチするだけです。
なお、Route53を利用する場合は月額$0.5かかります。無料に等しいのでケチるところではありません。
インスタンス内でパブリックDNSを取得するスクリプトの用意
EC2はインスタンスを起動するとIPアドレスが変わります。
そのおかげでスクリプト内でパブリックDNSを直に書いていた場合、起動し直したときに都度書き換えなければならなくなります。
そこで起動したときにIPアドレスとリージョンを取得してファイルに残しておくことにします。
Pythonでサクッと書きます。
# get_ipv4.py import subprocess result = subprocess.check_output(['curl', '-q', 'http://169.254.169.254/latest/meta-data/public-ipv4']).decode().split('.') result2 = subprocess.check_output(['curl', '-s', 'http://169.254.169.254/latest/meta-data/placement/availability-zone']).decode()[:-1] with open('/home/ctf/PublicDNS', 'w') as f: f.write('ec2-{}-{}-{}-{}.{}.compute.amazonaws.com'.format(result[0], result[1], result[2], result[3], result2))
IPアドレスとリージョンを取得し、それらを組み合わせて得られたパブリックDNSをPublicDNSというファイルに保存しています。
とりあえずスクリプトを書いた段階で一度実行しておきます。
$ python3 get_ipv4.py
スクリプトを起動時に実行するようにcrontabに記述します。
$ crontab -e -- @reboot python3 /home/ctf/get_ipv4.py
ドメインを指定するときはこのスクリプトが吐き出したファイルを参照するようにすることで、インスタンスを起動したときに毎回書き直す手間を省くことができます。
CTFdのインストール
TeraTermなどでSSHで接続します。
ユーザー名に「ubuntu」入力し、秘密鍵にインスタンスを立てるときに設定した秘密鍵を選択します。
とりあえずCTF用にユーザーを作成し、sudoに入れておきます。
$ sudo adduser ctf $ sudo gpasswd -a ctf sudo
ユーザーを変更してCTFdをインストールします。
$ su ctf $ cd ~ $ sudo apt update $ sudo apt upgrade $ git clone https://github.com/CTFd/CTFd.git $ cd CTFd $ git checkout 75a9a5a6972b7f053a018a0845e0755894729801 $ ./prepare.sh
サーバーの設定を変更します。
serve.pyというファイルの最後の行を、先ほど保存したパブリックDNSを参照するように書き換えます。ポートはお好みで。
#app.run(debug=True, threaded=True, host="127.0.0.1", port=4000) with open('/home/ctf/PublicDNS', 'r') as f: HOST = f.read() app.run(debug=True, threaded=True, host=HOST, port=8000)
これでCTFdが動くようになったので、CTFdを起動します。
$ sudo python serve.py
CTFdを起動したらブラウザからアクセスしてみます。
なお、この段階ではポートを80番以外に変更している場合、URLでポート番号を指定する必要があります。
セットアップの画面が表示されればOKです。
HTTPS化
HTTPS化する場合はLet’s Encryptで証明書を取得します。
基本的にはこのサイトの通りに進めていきます。
ox0xo.github.io
まずはnginxをインストールします。
$ sudo apt install nginx
新たにdefault.confというファイルを作成し、諸々を書いておきます。
※既にある場合は適宜書き換える
$ sudo vim /etc/nginx/conf.d/default.conf -- server { listen 80; server_name [自分のドメイン]; location / { root /usr/share/nginx/html; } }
nginxを再起動します。
$ sudo systemctl restart nginx
ブラウザから設定したドメインにアクセスしてみます。
nginxのウェルカムページが表示されればOKです。
statusを確認しろとか出てきたときはどこかミスっています。書き間違えている部分が無いか確認してみてください。
Webサーバの用意が出来たのでLet’s Encryptで証明書を取得します。
$ sudo apt-get install letsencrypt $ sudo letsencrypt certonly --webroot --webroot-path /usr/share/nginx/html -d [自分のドメイン]
途中で何か聞かれたりしますが、AgreeやらYesやら選択します。
/etc/letsencrypt/archive/に自分のドメインがあればOKです。
途中でErrorになってうまくいかない場合はnginxがちゃんと動いていない可能性があります。
nginxでリバースプロキシを構築
CTFdはHTTPで動作しているため、nginxが受け取ったリクエストをCTFdに転送するようにします。
新たにssl.confというファイルを作成します。
$ sudo vim /etc/nginx/conf.d/ssl.conf -- server { listen 443; ssl on; ssl_certificate /etc/letsencrypt/archive/[自分のドメイン]/cert1.pem; ssl_certificate_key /etc/letsencrypt/archive/[自分のドメイン]/privkey1.pem; ssl_session_timeout 5m; ssl_protocols TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-User $remote_user; proxy_pass http://[自分のドメイン]:8000; } }
default.confに、HTTPリクエストをHTTPSに書き換えるように追記します。
$ sudo vim /etc/nginx/conf.d/default.conf -- server { listen 80; server_name [自分のドメイン]; rewrite ^(.*) https://[自分のドメイン]$1 permanent; location / { root /usr/share/nginx/html; } }
nginxを再起動し、CTFdを起動します。
$ sudo systemctl restart nginx $ sudo python /home/ctf/CTFd/serve.py
ブラウザにアクセスしてみてHTTPSになっていればOKです。
どこもエラーを吐いていないのにHTTPSになっていないときはShiftキーを押しながらページを更新してみたり、キャッシュを削除してみたりしてください。
PHPの設定
今回は問題サーバーも兼ねるので、nginxでPHPが動くようにします。
ApacheではPHPをインストールするだけで簡単に動くようになりましたが、nginxではいくつか段階を踏まなければなりません。
まずはPHPの諸々をインストールします。
sudo apt install php php-fpm php-mysql php-gettext php-common php-mbstring
PHPの設定ファイルを弄ります。
$ sudo vim /etc/php/7.2/fpm/php.ini -- #778行目あたりでコメントアウトされているので、外して書き換える cgi.fix_pathinfo=0
nginxでPHPが動くように設定します。
しかし、CTFdが動いているため普通にPHPの設定をしただけではすべてのアクセスがCTFdに飛ばされてしまいます。
そこでURLに特定の文字列を含むときは普通にnginxでサイトを表示するときと同じような動きをするように設定します。
問題を置く予定なので「problems」にします。
$ sudo vim /etc/nginx/conf.d/ssl.conf -- server { (略) location / { (略) } location /problems/ { root /usr/share/nginx/html; index index.html index.htm index.php; } location ~ \.php$ { root /usr/share/nginx/html; try_files $uri =404; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_pass unix:/run/php/php7.2-fpm.sock; } }
nginxを再起動します。
$ sudo systemctl restart nginx
PHPが動いているか確認するために以下のファイルを用意します。
$ sudo vim /usr/share/nginx/html/problems/index.php -- <?php phpinfo(); ?>
ブラウザでアクセスして色々表示されればOKです。
CTFdのセットアップ
CTFdを普通に起動するとSSHで端末を開いている間しかアクセスすることができません。
そこでSSHを切断しても動かし続けるために、screenコマンドを使います。
$ screen $ sudo python /home/ctf/CTFd/serve.py
CTFdが起動したら「Ctrl+A」のあとに「D」でデタッチします。
これで端末から抜けてもCTFdが動き続けます。
起動したscreenに戻りたいときはまず「screen -ls」で一覧を表示します。
そして以下のコマンドを実行します。
$ screen -r [赤枠の番号]
CTFdを起動したらブラウザからアクセスします。
上からCTFのタイトル、Adminのユーザー名、メールアドレス、パスワード、CTFのモードです。CTFのモードはチーム戦か個人戦かで選ぶことができます。
セットアップが完了するとCTFdのロゴなどが表示されます。
右上のAdminから管理者用のページに移動します。
Challengesのページの+を押すと問題を登録することができます。
まずは問題の種類を選びます。
「dynamic」か「standard」のどちらかを選択します。
dynamicは解いた人の数で点数が変動し、standardは指定した点数で固定です。
次に問題名、ジャンル、問題文、点数を入力します。
submitすると次のような画面になります。
左らへんのFlagsやFilesなどから答えとなるFlagや配布するファイルを登録することができます。
また、MessageにはHTMLタグを利用することが可能です。
Stateでは問題の公開/非公開を指定できます。
問題を登録したら大会の開催期間を登録します。
Config > Timeで登録するページに移動します。
最後にページ全体の装飾です。
トップページがCTFdのロゴのままだったりするので、この部分をオリジナルのものに変えたりしましょう。
なお、トップページの内容は Pages > All pages の index から編集できます。
必要に応じて新たにページを作成することもできます。
編集が終わったらConfigのBackupからバックアップファイルをエクスポートしておきます。
環境が変わってもこのファイルをインポートするだけで問題を含め全ての設定が復元できます。
これで準備は完了です。時間になれば問題が公開されて解けるようになります。