diff --git a/src/assets/oauth-history.png.png b/src/assets/oauth-history.png.png new file mode 100644 index 0000000..79db913 Binary files /dev/null and b/src/assets/oauth-history.png.png differ diff --git a/src/assets/oauth-url.png b/src/assets/oauth-url.png new file mode 100644 index 0000000..a740b53 Binary files /dev/null and b/src/assets/oauth-url.png differ diff --git a/src/content/posts/spaces-bug.md b/src/content/posts/spaces-bug.md new file mode 100644 index 0000000..7b0282a --- /dev/null +++ b/src/content/posts/spaces-bug.md @@ -0,0 +1,76 @@ +--- +title: "Finding a CSRF Vuln in Hack Club Spaces" +description: "How I found a bug by reading source code" +pubDate: 15 Jan 2026 +--- + +Was planning on using spaces for my APCSA class and show it to my peers, teacher asked me if the code was secure and asked me to check it.... i ended up finding a CSRF bug in the admin panel. Here's how. + +## The Bug + +Admin endpoints read auth tokens from the request body instead of headers: + +```js +// src/middlewares/admin.middleware.js line 6 +const { authorization } = req.body; // bad + +// vs everywhere else +const authorization = req.headers.authorization; // good +``` + +Why does this matter? HTML forms can set body data, but they can't set custom headers. So I can make a form on any website that submits to their admin API. + +## The Attack + +```html +
+ + + +
+``` + +If an admin clicks that button (or I auto-submit it with JS), their browser sends the request. No CORS issues, no preflight - just a normal form submission. + +## Proving It + +Saved the HTML locally, opened it, clicked the button: + +- `Origin: null` - request came from a file, not their site +- `Sec-Fetch-Site: cross-site` - browser confirmed it's cross-origin +- Response: `401 Invalid authorization token` - server processed it, just rejected the fake token + +With a real token, this executes. Game over. + +## How Would You Get The Token? + +The OAuth flow leaks it in the URL fragment after login (`/#oauth_success=true&user_data={"authorization":"xxx"}`). This gets saved in browser history, can be logged by analytics, or leak via Referer header. Any XSS would also grab it. + +## The Fix + +One line change: + +```diff +- const { authorization } = req.body; ++ const authorization = req.headers.authorization; +``` + +# Status + +Reported on 15-01-26 + +On 15-01-26 Ivie (the maintainer of Space) contacted me stating: + +"Hey End! + +Re: the two security bounties you submitted + +Neither are these are eligible for a payout, due to spaces being in a private beta state. + +However, even if it was in production, they would not likely get a payout as: + +The admin csrf requires you to already have an admin token, and if you had an admin token you could just use the admin endpoints, no csrf needed + +Both will be fixed before spaces goes to production though, thanks for the report!" + +--- diff --git a/src/content/posts/spaces-bug2.md b/src/content/posts/spaces-bug2.md new file mode 100644 index 0000000..6731191 --- /dev/null +++ b/src/content/posts/spaces-bug2.md @@ -0,0 +1,82 @@ +--- +title: "OAuth Token Leak in Hack Club Spaces" +description: "Your auth token is in your browser history" +pubDate: 15 Jan 2026 +heroImage: /src/assets/oauth-url.png +--- + +# OAuth Token Leak in Hack Club Spaces + +Second bug I found while poking around Spaces. This one's simpler but arguably worse. + +## The Problem + +After you login with Hack Club OAuth, the app redirects you to: + +``` +https://spaces/#oauth_success=true&user_data={"authorization":"your_secret_token","username":"you"} +``` + +Your full auth token. Right there in the URL. + +## Why That's Bad + +URLs stick around: + +1. **Browser history** - anyone who opens your history sees it +2. **Analytics** - if they run Google Analytics or whatever, full URLs get logged +3. **Referer header** - click a link right after login, the next site might see it +4. **Extensions** - browser extensions can read URLs +5. **Shoulder surfing** - it's literally on screen + +The token gives full account access. Your spaces, your data, everything. + +## The Code + +```js +// src/api/oauth/oauth.route.js lines 184-186 +const encodedData = encodeURIComponent(JSON.stringify(userData)); +res.redirect(`/#oauth_success=true&user_data=${encodedData}`); +``` + +They're already setting an httpOnly cookie with the token on line 177. So why also put it in the URL? No idea. + +## The Fix + +Just... don't: + +```diff +- const encodedData = encodeURIComponent(JSON.stringify(userData)); +- res.redirect(`/#oauth_success=true&user_data=${encodedData}`); ++ res.redirect('/#oauth_success=true'); +``` + +Frontend can fetch user data from a `/me` endpoint using the cookie. Problem solved. + +## Proof + +Logged in, checked browser history: + +![history showing token](/src/assets/oauth-history.png) + +There it is. Permanently saved until you clear history. + +# Status + +Reported on 15-01-26 + +On 15-01-26 Ivie (the maintainer of Space) contacted me stating + +"Hey End! + +Re: the two security bounties you submitted: + +Neither are these are eligible for a payout, due to spaces being in a private beta state. + +However, even if it was in production, they would not likely get a payout as: + +The account token stored in URL: This is an issue, but due to it requiring access to the users computer to actually exploit, it is likely out of scope. + +Both will be fixed before spaces goes to production though, thanks for the report!" + +---