DEV Community

Cover image for Authorized Web Audit: When the App Has No Backend, You Audit Its Assumptions
Armando
Armando

Posted on

Authorized Web Audit: When the App Has No Backend, You Audit Its Assumptions

Field notes from an authorized audit of a small web store. The headline: there was no backend to attack, so the real findings were identifier manipulation (business logic) and clickjacking.

Setup / hypothesis

  • Small store on Vercel: catalog, product pages, cart, share-cart URLs, contact buttons.
  • Looked like Next.js. Turned out to be a React + Vite SPA — the initial misread that set the approach.
  • Mindset: from "how do I protect this?" to "how can I break it?"

Step 1 — Recon (domain only)

  • robots.txt, sitemap.xml, routes and params, product/cart flows, HTTP headers.
  • Result: 0 findings. No magic /admin, nothing in the sitemap.

Step 2 — "Where's the server?"

Expected /api/products, /api/cart. They did not exist.

$ curl -s https://TARGET/api/products
→ 404
Enter fullscreen mode Exit fullscreen mode

The app was a SPA with no server and no database.

Implication for the scope: no server-side attack surface → no SQLi, no auth-bypass/authorization against an API, no server-side HTML injection. When you can't attack an API, you attack the assumptions of the app.

Step 3 — ID manipulation

Walk identifiers through real flows: /producto/1 → 2 → 3 …

Finding 1: products shown as sold out became reachable by changing the ID within certain flows.

Classification note (do this in your own reports): this is NOT classic IDOR. IDOR means a server protects a resource; there is no server here. The precise label is identifier manipulation + insufficient business-logic validation. Classifying correctly is security work.

Step 4 — Share-cart tampering

Share-cart URLs could be modified to introduce sold-out products. The logic accepted states the owner never intended (a stock state the business had ruled out).

Step 5 — Headers

$ curl -I https://TARGET
X-Frame-Options: (absent)
Content-Security-Policy: frame-ancestors (absent)
Enter fullscreen mode Exit fullscreen mode

The app could be framed.

Step 6 — Clickjacking + combined PoC

<iframe src="https://TARGET"></iframe>
Enter fullscreen mode Exit fullscreen mode

It loaded. The store was embeddable.

Combined PoC: external page loading a tampered cart URL in an iframe + own visual elements on top.

Scoped results

Not obtained: passwords, RCE, server access, DB, control of the app.

Demonstrated: state manipulation, manipulable cart, embeddable app, and the combination. Business angle: a third-party page rendering the legit store inside it → price/availability confusion, loss of trust, visual impersonation, complaints hitting an innocent owner.

The patch

X-Frame-Options: DENY        # or CSP frame-ancestors 'none'
# business logic: stop trusting client data; validate availability on every flow
Enter fullscreen mode Exit fullscreen mode

Retest

Retried the attack after the fix → iframe no longer loads. Prefer "I tried again and it no longer works" over "I think it's fixed."

Lessons

  1. Architecture = attack surface. React + Vite ≠ frontend + API + backend + DB, even if the customer sees the same shop.
  2. Scanners miss business logic. No tool asks "should I be able to add a sold-out product to the cart?"
  3. Attacks start with small questions. Change the ID. Change the URL. Frame the page. Then combine.

AI was used as a second opinion (stack identification). The decisive question stayed human: "What happens if I use this functionality in a way the developer didn't expect?"


Adapted from my original post on TallerWeb.

Top comments (0)