UCSB iCTF 2018 - fantasticiot, hero_text_adventure

attack and deference形式のCTFにオンラインで参加した。dodododoは19位。

f:id:akiym:20180318184839p:plain

sshできるサーバが1台与えられて、そこで8つのサービスを正しく動かしつつ、攻撃と防御を行う。
flagは運営からサービスが正しく動いているかどうかの確認と一緒に送られてくる。例えば秘密のメモアプリみたいなのがあったとして、正規の方法だとメモの閲覧にはパスワードが必要だけど、サービスに残された脆弱性を使うと任意のメモを見られるになればflagを入手して提出することで攻撃できる。
サービスはxinetd経由で起動するようになっていて、サービスの脆弱性を潰すためバイナリを書き換えたり、途中にバリデーション用のスクリプトを挟んだりしてflagを持ち出されないように防御できる。

このCTF専用のサービス一覧/参加チーム取得やフラグ提出用のクライアントがあって、それを使うと手軽に攻撃の自動化ができる。 事前にサンプル が渡されたのだけど、結構ミスっていて困っていた。事前にテストして欲しい……

fantasticiot

% file ./fantasticiot
./fantasticiot: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=baa3e0fa48be4715a10784c2dd5e7d3adb5d9181, not stripped

冷蔵庫サービス。static linkされていて一瞬ぎょっとするけど、中身は単純にJSON経由で冷蔵庫に入れたりすることができるサービス。flagはsetflagで保存されたflag。

  • setflag
    • {"token": "A", "flag": "B", "id": "2", "service": "flag", "op": "setflag"}
    • flag/1 に書き込む
  • getflag
    • {"token": "A", "id": "1", "service": "flag", "op": "getflag"}
    • flag/1 から取得する
    • ただし正しいtokenである必要がある
  • addfridge
    • {"content": "AAAAAAAAAA", "item": "1", "service": "fridge", "op": "addfridge"}
    • fridge/1 に書き込む
  • getfridge
    • {"item": "1", "service": "fridge", "op": "getfridge"}
    • fridge/1 から取得する

脆弱性

  • getfridgeの際にserviceを flag に指定することでディレクトリを変更できる
  • getfridgeの際にitemを ../flag/1 にすることでディレクトリを変更できる

こういう雑なバリデータを挟むことで回避できる。

import sys
import json

src = sys.stdin.readline()

try:
    data = json.loads(src)
    if data['op'] == 'getfridge':
        assert data['service'] == 'fridge'
        assert '/' not in data['item']
    sys.stdout.write(src)
except:
    pass
  • strncmpにbackdoorが仕掛けられている

static linkされているのはこういうことだったのか!上位チームでも意外と気づかなかったチームが結構いた。

f:id:akiym:20180318184751p:plain

つまり、getflagの際にtokenを victor に指定すればtokenチェックが問答無用に突破可能。

def exp(s, flag_id):
    payload = {
        "service": "flag",
        "op": "getflag",
        "id": flag_id,
        "token": "victor",
    }
    s.send(json.dumps(payload) + '\n')
    return json.loads(s.recvuntil('\n'))['flag']

hero_text_adventure

% file ./hero_text_adventure
./hero_text_adventure: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=98aac0bf05483d6328b1640625404c533c785999, not stripped
% ./hero_text_adventure
Hello superhero, are you continuing your previous adventure? (y/N)
n
Welcome to the beta test of the Marvel Super Hero text adventure!
Enter your name:
test
Pick a character
1) Star Lord
2) Dr Strange
3) wanda maximoff
4) Hulk
5) Thor
1
test, what would you like to do?
1) BATTLE!
2) buy weapon
3) equip weapon
4) give weapon a new name
5) load game
6) exit
6
Exiting. Save game? (y/N)
y
enter a password to protect your save file
secretpassword!
id of your adventure: 7906397325484763355

アドベンチャーゲーム。戦ったり、武器を買ったり、データをロード/セーブできる。flagはセーブされているデータのname部分。

脆弱性

  • weaponのhandlerのアドレス書き換え
    • 1) BATTLE! のときに任意のアドレスに対して飛べるようになる
    • readlineの最初(0x400C8C)に飛ぶことで、bssのプレイヤー名のあるアドレス(0x603240)以降の書き換えができる
    • そこから装備中の武器のアドレスを書き換えると、 4) give weapon a new name から任意のアドレスへの書き込みができるようになる
    • GOTにあるstrcmpをstrchrに書き換えるとデータのロード時のパスワードチェックで、strcmpが strchr("real_password", "input") になり、これはNULLを返すので任意のデータをロードできるようになりflagが入手できる

exploit: hero_text_adventure · GitHub

防御に関しては、give weapon a new nameのときのreadlineが36バイト読み込むのを32バイトだけ読み込むようにバイナリを変更した。

感想

自分の現在の順位よりも上のチームにしか攻撃できない(flagの提出もできず、サーバへのアクセスも遮断されている)ようになっていて珍しいルールだった。よくあるA&D形式のCTFだと防御がボロボロだと上位チームに攻撃されて、点数が減る一方で最後までやる気がなくなってしまうのだけど、これならまだ最後までできる。
一見よいルールに見えるのだけど、順位をわざと下げて攻撃対象を増やして、一度に大量の得点を取得することが可能で(もちろん攻撃が塞がれてなければだけど)割と入れ替わりが激しく、崩壊していた気がする。

今回はRuCTFEのようにVPNの提供はなく、サーバが与えられて手元から他チームに攻撃するときはsocks経由でやってくれ、というかんじだったので運営のことを考えると準備の手間をあまり考える必要なく、これなら手軽にA&D形式のCTFを開催できるのではという気がした。
ただチェックシステムを準備するのが大変という話はありそうだけど、ちょっと調べたらRuCTFEで使われているコードが公開されていた。A&D形式のCTF増えて欲しい。

github.com