NFLabs. エンジニアブログ

セキュリティやソフトウェア開発に関する情報を発信する技術者向けのブログです。

CTFdをAWS上に構築してみた

はじめに

こんにちは、研究開発部 研究開発担当の北村と市岡です。

このたびMWS Cupのスコアサーバを作ってきました。様子は以下の記事で書きましたのでぜひぜひ見てください!
https://blog.nflabs.jp/entry/2022/10/31/130107

スコアサーバはOSSのCTFdを使用してAWS上に構築をおこないました。
CTFdはその名の通りCTF用のサーバを簡単に構築できるもので、幅広く利用されています。

幅広く利用されている反面、CTFdの使い方を日本語で解説した記事って見たことが無いって思ったので、この記事ではCTFdの使い方を日本語で説明してみたいと思います。
詳細な設定については英語の公式ドキュメントを参照していただければと思います。
僕たちが英語を読むのが苦手だったのもあり、同じような境遇の方のお手伝いになればと思います。

本記事の特徴

  • HTTPSでCTFdにアクセスする方法が書いてある
  • データベースをRDSにした場合の設定方法が書いてある
  • Basic認証の設定方法が書いてある
  • CTFdのプロセス数を設定する方法が書いてある

制限

本記事では以下の点を考慮していません。

  • AWSでのリソースの作成方法は書いていません。
  • CTFdが使うメールサーバを用意していません。MWS CupではSlackで問い合わせを受け、パスワード再発行などは担当者が実施しました。

AWSのリソースを作成する

AWSのリソースについては概要のみを説明します。

スコアサーバ構成図

上記の構成になるようにTerraformでリソースをデプロイしました。CTFdは上図のEC2インスタンスにインストールを行います。
最終的に以下を満たせば問題ないです。

  • EC2インスタンスが外部からHTTPおよびHTTPS接続できること
  • 自身がSSHでログインできること
  • EC2インスタンスのシェルからRDSに接続できること
  • EC2インスタンスのAMIはUbuntuを選択
  • RDSはMySQLを選択

CTFdをインストール

DockerおよびDocker Composeのインストール

まずはEC2インスタンスにログインし、必要なソフトウェアをインストールします。
本記事では、CTFdをDocker Composeで起動します。このため、以下の記事を参考に、DockerおよびDocker Composeをインストールしました。

まずは、既にインストールされているパッケージを最新化します。

sudo apt update
sudo apt upgrade
sudo reboot

次に、再度EC2インスタンスにログインしたうえで、DockerおよびDocker Composeをインストールします。

sudo apt-get update
sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

CTFdのダウンロード

DockerとDocker Composeをインストールできたら、CTFdの最新版をインストールします。wgetで指定するURLは、CTFdのGitHubから最新のバージョンのURLを確認して置き換えてください。

CTFdのリリース:https://github.com/CTFd/CTFd/releases

wget https://github.com/CTFd/CTFd/archive/refs/tags/3.5.0.zip
sudo apt install unzip
unzip 3.5.0.zip
cd CTFd-3.5.0

SSL/TLS証明書の取得

情報セキュリティの学会のイベントということでSSL/TLS証明書を取得し、HTTPSで通信させることにしました。

先に参考にしたWebサイトを載せておきます。
以下を参考にCertbot を用いて、SSL/TLS証明書を取得しました。

https://medium.com/csictf/self-hosting-a-ctf-platform-ctfd-90f3f1611587

具体的な手順を説明します。
まずはCertbotをインストールし、Certbotを使って証明書を取得します。

sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo apt-get update
sudo apt-get install certbot python3-certbot-nginx
sudo systemctl stop nginx
sudo systemctl disable nginx
sudo certbot certonly --standalone --debug -v

上記の手順を実施することで、Let's encryptにメールアドレスが登録され、3か月後に、もうすぐ証明書が失効する連絡が来ます。3か月以上利用する場合は証明書の更新が必要です。

その後、以下の手順でNginxの設定ファイルと同じディレクトリにファイルを配置します。example.comを取得したドメインに置き換えてください。

$ pwd
/home/ubuntu/CTFd-3.5.0/conf/nginx

sudo cp /etc/letsencrypt/live/example.com/fullchain.pem .
sudo cp /etc/letsencrypt/live/example.com/privkey.pem .
sudo chown ubuntu:ubuntu fullchain.pem
sudo chown ubuntu:ubuntu privkey.pem

Basic認証に必要なfileの作成

MWS CupではスコアサーバにBasic認証をかけていました。ここでBasic認証の手順も説明します。

まずはnginxのフォルダでBasic認証に必要な.htpasswdファイルを作成します。以下のコマンドで.htpasswdファイルが作れます。

$ pwd
/home/ubuntu/CTFd-3.5.0/conf/nginx

sudo apt install apache2-utils
htpasswd -c .htpasswd "ユーザの名前"

http.confの修正

次に/home/ubuntu/CTFd-3.5.0/conf/nginx/http.confに設定を入れます。
score.example.comを実際のスコアサーバのFQDNに置き換えます。

修正した点は主に以下の4つです。

  1. 80番ポートから443番ポートへのリダイレクトを記述
  2. 80番ポートで受け入れる設定だったCTFdへのリバースプロキシの設定を443番ポートへ変更
  3. SSL/TLSの証明書に関する記述を追加
  4. Basic認証の記述を追加

最終的な設定ファイルは以下のような感じです。(http.conf)

worker_processes 4;

events {

  worker_connections 1024;
}

http {

  # Configuration containing list of application servers
  upstream app_servers {

    server ctfd:8000;
  }

  # HTTP通信でアクセスしたときは、HTTPSヘリダイレクト(以下5行を追加)
  server {
    server_name score.example.com;
    listen 80;
    return 301 https://$host$request_uri;
  }


  server {
    server_name score.example.com;
    
    # 80番ポート向けだったものを443番ポート向けに修正(以下の1行を修正)
    listen 443 ssl;

    # HTTPS通信用の証明書と秘密鍵(以下2行を追加)
    ssl_certificate /etc/nginx/fullchain.pem;
    ssl_certificate_key /etc/nginx/privkey.pem;

    client_max_body_size 4G;

    # Basic認証のために以下の2行を追加
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/conf.d/.htpasswd;

    # Handle Server Sent Events for Notifications
    location /events {

      proxy_pass http://app_servers;
      proxy_set_header Connection '';
      proxy_http_version 1.1;
      chunked_transfer_encoding off;
      proxy_buffering off;
      proxy_cache off;
      proxy_redirect off;
      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-Host $server_name;
      
      # Basic認証のために以下の1行を追加
      proxy_set_header Authorization "";
    }

    # Proxy connections to the application servers
    location / {

      proxy_pass http://app_servers;
      proxy_redirect off;
      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-Host $server_name;
      
      # Basic認証のために以下の1行を追加
      proxy_set_header Authorization "";
    }
  }
}

docker-compose.ymlの修正

ここまで、さまざまなファイルを作りました。これらのファイルをdockerコンテナ上にコピーするため、/home/ubuntu/CTFd-3.5.0/docker-compose.ymlを修正します。

CTFdには、docker-compose.ymlで以下の4つのサーバを立てることになっています。

  • ctfd (CTFd本体)
  • nginx (HTTPプロキシサーバ)
  • db (データベースサーバ)
  • cache (キャッシュサーバ)

今回はdbをRDSへ移行したため、dbを削除し、nginxとctfdの設定を変更しました。

修正点は以下の4つです。

  1. CTFdのWORKERSの数を増やす(プロセス数を増やす)
  2. DATABASE_URLをRDSのエンドポイントに変更する
  3. docker-compose.ymlのdbコンテナの記述部分を削除する
  4. docker-compose.ymlのctfdコンテナのports設定の部分を削除する(※nginxからのアクセスのみを許可するため)

WORKERSを増やすと、CTFdは複数のプロセスになるため、複数のCPUで動作するようになります。

具体的な設定内容を以下で説明します。CTFdではWORKERSの数を増やすためにランダムな文字列が必要です。以下のコマンドにより、ランダムな文字列を取ります。(ここでは、aaaaaaaaaが得られたことにします。)

dd if=/dev/urandom bs=1 count=64 2>/dev/null| md5sum | head -c 13

1.のWORKERSの数を増やすには、以下の2つを変更する必要があります。

  • SECRET_KEYの追加
  • WORKERSを1より大きな数値に変更(vCPUの数に一致させるとよい)

最終的にdocker-compose.ymlは以下のようになります。修正箇所の直前にコメントをかきました。

version: '2'

services:
  ctfd:
    build: .
    user: root
    restart: always
    # (1箇所目)ports設定のコメントアウト(以下2行をコメントアウト)
    # ports:
    #   - "8000:8000"
    environment:
      # (2箇所目) SECRET_KEYを追加 (aaaaaaaaaを上記で生成したランダムな文字列に置き換える)
      - SECRET_KEY=aaaaaaaaa
      - UPLOAD_FOLDER=/var/uploads
      # (3箇所目) DATABASE_URLを修正(RDSのエンドポイントURL、Username、Passwordに修正)
      - DATABASE_URL=mysql+pymysql://<username>:<password>@wwww.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com/ctfd
      - REDIS_URL=redis://cache:6379
      # (4箇所目) WORKERSを4に増やした(vCPUの個数と一致させるのがおすすめ)
      - WORKERS=4
      - LOG_FOLDER=/var/log/CTFd
      - ACCESS_LOG=-
      - ERROR_LOG=-
      - REVERSE_PROXY=true
    volumes:
      - .data/CTFd/logs:/var/log/CTFd
      - .data/CTFd/uploads:/var/uploads
      - .:/opt/CTFd:ro
    # (5箇所目)dbを消したため、depends_onも削除(以下2行をコメントアウト)
    # depends_on:
    # - db
    networks:
        default:
        internal:

  nginx:
    image: nginx:stable
    restart: always
    volumes:
      - ./conf/nginx/http.conf:/etc/nginx/nginx.conf
      # (6箇所目)SSL/TLSの証明書、秘密鍵、.htpasswdの追加(以下3行を追加)
      - ./conf/nginx/fullchain.pem:/etc/nginx/fullchain.pem
      - ./conf/nginx/privkey.pem:/etc/nginx/privkey.pem
      - ./conf/nginx/.htpasswd:/etc/nginx/conf.d/.htpasswd
    ports:
      - 80:80
      # (7箇所目)443番ポートを追加(HTTPS追加)(以下1行を追加)
      - 443:443
    depends_on:
      - ctfd
  #(8箇所目)データベースのコメントアウト(以下14行をコメントアウト)
  # db:
  #  image: mariadb:10.4.12
  #  restart: always
  #  environment:
  #    - MYSQL_ROOT_PASSWORD=ctfd
  #    - MYSQL_USER=ctfd
  #    - MYSQL_PASSWORD=ctfd
  #    - MYSQL_DATABASE=ctfd
  #  volumes:
  #    - .data/mysql:/var/lib/mysql
  #  networks:
  #      internal:
  #   This command is required to set important mariadb defaults
  #  command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --wait_timeout=28800, --log-warnings=0]

  cache:
    image: redis:4
    restart: always
    volumes:
    - .data/redis:/data
    networks:
        internal:

networks:
    default:
    internal:
        internal: true

CTFdの起動

最後に以下のコマンドでサーバを起動すると使えるようになります。

sudo docker compose up -d

おわりに

今回はCTFdの構築方法を紹介しました。CTFdを使ってイベントを開催する方々の参考になればうれしいです。
身内で勉強会をするときなどにもちょうど良いと思いますので、みなさんぜひぜひ使ってみてください!