← articles

API · MITM · client trust

MITM: an API that trusts the client’s price

A checkout endpoint that charges whatever price the client sends.

The checkout request carries the price, and the server charges it. Anything the client sends can be changed before it reaches the API, so the price is whatever the user wants it to be. The bug is on the server. The client is just a convenient way to produce the request.

Intercept (MITM) live demo
proxy · intercept on · api.acme.example
ACME app
Pro plan
EUR 49.00 / month

Waiting for traffic… Tap “Pay” in the app.

The test phone trusts the proxy's certificate, so its HTTPS traffic can be read and edited.

amount := req.Price // whatever the app sent
Simulated. Runs entirely in your browser.

The bug

amount := req.Price

The API cannot tell the official client from a modified request or a script calling it directly. The same request could be sent with curl.

Intercepting the traffic

An intercepting proxy such as mitmproxy or Burp Suite sits between the client and the API. Once the client trusts the proxy’s CA certificate, TLS terminates at the proxy, which can show, hold and edit every request. TLS protects users from the network. It does nothing against the person who controls the client.

Browsers only need the CA installed. Mobile apps take more setup (system CA store, certificate pinning), but that only changes how you get into position, not where the bug is.

The same pattern elsewhere

  • Totals and discounts computed on the client.
  • Negative or fractional quantities.
  • Extra fields such as "premium": true or "role": "admin" bound straight to the model (mass assignment).
  • User or account IDs in the request body, which leads to IDOR.
  • Prices or flags in hidden form fields or in JavaScript.
  • Rate limits and feature gates enforced only in the client.

Fix

The client sends intent (which plan), never values (what it costs). The server derives everything that matters from its own state:

plan, ok := plans[req.Plan]
if !ok {
	http.Error(w, "unknown plan", http.StatusBadRequest)
	return
}
charge(ctx, user, plan.Price)

I test web apps, APIs and LLM integrations. Contact.