Exodos Labs SBOM Blog

We should already know where PHP is running

Written by Jens Schmidt | Oct 23, 2026, 8:07:48 PM

 

In a recent LinkedIn post, I wrote about a discussion in a security group: how long would it take to produce a complete list of systems running PHP?

The most common answer was: a few hours. Hardly a scientific study, but honestly, I was pretty shocked. We're talking about experienced CISOs, ISOs and cybersecurity people who know their stuff. And in 2026, we still need hours to find out where we're running PHP? That's the part I can't quite get out of my head. So I started thinking... and building.

The immediate context was PHP's 21 October security release, addressing two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, that can allow unauthenticated remote code execution. That is a fairly uncomfortable moment to start asking where PHP is installed.

And then there is the spreadsheet approach to inventory management. A container can be replaced in seconds while its inventory row still describes the image from last month. Adding a "last updated" column does not make it update itself. Honestly, Excel is not an option here, especially for technical experts dealing with something this critical.

If keeping that information accurate depends on someone remembering an extra administrative task after every change, we have built "forgetting" into the process.

The difficult word is "all"

I understand why the question is hard. Large organisations still struggle to identify every application, server and supplier operated service. Finding a component buried inside those systems adds another layer.

"Our developers don't use PHP" tells me something about our own development. It leaves open the internal tool, the CMS, the vendor appliance and the supplier's application. Even a familiar application name tells me very little about the runtime and packages underneath it.

An SBOM search will not discover a forgotten server that never entered our inventory. Missing supplier information remains a gap, too. Asset discovery, deployment information and supplier cooperation remain necessary.

But for the software we do know about, I expect component information to be available before an security advisory arrives.

That means collecting it as systems change, connecting it to the software we actually operate, and retaining enough context to explain what a search result means.

Make collection part of normal operations

For a Docker environment, the practical engineering question is straightforward: how do we automatically collect software composition from the images our containers actually use?

We already have much of what we need. Docker exposes an API. Syft generates SBOMs from container images. Exodos provides an API for ingesting and managing them.

So I connected those pieces with a small Python collector.

The workflow is simple:

  1. The mentioned Python collector connect to the Docker socket and list running containers.
  2. Inspect their image identities, then watch for subsequent container start events.
  3. Ask Syft to generate a CycloneDX SBOM for each relevant local image.
  4. Find or create an Exodos inventory for the host and container, then upload the SBOM.

Once the destination inventory is resolved, the upload is one authenticated REST request containing the SBOM file and the inventory ID. There is very little integration ceremony involved.

The relevant image is the one attached to the observed container. A registry's myapp:latest tag may already point to a newer image while production continues running the previous one. Scanning the image currently published under that tag would answer a different question.

The script therefore uses the inspected local image ID. It uploads Syft's output unchanged; Exodos handles the subsequent analysis. An accepted upload means the API has received the SBOM. Analysis can still be running asynchronously.

Docker supplies the observation, Syft supplies the component inventory, and the platform makes that inventory searchable and useful over time.

The integration code can stay fairly small because those interfaces already exist.

 

What happens to the PHP nobody installed "normally"?

There is an important boundary here: this scans the image used by a container. Files added to its writable layer, software downloaded at startup, and content in mounted volumes are mostly outside that scan. So are the host operating system and services on Docker hosts where the collector isn't running.

Consider an old PHP runtime compiled under /opt/vendor/php, still handling web requests after the distribution's PHP package has been updated. The package update succeeded. The application continues using a different, vulnerable runtime.

That is a fairly concrete way to end up with "patched" in an inventory while leaving the actual service exposed.

PHP in a custom location can still be detected inside the image. For software added at runtime or supplied through mounted volumes, we need to extend collection to the relevant filesystem.

An SBOM needs a connection to deployment state

An SBOM records composition at a particular point in time. It does not subscribe to Docker events or update itself when a container stops. Neither Syft nor Exodos can infer that live state from the uploaded file alone. The integration has to supply it.

The example is deliberately small: duplicate tracking lives in memory, there is no durable retry queue, and it does not process stop or delete events. Restarting the collector can upload the same image again. These are documented limitations of the collector.

There is a useful extension here, though.

Using Exodos's tagging system, a production collector could mark an SBOM as current for a particular host and container, together with the time its image was observed. When a replacement image appears, it could move that marker to the new SBOM. Start, stop and delete events could maintain additional labels such as online, offline or retired.

Those labels would describe the observed deployment associated with the SBOM. The same image might still be running elsewhere, so a single global "offline" label on an image would be misleading.

Periodic reconciliation with Docker would correct missed events. If the collector loses contact, the information should become stale or unknown; silence does not prove that a container stopped.

That gives "current" a useful, explicit meaning: this is the composition last observed for this deployment, at this time.

An unchanged image can acquire new findings

Once the composition is retained, we can reassess it as vulnerability information changes. At Exodos, that means monitoring components, surfacing new findings and sending alerts without requiring the application to change first.

The image can remain identical while our knowledge of its risk changes considerably.

This also explains why I care about keeping the whole component inventory. A package with no known vulnerability today can become the package everyone is asking about next week.

And yes, I can hear the objection: isn't this Docker vulnerability scanning with extra steps?

There are good tools for that. Docker Scout, for example, already uses retained image metadata to reassess vulnerabilities as new information appears. I am certainly not claiming we invented that approach.

For me, the reason to feed an SBOM platform is the wider connection: putting deployed image composition alongside our application releases, supplier SBOMs and recorded assessments. The Docker host then contributes to the same body of information we use elsewhere.

Where do we have PHP, and what changed?

You do not have to develop software to benefit from this.

For an open source application you operate, the useful connection is between its image, its components and the containers using it. For software you buy, it is between the installed product version, the supplier's component information and your deployments. For software you build, your own builds and releases provide that composition information.

The operational question stays the same: where do we have PHP, which version and build is it, and who looks after that system?

Imagine the same PHP-based helpdesk running on three Docker hosts. Two have received an updated image. The third still uses the previous one.

With composition snapshots connected to current deployment observations, we can identify the remaining installation. Comparing the SBOMs shows whether PHP changed, whether another dependency was added, and whether the old component disappeared. The deployment observations tell us where that change has actually arrived.

Later, a new advisory might affect a component in the older image. A supplier might publish an impact assessment. Our team might investigate and record why a finding applies to one installation.

Keeping that information together lets us follow what we observed, what we subsequently learned and what action we took. That is useful whether the software came from our developers, an open-source project or a commercial supplier.

For supplier software, the evidence must match the product version we actually use. An SBOM for the vendor's latest release does not answer questions about our older installation. Securely exchanging the relevant SBOMs and impact assessments is part of maintaining that connection.

Finding PHP is the start of the assessment

A useful result needs more detail than "PHP is present".

We need the exact version and build, the package or binary identity, and the system or image where it was found. Distribution context matters, too: vendors can backport security fixes without adopting the latest upstream version number. Matching only a shortened version string can therefore produce the wrong conclusion.

Then comes the environment. Which runtime is the service actually executing? Where is its configuration? Can attacker-controlled input reach the vulnerable processing path? Is the service exposed to the internet, accessible internally, or restricted by meaningful isolation?

This is also how I want to use CISA’s Known Exploited Vulnerabilities catalogue. A KEV entry tells us that a vulnerability has been exploited in the wild. That is an important prioritisation signal. It still needs to be connected to the affected component and build in our environment, followed by an assessment of the relevant attack paths and controls.

An internal service is not automatically unexploitable. Isolation can reduce attack paths; it does not remove the vulnerable code.

For these PHP vulnerabilities, even application-level reachability needs care. PHP’s advisory describes a demonstrated mod_php attack that can trigger before application code runs. Looking only at which functions our own code calls would miss that distinction.

And this is where the spreadsheet problem returns. A row saying "PHP + updated" leaves some fairly important questions unanswered. Which PHP? The distribution package? The second binary under /opt? A runtime supplied through a mounted volume?

You can add columns for versions and paths. Keeping those columns tied to what is actually deployed is the difficult part.

An SBOM gives us component evidence within its collection scope. Deployment, configuration and runtime information supply the rest. The first lookup should be quick; investigating the impact can reasonably take longer.

Useful before the next vulnerability appears

The same composition data supports other decisions.

Comparing successive images can reveal dependencies that appeared unexpectedly (imagine LiteLLM popping up out of the sudden) or packages that remained unchanged through several updates. Package health information can help identify maintenance concerns and plan upgrades. Licence analysis can support decisions about how software is used or distributed.

With additional provenance information, there are also supplier and geographic risk questions to investigate. An SBOM provides a starting point for those analyses; the quality of the supporting information still matters.

My homelab probably does not need a geopolitical risk committee. A company distributing products or depending on multiple suppliers may have rather more use for those questions.

This is why I find the broader SBOM approach useful. The effort spent collecting composition can support several decisions, including ones we did not anticipate when we generated the file.

Have the inventory ready before the advisory

PHP had already published an advance notice on Tuesday, 20 October. With maintained component information, that was enough to start identifying known PHP installations and the people responsible for them before the full security release arrived. The detailed impact assessment could follow the advisory.

That is the response I want to make routine.

The collection example is available in the Docker-to-Exodos repository on GitHub. I deliberately kept it short: the current script is 139 lines long. Good SDKs and straightforward REST interfaces make an enormous difference. Docker, Syft and Exodos already do the substantial work; the script connects them.

The README covers the dependencies, Docker connection and Exodos API configuration. Start with one host and inspect the results, including whether the components you expect are actually there.

Exodos’s Community tier includes free basic vulnerability matching and API access. Our fair-use allowance of 100 SBOMs per month gives you room to try this with your own images.

For the systems covered by maintained data, I expect "where do we have PHP?" to become a lookup that takes seconds. And when the result says "no PHP found", I want to see what was searched, when it was observed and where information is still missing.

The next critical advisory will give us enough work. Reconstructing our software inventory should not be part of it.