Internet-Facing PHP Servers: Find, Verify, Fix

Internet-Facing PHP Servers: Find, Verify, Fix

Most organisations can name their PHP applications. The number they can name is rarely the number that answers on port 443. Internet-facing PHP servers accumulate quietly: a campaign microsite built by an agency in 2019, a staging copy of the main site that was never taken down, a form handler bolted onto a subdomain for one event. Each one keeps running the version of PHP it was installed with, long after anyone has a reason to log in.

This piece works the problem in the order an external analyst works it. Find the hosts, read the evidence honestly, then rank what you found so the fix list can actually be finished.

What an outsider can already tell about your PHP estate

The signals that travel without anyone scanning

PHP is unusually talkative. A plain HTTP request to a public host often returns a Server header naming Apache or nginx, an X-Powered-By header naming PHP and a version string, and a Set-Cookie line containing PHPSESSID. URLs carry .php extensions. Error pages use recognisable wording. Framework defaults give the rest away: /wp-login.php, /wp-json/, /index.php?option=com_, a Laravel debug page rendered in that distinctive orange.

None of this requires a scanner. It is what the server sends to anybody who asks for a page, including the crawler that indexed it and the attacker working through a list of your subdomains.

Why PHP hso a long tail

Other stacks tend to die with their projects. PHP sites outlive theirs. The deployment model is partly to blame: drop files in a web root, point a virtual host at it, and the thing runs indefinitely with no build pipeline to break and no container to be rebuilt. WordPress multiplies this, because a brochure site is cheap to create and nobody schedules its retirement.

So the long tail is predictable. Legacy WordPress installs, a Joomla site from two rebrands ago, one-off form handlers, a phpMyAdmin instance a developer installed for an afternoon. None of these is exotic. They are the ordinary sediment of an organisation that has existed for a few years and moved quickly.

What external observation is, and is not

Worth being precise here, because it changes what you are allowed to conclude. Looking at your estate from the outside means requesting pages that are already public and reading the responses. It does not mean exploiting anything, and it does not make your hosts more interesting to attackers, who are already indexing them. What it buys you is the attacker’s view: the same hostnames, the same headers, the same reachable paths, the way an attacker does, from the outside.

Finding the PHP servers nobody wrote down

Expanding from a seed domain

Start with what you know: your registered domains. From there, three sources do most of the work. Certificate transparency logs list every certificate issued for a name, which means every hostname anyone has ever requested a certificate for, including the staging copies. DNS records give you A, AAAA, CNAME and MX entries, and zone data where you can get it. Registration and WHOIS data turns up sibling domains bought for campaigns and never folded into the register.

Certificate logs are the highest-yield source and the most misread, because they show names that were requested, not names that still resolve. That gap is worth understanding before you start counting findings, and our note on the discovery gaps in certificate transparency logs covers where the method goes blind. The broader method, from seed domain through to a usable inventory, is set out in how EASM discovers assets.

Resolving candidates and seeing which ones answer

Resolve every candidate name to an address, then check which addresses answer on 80 and 443. Then check the alternate ports, because PHP tooling has a habit of living on 8080 and 8443: a Tomcat-fronted legacy app, a control panel, a developer’s port-forwarded test instance. For each host that answers, record the response headers, the cookie names, the title and copyright year, and whether any obvious PHP paths return something other than a 404.

Why the output is a candidate list, not a finding list

Wildcard DNS will resolve every name you invent, so asdf.example.co.uk answering proves nothing. Parked names answer with a registrar page. Shared hosting means an IP serves dozens of sites belonging to other people. Third-party platforms (the email provider, the survey tool, the recruitment portal) answer on your subdomain while running on infrastructure you cannot touch, which is a question of ownership rather than exposure, and who owns which third-party subdomain is usually the first thing to settle.

The pattern to expect

Here is the shape of a typical result. A seed expansion turns up legacy.example.co.uk in certificate transparency logs. It resolves. It answers on 443, sets a PHPSESSID cookie, carries a 2019 copyright notice in the footer, and /pma returns a database administration login. Nothing in the asset register mentions it. Nobody in IT recognises the name. The agency that built it stopped working with you in 2021.

That host is the finding. The ones in your register are mostly fine, because somebody patches them.

Likely versus definitely: reading PHP version evidence without being fooled

The banner misleads in both directions

This is where most PHP exposure reports fall apart. A header reading PHP/8.1.2-1ubuntu2.14 looks alarming against a CVE list, because naive matching compares 8.1.2 to the fixed upstream version and flags everything in between. But Debian, Ubuntu and Red Hat backport security fixes while keeping the upstream version string. That host may carry every relevant patch. The suffix after the version is the only hint that it has been rebuilt, and automated matchers routinely ignore it.

The reverse is worse. A bare PHP/8.1.2, with no distribution suffix, tells you almost nothing: it could be a compiled-from-source build that has not been touched since installation, or a container image rebuilt last week. And a host with expose_php=Off, or one sitting behind a CDN that strips headers, tells you nothing at all. Treat silence as unknown, not as safe.

Say “likely running an unsupported branch”and a server owner will check. Say “vulnerable to CVE-2024-XXXX”on banner evidence alone and your report comes back rejected, with the owner pasting their apt changelog output into the ticket. That distinction between likely and definitely is the whole discipline in one sentence.

CVE-2024-4577, and exactly where inference stops

The PHP CGI argument injection flaw disclosed in June 2024 is a clean illustration. It affects Windows installations that run PHP through CGI, including default XAMPP configurations under certain system code pages, and it was added to CISA’s Known Exploited Vulnerabilities catalogue after being weaponised within days of disclosure.

From outside, you can often infer Windows and PHP with reasonable confidence: an IIS server header, backslash-flavoured paths, a Windows-style error page. What you cannot see is the handler. Whether PHP is running through CGI or FastCGI, and which locale the system is set to, decides whether the flaw applies at all. It is an inference, not a confirmed fact. That is the point at which external observation hands over to a host-side check.

The sequence that holds up

Observe externally. Confirm internally with php -v, the package manager’s changelog or an authenticated scan. Record the verdict against the asset, with the date and the evidence, so the next analyst does not redo the work. Active testing earns its place at step two, on hosts you have confirmed you own, not as the opening move against a name you found an hour ago.

The exposures that usually matter more than the version number

Version drift is a slow problem. Reachable tooling is an immediate one.

  • Admin and tooling paths. /phpmyadmin and its aliases, adminer.php, install.php or setup.php left behind after installation, debug endpoints, and phpinfo() pages that Google has already indexed. An installer that still runs will let a stranger repoint the application at their own database.
  • Files served as plain text. A .env file containing live database and API credentials, because the web root was not configured to deny dotfiles. Also composer.lock (a precise dependency inventory, handed over), .git directories that allow the whole source tree to be reconstructed, and backup.sql or site.zip sitting in the web root.
  • Origin exposure behind a CDN. The public hostname returns hardened headers through the WAF and scores well on every external check. The origin IP still answers directly on 443, serving the same application with nothing in front of it. Comparing historical DNS and certificate data against the addresses currently answering is how that gap surfaces.
  • Virtual host sprawl on one IP. Fifteen sites on a single server, sharing a PHP version and often a filesystem. One neglected site weakens all fifteen. Certificate hygiene follows the same pattern: expired or mismatched certificates cluster on the hosts nobody owns.

Two findings on the same estate make the ranking obvious. Host A runs an end-of-life PHP branch behind single sign-on with no data store attached. Host B runs a current PHP build and serves a readable .env containing live database credentials. Host B is the emergency. Host A is a project.

Prioritising the findings so the fix list is finishable

Rank by reachability, authentication and data

Three questions, in order. Can an unauthenticated stranger reach it? Is there any authentication in front of it? Does it touch real data? An unauthenticated admin panel on a live database outranks an unsupported branch behind SSO every time, regardless of which one has the scarier CVSS score attached.

Where supported-version rules bite in the UK

Cyber Essentials requires that software on internet-facing services is supported by its vendor, and that patches rated high or critical are applied within 14 days of release. An unsupported PHP branch on a public host is so a certification problem as well as a security one, and assessors do look. For organisations likely to fall inside the incoming regime, the same exposures feature in what to fix first under the Cyber Security and Resilience Bill. The NCSC’s position on this is consistent and unglamorous: know what you own, keep it supported, patch the internet-facing things first.

On dates, check php.net’s supported versions page rather than trusting memory. At the time of writing, 7.4 and 8.0 are long past end of life, 8.1’s security-fix window has closed, 8.2 is in security-fix-only status, and active support sits with 8.3 and later. A diary entry for the next branch retirement is worth more than another scan.

The decommission-first test

Before anyone plans an upgrade, ask whether the site should exist at all. Turning off a microsite nobody has edited for four years removes the PHP version, the admin panel, the credentials and the certificate renewal in one action, and it is usually faster to get approved than a migration project. The uncomfortable truth is that a large share of PHP findings are better deleted than fixed.

One caveat. When the host goes, make sure the DNS record goes with it, or you have traded a stale application for a dangling record pointing at nothing that somebody else can claim.

Hosts you do not control

Plenty of what you find belongs to an agency, a hosting reseller or a business unit that commissioned it directly. Raise it with the named owner, in writing, with the evidence and a date. When nobody claims the name, the decision is yours to force: either someone accepts ownership and the patching obligation, or the record comes out of DNS. Unowned and live is the worst of the three states.

Keeping PHP exposure closed after the clean-up

A clean-up is a snapshot of a river. It was accurate at the instant it was taken and began drifting immediately, because a developer deploys a new service, a DNS record is added for a launch, an agency restores a backup to a subdomain. So watch for change rather than re-running a full discovery every quarter: a new hostname serving .php, a path that returned 403 last week and returns 200 today, an origin IP that has started answering directly again, a branch that has just passed end of life. The case for monitoring continuously rather than periodically rests on exactly that drift.

Alert fatigue, when it arrives, is usually self-inflicted. The same forgotten microsite gets reported four times: once for the PHP branch, once for the certificate, once for the admin path, once for a missing header. Group findings by asset so the host appears as one item with four facts attached. Suppress accepted risks with an expiry date, not permanently, so the suppression itself comes back for review. Reserve new alerts for genuine change.

And keep the boundaries straight. External observation finds the hosts and tells you what an attacker can see; authenticated vulnerability scanning tells you what is actually installed; configuration management stops the next one appearing. External discovery replaces neither of the other two. It just makes sure they are pointed at the whole estate, with the fields recorded against each asset that let someone else pick the work up.

The Short Version

  • Expand from your seed domains through certificate transparency logs, DNS and registration data, then see which candidates answer on 80, 443, 8080 and 8443. Treat the output as candidates, not certainties.
  • Version banners mislead in both directions. Distribution backporting means PHP/8.1.2-1ubuntu2.14 may be fully patched; a stripped banner means unknown, not safe.
  • Reachable admin paths, readable .env files and directly answering origin IPs normally outrank the version number. Rank by reachability, authentication and data.
  • Cyber Essentials expects supported software on internet-facing services and high or critical patches inside 14 days, so an unsupported branch is a certification issue too.
  • On forgotten estate, decommission before you plan an upgrade, then watch the DNS record so the name does not become a takeover candidate.

Frequently Asked Questions

How do I find every internet-facing PHP server on my domains?

Start from your registered domains and expand outwards using certificate transparency logs, DNS records and registration data to build a candidate hostname list. Resolve those names, check which answer on 80, 443 and common alternate ports, and record the PHP signals each one returns. “Every”is the wrong target, honestly: you will find the ones that are reachable and discoverable, which is the same set an attacker finds.

Can an external check prove which PHP version a server is running?

No. It can support a likely conclusion, which is a different thing. Distribution packagers backport security fixes while keeping the upstream version string, so a header showing an old version may belong to a fully patched build, and a suppressed header tells you nothing. Confirm with php -v, the package changelog or an authenticated scan before you write “vulnerable”in a report.

Which PHP versions are still receiving security support?

Check php.net’s supported versions page for the current position, because the dates move every December. At the time of writing, 7.4, 8.0 and 8.1 are past their security-fix windows, 8.2 is in security-fix-only status, and 8.3 and later remain in active support. Put the next retirement date in a calendar rather than rediscovering it in a scan.

Is an exposed phpMyAdmin or.env file worse than an old PHP version?

In most cases, yes, and by a wide margin. An old branch is a latent risk that requires an attacker to find and use a working exploit; a readable .env with live database credentials is an immediate compromise requiring no skill at all. Fix the reachable credential and the unauthenticated admin panel first, then schedule the version work.

Does checking my own PHP servers from the outside count as hacking them?

Requesting public pages and reading the responses is what every crawler does and does not constitute exploitation. The line sits at active testing: sending payloads, attempting logins or probing for injection flaws goes beyond observation, and in the UK the Computer Misuse Act 1990 makes authorisation the deciding factor. Get written authorisation before active testing, and be careful about assets that turn out to belong to a supplier rather than to you.