Disclaimer: This finding was reported to the vendor through responsible disclosure before publication. Every request shown here was sent against my own registered account to demonstrate a business-logic flaw. Do not replicate this against any service you don't own or don't have explicit permission to test.

Hi everyone! Hope you're all doing well.

Not every serious vulnerability is a gnarly memory-corruption bug or a five-stage exploit chain. Some of the most damaging ones are embarrassingly simple — a single API endpoint that trusts you when it absolutely shouldn't. This is the story of one of those: how I turned a free reader account into a full premium subscriber on MalakaBooks without paying a single rupiah, using one curl command.

What Is MalakaBooks?

MalakaBooks.id is an Indonesian digital book platform — think of it as a curated online library where the community chips in through a membership fee to keep the collection alive and growing. You sign up for free, browse the catalog, and if you want to read the premium ("iuran keanggotaan") titles, you subscribe to one of their plans.

The pricing is straightforward, three tiers:

  • 3 Months — Rp80.000
  • 6 Months — Rp140.000
  • 12 Months — Rp240.000 (the "recommended" one)
MalakaBooks membership page showing three subscription tiers: 3, 6, and 12 months

The membership page — three paid tiers, the whole revenue model in one screen. Everything premium sits behind these.

All perfectly normal SaaS stuff. The whole business runs on those subscription payments — so the one thing that should be airtight is the boundary between "paid member" and "not paid member." That's exactly the boundary that turned out to be made of paper.

The Endpoint That Shouldn't Exist

I registered a normal account and started poking at the API behind the app. Like most modern platforms, MalakaBooks splits its frontend (malakabooks.id) from its API (api.malakabooks.id), and the subscription logic lives in a handful of REST endpoints.

One of them stopped me cold the moment I saw it:

POST /subscriptions/mock-activate

Read that endpoint name again. Mock. In production. That word usually belongs in a test suite or a staging environment — a stub that fakes a real payment so developers don't have to run actual transactions while building the feature. What it should never be is a live, reachable route on the production API. But there it was.

Confirming I Was Actually Locked Out

Before claiming a bypass, I first proved I genuinely didn't have access. I opened a premium book — "Dari Logika Mistika, Lewat Filsafat, Menuju Ilmu Pengetahuan" — and, as expected, got hit with a paywall popup: "Iuran Diperlukan" (Membership Required).

MalakaBooks paywall popup reading Iuran Diperlukan / Membership Required, blocking a premium book

The honest gate — "you need a membership to read this." Every chapter in the table of contents is locked.

To confirm it at the API level rather than just the UI, I checked my own subscription status:

GET /subscriptions/my-status

The response was unambiguous:

{
  "userId": "7577ae0e-a87f-4d34-ab69-6ebff9c484dc",
  "subscription": { "id": "", "status": "none" },
  "accessLevel": { "canAccessSubscriptionBooks": false }
}
Burp Suite showing GET /subscriptions/my-status returning canAccessSubscriptionBooks false

Baseline confirmed at the API level: status: "none", canAccessSubscriptionBooks: false. (Bearer token redacted.)

"canAccessSubscriptionBooks": false. No active subscription, no access. This is the honest, expected state of a free account. Good — now I had a clean baseline to bypass.

One Request, Full Premium

Here's the entire "exploit." No burp macros, no token forging, no race condition — just a direct POST to that mock endpoint with a valid planId:

curl -X POST https://api.malakabooks.id/subscriptions/mock-activate \
  -H "Authorization: Bearer <YOUR_OWN_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"planId":"<VALID_PLAN_ID>"}'

The planId values aren't even secret — they're returned in plaintext by the public GET /api/frontsite/plans endpoint that the pricing page itself calls. I grabbed the 12-month plan ID from there and dropped it into the request. The server's answer:

HTTP/2 201 Created
{
  "subscription": {
    "status": "active",
    "plan": { "name": "12 Bulan", "durationDays": 360, "priceAmount": "240000" },
    "startedAt": "2026-06-25...", "expiresAt": "2027-06-20..."
  },
  "message": "Subscription activated successfully"
}
Burp Suite showing POST /subscriptions/mock-activate returning 201 Created and Subscription activated successfully

One POST to /mock-activate201 Created, a live 12-month plan, and "Subscription activated successfully." No payment ever happened. (Bearer token redacted.)

"Subscription activated successfully" — a full 12-month premium plan worth Rp240.000, activated for free, in one request. And when I re-checked /subscriptions/my-status:

"accessLevel": { "canAccessSubscriptionBooks": true }
Burp Suite showing GET /subscriptions/my-status now returning status active and canAccessSubscriptionBooks true

Same status endpoint, seconds later — status: "active", 360 days remaining, canAccessSubscriptionBooks: true. The account is now a paying member that never paid. (Bearer token redacted.)

The flag flipped server-side, instantly. I went back to that same premium book, and the paywall was gone — "Start Reading" and "Save to Library" right where the "Membership Required" popup used to be. Full access. No payment, no transaction record, nothing.

The previously locked MalakaBooks premium book now showing an IN SUBSCRIPTION badge and a Start Reading button

The payoff — same book, now tagged "IN SUBSCRIPTION" with a live "Start Reading" button. The paywall popup is gone entirely.

The affected plans — all three tiers were activatable the same way, just by swapping the planId:

91bf8076-... → 3 Months (Rp80.000)
b24cbe9d-... → 6 Months (Rp140.000)
b10a3d56-... → 12 Months (Rp240.000)

Root Cause: Zero Validation

This one endpoint fails at three separate layers, any one of which would have stopped the attack on its own:

1. No payment verification. The endpoint never checks whether a real transaction happened. It accepts a planId and flips your subscription to active — no payment gateway callback, no order reference, no receipt. The "mock" in mock-activate tells the whole story: it was built to fake a successful payment during development, and it kept doing exactly that in production.

2. No authorization boundary. Any authenticated user with the default reader role can call it. There's no check that this is an admin action, no server-side gate that says "only the payment system may change subscription state." If you can log in, you can subscribe yourself for free.

3. No transaction integrity. Subscription state — a payment-critical, money-tied piece of data — can be written directly by the client with no cross-check against the payment provider. The endpoint is effectively an unauthenticated write to the billing database wearing a thin coat of "you must be logged in."

The most likely story here is a classic one: a test/staging endpoint that made it into the production build and never got removed or gated. It happens — especially in fast-moving startups — but on a payment path it's about as costly a leftover as you can ship.

Why This Matters

This isn't a theoretical "what if." The business impact is direct and stackable:

  • Straight revenue loss. Every bypassed subscription is a sale that never happened. The entire platform is funded by these memberships.
  • Trivially automatable at scale. Register account → grab token → one POST → premium. Script that loop and you have unlimited free premium accounts on tap.
  • Subscription model collapse. Once "you can just skip paying" is common knowledge, the paying-customer incentive evaporates.
  • Broken business-process integrity. If subscription status can be mutated without payment, the billing records no longer reflect reality — which poisons analytics, revenue reporting, and any downstream system that trusts them.
  • Infrastructure cost. Free premium access means more heavy readers, more bandwidth, more load — all uncompensated.

Given how trivially it's exploited and how squarely it hits the revenue model, I rated this High severity.

Responsible Disclosure

I documented the full finding — summary, reproduction steps, affected plan IDs, impact, and remediation — and reported it to MalakaBooks through two channels to make sure it actually reached a human:

  • Email — a detailed vulnerability report sent to the team.
  • Instagram — a follow-up through their official account, in case the email slipped through the cracks (a small platform's inbox isn't always monitored the way a dedicated security channel would be).

The point of responsible disclosure isn't to dunk on anyone — it's to get the hole closed before it's public knowledge and before someone less friendly finds it. I gave them the details and the time to fix it, and I'm writing this up factually, with the actual payload values redacted to placeholders, precisely because the goal is awareness, not a how-to.

The Fix

For the MalakaBooks team, and anyone building a subscription flow, the remediation is standard:

  • Kill the mock endpoint in production. Test stubs like /mock-activate should live behind an environment flag that is hard-off in prod, or not be deployed there at all.
  • Verify payment before granting access. Subscription state should only change in response to a confirmed payment-gateway callback (Midtrans, Xendit, Stripe, whatever's in use) — never on a bare client request.
  • Enforce server-side authorization. Explicitly restrict who and what can mutate subscription status. Default to deny.
  • Log and monitor subscription changes. Every activation should be auditable. One account activating multiple plan IDs, or an activation with no matching transaction, should raise a flag.

Conclusion

MalakaBooks is a genuinely nice platform with a good mission — keeping a community-funded digital library alive. This bug isn't a knock on that; it's the kind of thing that slips through when you're shipping features fast and security review comes later. The fix is quick, and I'm optimistic they'll close it.

The lesson I keep relearning: the scariest vulnerabilities aren't always the complex ones. Sometimes it's a single endpoint with the word "mock" in its name, sitting in production, quietly trusting everyone who asks. If you run anything with a paywall, go audit the authorization logic on your money-critical endpoints today — your revenue is sitting behind it.

Key Takeaway: a "mock" or "test" endpoint left reachable in production is a live vulnerability, not a harmless leftover. Anything that can change a paid-access flag must verify a real payment first — client requests are never proof of one.

Maybe that's all from me. I'm RyuuKhagetsu, see you in next article.