Skip to main content

Fetch the Flag CTF 2022の解説:Treasure Trove

著者

Luke Watts

feature ctf treasure trove

2022年11月10日

0 分で読めます

Fetchに参加してくれてありがとう! 参加してくれた数千人の皆さん、 Fetch the Flag CTFへの参加、おめでとうございます。また、チャレンジを作成し、テストし、解説を書いてくれたSnykerの皆さんにも心から感謝します!

今年のFetch the Flagは最高に楽しく、同時に大変な作業でもありました!16個のチャレンジと限られた時間の中で、すべてをクリアできたチームばかりではありません。時間内に解けなかった皆さんに向けて、私たちのチームがTreasure Troveに挑んだ方法をご紹介します。

解説

チャレンジの説明は「最新のTreasure Troveが公開されたけれど、ライセンスキーをどこに置いたっけ!」というものでした。まず、リバースエンジニアリングというタグがあり、obfuscator.jsファイルが提供されています。

リンク先のWebサイトには、5×5文字の入力欄とボタンがあり、Curse of Monkey Islandをプレイした頃を思い出させてくれる素晴らしいアートワークもあります。

トロピカルな島にある連結した宝箱、海賊船、コード入力欄、「引き換えて大海原へ出航!」ボタンが表示されたTreasure Troveのブラウザーゲーム

提供されたコードを確認すると、人が読めるコードを意味不明な文字列に変換するツールが見つかります。

次にWebサイトを見てみましょう。swashbuckle.jsエンドポイントにはvalidateというメソッドがあります。中のコードは、obfuscatorが生成する出力と一致しているようです。うーん……

もう少し詳しく見ると、validateメソッドはwindow上で利用でき、フォームの送信時に呼び出されることがわかります。なるほど、validateをリバースエンジニアリングするしかなさそうです……ブラウザーを使えば何かわかるかも?ブラウザーのコンソールからvalidate.toString()を実行してみましょう。ビンゴ!

function anonymous(
) {

const segementOne = document.getElementById('seg-1');
const segementTwo = document.getElementById('seg-2');
const segementThree = document.getElementById('seg-3');
const segementFour = document.getElementById('seg-4');
const segementFive = document.getElementById('seg-5');

const setError = (message) => {
  document.getElementById('error-msg').innerText = message;
}

setError('');
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const key = segementOne.value + segementTwo.value + segementThree.value + segementFour.value + segementFive.value;
if (key.length != 25) {
  setError('Invalid redemtion code!')
  return;
}

let val = 0;
for (let i = 0; i < 25; i++) {
  val += key.charCodeAt(i);
  if (!characters.includes(key[i])) {
    setError('invalid characters');
    return;
  }
}

if (val != 1800) {
  setError('invalid code');
  return;
}

fetch('/api/validatekey/' + key)
.then(async res => {
  const result = await res.json();
  setError('Argg ye got it! ' + result.flag);
});

}

どうやら、コードは次の処理を行っています。

  1. HTMLの入力欄から値を取得する

  2. 各文字がA-Z0-9の範囲にあることを確認する

  3. 文字をループ処理し、charCodeに変換して値を合計する。charCodeAt()メソッドは、指定したインデックスにあるUTF-16コード単位を表す、0~65535の整数を返します。 出典

  4. 値が1800でなければエラーをスローする

  5. 値が1800の場合、元の入力値がAPIエンドポイントに送信される

ここで最も重要なのは、値が1800でない限り、コードは無効だということです。

  1. 1800 ÷ 25(入力可能な文字数の合計)

  2. 72

  3.  String.fromCharCode(72)

  4. Hを返す

それぞれの入力欄にHHHHHを入力する

「Treasure Trove」と題されたブラウザーゲームの画面。島のヤシの木の下に鎖でつながれた宝箱、海賊船、「Redeem & Sail the high seas!」の文字が表示されている。

ダブルビンゴ!

「Treasure Trove」というタイトルのブラウザページ。ヤシの木が生えた島に鎖でつながれた宝箱、海賊船、エンコードされたテキスト、5つの入力欄が表示されている

obfuscatorのコードベースを調べることなく、このチャレンジを解くことができました。おかげで次のチャレンジに取りかかる時間もたっぷり取れました!

ほかのフラグをどう見つけたのか知りたいですか?Fetch the Flagの解答ページで、その方法をご覧ください。