Files
UploadShied/INTEGRATION.md
Gregor Klevze 037b176892 fix(scanner): avoid SVG/XML false positives; add allowlist and .gitignore
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)
2026-02-07 15:11:15 +01:00

9.1 KiB
Raw Blame History

🔐 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.


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)

Edit the sites 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.


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)

Edit the sites 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 imfile forwarding 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 logrotate snippet 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 .security directory 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 777 permissions
  • Keep .security owned by root
  • Regularly review logs
  • Update PHP and extensions
  • Combine with OS-level auditing for best results

Weekly:

grep ALERT .security/logs/uploads.log