Post

FEAT:🚩 Daily-AlpacaHack 「Catrunner 2」Easy

連続したスラッシュ記号の取り扱いを利用したパストラバーサル

FEAT:🚩 Daily-AlpacaHack 「Catrunner 2」Easy

20260618-daily_alpaca-misc-easy-catrunner_2

Summary

本問は,連続したスラッシュ記号の取り扱いを利用したパストラバーサル問題です.

  • Category: Misc
  • Description: 🐈️ 💨💨💨 🐈️ 💨💨💨💨💨💨
  • Tools & TechStack:
    • Python
  • Release: 2026/06/18

階層構造

1
2
3
4
5
6
7
8
9
.
├── app.py
├── compose.yaml
├── Dockerfile
├── flag.txt
├── hello.txt
└── thanks.txt

1 directory, 6 files

ソースコードの調査

ソースコードの一連の処理フローは以下のようになっていました.

  1. 入力されたパス pathabspath() で絶対パスに正規化.
  2. 正規化されたファイルパスが実際に存在しているかを,シンボリックリンク含め確認.
  3. "/flag.txt" という文字列が含まれていればアクセス拒否.
  4. 権限内かつ,"/flag.txt" でなければそのファイルを cat に渡す.

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
import subprocess

path = input("Example: /app/hello.txt\n$ cat ")
path = os.path.abspath(path) # 絶対パス
print("Path: " + path, flush=True) # DEBUG

if not os.path.isfile(path): # ファイルのみ
    print("File not found")
    exit(1)

# "/flag.txt" という固定文字列を介さずにアクセスすればいい
if path == "/flag.txt" or "/proc" in path:
    print("Not permitted")
    exit(1)

subprocess.run(["cat", path])

連続したスラッシュ記号に対する挙動を利用した Path Traversal

まず,POSIX系システム において,連続する / 記号は1つとして扱う ただし,2つのみ連続している / 記号は正規化しない という例外ルールがあります.(これは,ネットワーク機能などの名残だと思われます) os.path.abspath() は,この仕様に従います.

そのため,以下の挙動を示します.

  • 通常: os.path.abspath('/flag.txt') -> /flag.txt
  • // から始まる絶対パス: os.path.abspath('//flag.txt') -> //flag.txt (例外ルールによりそのまま残る)
  • / 記号が3つ以上: os.path.abspath('///flag.txt') -> /flag.txt (3つ以上はPOSIXの例外に当たらないため正規化される)

この挙動と,"/flag.txt" という固定の文字列だけでバリデーションチェックを行っている事を利用すれば,文字列比較をバイパスすることが可能になります.

  • Python側: //flag.txt でバイパスされる.
  • Linux側: subprocess.run() で OS (Linux) に渡される.そこでも,/// と同一のパスとして扱うため,cat コマンドが実行される.
1
2
3
4
5
nc localhost 1337
Example: /app/hello.txt
$ cat //flag.txt
Path: //flag.txt
Alpaca{REDACTED}

Post-Mortem & Dead ends

References

This post is licensed under CC BY 4.0 by the author.