I am a huge fan of Salt and its ability to transform an organization with its infrastructure-as-code capabilities. Yes, yes, it's owned by Broadcom, a company whose popularity is only slightly higher than Ebola, but they haven't effed it up quite yet. Even so, if you don't have an IaC solution in your stack, Salt's open-source model gives you staggering options.

I won't go into a deep dive on how Salt works here, except to say it's a master-minion relationship. The Salt Master server pushes configs (known as states) to the Salt minions under its purview, which can include enforcing firewalls, sending dynamic files using Jinja, and locking down endpoints. The control is practically endless.


But the real magic isn't just pushing configs; it's the Master's ability to actively query the minion fleet. And herein lies its true power over traditional agentless vulnerability scanners and static CMDBs. Standard security tools rely blindly on known OS package manager registries (rpm, dpkg, or Windows Installer databases) to inventory installed software. If a runtime or application is dropped in via a manual ZIP extraction, compiled from source, or tucked away inside a container layer, standard tools are completely blind to it. Salt turns your entire server fleet into an active, programmable query engine that pierces straight through those blind spots.


Salt doesn't just manage your infrastructure; it forces it to tell you the truth.


Case in point: I knew I had one PHP installation in a network I was using Salt to manage. One. I knew about one. Once I used Salt to query all the server-based minions and audit runtimes, I found an additional two installations. Each of these three PHP installs was vulnerable to PHP2Shell, and two of them had essentially an unknown existence. Remember, PHP may not be installed in a known path, or could be hiding in a container, a Snap application, or a random directory so finding them manually, let alone knowing they're there, is an exercise in futility. Salt doesn't just manage your infrastructure; it forces it to tell you the truth.


Again, I won't go into Salt installation at all here (ask your AI-du-jour), however here are two scripts I had Claude build me to find errant PHP installs in Docker containers and in Snap applications:

#!/bin/sh

# Report the PHP version inside every running container on this minion.

# Output: host|container|image|php_version.  

HOST=$(hostname)


for c in $(docker ps -q); do

NAME=$(docker inspect --format '{{.Name}}' "$c" | sed 's#^/##')

IMAGE=$(docker inspect --format '{{.Config.Image}}' "$c")


VER=$(docker exec "$c" sh -c '

if command -v php >/dev/null 2>&1; then

  php -r "echo PHP_VERSION;"

elif command -v php-fpm >/dev/null 2>&1; then

  php-fpm -v 2>/dev/null | head -1

else

  echo NOT_FOUND

fi' 2>/dev/null)


echo "${HOST}|${NAME}|${IMAGE}|${VER:-EXEC_FAILED}"

done


To run this a Minion grain named Role is expected to contain "docker":

[root@saltmaster user@hostname]# salt -G 'roles:docker' cmd.script salt://scripts/php_in_containers.sh --out=txt

aichat.STS.LOCAL: {'pid': 12054, 'retcode': 0, 'stdout': 'aichat.sts.local|open-webui|ghcr.io/open-webui/open-webui:main|NOT_FOUND\nchatter.sts.local|llama-phi4-mini|ghcr.io/ggml-org/llama.cpp:server-cuda|NOT_FOUND\naichat.sts.local|comfyui|ghcr.io/ai-dock/comfyui:latest-cuda|NOT_FOUND', 'stderr': ''}

someproxy: {'pid': 188039, 'retcode': 0, 'stdout': 'someproxy|netdisco-backend|netdisco/netdisco:latest-backend|NOT_FOUND\nsomeproxy|netdisco-web|netdisco/netdisco:latest-web|NOT_FOUND\nsomeproxy|netdisco-postgresql|netdisco/netdisco:latest-postgresql|NOT_FOUND', 'stderr': ''}


While none were found in the above containers, we now *know* they don't include a hidden PHP install.


Here is a Linux shell script that Salt can run to peer into Snap applications, such as Netcloud:

#!/bin/sh

# snap_php.sh

# Reports PHP inside installed snaps. Where a snap has an app wrapper in

# /snap/bin (e.g. nextcloud.occ), PHP runs through `snap run --shell`, which

# supplies the snap's own library environment. Otherwise the script runs any

# PHP binary it finds directly.

# Output: host|snap|method|app_or_path|version


HOST=$(hostname -f 2>/dev/null || hostname)


for current in /snap/*/current; do

[ -d "$current" ] || continue

name=$(basename "$(dirname "$current")")

case "$name" in

core|core18|core20|core22|core24|snapd|bare|bin) continue ;;

esac


# Preferred: run PHP inside the snap's environment through an app wrapper

app=""

[ -x "/snap/bin/${name}.php" ] && app="${name}.php"

[ -z "$app" ] && [ -x "/snap/bin/${name}.occ" ] && app="${name}.occ"

if [ -n "$app" ]; then

v=$(snap run --shell "$app" -c 'php -r "echo PHP_VERSION;"' 2>/dev/null)

echo "${HOST}|${name}|snap-run|${app}|${v:-UNKNOWN}"

continue

fi


# Fallback: run any PHP binary found in the snap directly

find "$current/" -type f \( -name 'php' -o -name 'php[0-9]*' -o -name 'php-fpm*' \) 2>/dev/null |

while read -r bin; do

[ -x "$bin" ] || continue

case "$(basename "$bin")" in

  php-fpm*) v=$("$bin" -v 2>/dev/null | head -n 1) ;;

  *)        v=$("$bin" -r 'echo PHP_VERSION;' 2>/dev/null) ;;

esac

echo "${HOST}|${name}|direct|${bin}|${v:-UNKNOWN}"

done

done


Which when ran will report any PHP versions lurking in Snap installs, and this one hits pay dirt as 8.2.26 is vulnerable to PHP2Shell:

[root@salt user@host]# salt -G 'roles:nextcloud' cmd.script salt://scripts/php_in_snap.sh --out=txt

my-nextcloud.domain.com: {'pid': 2389795, 'retcode': 0, 'stdout': 'my-nextcloud.domain.com|nextcloud|snap-run|nextcloud.occ|8.2.26', 'stderr': ''}


Salt can do so much more than this, but this goes to show the kind of power it offers when you start to think outside the box.




Darren Duke   |   October 25 2026 12:20:09 PM   |    security salt    |  
  |   Next Document   |   Previous Document

Comments (0)

No Comments Found