Your wp-config.php Is Fine. The Exposed File Next To It Is Not..
wp-config.php is safe because PHP executes it. Rename it .bak and the same secrets are served as plain text. Here is the probe, and the fix.
Short answer: a wp-config exposed to the internet is almost never wp-config.php itself. PHP executes that file and sends nothing. The problem is wp-config.php.bak, wp-config.php~, wp-config.txt and their relatives, which the web server does not recognise as code and therefore hands over as plain text, database password included.
I have been on the POSIMYTH side of enough support tickets to know how these files get there.
Nobody is careless.
Somebody was about to do something risky, took a copy first, fixed the thing, and moved on. That is good instinct. The copy is what stays behind.
Why one file is safe and its twin is not
WordPress does not protect wp-config.php by hiding it. It is protected by being executed.
The server sees .php, hands it to PHP, PHP runs it, and the constants become variables in memory. Nothing is printed. The response body is empty.
Change the extension and that entire chain stops.
.bak is not mapped to a handler, so the server falls back to what it does with any unknown file. It reads it and sends it.
Here is the same install answering both requests:

mechanism is real even though the password is not. One character of difference between the two filenames, and the second one prints the database credentials to anybody who asks.*
No exploit. No vulnerability in WordPress. Just a file being served, exactly as designed.
The filenames actually worth checking
When I look at a site I have inherited, I do not check one name. I check the family, because each one is created by a different ordinary habit.

They are what an editor leaves behind after a crash, or what a person creates thirty seconds before a migration they are nervous about.*
.swp is the one people dismiss. It is a binary vim swap file, so it looks like noise.
Run strings over it and the password is right there.
Check a site for an exposed wp-config in about fifteen seconds
Point this at any domain you look after:
for f in wp-config.php.bak wp-config.php~ wp-config.txt wp-config.php.save \
wp-config.php.old .env debug.log; do
printf '%-24s ' "$f"
curl -s -o /dev/null -w '%{http_code} %{size_download} bytes\n' "https://example.com/$f"
done
What you want is 403 or 404 on every line.
What should worry you is 200 with a non-zero byte count. That is the file being served.
A 200 with 0 bytes is fine, which is why the size column is in there. Status code alone will tell you the wrong thing about wp-config.php itself.
Closing it
Two moves, and the second is the one that lasts.

inventing new suffixes, and a rule listing only .bak will not catch the .save that nano writes next month.*
Then verify it. Request the file again and confirm you now get 403 rather than 200.
An unverified rule is not a fix. It is a note to yourself.
And if a file was exposed, treat the credentials as burned. Rotate the database password and the salts in wp-config.php. A deny rule added today does nothing about who read the file last March.
Why this one is worth automating
The check is trivial. That is exactly the problem.
It takes fifteen seconds, it needs doing on every site you look after, it needs doing again after every migration and every hosting move, and it produces nothing to show a client when it passes. So it gets done once, thoroughly, during onboarding, and then never again. It is the same unbilled half hour that swallows every other check on the list.
Meanwhile the file that leaks is created later, by the migration, by the crash, by the careful person taking a backup before a risky change.
This is one of the checks Protuno’s free audit runs from the domain alone, because it needs nothing but HTTP requests. Straight with you about the rest: the Super Agents that would keep re-checking it on a schedule are built but not live yet.
Until then, the loop above is yours. Run it across your portfolio once this week.
If you find one, I would rather you found it than someone else did. The same logic applies to the plugins that quietly left the directory, and to the scheduled jobs that stopped without telling anyone: silent, cheap to fix, and invisible until they are not.
You can run the free audit on a domain without installing anything.
Comments