Harden web root, secrets and realtime auth
- .htaccess: refuse .git, .env, docker, nodejs, sdlc*, scripts, archives and app internals; security headers, HSTS over TLS, optional HTTPS redirect - uploads served through app/file.php to signed-in users only - Apache/PHP hardening config for the container (ServerTokens, expose_php) - least-privilege DB account and APP_SECRET_KEY via docker/php/provision.php; SMTP passwords re-encrypted with a random IV (secret_box.php) - Socket.IO rooms from a PHP-signed token; /emit and cron refuse empty secrets - escape notification text; CLI guards on build scripts; no fixed demo password
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* file.php — serves files from app/uploads/ to signed-in users only.
|
||||
*
|
||||
* The root .htaccess rewrites every /app/uploads/<path> request here, so the
|
||||
* existing <img src=".../uploads/profile/x.png"> URLs keep working but an
|
||||
* anonymous visitor gets 401 instead of the file. Only the upload types that
|
||||
* FileUploader accepts are served, and never anything that could execute.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/session.php';
|
||||
|
||||
if (empty($_SESSION['login_company_id'])) {
|
||||
http_response_code(401);
|
||||
exit;
|
||||
}
|
||||
// The session is only read from here on; release its lock so pages that load
|
||||
// many images do not queue behind each other.
|
||||
session_write_close();
|
||||
|
||||
$types = [
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
'pdf' => 'application/pdf',
|
||||
];
|
||||
|
||||
$base = realpath(__DIR__ . '/uploads');
|
||||
$rel = (string)($_GET['path'] ?? '');
|
||||
$file = $base ? realpath($base . '/' . $rel) : false;
|
||||
|
||||
// realpath() resolves ../ and symlinks; anything outside uploads/ is refused.
|
||||
if ($base === false || $file === false || !is_file($file) || strpos($file, $base . DIRECTORY_SEPARATOR) !== 0) {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
if (!isset($types[$ext])) {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header('Content-Type: ' . $types[$ext]);
|
||||
header('Content-Length: ' . filesize($file));
|
||||
header('Content-Disposition: ' . ($ext === 'pdf' ? 'attachment' : 'inline') . '; filename="' . basename($file) . '"');
|
||||
header('Cache-Control: private, max-age=3600');
|
||||
header("Content-Security-Policy: default-src 'none'; img-src 'self'; style-src 'unsafe-inline'; sandbox");
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
readfile($file);
|
||||
Reference in New Issue
Block a user