The proprietary trading architecture

Proprietary trading platforms operating in Nigeria follow a specific architecture to sell evaluation challenges to retail forex traders. Customers purchase these challenge packages using Naira or USD, complete a test phase on simulated accounts, and receive payouts based on their trading performance.

Behind the front-facing web dashboard, the backend coordinates several moving parts. A typical implementation ties three main services together:

Because MetaTrader servers do not communicate via standard web APIs natively, this architecture relies on periodic database updates. This asynchronous state tracking introduces security vulnerabilities if the synchronization is not designed defensively.

Exploit 1: Payment gateway bypass via webhook spoofing

When a user completes a checkout payment, the gateway notifies the dashboard backend via an HTTP POST webhook request. If the backend fails to verify the signature of these incoming requests, or if the transaction reference is not validated back against the gateway server-to-server API, attackers can activate accounts without paying.

An attacker bypasses the checkout page entirely and sends a forged webhook payload directly to the public webhook endpoint (such as POST /api/webhooks/paystack):

{
  "event": "charge.success",
  "data": {
    "reference": "sp_fake_ref_9921048",
    "amount": 25000000,
    "status": "success",
    "customer": {
      "email": "attacker@simpalabs.com"
    },
    "metadata": {
      "challenge_tier_id": "100k_evaluation",
      "user_id": "usr_998ab3"
    }
  }
}

If the backend checks only the status parameter without signature confirmation, the webhook handler executes the checkout completion sequence. The database registers a valid purchase and instructs the MetaTrader bridge to open a $100,000 challenge account on the live server, allowing the attacker to acquire high-value assets for free.

Exploit 2: MetaTrader API latency arbitrage

Direct queries to MetaTrader servers are heavy and latency-prone, so web dashboards usually do not check active positions or live balance values on the MT4/MT5 server for every page load. Instead, the backend database reads balance and equity states from the MetaTrader server via the bridge on a cron job or worker queue (e.g., every 60 or 120 seconds).

This sync delay creates a latency window. Attackers exploit this delay by opening high-volume positions on the MetaTrader terminal and initiating withdrawals simultaneously. The exploit path works as follows:

  1. The trader has a cached balance of $5,000 in the dashboard database from the last sync cycle.
  2. The trader opens a high-risk position on MT4/MT5 (e.g., shorting Gold or USD/NGN with high lot counts).
  3. While the high-risk trade is active on the MetaTrader server and the live equity is fluctuating, the trader goes to the web dashboard and requests a withdrawal of $3,000.
  4. The dashboard API checks the local database, confirms the stale record showing a balance of $5,000 with no recorded open trades, and approves the payout through local settlement rails like Flutterwave.
  5. Seconds later, the active MT4 trade goes deep into loss and triggers a margin call, wiping out the live trading account on the MetaTrader server.

By the time the next database sync completes, the trading account is blown, but the payout is already approved and routed. The platform suffers direct capital loss.

Exploit 3: BOLA on trading account credentials

Prop-trading platforms allow users to reset their trading passwords directly from their dashboard. The web backend exposes an endpoint to trigger this reset:

POST /api/accounts/77241/reset-password HTTP/1.1
Host: api.proptradingfirm.ng
Authorization: Bearer [attacker_jwt_token]
Content-Type: application/json

{
  "new_password": "HackedPassword999!"
}

A Broken Object Level Authorization (BOLA) vulnerability occurs when the backend validates the JWT token (verifying the caller has a valid account) but does not check if the owner of that JWT token owns the trading account ID 77241.

An attacker changes the accountId in the URL to match other active trader accounts (which are often sequential or discoverable on leaderboard pages). When the request is sent, the backend maps the action directly to the MetaTrader bridge, changing the victim's account password. The attacker then logs into the trading terminal to close profitable trades, open losing ones, or copy strategies.

Business impact on local platforms

Lax security controls on proprietary trading platforms lead to immediate financial and operational damage:

Specific mitigations for developers

Securing a prop-trading platform requires hardening the synchronization logic and enforcing cryptographically signed payloads.

1. Prevent latency arbitrage with live state validation

Lock all dashboard-level actions (including withdrawals, tier changes, and refund requests) whenever a trading account has active open positions. Query the live MT4/MT5 server directly before processing any value operation; never trust cached database balances.

async function handleWithdrawal(userId: string, accountUuid: string, amount: number) {
  const account = await db.tradingAccount.findFirst({
    where: {
      uuid: accountUuid,
      userId: userId,
    }
  });

  if (!account) {
    throw new Error("Unauthorized account access");
  }

  // Query live MetaTrader state directly from the bridge server API
  const liveState = await mtBridge.getLiveAccountState(account.metaTraderLogin);

  // Block withdrawals if there are active trades running
  if (liveState.openPositionsCount > 0) {
    throw new Error("Cannot process withdrawals while trades are open on MetaTrader");
  }

  // Compare requested amount against live balance and equity
  if (liveState.balance < amount || liveState.equity < amount) {
    throw new Error("Insufficient balance on the live trading server");
  }

  // Execute payout logic here
}

2. Enforce strict webhook validation

Calculate the HMAC-SHA512 signature using the gateway's secret key and match it against the signature headers. Verify the transaction status directly through a server-to-server API check to prevent replay attacks.

const crypto = require('crypto');
const axios = require('axios');

app.post('/api/webhooks/paystack', async (req, res) => {
  const signature = req.headers['x-paystack-signature'];
  if (!signature) {
    return res.status(401).send('Signature header missing');
  }

  const hash = crypto
    .createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (hash !== signature) {
    return res.status(401).send('Signature mismatch');
  }

  const { reference, status, amount } = req.body.data;
  if (status !== 'success') {
    return res.status(200).send('Ignored non-success event');
  }

  // Verify the transaction reference directly with Paystack API
  const verifyUrl = `https://api.paystack.co/transaction/verify/${reference}`;
  const response = await axios.get(verifyUrl, {
    headers: {
      Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`
    }
  });

  if (response.data.data.status !== 'success' || response.data.data.amount !== amount) {
    return res.status(400).send('Transaction verification failed');
  }

  // Proceed with safe challenge activation
  return res.status(200).send('Success');
});

3. Implement BOLA checks and data encryption

Enforce authorization checks in every database controller. Do not expose auto-incrementing IDs to the frontend; use non-sequential UUIDs for public API parameters. Encrypt sensitive bridge credentials and API tokens at the column level.

// Prisma 7 authorization check using UUID lookup
const account = await prisma.tradingAccount.findFirst({
  where: {
    id: accountUuid,
    userId: authenticatedUserId, // Enforced from session
  },
});

if (!account) {
  return res.status(403).json({ error: 'Unauthorized account access' });
}
Example finding

Exposed and Unauthenticated MT4 Bridge API

We reviewed a proprietary trading platform where the MT4 bridge API had no authentication and was exposed on port 443. An attacker could send raw JSON payloads to reset MT4 passwords or modify the balance parameter of any trading account. Fix priority: critical.

Are your proprietary trading API integrations leaking funds due to stale state checks?

Get a security review

Frequently asked questions

Does cTrader have the same latency issues as MT4/MT5?

cTrader's OpenAPI has lower latency but the integration layer still requires real-time validation of active positions before any dashboard withdrawal is approved.

What payment gateway is most secure for prop-trading?

Paystack and Flutterwave are equally secure if you implement webhook signature verification and prevent duplicate reference processing.

How do we secure the bridge server?

Run the bridge service inside a private subnet on AWS, and restrict access exclusively to the main dashboard server's IP address.

Related reading

Blog: Webhook Security for Payment Platforms · Rate Limiting and Anti-Fraud Patterns

Services: API security testing · Secure architecture review