Relax payload scanner for XML/SVG by passing content-type into checks Skip JS-style eval() detection when content-type is XML/SVG Pass request Content-Type through sniff_file_for_php_payload() and raw-body checks Add common XML/SVG content-types to allowlist.json Add repository .gitignore (ignore logs, quarantine/, state/, env, vendor, IDE files)
9.1 KiB
🔐 Per-Site PHP Upload Guard Integration Guide
This guide explains how to integrate a global PHP upload monitoring script
using auto_prepend_file, on a per-site basis, with isolated security
folders.
📁 1. Recommended Folder Structure
Each website should contain its own hidden security directory:
/var/www/sites/example-site/
├── public/
├── app/
├── uploads/
├── .security/
│ ├── upload_guard.php
│ └── logs/
│ └── uploads.log
Benefits:
- Per-site isolation
- Easier debugging
- Independent log files
- Reduced attack surface
🔧 2. Create the Security Directory
From the site root:
cd /var/www/sites/example-site
mkdir .security
mkdir .security/logs
Set secure permissions:
chown -R root:www-data .security
chmod 750 .security
chmod 750 .security/logs
📄 3. Install the Upload Guard Script
Create the script file:
nano .security/upload_guard.php
Paste your hardened upload monitoring script.
Inside the script, configure logging:
$logFile = __DIR__ . '/logs/uploads.log';
Lock the script:
chown root:root .security/upload_guard.php
chmod 644 .security/upload_guard.php
⚙️ 4. Enable auto_prepend_file (Per Site)
Option A — PHP-FPM Pool (Recommended)
Edit the site’s PHP-FPM pool configuration:
nano /etc/php/8.x/fpm/pool.d/example-site.conf
Add:
php_admin_value[auto_prepend_file] = /var/www/sites/example-site/.security/upload_guard.php
Reload PHP-FPM:
systemctl reload php8.x-fpm
🔐 Per-Site PHP Upload Guard Integration Guide
This guide explains how to integrate a global PHP upload monitoring script
using auto_prepend_file, on a per-site basis, with isolated security
folders.
📁 1. Recommended Folder Structure
Each website should contain its own hidden security directory:
/var/www/sites/example-site/
├── public/
├── app/
├── uploads/
├── .security/
│ ├── upload-logger.php
│ └── logs/
│ └── uploads.log
Benefits:
- Per-site isolation
- Easier debugging
- Independent log files
- Reduced attack surface
🔧 2. Create the Security Directory
From the site root:
cd /var/www/sites/example-site
mkdir .security
mkdir .security/logs
Set secure permissions:
chown -R root:www-data .security
chmod 750 .security
chmod 750 .security/logs
📄 3. Install the Upload Guard Script
Create the script file:
nano .security/upload-logger.php
Paste your hardened upload monitoring script.
Inside the script, configure logging:
$logFile = __DIR__ . '/logs/uploads.log';
Lock the script:
chown root:root .security/upload-logger.php
chmod 644 .security/upload-logger.php
⚙️ 4. Enable auto_prepend_file (Per Site)
Option A — PHP-FPM Pool (Recommended)
Edit the site’s PHP-FPM pool configuration:
nano /etc/php/8.x/fpm/pool.d/example-site.conf
Add:
php_admin_value[auto_prepend_file] = /var/www/sites/example-site/.security/upload-logger.php
Reload PHP-FPM (adjust service name to match your PHP version):
systemctl reload php8.x-fpm
Option B — Apache Virtual Host
If using a shared PHP-FPM pool, configure in the vHost:
<Directory /var/www/sites/example-site>
php_admin_value auto_prepend_file /var/www/sites/example-site/.security/upload-logger.php
</Directory>
Reload Apache:
systemctl reload apache2
🚫 5. Block Web Access to .security
Prevent direct HTTP access to the security folder.
In the vHost:
<Directory /var/www/sites/example-site/.security>
Require all denied
</Directory>
Or in .htaccess (if allowed):
Require all denied
✅ 6. Verify Installation
Create a temporary file:
<?php phpinfo();
Open it in browser and search for:
auto_prepend_file
Expected output:
/var/www/sites/example-site/.security/upload_guard.php
Remove the test file after verification.
🧪 7. Test Upload Logging
Create a simple upload test:
<form method="post" enctype="multipart/form-data">
<input type="file" name="testfile">
<button>Upload</button>
</form>
Upload any file and check logs:
cat .security/logs/uploads.log
You should see a new entry.
🔒 8. Disable PHP Execution in Uploads
Always block PHP execution in upload directories.
Example (Apache):
<Directory /var/www/sites/example-site/uploads>
php_admin_flag engine off
AllowOverride None
</Directory>
Reload Apache after changes.
🛡️ 9. Enable Blocking Mode (Optional)
After monitoring for some time, enable blocking.
Edit:
$BLOCK_SUSPICIOUS = true;
Then reload PHP-FPM.
📊 10. (Optional) Fail2Ban Integration (JSON logs)
Create a JSON-aware filter that matches event: "suspicious" and extracts the IP address.
nano /etc/fail2ban/filter.d/php-upload.conf
[Definition]
# Match JSON lines where event == "suspicious" and capture the IPv4 address as <HOST>
failregex = ^.*"event"\s*:\s*"suspicious".*"ip"\s*:\s*"(?P<host>\d{1,3}(?:\.\d{1,3}){3})".*$
ignoreregex =
Create a jail that points to the per-site logs (or a central aggregated log):
[php-upload]
enabled = true
filter = php-upload
logpath = /var/www/sites/*/.security/logs/uploads.log
maxretry = 3
findtime = 600
bantime = 86400
action = iptables-multiport[name=php-upload, port="http,https", protocol=tcp]
Restart Fail2Ban:
systemctl restart fail2ban
Fail2Ban action: nftables example
If your host uses nftables, prefer the nftables action so bans use the system firewall:
[php-upload]
enabled = true
filter = php-upload
logpath = /var/www/sites/*/.security/logs/uploads.log
maxretry = 3
findtime = 600
bantime = 86400
action = nftables[name=php-upload, port="http,https", protocol=tcp]
This uses Fail2Ban's nftables action (available on modern distributions). Adjust port/protocol to match your services.
Central log aggregation (Filebeat / rsyslog)
Forwarding per-site JSON logs to a central collector simplifies alerts and Fail2Ban at scale. Two lightweight options:
- Filebeat prospector (send to Logstash/Elasticsearch):
filebeat.inputs:
- type: log
paths:
- /var/www/sites/*/.security/logs/uploads.log
json.keys_under_root: true
json.add_error_key: true
fields:
source: php-upload-logger
output.logstash:
hosts: ["logserver:5044"]
- rsyslog
imfileforwarding to remote syslog (central rsyslog/logstash):
Add to /etc/rsyslog.d/10-upload-logger.conf:
module(load="imfile" PollingInterval="10")
input(type="imfile" File="/var/www/sites/*/.security/logs/uploads.log" Tag="uploadlogger" Severity="info" Facility="local7")
*.* @@logserver:514
Both options keep JSON intact for downstream parsing and reduce per-host Fail2Ban complexity.
Testing your Fail2Ban filter
Create a temporary file containing a representative JSON log line emitted by upload-logger.php and run fail2ban-regex against your filter to validate detection.
# create test file with a suspicious event
cat > /tmp/test_upload.log <<'JSON'
{"ts":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","event":"suspicious","ip":"1.2.3.4","user":"guest","name":"evil.php.jpg","real_mime":"application/x-php","reasons":["bad_name","php_payload"]}
JSON
# test the filter (adjust path to filter if different)
fail2ban-regex /tmp/test_upload.log /etc/fail2ban/filter.d/php-upload.conf
fail2ban-regex will report how many matches were found and display sample matched groups (including the captured <HOST>). Use this to iterate on the failregex if it doesn't extract the IP as expected.
🏁 Final Architecture
Client → Web Server → PHP (auto_prepend) → Application → Disk
↓
Log / Alert / Ban
This provides multi-layer upload monitoring and protection.
🗂️ Log rotation & SELinux/AppArmor notes
- Example
logrotatesnippet to rotate per-site logs weekly and keep 8 rotations:
/var/www/sites/*/.security/logs/uploads.log {
weekly
rotate 8
compress
missingok
notifempty
create 0640 root adm
}
- If your host enforces SELinux or AppArmor, ensure the
.securitydirectory and log files have the correct context so PHP-FPM can read the script and write logs. For SELinux (RHEL/CentOS) you may need:
chcon -R -t httpd_sys_rw_content_t /var/www/sites/example-site/.security/logs
restorecon -R /var/www/sites/example-site/.security
Adjust commands to match your platform and policy. AppArmor profiles may require adding paths to the PHP-FPM profile.
⚠️ Security Notes
- Never use
777permissions - Keep
.securityowned byroot - Regularly review logs
- Update PHP and extensions
- Combine with OS-level auditing for best results
📌 Recommended Maintenance
Weekly:
grep ALERT .security/logs/uploads.log