Rename project to UploadShield: update runtime, configs, docs, and provisioning; run lint/tests

This commit is contained in:
2026-02-12 14:48:07 +01:00
parent d1310e0844
commit 7c8bccc911
20 changed files with 95 additions and 104 deletions

2
.gitignore vendored
View File

@@ -8,7 +8,7 @@
uploads.log
# Peek allow marker (local only)
/.upload_logger_allow_peek
/.uploadshield_allow_peek
# Local environment files
.env

View File

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

View File

@@ -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
```

View File

@@ -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`.

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
{

View File

@@ -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. PHPFPM 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.

View File

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

View File

@@ -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>

View File

@@ -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'

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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]

View File

@@ -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";

View File

@@ -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));