KBeezie

There's no place like ::1

Menu
  • Home
  • Start Here
  • Security Series
  • About

Detecting Compromised PHP Sites with a Squid Proxy

2026/05/13 in Security

A compromised WordPress plugin doesn't always deface your site. Sometimes it just opens a socket, sends your database credentials to a command-and-control server, and waits. Inbound firewalls see nothing. File scanners might miss it if the code is obfuscated. But the outbound connection — PHP phoning home — is visible if you're watching the right layer. A local Squid proxy forces all PHP outbound traffic through one monitored choke point, and a Python correlation script matches every external connection back to the exact script and site that made it.

The blind spot

By default, PHP-FPM workers can open outbound connections to any host on any port. A malicious actor who injects code into a theme or plugin can exfiltrate your database, POST your wp-config.php to a remote server, or register the machine as a node in a botnet — all without triggering a single inbound firewall rule. Your Nginx access logs show what came in. They show nothing about what PHP did after the request completed.

Putting a proxy in front of PHP's outbound traffic changes that. Every connection — cURL requests, file_get_contents() calls, stream wrappers, SOAP clients — routes through Squid. Squid logs it. You audit it periodically. The built-in threat-intelligence blocklist stops connections to known C2 domains before they leave the machine. What's left in the log after filtering out trusted traffic is a short list of connections worth investigating.


Step 1: Install Squid

sudo apt install squid

Squid binds to localhost only — it's not a public proxy. Nothing outside the server can reach it. Only processes running on the same machine (PHP-FPM, cron jobs, your audit scripts) can use it.


Step 2: Configure Squid

Replace /etc/squid/squid.conf:

# Bind to localhost only — not reachable from outside
http_port 127.0.0.1:3128

# Allow only standard web ports
acl Safe_ports port 80
acl Safe_ports port 443
acl SSL_ports port 443
acl CONNECT method CONNECT

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports

# Only localhost and PHP-FPM can use this proxy
acl php_local src 127.0.0.1 ::1
http_access allow localhost
http_access allow php_local
http_access allow php_local to_localhost

# Threat intelligence blocklist — prevents connections to known C2 domains
acl malicious_sites dstdomain "/etc/squid/malicious_domains.txt"
http_access deny malicious_sites

# Block connections to internal/LAN addresses
http_access deny to_localhost
http_access deny to_linklocal

include /etc/squid/conf.d/*.conf
http_access deny all

# Lightweight caching — reduces repeat outbound requests for static assets
cache_mem 64 MB
maximum_object_size_in_memory 512 KB
cache_dir ufs /var/spool/squid 100 16 256
maximum_object_size 10 MB

refresh_pattern -i \.(css|js|woff|woff2|png|jpg|jpeg|ico)$ 4320 100% 43200 override-expire override-lastmod reload-into-ims
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
refresh_pattern .               0       20%     4320

A few things worth calling out:

  • http_access deny !Safe_ports — PHP can only connect to ports 80 and 443. No SMTP (port 25), no SSH (port 22), no arbitrary high ports. A compromised script can't use your server as a spam relay or open a raw socket to a C2 server on a non-standard port.
  • http_access deny malicious_sites — Connections to domains on the blocklist are dropped before they leave the machine. The update script in Step 5 keeps this list current.
  • http_access deny to_localhost — After explicitly allowing PHP to connect to localhost services, this catch-all blocks everything else from accessing internal addresses. Defense in depth.

  • ← Previous
  • 1
  • 2
  • 3
  • 4
  • Next →
Tags: squid, php-fpm, python, security, monitoring, outbound, compromise
©2026 KBeezie | Disclaimer | Privacy Notice