I pointed my own toolchain at this domain and treated it like a client engagement. Three findings survived to remediation. Here is each one, and the fix.
It felt wrong to publish CTF write-ups from a site I had never actually tested myself. So before anything else went live, I ran the same first pass I would run against a client: enumerate what’s public, check what a script kiddie’s default toolkit would catch in the first ten minutes. It caught three things.
Finding 1: Username enumeration via the REST API
Severity: Low, but free. Where: /wp-json/wp/v2/users.
WordPress’s REST API exposes a users endpoint by default, unauthenticated. Anyone can request it and get back every author’s display name and, more usefully, their exact login slug, the same slug that goes in the username field of a login form. No scanning, no guessing: just a GET request.
curl -s https://selimcelebi.be/wp-json/wp/v2/users | jq
That single request handed over the exact string a bruteforce or credential-stuffing attempt would need for the username field. Half of a login is a much smaller search space than half of a login plus a guessed username.
Fix: filtered the REST response to strip user data for unauthenticated requests, and disabled author-archive URLs (/?author=1 and friends), which leak the same slug through a different door.
Finding 2: A plaintext address, harvestable in one line
Severity: Low individually, compounding over time. Where: the contact page, in the page source.
The original contact page had an email address sitting in the HTML as plain text. A human reader never notices; a scraper does. Every mail-harvesting bot on the internet runs some variant of a regex over page source looking for [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}. Publish an address anywhere it can crawl, and it will end up in a spam list within days. This is not a dramatic vulnerability. It is a slow, compounding tax that never goes away once it starts.
Fix: removed the address from every page and replaced it with a form (SureForms) that routes messages server-side. The address itself never appears in any HTML response, so there is nothing for a scraper to find.
Finding 3: Unencrypted HTTP still answering on port 80
Severity: Higher than the other two. Where: the network layer, not the application.
The site’s own links all pointed to HTTPS, but the server still accepted and served plain HTTP on port 80 without forcing a redirect. That matters for a reason that has nothing to do with what this particular site contains: anyone on a shared network (a coffee shop Wi-Fi, a compromised router) could intercept an HTTP session and read or tamper with it in transit. A contact form submission, in particular, has no business ever leaving a browser unencrypted.
Fix: forced an HTTP→HTTPS redirect at the server level and added Strict-Transport-Security so a browser that has visited once will refuse to downgrade on a later visit, even if a link somewhere still points to the plain HTTP version.
What this was, and wasn’t
None of these three is a serious vulnerability on its own. That’s the point worth making, not hiding: most real exposure isn’t a dramatic zero-day, it’s a handful of small, boring gaps that a first-pass scan catches in minutes because nobody ever ran the scan. The value of doing this pass wasn’t finding something impressive. It was proving I actually run the checks I’d run for anyone else, against the one target I have no excuse to skip.
Hardening on this site now also covers XML-RPC, WordPress version fingerprinting, dashboard file editing, and PHP execution inside the uploads directory: background items with no story attached, fixed at the same time as these three.