Files
UploadShied/INTEGRATION.md

13 KiB
Raw Blame History

Integration

Example upload-logger.json (commented for easy copy/paste into your environment):

// {
//   "modules": {
//     "flood": true,
//     "filename": true,
//     "mime_sniff": true,
//     "hashing": true,
//     "base64_detection": true,
//     "raw_peek": false,
//     "archive_inspect": true,
//     "quarantine": true
//   },
//   "paths": {
//     "log_file": "logs/uploads.log",
//     "quarantine_dir": "quarantine",
//     "state_dir": "state",
//     "allowlist_file": "allowlist.json"
//   },
//   "limits": {
//     "max_size": 52428800,
//     "raw_body_min": 512000,
//     "sniff_max_bytes": 8192,
//     "sniff_max_filesize": 2097152,
//     "hash_max_filesize": 10485760,
//     "archive_max_inspect_size": 52428800,
//     "archive_max_entries": 200
//   },
//   "ops": {
//     "quarantine_owner": "root",
//     "quarantine_group": "www-data",
//     "quarantine_dir_perms": "0700",
//     "log_rotate": {
//       "enabled": true,
//       "size": 10485760,
//       "keep": 7
//     }
//   },
//   "allowlists": {
//     "base64_uris": [
//       "/api/uploads/avatars",
//       "/api/v1/avatars",
//       "/user/avatar",
//       "/media/upload",
//       "/api/media",
//       "/api/uploads",
//       "/api/v1/uploads",
//       "/attachments/upload",
//       "/upload",
//       "#^/internal/webhook#",
//       "#/hooks/(github|gitlab|stripe|slack)#",
//       "/services/avatars",
//       "/api/profile/photo"
//     ],
//     "ctypes": ["image/svg+xml","application/xml","text/xml"]
//   }
// }

Notes:

  • Remove the leading // when copying this into a real upload-logger.json file.
  • Adjust paths, owners, and limits to match your environment and PHP-FPM worker permissions.

ContentDetector tuning and false-positive guidance

  • The repository includes a ContentDetector that performs a fast head-scan of uploaded files to detect PHP open-tags and common webshell indicators (for example passthru(), system(), exec(), shell_exec(), proc_open(), popen(), base64_decode(), eval(), assert()). It intentionally limits the scan to a small number of bytes to reduce CPU/IO overhead.

  • Tuning options (place these in upload-logger.json):

    • limits.sniff_max_bytes (integer): number of bytes to read from the file head for scanning. Default: 8192.
    • limits.sniff_max_filesize (integer): only perform head-scan on files with size <= this value. Default: 2097152 (2 MB).
    • allowlists.ctypes (array): content-types that should be considered trusted for base64/raw payloads (for example image/svg+xml, application/xml, text/xml) and may relax some detections.
    • allowlists.base64_uris (array): URI patterns that should be ignored for large base64 payloads (webhooks, avatar uploads, etc.).
  • False positives: eval( and other tokens commonly appear in client-side JS inside SVG files or in benign templating contexts. If you observe false positives:

    • Add trusted URIs to allowlists.base64_uris for endpoints that legitimately accept encoded content.
    • Add trusted content-types to allowlists.ctypes to relax detection for XML/SVG uploads.
    • Tune limits.sniff_max_bytes and limits.sniff_max_filesize to increase or decrease sensitivity.
  • Suggested (example) detector tuning block (commented):

// "detectors": {
//   "content": {
//     "enabled": true,
//     "sniff_max_bytes": 8192,
//     "sniff_max_filesize": 2097152,
//     "allow_xml_eval": false
//   }
// }

Remove the leading // when copying these example snippets into a real upload-logger.json file.

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

  • Set secure permissions:
chown -R root:www-data .security
chmod 750 .security
chmod 750 .security/logs

Quarantine hardening (important):

  • Ensure the quarantine directory is owner root, group www-data, and mode 0700 so quarantined files are not accessible to other system users. Example provisioning script scripts/provision_dirs.sh now enforces these permissions and tightens existing files to 0600.

  • If using Ansible, the playbook scripts/ansible/upload-logger-provision.yml includes a task that sets any existing files in the quarantine directory to 0600 and enforces owner/group.

  • Verify SELinux/AppArmor contexts after provisioning; the script attempts to register fcontext entries and calls restorecon when available.


📄 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