## Integration Example `upload-logger.json` (commented for easy copy/paste into your environment): ```json // { // "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): ```json // "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. --- ## πŸ“ 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: ```bash cd /var/www/sites/example-site mkdir .security mkdir .security/logs ```` Set secure permissions: - Set secure permissions: ```bash 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: ```bash nano .security/upload_guard.php ``` Paste your hardened upload monitoring script. Inside the script, configure logging: ```php $logFile = __DIR__ . '/logs/uploads.log'; ``` Lock the script: ```bash 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: ```bash nano /etc/php/8.x/fpm/pool.d/example-site.conf ``` Add: ```ini php_admin_value[auto_prepend_file] = /var/www/sites/example-site/.security/upload_guard.php ``` Reload PHP-FPM: ```bash 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: ```text /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: ```bash cd /var/www/sites/example-site mkdir .security mkdir .security/logs ``` Set secure permissions: ```bash chown -R root:www-data .security chmod 750 .security chmod 750 .security/logs ``` --- ## πŸ“„ 3. Install the Upload Guard Script Create the script file: ```bash nano .security/upload-logger.php ``` Paste your hardened upload monitoring script. Inside the script, configure logging: ```php $logFile = __DIR__ . '/logs/uploads.log'; ``` Lock the script: ```bash 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: ```bash nano /etc/php/8.x/fpm/pool.d/example-site.conf ``` Add: ```ini 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): ```bash systemctl reload php8.x-fpm ``` --- ### Option B β€” Apache Virtual Host If using a shared PHP-FPM pool, configure in the vHost: ```apache php_admin_value auto_prepend_file /var/www/sites/example-site/.security/upload-logger.php ``` Reload Apache: ```bash systemctl reload apache2 ``` --- ## 🚫 5. Block Web Access to `.security` Prevent direct HTTP access to the security folder. In the vHost: ```apache Require all denied ``` Or in `.htaccess` (if allowed): ```apache Require all denied ``` --- ## βœ… 6. Verify Installation Create a temporary file: ```php ``` Upload any file and check logs: ```bash 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): ```apache php_admin_flag engine off AllowOverride None ``` Reload Apache after changes. --- ## πŸ›‘οΈ 9. Enable Blocking Mode (Optional) After monitoring for some time, enable blocking. Edit: ```php $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. ```bash nano /etc/fail2ban/filter.d/php-upload.conf ``` ```ini [Definition] # Match JSON lines where event == "suspicious" and capture the IPv4 address as failregex = ^.*"event"\s*:\s*"suspicious".*"ip"\s*:\s*"(?P\d{1,3}(?:\.\d{1,3}){3})".*$ ignoreregex = ``` Create a jail that points to the per-site logs (or a central aggregated log): ```ini [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: ```bash systemctl restart fail2ban ``` ### Fail2Ban action: nftables example If your host uses nftables, prefer the `nftables` action so bans use the system firewall: ```ini [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): ```yaml 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`: ```text 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. ```bash # 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 ``). Use this to iterate on the `failregex` if it doesn't extract the IP as expected. --- ## 🏁 Final Architecture ```text 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: ```text /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: ```bash 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 --- ## πŸ“Œ Recommended Maintenance Weekly: ```bash grep ALERT .security/logs/uploads.log ```