Step 5: Add a threat intelligence blocklist
Squid can block connections to known C2 domains before they ever leave your server. Create /etc/squid/malicious_domains.txt with one domain per line in Squid ACL format (dot-prefixed for wildcard matching):
.c2-server.example.com
.exfil-collector.net
.botnet-mothership.org
An automated update script fetches fresh lists and reloads Squid. Save as /etc/squid/update_blacklist.sh:
#!/bin/bash
URL="https://raw.githubusercontent.com/example/threat-feed/main/domains.txt"
TMP_FILE="/tmp/tif_raw.txt"
TARGET_FILE="/etc/squid/malicious_domains.txt"
PROXY="http://127.0.0.1:3128"
echo "Downloading latest malicious domain feeds via local Squid proxy..."
curl -x "$PROXY" -s -S "$URL" -o "$TMP_FILE"
if [ -s "$TMP_FILE" ]; then
echo "Processing format maps for Squid ACL engine..."
grep -v '^#' "$TMP_FILE" | grep -v '^$' | sed 's/^\./ /g' | awk '{print "."$1}' > "$TARGET_FILE"
echo "Enforcing Squid verification checks..."
squid -k parse
if [ $? -eq 0 ]; then
echo "Reloading configuration layers..."
squid -k reconfigure
echo "Malicious domain database update successful."
else
echo "Warning: Format check failed. Reverting changes."
fi
else
echo "Network connection failure. Upstream list could not be fetched via proxy."
fi
rm -f "$TMP_FILE"
sudo chmod +x /etc/squid/update_blacklist.sh
Replace the URL with an actual threat feed. Squid's dstdomain ACL with dot-prefixed entries (.example.com) matches the domain and all subdomains — evil.example.com, www.evil.example.com, and so on.
Step 6: The audit script
Save as /usr/local/sbin/squid_list.py:
#!/usr/bin/env python3
import re
import sys
from datetime import datetime
SQUID_LOG = "/var/log/squid/access.log"
PHP_LOG = "/var/log/php8.5-fpm.access.log"
# Domains your infrastructure legitimately connects to.
# These are filtered out so the output only shows connections
# worth investigating — unexpected domains, C2 callbacks, exfiltration.
TRUSTED_DOMAINS = [
"api.wordpress.org", "wordpress.org",
"githubusercontent.com", "perfops.one",
]
def parse_squid():
"""Extract timestamp and destination domain from Squid CONNECT logs."""
squid_events = []
squid_regex = re.compile(
r"^(\d+\.\d+)\s+\d+\s+127\.0\.0\.1\s+\S+\s+\d+\s+CONNECT\s+([^:]+)"
)
try:
with open(SQUID_LOG, "r") as f:
for line in f:
match = squid_regex.match(line)
if match:
unix_ts = float(match.group(1))
dt = datetime.fromtimestamp(unix_ts).replace(microsecond=0)
squid_events.append({"time": dt, "domain": match.group(2)})
except FileNotFoundError:
pass
return squid_events
def parse_php():
"""Extract timestamp, script path, and site host from PHP-FPM access log."""
php_events = []
# Log format: [%t] %m %{REQUEST_METHOD}e %f %{HTTP_HOST}e
php_regex = re.compile(
r"^\[([^\]]+)\]\s+\S+\s+\S+\s+(\S+)\s+(\S+)"
)
try:
with open(PHP_LOG, "r") as f:
for line in f:
match = php_regex.match(line)
if match:
time_str = match.group(1).split(" ")[0]
dt = datetime.strptime(time_str, "%d/%b/%Y:%H:%M:%S")
script_path = match.group(2)
vhost = match.group(3)
php_events.append({
"time": dt,
"script": script_path,
"vhost": vhost,
})
except FileNotFoundError:
pass
return php_events
def extract_readable_path(full_path):
"""Strip the document root to show a clean site/script pair."""
match = re.search(r"/www/([^/]+)/public_html/(.+\.php)$", full_path)
if match:
return match.group(1), match.group(2)
return None, full_path[-40:]
def cross_reference(filter_vhost=None):
"""Correlate Squid outbound connections with the PHP script that made them."""
squid = parse_squid()
php = parse_php()
if not squid or not php:
return "No log data available to correlate."
lines = []
for p_ev in php:
for s_ev in squid:
# Skip trusted infrastructure domains — these are expected
if any(trusted in s_ev["domain"] for trusted in TRUSTED_DOMAINS):
continue
# Match events within 2 seconds of each other
if abs((p_ev["time"] - s_ev["time"]).total_seconds()) <= 2:
vhost = p_ev["vhost"] if p_ev["vhost"] else "UNKNOWN"
_, script = extract_readable_path(p_ev["script"])
if filter_vhost and filter_vhost not in vhost:
continue
time_str = p_ev["time"].strftime("%m-%d %H:%M:%S")
lines.append(
f"{time_str} | {vhost} -> {script} | {s_ev['domain']}"
)
return "\n".join(lines[-20:]) if lines else "No suspicious outbound connections detected."
if __name__ == "__main__":
search_arg = sys.argv[1] if len(sys.argv) > 1 else None
print(cross_reference(search_arg))
sudo chmod +x /usr/local/sbin/squid_list.py
Run it to see the last 20 unexpected outbound connections:
sudo /usr/local/sbin/squid_list.py
Filter by a specific site:
sudo /usr/local/sbin/squid_list.py mysite.com
Sample output:
05-13 10:30:45 | mysite.com -> wp-content/plugins/analytics-plus/collector.php | tracking.example.com
05-13 10:31:02 | mysite.com -> wp-admin/admin-ajax.php | cdn.malicious.net
05-13 10:31:15 | anothersite.com -> wp-cron.php | api.phone-home.io
Each line tells you when it happened, which site, which script initiated the connection, and where it went. A domain that doesn't show up in your trusted list is worth investigating. A domain that appears in the threat-intelligence blocklist was already dropped at the Squid level before the connection left the machine — but the log entry is still there for you to review during your next audit pass.