チャレンジ一覧
AWS CLI - S3バケット調査
S3 の設定をオフラインでエクスポートした監査記録が `/challenge/s3_audit/` にあります(`buckets.json` と、バケットごとの `*_publicaccessblock.json`・`*_policy.json`・`*_acl.json`・`*_objects.json`)。機密オブジェクト(`backups/prod.env` または `credentials/service-account.json`)を持つバケットは多数あり、どの `object_preview` にもフラグの形をした値があります。フラグは、本当に世界中に公開されているただ1つのバケットの機密オブジェクトにある値です。その条件は、(1) PublicAccessBlock の4設定がすべて false で、(2) ポリシーに `Effect: Allow`・`Principal: "*"`・`Action` に `s3:GetObject` を含み・`Resource` に `arn:aws:s3:::<バケット名>/*` を含み・`Condition` が無い statement があることです。ほかのバケットは、このどれか1つだけを外したおとりです。
Ansible自動化 - サーバー構成管理
Ansibleや実サーバーを実行せず、生成済みの実行記録 artifacts/ansible/vault-export.yml を解析してください。記録には多数のタスク結果があり、そのうち多くに vault_payload_hex が付いています。本物は、host が web-03、task が deploy-index、status が changed の3つがすべて一致する1件だけです(名前の一部だけが一致する記録や、成功していない記録はおとりです)。値は Ansible vault の本体と同じく16進で書かれていて、16進を戻すと Base64 の文字列になります。それをさらに戻すとフラグです。
CI/CDセキュリティ - GitHub Actions 脆弱性
`/challenge/repo/.github/workflows/` にある20個の GitHub Actions ワークフローを監査します。多くのステップに `# audit-ticket: KINOCO{...}` というコメントが付いています。次の3条件をすべて満たす「pwn request」脆弱性を持つワークフローは1つだけで、そのステップの audit-ticket がフラグです。(1) トリガーが `pull_request_target`。(2) あるジョブが、チェックアウトの `ref:` に `github.event.pull_request.head.sha` や `head.ref` を指定して PR の head を明示的にチェックアウトしている。(3) その同じジョブが `secrets.*` を参照している。どれか1つでも欠けるワークフローはおとりです。
Infrastructure as Code - Terraform セキュリティ
Terraform providerを初期化せず、生成済みの terraform/terraform.tfstate を解析してください。outputs の recovery_secret_arn が、復旧用シークレットの ARN を指しています。resources の aws_secretsmanager_secret_version のうち、secret_id がその ARN と完全に一致し、version_stages に AWSCURRENT を含む版が現在の値です。その secret_string(JSON を文字列にしたもの)の recovery_token を Base64 デコードするとフラグになります。
Kubernetes入門 - クラスター調査
実Kubernetesクラスターへ接続せず、artifacts/kubernetes/ の2つのスナップショット(pod-snapshot.yaml と secret-snapshot.yaml)を調べてください。namespace security-lab の Pod suspicious-audit-pod は、環境変数 AUDIT_TOKEN を Secret から読んでいます。その secretKeyRef が指す Secret(同じ namespace で同じ名前)の、指定されたキーの値を Base64 デコードするとフラグになります。別の namespace の同名の Pod や Secret、似た名前の Secret、同じ Secret の別のキーはおとりです。
Python上級 - カスタムVMバイトコードの実行
program.kvmは独自の4レジスタVM用バイトコードです。READMEの命令仕様を基にPythonでインタプリタを実装し、VMの出力を復元してください。
Web-26. User Profile Managerのプロトタイプ汚染
このNode.js/Express製アプリではログイン中のユーザーが自分のプロフィールを更新できます。更新処理に使われている再帰的なマージ関数が特殊なキーをきちんと除外しているか確認してみましょう。
Web-27. GraphQL APIのイントロスペクション漏洩
このGraphQL APIはUserオブジェクトのスキーマを公開しており、イントロスペクションが有効になっています。公式には案内されていないクエリやフィールドが存在しないか調べてみましょう。
Web-28. OAuth認可コードの横取り
このOAuth 2.0認可プロバイダーはログインフローを提供していますが、redirect_uriの検証が甘い可能性があります。認可コードを横取りして管理者権限のアクセストークンを得られないか確認してみましょう。
Web-29. MongoDBログインフォームのNoSQLインジェクション
このログインポータルはMongoDBを使っており、ユーザー名・パスワードをJSONとしてそのままクエリに組み込んでいます。パスワードを知らなくても管理者としてログインできないか確認してみましょう。
Web-30. KinoVaultの安全でないデシリアライズ
レガシーなバックアップシステム「KinoVault」は、ユーザーセッションをPythonのpickleでシリアライズしてCookieに保存していると噂されています。Cookieの形式を解析し、悪意あるpickleペイロードで権限を昇格できないか調べてみましょう。
カスタムツール開発 - Webディレクトリスキャナー
ライブWebサイトを走査せず、ローカル生成された artifacts/scanner/directory-report.ndjson を解析してください。スキャナーは同じサイトを複数回(run)走査しています。最新の run で、path がちょうど /secret_admin/backup、status が 200 の記録だけが本物です。その body_b64 は gzip で圧縮した応答本文を Base64 にしたもので、戻すとフラグになります。
ネットワークトンネリング - SSH ポートフォワーディング
単一コンテナ内でSSHローカルポートフォワーディングを練習します。SSHは127.0.0.1:22、内部サービスは/run/tunnel/database.sockにあります。内部ソケットはローカルのプレイヤーから直接アクセスできません。転送専用ユーザーtunnel(パスワードpassword123)でSSH認証し、127.0.0.1:8888へ転送して応答を取得してください。外部ホストや実データベースは使用しません。
バイナリ修復 - ファームウェアヘッダーとCRC32
firmware.bin の先頭4バイトを KINO に修復し、byte 12以降の CRC32 をリトルエンディアンで byte 8-11 に書き戻してください。修正ファイルを challenge.py に渡すと起動時注入 FLAG が表示されます。
プログラミング - C2通信の解析
起動のたびに乱数で作られた合成ログ beacon.log を解析します(ネットワーク接続は不要です)。複数の端末(セッション)の通信が時刻順に混ざっています。各セッションは最初の hello で nonce を送り、そのセッションの鍵は SHA-256(b"beacon-v1" + bytes.fromhex(nonce)) の先頭8バイトです。pulse の payload を seq の順につなぐと Base64 になり、それを復号して鍵で繰り返し XOR すると `task=... code=...` のような `キー=値` の平文になります(通信が途切れると同じ seq が再送されます)。task がちょうど `exfil` のセッションの code の値(16文字)が答えです。似た名前のタスクと取り違えないでください。答えを `python3 challenge.py '<答え>'` に渡すと、正解のときだけフラグが表示されます。
プログラミング - バッファオーバーフロー・クラッシュ解析
起動のたびに乱数で作られたクラッシュ報告 crash_01.txt から crash_06.txt を解析します(実行可能なコードは含まれません)。各報告には、送った入力そのもの(input:)とクラッシュ時の命令ポインタ(32ビットは eip、64ビットは rip)が書かれています。入力には同じ4文字が2度現れない巡回パターン(英小文字)が使われ、先頭にコマンド名などの前置きが付くことがあります。signal が SIGSEGV で、かつ命令ポインタの値が送った入力のバイト列で上書きされている報告だけを対象に、その値(メモリ上ではリトルエンディアン)をバイト列に直し、入力の先頭を 0 としたとき何バイト目から始まるかを求めます。対象の報告のオフセットを報告番号の順に `-` でつないだもの(例: `52-140-88-203`)が答えです。答えを `python3 challenge.py '<答え>'` に渡すと、正解のときだけフラグが表示されます。
プログラミング - ファジング結果の解析
起動のたびに乱数で作られたファジング結果のログ fuzz-results.log を解析します(入力は保存されているだけで、何も実行されません)。1行が1ケースで、`case=<6桁16進> result=<OK|TIMEOUT|CRASH> signal=<SIGSEGV|SIGABRT|-> stack=<8桁16進|-> input=<16進>` の形です。`signal=SIGSEGV` のクラッシュだけを対象に、stack(同じ stack は同じバグ)ごとにまとめ、入力(input の16進をバイトに直した長さ)が最も短いケースを選びます(同じ長さなら case の値が小さい方)。選んだケースの case を stack の値の昇順に `-` でつないだもの(例: `1a2b3c-4d5e6f-...`)が答えです。答えを `python3 challenge.py '<答え>'` に渡すと、正解のときだけフラグが表示されます。
プログラミング - ブロックチェーンイベントの再構築
/challenge/blocks.json は、コンテナの起動のたびに乱数で作り直される、オフラインの開発用チェーンのブロック一覧です(順不同。フォークでできた、正規チェーンに入らない孤立ブロックを含みます)。height が最も大きいブロックから parent_hash をたどった正規チェーンのブロックにある、status が 1 の取引の Fragment イベントを (height, log_index) の順に並べ、data(0x の後ろ)をつないで16進デコードしたトークン(英小文字と数字)を求め、python3 challenge.py '<トークン>' に渡してください。正しいときだけフラグが表示されます。
プログラミング - ルートキットのビュー差分
proc_ps.txt(ユーザー空間の ps の結果)と proc_kernel.txt(カーネルのタスクリストを直接たどった結果)は、起動のたびに乱数で作られます(/challenge にあります)。どちらも先頭行に取得時刻 snapshot_time(UNIX 秒)があり、2つは同時には取られていません。ps から隠されていたプロセスは、カーネル側にだけあり、かつ start_time が ps の取得時刻以前のものです(ps の後に起動したプロセスや、終了したプロセスは隠しプロセスではありません)。隠しプロセスの cookie(6桁の16進)を pid の小さい順(数値として)に - でつないだ文字列を求め、python3 challenge.py '<答え>' に渡すとフラグが表示されます。
プログラミング - 異常リクエストの検出
/challenge/requests.csv は、コンテナの起動のたびに乱数で作り直される Web API のアクセス記録です(request_id, timestamp, path, status, latency_ms, bytes。行は時刻順ではありません)。API ごとに普段の応答時間は違うため、path ごとに latency_ms の平均と母標準偏差(statistics.pstdev)を求め、z = (latency_ms - その path の平均) / その path の母標準偏差 が 4 以上の行を異常とします(bytes は判定に使いません)。異常な行の request_id を timestamp の古い順に - でつないだもの(例: 1a2b3c-4d5e6f)を、python3 challenge.py '<答え>' に渡してください。正しいときだけフラグが表示されます。
プログラミング - 診断結果の解析
findings.json は起動のたびに乱数で作られる診断結果です(/challenge にあります)。entry(edge-gw)、objective(vault)、ホストの一覧 hosts があり、各ホストの findings には id(6桁の16進)、cvss、exploitable、grants(その finding で移れるホスト)が入っています。ホストを掌握していると、そのホストの finding のうち exploitable が true かつ cvss が 7.0 以上のものだけを使って、grants のホストへ移れます。entry から objective までの最短の攻撃経路(一つに決まります)を求め、使った finding の id を経路の順に - でつないだ文字列を python3 challenge.py '<答え>' に渡すとフラグが表示されます。
プログラミング - 非同期スキャン結果の解析
/challenge/scan-events.jsonl は、コンテナの起動のたびに乱数で作り直される、非同期ポートスキャナーのイベント記録です(行は順不同で、同じイベントが重複していることもあります)。state イベントは (host, port) ごとに t が最も大きいものが最終状態で、banner イベントは stream の中を seq の順につなぎ16進デコードすると1行のテキストになります。最終状態が open の 443/tcp を持つホストについて、443 のバナーから cert-fp=(8桁の16進)を取り出し、ホストの IP アドレスの数値順に - でつないだもの(例: 0a1b2c3d-4e5f6a7b)を、python3 challenge.py '<答え>' に渡してください。正しいときだけフラグが表示されます。
暗号化通信 - GPG公開鍵暗号
GnuPG の復号を練習します。`/challenge/keys/` にある秘密鍵をすべてインポートし、`/challenge/messages/` の各暗号文を復号してください。フラグを含む平文になるのは、本物の受信者鍵で暗号化された1通だけです。他は無害な業務連絡のおとりで、宛先鍵IDの突合でも一意に絞れます。本物の GnuPG が非特権環境で動作します。
脆弱性スキャン - Nmap NSE 高度活用
社内サブネット `10.66.13.0/24` に `nmap -sV -sC --script vuln` を実行した結果が `/challenge/scan_results/nmap_scan.nmap`(通常形式)と `nmap_scan.gnmap`(grep 形式)にあります。ライブのターゲットは無く、レポートを読み解く問題です。NSE の vuln スクリプトの判定ブロックにはどれも `Finding ID: KINOCO{...}` が付いています。フラグは、`IDs:` が CVE-2020-1938(AJP の Ghostcat)で、かつ `State: VULNERABLE (Exploitable)` の判定ブロックの Finding ID です(1件だけ)。Ghostcat の LIKELY VULNERABLE / NOT VULNERABLE や、ほかの CVE の VULNERABLE (Exploitable) はおとりです。