Rename project to UploadShield: update runtime, configs, docs, and provisioning; run lint/tests
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,7 +8,7 @@
|
||||
uploads.log
|
||||
|
||||
# Peek allow marker (local only)
|
||||
/.upload_logger_allow_peek
|
||||
/.uploadshield_allow_peek
|
||||
|
||||
# Local environment files
|
||||
.env
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Configuration Reference
|
||||
|
||||
This file maps the top-level configuration keys used by `upload-logger.json` (UploadShield) to their effect and defaults. Use absolute paths in production where possible.
|
||||
This file maps the top-level configuration keys used by `uploadshield.json` (UploadShield) to their effect and defaults. Use absolute paths in production where possible.
|
||||
|
||||
## Top-level sections
|
||||
|
||||
@@ -48,7 +48,7 @@ This file maps the top-level configuration keys used by `upload-logger.json` (Up
|
||||
- `allow_xml_eval` (bool) — relax `eval()` detection for XML/SVG content when true.
|
||||
- `custom_patterns` (array) — array of PCRE patterns (as strings) applied to the file head. Invalid patterns are ignored.
|
||||
|
||||
## Example `upload-logger.json`
|
||||
## Example `uploadshield.json`
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -67,6 +67,6 @@ This file maps the top-level configuration keys used by `upload-logger.json` (Up
|
||||
- Use absolute paths in `paths.*` when deploying under systemd/Ansible to avoid cwd surprises.
|
||||
- Ensure `quarantine_dir` is inaccessible from the web and set to owner `root` and mode `0700`; files inside should be `0600`.
|
||||
|
||||
If you want, I can generate a per-site `upload-logger.json` filled with your preferred absolute paths and ownership values.
|
||||
If you want, I can generate a per-site `uploadshield.json` filled with your preferred absolute paths and ownership values.
|
||||
|
||||
--
|
||||
|
||||
@@ -51,7 +51,7 @@ Add CONFIG_REFERENCE.md mapping configuration options
|
||||
- A basic smoke harness exists under `tests/smoke/`. To run locally:
|
||||
|
||||
```bash
|
||||
php -S 127.0.0.1:8000 -t tests/smoke/public -d auto_prepend_file=$(pwd)/upload-logger.php
|
||||
php -S 127.0.0.1:8000 -t tests/smoke/public -d auto_prepend_file=$(pwd)/uploadshield.php
|
||||
# then POST files with curl or a test client
|
||||
```
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This document complements the installation steps in [docs/INSTALLATION.md](docs/INSTALLATION.md) by focusing on detector tuning, allowlists, and advanced integrations (log forwarding, Fail2Ban, etc.).
|
||||
|
||||
Example `upload-logger.json` (simplified) for UploadShield:
|
||||
Example `uploadshield.json` (simplified) for UploadShield:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -50,7 +50,7 @@ Notes:
|
||||
### Content detector tuning
|
||||
|
||||
- The `ContentDetector` performs a fast head-scan to detect PHP open-tags and common webshell indicators (e.g., `passthru`, `system`, `exec`, `base64_decode`, `eval`, `assert`).
|
||||
- Tuning options (in `upload-logger.json`):
|
||||
- Tuning options (in `uploadshield.json`):
|
||||
- `limits.sniff_max_bytes` (default 8192) — how many bytes to scan from the file head.
|
||||
- `limits.sniff_max_filesize` (default 2097152) — only scan files up to this size.
|
||||
- `detectors.content.allow_xml_eval` — relax `eval()` detection for XML/SVG when appropriate.
|
||||
@@ -110,14 +110,14 @@ filebeat.inputs:
|
||||
json.keys_under_root: true
|
||||
json.add_error_key: true
|
||||
fields:
|
||||
source: php-upload-logger
|
||||
source: php-uploadshield
|
||||
output.logstash:
|
||||
hosts: ["logserver:5044"]
|
||||
```
|
||||
|
||||
## Logrotate & SELinux notes
|
||||
|
||||
Per-site `logrotate` snippets are included in `examples/logrotate.d/upload-logger`. Use `copytruncate` or reload PHP-FPM after rotation.
|
||||
Per-site `logrotate` snippets are included in `examples/logrotate.d/uploadshield`. Use `copytruncate` or reload PHP-FPM after rotation.
|
||||
|
||||
If SELinux is enabled, the provisioning script attempts to register fcontexts and run `restorecon`. Verify contexts manually as needed.
|
||||
|
||||
@@ -125,6 +125,6 @@ If SELinux is enabled, the provisioning script attempts to register fcontexts an
|
||||
|
||||
- Use observe mode (`ops.block_suspicious: false`) while tuning.
|
||||
- After tuning, enable blocking in a controlled rollout (canary hosts first).
|
||||
- Keep `upload-logger.php` and `.security` owned by `root` and ensure logs and quarantine are not web-accessible.
|
||||
- Keep `uploadshield.php` and `.security` owned by `root` and ensure logs and quarantine are not web-accessible.
|
||||
|
||||
For installation steps and per-site configuration, see `docs/INSTALLATION.md`.
|
||||
|
||||
18
README.md
18
README.md
@@ -2,13 +2,13 @@
|
||||
|
||||
This repository contains UploadShield (formerly "Upload Logger"): a hardened PHP upload protection helper. It provides a single-file monitor that logs uploads, detects common evasion techniques, quarantines suspicious files, and can optionally block malicious uploads.
|
||||
|
||||
Primary file: [upload-logger.php](upload-logger.php)
|
||||
Primary file: [uploadshield.php](uploadshield.php)
|
||||
|
||||
Summary
|
||||
- Purpose: Log normal uploads and raw-body uploads, detect double-extension tricks, fake images, PHP payloads embedded in files, and provide flood detection.
|
||||
- Runs only for HTTP requests; recommended to enable via `auto_prepend_file` in a per-site PHP-FPM pool for broad coverage.
|
||||
|
||||
Key configuration (top of `upload-logger.php`)
|
||||
Key configuration (top of `uploadshield.php`)
|
||||
- `$logFile` — path to the log file (default: `__DIR__ . '/logs/uploads.log'`).
|
||||
- `$BLOCK_SUSPICIOUS` — if `true` the script returns `403` and exits when suspicious uploads are detected.
|
||||
- `$MAX_SIZE` — threshold for `WARN big_upload` (default 50 MB).
|
||||
@@ -31,7 +31,7 @@ Logging and alerts
|
||||
- Other notes: `WARN big_upload`, `NOTE archive_upload`, `MULTIPART_NO_FILES`, and `RAW_BODY` are emitted when appropriate.
|
||||
|
||||
Integration notes
|
||||
- Preferred deployment: set `php_admin_value[auto_prepend_file]` in the site-specific PHP-FPM pool to the absolute path of `upload-logger.php` so it runs before application code.
|
||||
- Preferred deployment: set `php_admin_value[auto_prepend_file]` in the site-specific PHP-FPM pool to the absolute path of `uploadshield.php` so it runs before application code.
|
||||
- If using sessions for user identification, the script safely reads `$_SESSION['user_id']` only when a session is active; do not rely on it being present unless your app starts sessions earlier.
|
||||
- The script uses `is_uploaded_file()`/`finfo` where available; ensure the PHP `fileinfo` extension is enabled for best MIME detection.
|
||||
- The script uses `is_uploaded_file()`/`finfo` where available; ensure the PHP `fileinfo` extension is enabled for best MIME detection.
|
||||
@@ -39,16 +39,16 @@ Integration notes
|
||||
Content detector & tuning
|
||||
|
||||
- `ContentDetector` is now included and performs a fast head-scan of uploaded files to detect PHP open-tags and common webshell indicators (e.g., `passthru()`, `system()`, `exec()`, `shell_exec()`, `proc_open()`, `popen()`, `base64_decode()`, `eval()`, `assert()`).
|
||||
- The detector only scans the first N bytes of a file to limit CPU/io work; tune these limits in `upload-logger.json`:
|
||||
- The detector only scans the first N bytes of a file to limit CPU/io work; tune these limits in `uploadshield.json`:
|
||||
- `limits.sniff_max_bytes` — number of bytes to scan from file head (default `8192`).
|
||||
- `limits.sniff_max_filesize` — only scan files up to this size in bytes (default `2097152` / 2MB).
|
||||
- Behavior note: `eval()` and similar tokens commonly appear inside SVG/JS contexts. The detector uses the detected MIME to be more permissive for XML/SVG-like content, but you should test and tune for your application's upload patterns to avoid false positives (see `INTEGRATION.md`).
|
||||
- If your application legitimately accepts encoded or templated payloads, add application-specific allowlist rules (URI or content-type) in `allowlist.json` or extend `upload-logger.json` with detector-specific tuning before enabling blocking mode.
|
||||
If your application legitimately accepts encoded or templated payloads, add application-specific allowlist rules (URI or content-type) in `allowlist.json` or extend `uploadshield.json` with detector-specific tuning before enabling blocking mode.
|
||||
Further integration
|
||||
- Read the `INTEGRATION.md` for detector tuning, allowlists, and examples for log forwarding and Fail2Ban.
|
||||
- See `docs/INSTALLATION.md` for a step-by-step per-site install and `auto_prepend_file` examples.
|
||||
- Provision the required directories (`quarantine`, `state`) and set ownership/SELinux via the included provisioning script: `scripts/provision_dirs.sh`.
|
||||
- Example automation: `scripts/ansible/upload-logger-provision.yml` and `scripts/systemd/upload-logger-provision.service` are included as examples to run provisioning at deploy-time or boot.
|
||||
- Example automation: `scripts/ansible/uploadshield-provision.yml` and `scripts/systemd/uploadshield-provision.service` are included as examples to run provisioning at deploy-time or boot.
|
||||
|
||||
Operational recommendations
|
||||
- Place the `logs/` directory outside the webroot or deny web access to it.
|
||||
@@ -61,16 +61,16 @@ Limitations & safety
|
||||
- Content sniffing is limited to a head-scan to reduce CPU and false positives; tune `$SNIFF_MAX_BYTES` and `$SNIFF_MAX_FILESIZE` to balance coverage and performance.
|
||||
|
||||
Quick start
|
||||
1. Place `upload-logger.php` in a per-site secure folder (see `INTEGRATION.md`).
|
||||
1. Place `uploadshield.php` in a per-site secure folder (see `INTEGRATION.md`).
|
||||
2. Ensure the `logs/` directory exists and is writable by PHP-FPM.
|
||||
3. Enable as an `auto_prepend_file` in the site pool and reload PHP-FPM.
|
||||
4. Monitor `logs/uploads.log` and adjust configuration options at the top of the script.
|
||||
|
||||
Support & changes
|
||||
- For changes, edit configuration variables at the top of `upload-logger.php` or adapt detection helpers as needed.
|
||||
- For changes, edit configuration variables at the top of `uploadshield.php` or adapt detection helpers as needed.
|
||||
|
||||
---
|
||||
Generated for upload-logger.php (UploadShield v3).
|
||||
Generated for uploadshield.php (UploadShield v3).
|
||||
|
||||
## Additional documentation
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
"archive_inspect": true
|
||||
},
|
||||
"paths": {
|
||||
"quarantine_dir": "/var/lib/upload-logger/quarantine",
|
||||
"state_dir": "/var/lib/upload-logger/state",
|
||||
"allowlist_file": "/etc/upload-logger/allowlist.json"
|
||||
"quarantine_dir": "/var/lib/uploadshield/quarantine",
|
||||
"state_dir": "/var/lib/uploadshield/state",
|
||||
"allowlist_file": "/etc/uploadshield/allowlist.json"
|
||||
},
|
||||
"limits": {
|
||||
"max_size": 52428800,
|
||||
@@ -7,9 +7,9 @@
|
||||
"archive_inspect": true
|
||||
},
|
||||
"paths": {
|
||||
"quarantine_dir": "/var/lib/upload-logger/quarantine",
|
||||
"state_dir": "/var/lib/upload-logger/state",
|
||||
"allowlist_file": "/etc/upload-logger/allowlist.json"
|
||||
"quarantine_dir": "/var/lib/uploadshield/quarantine",
|
||||
"state_dir": "/var/lib/uploadshield/state",
|
||||
"allowlist_file": "/etc/uploadshield/allowlist.json"
|
||||
},
|
||||
"limits": {
|
||||
"max_size": 52428800,
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace UploadLogger\Core;
|
||||
|
||||
/**
|
||||
* Simple immutable configuration holder for UploadShield (upload-logger.json).
|
||||
* Simple immutable configuration holder for UploadShield (uploadshield.json).
|
||||
*/
|
||||
final class Config
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Installation & Production Deployment Guide
|
||||
|
||||
This guide shows a minimal, secure installation and rollout path for UploadShield's primary script (`upload-logger.php`).
|
||||
This guide shows a minimal, secure installation and rollout path for UploadShield's primary script (`uploadshield.php`).
|
||||
Follow these steps in a staging environment first; do not enable blocking until detectors are tuned.
|
||||
|
||||
**Prerequisites**
|
||||
@@ -9,9 +9,9 @@ Follow these steps in a staging environment first; do not enable blocking until
|
||||
- SSH/privileged access to configure the site pool and run provisioning scripts.
|
||||
|
||||
**Quick overview**
|
||||
1. Place `upload-logger.php` in a secure per-site folder (recommended `.security`).
|
||||
1. Place `uploadshield.php` in a secure per-site folder (recommended `.security`).
|
||||
2. Create `logs/`, `quarantine/`, `state/` and set strict ownership and permissions.
|
||||
3. Configure `upload-logger.json` for your environment; keep `ops.block_suspicious` off for initial tuning.
|
||||
3. Configure `uploadshield.json` for your environment; keep `ops.block_suspicious` off for initial tuning.
|
||||
4. Enable `auto_prepend_file` in the site PHP-FPM pool to run the logger before application code.
|
||||
5. Verify logging, tune detectors, deploy log rotation, and enable alerting.
|
||||
|
||||
@@ -31,7 +31,7 @@ vendor/bin/phpstan analyse -c phpstan.neon
|
||||
```
|
||||
|
||||
**2. Recommended file layout (per-site)**
|
||||
Use a hidden per-site security folder so `upload-logger.php` is not web-accessible.
|
||||
Use a hidden per-site security folder so `uploadshield.php` is not web-accessible.
|
||||
|
||||
Example layout:
|
||||
|
||||
@@ -41,16 +41,16 @@ Example layout:
|
||||
├── app/
|
||||
├── uploads/
|
||||
├── .security/
|
||||
│ ├── upload-logger.php
|
||||
│ ├── uploadshield.php
|
||||
│ └── logs/
|
||||
│ └── uploads.log
|
||||
├── quarantine/
|
||||
└── state/
|
||||
```
|
||||
Place `uploadshield.php` into `.security/uploadshield.php`.
|
||||
|
||||
**3. Copy files & configure**
|
||||
- Place `upload-logger.php` into `.security/upload-logger.php`.
|
||||
- Copy `upload-logger.json` from the repository to the same directory and edit paths to absolute values, e.g.:
|
||||
- Place `uploadshield.php` into `.security/uploadshield.php`.
|
||||
- Copy `uploadshield.json` from the repository to the same directory and edit paths to absolute values, e.g.:
|
||||
- `paths.log_file` → `/var/www/sites/example-site/.security/logs/uploads.log`
|
||||
- `paths.quarantine_dir` → `/var/www/sites/example-site/quarantine`
|
||||
- `paths.state_dir` → `/var/www/sites/example-site/state`
|
||||
@@ -81,14 +81,14 @@ chmod 0640 /var/www/sites/example-site/.security/logs/uploads.log
|
||||
|
||||
Alternatively use the included provisioning scripts on the host:
|
||||
- `scripts/provision_dirs.sh` — run as root; idempotent and attempts to set SELinux fcontext.
|
||||
- `scripts/ansible/upload-logger-provision.yml` — Ansible playbook for bulk provisioning.
|
||||
- `scripts/ansible/uploadshield-provision.yml` — Ansible playbook for bulk provisioning.
|
||||
|
||||
**5. PHP‑FPM configuration (per-site pool)**
|
||||
Edit the site's FPM pool (example: `/etc/php/8.1/fpm/pool.d/example-site.conf`) and add:
|
||||
|
||||
php_admin_value[auto_prepend_file] = /var/www/sites/example-site/.security/uploadshield.php
|
||||
```ini
|
||||
; Ensure upload-logger runs before application code
|
||||
php_admin_value[auto_prepend_file] = /var/www/sites/example-site/.security/upload-logger.php
|
||||
; Ensure uploadshield runs before application code
|
||||
php_admin_value[auto_prepend_file] = /var/www/sites/example-site/.security/uploadshield.php
|
||||
```
|
||||
|
||||
Then reload PHP-FPM:
|
||||
@@ -98,11 +98,11 @@ sudo systemctl reload php8.1-fpm
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Use the absolute path to `upload-logger.php`.
|
||||
- Use the absolute path to `uploadshield.php`.
|
||||
- If your host uses a shared PHP-FPM pool, consider enabling per-vhost `auto_prepend_file` via nginx/apache or create a dedicated pool for the site.
|
||||
|
||||
**6. Log rotation**
|
||||
Create `/etc/logrotate.d/upload-logger` with content adapted to your paths:
|
||||
Create `/etc/logrotate.d/uploadshield` with content adapted to your paths:
|
||||
|
||||
```
|
||||
/var/www/sites/example-site/.security/logs/uploads.log {
|
||||
@@ -129,10 +129,10 @@ Create `/etc/logrotate.d/upload-logger` with content adapted to your paths:
|
||||
- Upload a file containing `<?php` to confirm detectors log `suspicious_upload` entries.
|
||||
- Check `logs/uploads.log` for events.
|
||||
|
||||
Example quick test (on host):
|
||||
php -S 127.0.0.1:8000 -t . -d auto_prepend_file=/var/www/sites/example-site/.security/uploadshield.php
|
||||
```bash
|
||||
# from site root
|
||||
php -S 127.0.0.1:8000 -t . -d auto_prepend_file=/var/www/sites/example-site/.security/upload-logger.php
|
||||
php -S 127.0.0.1:8000 -t . -d auto_prepend_file=/var/www/sites/example-site/.security/uploadshield.php
|
||||
# then curl a file POST to your test endpoint
|
||||
curl -F "file=@/path/to/sample.txt" http://127.0.0.1:8000/upload_test.php
|
||||
```
|
||||
@@ -146,7 +146,7 @@ curl -F "file=@/path/to/sample.txt" http://127.0.0.1:8000/upload_test.php
|
||||
|
||||
**9. Gradual enable blocking**
|
||||
- After tuning, enable blocking in a controlled manner:
|
||||
1. Set `ops.block_suspicious` → `true` in `upload-logger.json` on a small subset of sites or a canary host.
|
||||
1. Set `ops.block_suspicious` → `true` in `uploadshield.json` on a small subset of sites or a canary host.
|
||||
2. Monitor errors, rollback quickly if issues appear.
|
||||
3. Gradually roll out to remaining hosts.
|
||||
|
||||
@@ -159,17 +159,17 @@ curl -F "file=@/path/to/sample.txt" http://127.0.0.1:8000/upload_test.php
|
||||
- Ensure `quarantine/` and `.security/logs` are not accessible from the web server.
|
||||
- Verify SELinux/AppArmor contexts after running provisioning.
|
||||
- Ensure owner/group are set to root and web group (e.g., `root:www-data`) and modes match the guide.
|
||||
- Keep `upload-logger.php` readable by root (644) and the logs readable only by the intended group (640).
|
||||
Keep `uploadshield.php` readable by root (644) and the logs readable only by the intended group (640).
|
||||
|
||||
**Rollback**
|
||||
- Disable `php_admin_value[auto_prepend_file]` in the pool and reload PHP-FPM.
|
||||
- Remove or rotate the `upload-logger` files if needed.
|
||||
- Remove or rotate the `uploadshield` files if needed.
|
||||
|
||||
**Further reading & files**
|
||||
- Integration notes: [INTEGRATION.md](INTEGRATION.md)
|
||||
- Provisioning script: `scripts/provision_dirs.sh`
|
||||
- Ansible playbook: `scripts/ansible/upload-logger-provision.yml`
|
||||
- Example configuration: `upload-logger.json`
|
||||
- Ansible playbook: `scripts/ansible/uploadshield-provision.yml`
|
||||
- Example configuration: `uploadshield.json`
|
||||
|
||||
---
|
||||
If you want, I can: (a) generate a site-specific copy of these snippets for your exact paths/PHP version, (b) open a PR with the updated documentation, or (c) produce a one-command installer playbook that runs the provisioning and copies files to a remote host. Tell me which option you prefer.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Release & Deploy Checklist
|
||||
|
||||
This checklist helps you deploy UploadShield's primary script (`upload-logger.php`) to production safely.
|
||||
This checklist helps you deploy UploadShield's primary script (`uploadshield.php`) to production safely.
|
||||
|
||||
## Pre-release
|
||||
|
||||
- [ ] Review and pin configuration in `upload-logger.json` (see `examples/upload-logger.json`).
|
||||
- [ ] Review and pin configuration in `uploadshield.json` (see `examples/uploadshield.json`).
|
||||
- [ ] Ensure unit tests pass and CI workflows are green for the release branch.
|
||||
- [ ] Run static analysis (`vendor/bin/phpstan analyse`) and fix any new issues.
|
||||
- [ ] Run `composer audit` to confirm no advisories remain.
|
||||
@@ -28,7 +28,7 @@ This checklist helps you deploy UploadShield's primary script (`upload-logger.ph
|
||||
|
||||
## Configuration
|
||||
|
||||
- [ ] Create `upload-logger.json` from `examples/upload-logger.json` and adjust values:
|
||||
- [ ] Create `uploadshield.json` from `examples/uploadshield.json` and adjust values:
|
||||
- `paths.quarantine_dir` — absolute path to `quarantine/`.
|
||||
- `paths.state_dir` — absolute path to `state/`.
|
||||
- `paths.allowlist_file` — path to `allowlist.json`.
|
||||
@@ -37,7 +37,7 @@ This checklist helps you deploy UploadShield's primary script (`upload-logger.ph
|
||||
|
||||
## Deployment
|
||||
|
||||
- [ ] Ensure `php_admin_value[auto_prepend_file]` is configured in the site pool for PHP-FPM to include `upload-logger.php` (UploadShield).
|
||||
- [ ] Ensure `php_admin_value[auto_prepend_file]` is configured in the site pool for PHP-FPM to include `uploadshield.php` (UploadShield).
|
||||
- [ ] Reload or restart PHP-FPM gracefully after changing pool settings.
|
||||
- [ ] Verify the web server denies direct access to `logs/` and `quarantine/`.
|
||||
|
||||
@@ -50,7 +50,7 @@ This checklist helps you deploy UploadShield's primary script (`upload-logger.ph
|
||||
|
||||
## Post-release
|
||||
|
||||
- [ ] Configure log rotation (see `examples/logrotate.d/upload-logger`).
|
||||
- [ ] Configure log rotation (see `examples/logrotate.d/uploadshield`).
|
||||
- [ ] Set up monitoring/alerting on log file growth, error events, and flood alerts.
|
||||
- [ ] Schedule periodic dependency checks (Dependabot and weekly `composer audit`).
|
||||
- [ ] Periodically review `allowlist.json` and detector tuning to reduce false positives.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit bootstrap="vendor/autoload.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite name="upload-logger">
|
||||
<testsuite name="uploadshield">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
- hosts: web
|
||||
become: true
|
||||
vars:
|
||||
upload_logger_root: "{{ playbook_dir | default('.') | dirname | realpath }}"
|
||||
logs_dir: "{{ upload_logger_root }}/logs"
|
||||
quarantine_dir: "{{ upload_logger_root }}/quarantine"
|
||||
state_dir: "{{ upload_logger_root }}/state"
|
||||
examples_dir: "{{ upload_logger_root }}/examples"
|
||||
uploadshield_root: "{{ playbook_dir | default('.') | dirname | realpath }}"
|
||||
logs_dir: "{{ uploadshield_root }}/logs"
|
||||
quarantine_dir: "{{ uploadshield_root }}/quarantine"
|
||||
state_dir: "{{ uploadshield_root }}/state"
|
||||
examples_dir: "{{ uploadshield_root }}/examples"
|
||||
quarantine_owner: "root"
|
||||
quarantine_group: "www-data"
|
||||
quarantine_perms: "0700"
|
||||
@@ -17,8 +17,8 @@
|
||||
logs_perms: "0750"
|
||||
log_file_mode: "0640"
|
||||
selinux_fcontext: "httpd_sys_rw_content_t"
|
||||
tmpfiles_conf: "/etc/tmpfiles.d/upload-logger.conf"
|
||||
logrotate_dest: "/etc/logrotate.d/upload-logger"
|
||||
tmpfiles_conf: "/etc/tmpfiles.d/uploadshield.conf"
|
||||
logrotate_dest: "/etc/logrotate.d/uploadshield"
|
||||
|
||||
tasks:
|
||||
- name: Ensure logs directory exists
|
||||
@@ -45,14 +45,14 @@
|
||||
group: "{{ quarantine_group }}"
|
||||
mode: "{{ state_perms }}"
|
||||
|
||||
- name: Ensure example upload-logger.json is copied (only when missing)
|
||||
- name: Ensure example uploadshield.json is copied (only when missing)
|
||||
copy:
|
||||
src: "{{ examples_dir }}/upload-logger.json"
|
||||
dest: "{{ upload_logger_root }}/upload-logger.json"
|
||||
src: "{{ examples_dir }}/uploadshield.json"
|
||||
dest: "{{ uploadshield_root }}/uploadshield.json"
|
||||
owner: "{{ quarantine_owner }}"
|
||||
group: "{{ quarantine_group }}"
|
||||
mode: "0644"
|
||||
when: not (upload_logger_root + '/upload-logger.json') | path_exists
|
||||
when: not (uploadshield_root + '/uploadshield.json') | path_exists
|
||||
|
||||
- name: Install tmpfiles.d entry to recreate dirs at boot
|
||||
copy:
|
||||
@@ -66,12 +66,12 @@
|
||||
|
||||
- name: Install logrotate snippet if example exists
|
||||
copy:
|
||||
src: "{{ examples_dir }}/logrotate.d/upload-logger"
|
||||
src: "{{ examples_dir }}/logrotate.d/uploadshield"
|
||||
dest: "{{ logrotate_dest }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
when: (examples_dir + '/logrotate.d/upload-logger') | path_exists
|
||||
when: (examples_dir + '/logrotate.d/uploadshield') | path_exists
|
||||
|
||||
- name: Set SELinux fcontext for directories when selinux enabled
|
||||
when: ansible_selinux.status == 'enabled'
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
# Ansible playbook snippet to provision UploadShield directories and permissions.
|
||||
# Usage: ansible-playbook -i inventory scripts/ansible/upload-logger-provision.yml
|
||||
# Usage: ansible-playbook -i inventory scripts/ansible/uploadshield-provision.yml
|
||||
|
||||
- hosts: web
|
||||
become: true
|
||||
vars:
|
||||
upload_logger_root: "{{ playbook_dir | default('.') | dirname | realpath }}"
|
||||
quarantine_dir: "{{ upload_logger_root }}/quarantine"
|
||||
state_dir: "{{ upload_logger_root }}/state"
|
||||
uploadshield_root: "{{ playbook_dir | default('.') | dirname | realpath }}"
|
||||
quarantine_dir: "{{ uploadshield_root }}/quarantine"
|
||||
state_dir: "{{ uploadshield_root }}/state"
|
||||
quarantine_owner: "root"
|
||||
quarantine_group: "www-data"
|
||||
quarantine_perms: "0700"
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# Provision quarantine and state directories for UploadShield (upload-logger.php)
|
||||
# Usage: sudo ./provision_dirs.sh [--config path/to/upload-logger.json]
|
||||
# Provision quarantine and state directories for UploadShield (uploadshield.php)
|
||||
# Usage: sudo ./provision_dirs.sh [--config path/to/uploadshield.json]
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CFG="${1:-$ROOT_DIR/upload-logger.json}"
|
||||
CFG="${1:-$ROOT_DIR/uploadshield.json}"
|
||||
|
||||
QUIET=0
|
||||
if [[ "${2:-}" == "--quiet" ]]; then QUIET=1; fi
|
||||
@@ -85,7 +85,7 @@ else
|
||||
fi
|
||||
|
||||
# Optional tmpfiles.d entry to recreate directories at boot (idempotent)
|
||||
TMPFILE="/etc/tmpfiles.d/upload-logger.conf"
|
||||
TMPFILE="/etc/tmpfiles.d/uploadshield.conf"
|
||||
if [[ -w /etc/tmpfiles.d || $QUIET -eq 1 ]]; then
|
||||
info "Writing tmpfiles.d entry to ${TMPFILE}"
|
||||
cat > "$TMPFILE" <<EOF
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ACTIVE_CFG="$ROOT_DIR/upload-logger.json"
|
||||
PROD_CFG="$ROOT_DIR/config/upload-logger.prod.json"
|
||||
BLOCK_CFG="$ROOT_DIR/config/upload-logger.blocking.json"
|
||||
ACTIVE_CFG="$ROOT_DIR/uploadshield.json"
|
||||
PROD_CFG="$ROOT_DIR/config/uploadshield.prod.json"
|
||||
BLOCK_CFG="$ROOT_DIR/config/uploadshield.blocking.json"
|
||||
BACKUP_DIR="$ROOT_DIR/config/backups"
|
||||
DRY_RUN=0
|
||||
CONFIRM=0
|
||||
@@ -46,8 +46,8 @@ fi
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
TS=$(date +%Y%m%dT%H%M%S)
|
||||
if [[ -f "$ACTIVE_CFG" ]]; then
|
||||
cp -a "$ACTIVE_CFG" "$BACKUP_DIR/upload-logger.json.bak.$TS"
|
||||
echo "Backed up current config to $BACKUP_DIR/upload-logger.json.bak.$TS"
|
||||
cp -a "$ACTIVE_CFG" "$BACKUP_DIR/uploadshield.json.bak.$TS"
|
||||
echo "Backed up current config to $BACKUP_DIR/uploadshield.json.bak.$TS"
|
||||
fi
|
||||
|
||||
cp -a "$BLOCK_CFG" "$ACTIVE_CFG"
|
||||
|
||||
@@ -4,7 +4,7 @@ After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/upload-logger-provision.sh /opt/upload-logger/upload-logger.json
|
||||
ExecStart=/usr/local/bin/uploadshield-provision.sh /opt/uploadshield/uploadshield.json
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Simple endpoint that accepts file uploads; auto_prepend_file will run upload-logger.php
|
||||
// Simple endpoint that accepts file uploads; auto_prepend_file will run uploadshield.php
|
||||
header('Content-Type: text/plain');
|
||||
if (empty($_FILES)) {
|
||||
echo "no files\n";
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Global Upload Logger (Hardened v3) — part of UploadShield
|
||||
* Project: TheWallpapers
|
||||
*
|
||||
* UploadShield runtime
|
||||
* Purpose:
|
||||
* - Log ALL normal uploads via $_FILES (single + multi)
|
||||
* - Detect common evasion (double extensions, fake images, path tricks, PHP payload in non-php files)
|
||||
* - Log suspicious "raw body" uploads (php://input / octet-stream) that bypass $_FILES
|
||||
* - Optional blocking mode
|
||||
* - Log uploads and detect suspicious uploads before application code runs
|
||||
* - Configured via `uploadshield.json` (env `UPLOADSHIELD_CONFIG` supported)
|
||||
*
|
||||
* Install:
|
||||
* - Use as PHP-FPM pool `auto_prepend_file=.../upload_logger.php`
|
||||
*
|
||||
* Notes:
|
||||
* - This cannot *guarantee* interception of every file-write exploit (file_put_contents, ZipArchive extract, etc.)
|
||||
* but it catches most real-world upload vectors and provides strong forensic logging.
|
||||
* Install as PHP-FPM pool `auto_prepend_file=/path/to/uploadshield.php`
|
||||
*/
|
||||
|
||||
// Ignore CLI
|
||||
@@ -74,11 +65,11 @@ $PEEK_RAW_INPUT = false;
|
||||
$TRUSTED_PROXY_IPS = ['127.0.0.1', '::1'];
|
||||
|
||||
// Environment variable name or marker file to explicitly allow peeking
|
||||
$ALLOW_PEEK_ENV = 'UPLOAD_LOGGER_ALLOW_PEEK';
|
||||
$PEEK_ALLOW_FILE = __DIR__ . '/.upload_logger_allow_peek';
|
||||
$ALLOW_PEEK_ENV = 'UPLOADSHIELD_ALLOW_PEEK';
|
||||
$PEEK_ALLOW_FILE = __DIR__ . '/.uploadshield_allow_peek';
|
||||
|
||||
// Auto-enable peek only when explicitly allowed by environment/file or when a
|
||||
// trusted frontend indicates the body was buffered via header `X-Upload-Logger-Peek: 1`.
|
||||
// trusted frontend indicates the body was buffered via header `X-UploadShield-Peek: 1`.
|
||||
// This avoids consuming request bodies unexpectedly.
|
||||
try {
|
||||
$envAllow = getenv($ALLOW_PEEK_ENV) === '1';
|
||||
@@ -115,7 +106,7 @@ $BASE64_ALLOWLIST_CTYPE = [];
|
||||
|
||||
// Allowlist file location and environment override
|
||||
$ALLOWLIST_FILE_DEFAULT = __DIR__ . '/allowlist.json';
|
||||
$ALLOWLIST_FILE = getenv('UPLOAD_LOGGER_ALLOWLIST') ?: $ALLOWLIST_FILE_DEFAULT;
|
||||
$ALLOWLIST_FILE = getenv('UPLOADSHIELD_ALLOWLIST') ?: $ALLOWLIST_FILE_DEFAULT;
|
||||
|
||||
if (is_file($ALLOWLIST_FILE)) {
|
||||
$raw = @file_get_contents($ALLOWLIST_FILE);
|
||||
@@ -131,9 +122,9 @@ if (is_file($ALLOWLIST_FILE)) {
|
||||
}
|
||||
|
||||
// Load config (JSON) or fall back to inline defaults.
|
||||
// Config file path may be overridden with env `UPLOAD_LOGGER_CONFIG`.
|
||||
$CONFIG_FILE_DEFAULT = __DIR__ . '/upload-logger.json';
|
||||
$CONFIG_FILE = getenv('UPLOAD_LOGGER_CONFIG') ?: $CONFIG_FILE_DEFAULT;
|
||||
// Config file path may be overridden with env `UPLOADSHIELD_CONFIG`.
|
||||
$CONFIG_FILE_DEFAULT = __DIR__ . '/uploadshield.json';
|
||||
$CONFIG_FILE = getenv('UPLOADSHIELD_CONFIG') ?: $CONFIG_FILE_DEFAULT;
|
||||
|
||||
// Default modules and settings
|
||||
$DEFAULT_CONFIG = [
|
||||
@@ -168,7 +159,7 @@ if (is_file($CONFIG_FILE)) {
|
||||
|
||||
$cfgLogFile = $CONFIG_DATA['paths']['log_file'] ?? null;
|
||||
if (is_string($cfgLogFile) && $cfgLogFile !== '') {
|
||||
$isAbs = preg_match('#^[A-Za-z]:[\\/]#', $cfgLogFile) === 1
|
||||
$isAbs = preg_match('#^[A-Za-z]:[\/]#', $cfgLogFile) === 1
|
||||
|| (strlen($cfgLogFile) > 0 && ($cfgLogFile[0] === '/' || $cfgLogFile[0] === '\\'));
|
||||
if ($isAbs) {
|
||||
$logFile = $cfgLogFile;
|
||||
@@ -183,7 +174,7 @@ $BOOT_LOGGER = new \UploadLogger\Core\Services\LogService($logFile, []);
|
||||
|
||||
$fileAllow = is_file($PEEK_ALLOW_FILE);
|
||||
$headerAllow = false;
|
||||
if (isset($_SERVER['HTTP_X_UPLOAD_LOGGER_PEEK']) && $_SERVER['HTTP_X_UPLOAD_LOGGER_PEEK'] === '1') {
|
||||
if (isset($_SERVER['HTTP_X_UPLOADSHIELD_PEEK']) && $_SERVER['HTTP_X_UPLOADSHIELD_PEEK'] === '1') {
|
||||
$clientIp = $REQ->getClientIp();
|
||||
if (in_array($clientIp, $TRUSTED_PROXY_IPS, true)) {
|
||||
$headerAllow = true;
|
||||
@@ -380,7 +371,7 @@ $LOGGER = new \UploadLogger\Core\Logger($logFile, $REQUEST_CTX, $CONFIG);
|
||||
/*
|
||||
* Map frequently-used legacy globals to values from `Config` so the rest of
|
||||
* the procedural helpers can continue to reference globals but operators
|
||||
* may control behavior via `upload-logger.json`.
|
||||
* may control behavior via `uploadshield.json`.
|
||||
*/
|
||||
$BLOCK_SUSPICIOUS = $CONFIG->get('ops.block_suspicious', $BLOCK_SUSPICIOUS ?? false);
|
||||
$MAX_SIZE = (int)$CONFIG->get('limits.max_size', $MAX_SIZE ?? (50 * 1024 * 1024));
|
||||
Reference in New Issue
Block a user