こんにちは。19Bの@mazreanです。普段はSysAd班でanke-toというアプリケーションの開発・運用を行なっています。
CPCTF2021ではインフラとWebshellの開発を行なっていました。
インフラについては
をご覧ください。
この記事ではCPCTF2021で使用したWebshellについて書いていきます[1]。
CPCTFにおけるWebshell
CPCTFは競技プログラミング・CTFを体験できる新歓イベントです。
このためコアターゲットは新入生です。
しかし、新入生にはシェル環境を持たない方も多いです。
また、CTFでよく使うツール類が準備済み新入生となるとかなり限られてくるでしょう。
このようなことから、「ツールのインストールなどで手間取り競技プログラミング・CTFを楽しめない」といったことを回避するためにCPCTFでは各種CTFで使うツールなどが準備済みの環境が使えるWebshellを用意しています。
これまでのWebshellと問題点
これまでのCPCTFで用いていたWebshellは以下のような構成のものでした。
WeTTYはブラウザで開けるターミナルのUIを提供し、その入出力をssh接続に流すことのできるOSSです。
このOSSを利用して、プロセスの隠蔽や権限の設定などがされた全参加者共通のVPSへssh接続する、という方法でこれまでのWebshellは実現されていました。
しかし、この方法では
- CPUやメモリなどのリソースに対する制限がないため、CPUを使い果たすなどの方法で他の参加者がWebshellを使えないようにできる
- 権限の設定ミスで容易に重大な脆弱性が発生しうる
といった問題がありました。
これらの問題を回避するためにCPCTF2021では新しいWebshellの仕組みを用意しました。
新しいWebshell
これまでのWebshellの問題点は主に全参加者共通のVPSに対してssh接続が行われていることで生じていました。
このため、参加者ごとに用意された仮想環境に対してssh接続が行われるようにすれば良いです。
しかし、この方法には「WeTTYにはssh接続をユーザーごとに別のホストへ振り分けるような機能はない」という課題がありました。
この問題を解決するために開発したのがssh-separatorです。
ssh-separatorはssh接続をユーザーごとに用意したDockerコンテナへ振り分けるアプリケーションです。
これによって実現されたのが以下の構成です。
これまでの構成で1台のVPSであった部分がssh-separatorをはさんで、各参加者ごとに作られたdockerコンテナとなっています。
これによって、参加者1人が使用できるリソースに制限をかけることができ、cgroupにより隔離された環境内での操作しかできないように制限することができました。
ssh-separator
ここからは新しいWebshellの構成を実現するために開発したssh-separatorについて紹介していきます。
リポジトリは https://github.com/mazrean/ssh-separator です。
機能
ssh-separatorが提供する主な機能は
- REST APIからのユーザー作成・環境リセット
- ssh接続からの入出力をユーザーごとのDockerコンテナ内のシェルに繋げる
と言うものです。
REST APIはスコアサーバー上でWebshellの有効化やWebshell環境リセットの操作が行われた際にサーバーから叩かれます。
OpenAPIは https://mazrean.github.io/ssh-separator/openapi/ のようになっています。
Webshellの有効化時にはPOST /new
が叩かれ、ユーザーのパスワードなどの認証情報の保存とユーザーごとのDockerコンテナ作成が行われます。
環境リセットではPUT /reset
が叩かれ、ユーザーのDockerコンテナが消されて新しいDockerコンテナが作り直されます。
ssh接続ではパスワードを用いての認証を行い、ユーザーごとのDockerコンテナ内で起動した/bin/bash
などのシェルにssh接続の入出力を繋げます。
性能
ssh-separatorではPrometheus用のメトリクスを取れるようにしてあります。
以下はそれを利用して大会中に計測したデータです。
Dockerコンテナ数
合計で75人の参加者が使用していました。また、ピーク時で22人の参加者が同時接続し、コンテナが起動していました。
Dockerコンテナはssh接続がない時にはstopしてCPUやメモリの使用を抑える工夫をしていたのですが、それがなければそれなりに負荷が大きくなっていたと思われます。
HTTPリクエスト数
スコアサーバーによりREST APIが叩かれた回数とそのレスポンスです。
全てエラーなく正常にレスポンスを返せていることがわかります。
また、スコアサーバーなどと比べると数は少ないですが、隔離環境の作成などを行うアプリケーションとしてみると悪くない数のリクエストをさばいているのではないでしょうか。
使用技術
言語は
- traPに使い慣れた人が多く、引き継ぎがしやすいこと
- HTTPサーバーやsshサーバーなど、並列処理が有用な場面が多そうであること
を踏まえてGo言語を用いています。
また、ライブラリとして
- Echo
- Goの軽量Webフレームワーク
- スコアサーバーがユーザー作成・環境リセットするときに叩くRESTのAPIを提供するのに使用
- badger
- Goの埋め込み型のDBMSライブラリ
- https://dgraph.io/docs/badger/get-started/
- ユーザーとパスワードを紐付けるだけでよかったので、小規模で速くて楽に使えるbadgerを採用
- Go言語のDocker Engine SDK[2]
- gliderlabs/ssh
- Go言語準標準の
x/crypto/ssh
パッケージのラッパー。かなり楽にsshサーバーを立てられる
- Go言語準標準の
を使用しました。
Docker Engine APIを通じてのdockerの操作
普段よく使うdocker
コマンドはDocker CLIがdockerdの提供するREST API、Docker Engine APIを叩くことで動いています。
ssh-separatorではDocker Engine APIをGolangのDocker Engine SDK[2:1]を用いて叩くことで、Dockerコンテナのリセットを行なっています。
Go言語でのsshサーバーの作成
Go言語の準標準パッケージのgolang.org/x/crypto/ssh
を利用するだけでもかなり楽にsshサーバーを作ることができます。
しかし、今回はx/crypto/ssh
のラッパーであるgliderlabs/ssh
を利用しました。
このパッケージではnet/http
に近いAPIを持つため、HTTPサーバーを書き、net/http
を見慣れている自分にとっては非常に使いやすかったです。
また、exampleが充実しており、かなり高い確率でexampleに少し手を加えるだけでやりたいことを実現できると思います。
実はssh接続をDockerコンテナに流す例もあり、ssh-separatorはそれを参考にしつつ実装しました。
ssh-separatorは1日で構想から実装まで終わらせて動くところまで持っていったのですが、この超短期間での開発を実現できた理由の1つがgliderlabs/ssh
だと思います。
今後の展望
CPCTFのWebshellをより安全にするために開発したssh-separatorですが、まだまだ活用できる範囲はあると考えています。
具体的には、CTFでよくあるユーザーごとに環境が用意されており、ssh接続するタイプの問題です。
2021年のCPCTFではユーザー管理まわりの調整が間に合わず問題環境構築では利用できませんでしたが、来年以降のCPCTFでは利用できるように調整中です。
また、MITライセンスとなっているのでtraP外の方でも利用できるようになっています。
CPCTF以外のCTFの大会などで利用していただけたら嬉しいと考え、Docker以外のコンテナ型仮想化やbadger以外のデータベースへの対応なども行なっています。
追加で欲しい機能などがあればissueなどで教えていただけると嬉しいです。
終わりに
今回はCPCTFで用いたWebshellとその中で用いたssh-separatorについて説明させていただきました。
かなりの短い期間で作ったWebshellですが、快適に使用していただけたなら嬉しいです。
明日の担当は@reyuです。お楽しみに!
非常に遅くなってしまい申し訳ありません… ↩︎
Docker Engine SDKについては https://docs.docker.com/engine/api/sdk/ 参照 ↩︎ ↩︎