Security hardening: CSRF, rate limiting, session/password policy, audit log

Fixes critical pre-existing issues found during review: bulk_action.php had no
auth check at all (unauthenticated download/delete of any qrcode) and built a
table name from unwhitelisted user input (SQL injection); the QR generator
classes wrote files from unvalidated filename/format, allowing path traversal
and arbitrary file writes. Also pins chillerlan/php-qrcode to 5.0.5 since
master now requires PHP 8.4, breaking the PHP 8.3 build.

- CSRF tokens on all POST forms and the bulk_action.php JSON endpoint
- Login rate limiting (5 attempts / 15 min) via new login_attempts table
- Hardened sessions: httponly/samesite cookies, 30 min idle timeout,
  session regeneration on login
- Forced password change for the default superadmin/superadmin account
- Server-side validation in Users/DynamicQrcode/Qrcode classes
- Audit log table for auth, user, and qrcode actions
- Checked-in db schema (db/init.sql, migrations/) instead of relying on an
  opaque prebuilt db image
- Production docker-compose with Nginx + php-fpm instead of the PHP dev server

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 15:00:33 +02:00
parent 268a6a3f65
commit feb5380b28
48 changed files with 946 additions and 127 deletions
+26 -21
View File
@@ -1,48 +1,53 @@
version: "3.2"
services:
php-dynamic-qrcode:
image: "giandonatoinverso/php-dynamic-qr-code:latest"
build:
context: .
dockerfile: Dockerfile
restart: "unless-stopped"
environment:
TYPE: "docker"
QRCODE_GENERATOR: "internal-chillerlan.qrcode"
BASE_URL: "https://mydomain.com"
TYPE: "${TYPE:-docker}"
QRCODE_GENERATOR: "${QRCODE_GENERATOR:-internal-chillerlan.qrcode}"
BASE_URL: "${BASE_URL:-http://localhost}"
DATABASE_HOST: "php-dynamic-qrcode-db"
DATABASE_PORT: "3306"
DATABASE_NAME: "qrcode"
DATABASE_USER: "qrcode"
DATABASE_PASSWORD: "changeme"
DATABASE_PREFIX: ""
DATABASE_CHARSET: "utf8"
DATABASE_NAME: "${DATABASE_NAME:-qrcode}"
DATABASE_USER: "${DATABASE_USER:-qrcode}"
DATABASE_PASSWORD: "${DATABASE_PASSWORD:?zet DATABASE_PASSWORD in .env}"
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
ports:
- 80:80
- "80:80"
depends_on:
- php-dynamic-qrcode-db
php-dynamic-qrcode-db:
condition: service_healthy
volumes:
- php_dynamic_qrcode_saved_qrcode_data:/var/www/html/saved_qrcode
networks:
- php-dynamic-qrcode-network
php-dynamic-qrcode-db:
image: "giandonatoinverso/php-dynamic-qr-code-db:latest"
image: "mysql:8.0"
restart: "unless-stopped"
volumes:
- php_dynamic_qrcode_db_data:/var/lib/mysql
ports:
- '13306:3306'
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
environment:
MYSQL_ROOT_PASSWORD: "changeme"
MYSQL_DATABASE: "qrcode"
MYSQL_USER: "qrcode"
MYSQL_PASSWORD: "changeme"
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?zet MYSQL_ROOT_PASSWORD in .env}"
MYSQL_DATABASE: "${DATABASE_NAME:-qrcode}"
MYSQL_USER: "${DATABASE_USER:-qrcode}"
MYSQL_PASSWORD: "${DATABASE_PASSWORD:?zet DATABASE_PASSWORD in .env}"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 5s
timeout: 5s
retries: 10
networks:
- php-dynamic-qrcode-network
volumes:
php_dynamic_qrcode_db_data:
php_dynamic_qrcode_config_data:
php_dynamic_qrcode_saved_qrcode_data:
networks:
php-dynamic-qrcode-network:
driver: bridge
driver: bridge