How It Started

I was looking for government bug bounty programs in Indonesia that actually accept external reports. Most of them have clauses requiring the reporter to be the system owner, which effectively closes the door for independent researchers. CSIRT Jakarta was different. Their portal at csirt.jakarta.go.id has a cyber report form with a clearly defined scope covering all *.jakarta.go.id subdomains, open to anyone.

I started with subdomain enumeration using certificate transparency logs, pulled around 800 subdomains, and started scanning which ones were actually live. One that stood out immediately was pajakonline.jakarta.go.id, the online tax payment portal for DKI Jakarta residents. Tax systems are always interesting because they handle real financial data and are used by the general public, which means the registration flow is usually accessible without needing an existing account.

I opened the registration page and the first thing I noticed was a reCAPTCHA widget at the bottom of the form showing a message I hadn't seen in a while.

Registration page showing reCAPTCHA over quota

"This site is exceeding reCAPTCHA Enterprise free quota." The bot protection was already broken before I even started.

The Registration Flow

The form collects a phone number, mobile number, email, and password before sending an OTP to verify the mobile number. Standard registration flow. I filled in dummy data, hit submit with Burp running in the background, and watched what happened in HTTP History.

The registration triggered a POST request to /wp/send-otp with the phone number in the body as the nohp parameter. Server came back with HTTP 200 and a JSON response.

Burp Suite showing POST /wp/send-otp request and response

POST /wp/send-otp with nohp in the body. Response: "OTP berhasil dikirim." PHP 7.1.33 visible in headers.

Finding the Vulnerability

After the OTP was sent, the UI showed a modal with a 30-second cooldown timer before you could request another one. Client-side protection. The question was whether the server actually enforced that cooldown or just trusted the frontend to handle it.

OTP modal showing 30 second cooldown timer

30 second cooldown in the UI. Looks like protection. It isn't.

I sent the request to Burp Repeater, changed the nohp value to an arbitrary phone number, and started clicking Send. No waiting, no cooldown, no CAPTCHA to solve. Just straight requests.

Ten requests in a row. Every single one came back HTTP 200 with "OTP berhasil dikirim". Not a single 429. No rate limit headers anywhere in the response. The server had no enforcement at all. The 30-second timer existed only in the browser.

Burp Repeater showing repeated OTP requests all returning 200

10+ requests, all 200 OK, all "OTP berhasil dikirim." No 429, no rate limit, nothing.

Proof of Concept

POST /wp/send-otp HTTP/2
Host: pajakonline.jakarta.go.id
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
Origin: https://pajakonline.jakarta.go.id

_token=[REDACTED]&nohp=[TARGET_PHONE]&type=register

--- Response (every single time) ---
HTTP/2 200 OK
X-Powered-By: PHP/7.1.33
Content-Type: application/json

{
  "message": "OTP berhasil dikirim"
}

Replace [TARGET_PHONE] with any Indonesian mobile number and send as many times as you want. The server will keep firing SMS messages with no limit whatsoever.

Impact Analysis

This is a public-facing tax portal used by Jakarta residents to pay taxes online. The OTP endpoint accepts any phone number without authentication, without CAPTCHA validation (which was already broken due to over quota), and without any server-side rate limiting. The 30-second cooldown only lives in the browser and disappears the moment you use Burp.

Anyone can use this to flood SMS messages to any phone number in Indonesia, using Pemprov DKI Jakarta's infrastructure to do it. The target doesn't need to have an account. They just need a phone number. For users who are in the middle of completing a tax payment, getting spammed with OTP messages from an official government system is also a convincing social engineering vector.

Worth noting: the server is also running PHP/7.1.33, which reached end of life in December 2019 and has received no security patches since then. That's a separate issue but it's sitting right there in every response header.

Remediation

Rate limiting on /wp/send-otp needs to be enforced server-side, not just in the browser. A reasonable limit would be 3 to 5 OTP requests per phone number per 10 minutes with exponential backoff. The CAPTCHA also needs to be validated on the backend before the OTP is sent, not just rendered on the frontend. And the reCAPTCHA quota situation needs to be resolved regardless, because a broken CAPTCHA is the same as no CAPTCHA. As a separate item, PHP 7.1.33 should be upgraded to a version that still receives security updates.

The Response

I reported this to CSIRT Jakarta via email at csirt@jakarta.go.id with a full description, proof of concept, and video recording. They got back to me the next day.

Email response from CSIRT Jakarta stating the finding is VALID

VALID. One word that makes the whole session worth it.

They marked it as VALID but noted it fell outside the criteria for an appreciation certificate since their policy only covers High and Critical severity findings. Which is a bit of a contradiction given I submitted it as High, but that's their call to make. The finding is acknowledged, it's on record, and the vulnerability is real regardless of what label gets attached to it at the end.

That's just how responsible disclosure works sometimes. You report it, they fix it hopefully, and you write it up. The certificate would have been nice but it was never the point.

Lessons Learned

Client-side protections are not protections. A 30-second cooldown timer in JavaScript, a CAPTCHA that's over quota, a disabled submit button. None of these mean anything once someone opens Burp and talks directly to the server. The only protection that counts is what the server enforces. If the server doesn't check it, it doesn't exist.
OTP Flooding SMS Bombing Rate Limit Bypass CAPTCHA Bypass PHP EOL Burp Suite Government Jakarta