Security: verwijder hardcoded wachtwoord, voeg random-wachtwoord-generator toe bij eerste installatie

This commit is contained in:
2026-08-27 08:57:05 +00:00
parent 6485f693dc
commit 74612aefbb
55 changed files with 6019 additions and 876 deletions
+1773 -110
View File
File diff suppressed because it is too large Load Diff
+35
View File
@@ -1,4 +1,16 @@
<?php
/**
* Asset-server voor productie: serveert statische bestanden uit themes/,
* admin/theme/ en plugins/.
*
* Dit bestand wordt op productie gebruikt wanneer de PHP-dev-router niet
* beschikbaar is. public/.htaccess herschrijft /themes/, /admin/assets/ en
* /plugins/ URLs naar dit bestand. Paden worden via realpath() + prefix-
* check afgedwongen binnen de juiste asset-map om path traversal te voorkomen.
*
* @since 2.6.4
* @package CodePress
*/
/**
* Asset server - serves static files from themes/, admin/theme/, and plugins/
* This file is used on production servers where the PHP router is not available.
@@ -25,6 +37,11 @@ $mimeTypes = [
// Determine which asset directory to serve from
$assetFile = null;
/**
* /themes/<naam>/assets/...: theme-asset matchen en pad oplossen.
*
* @since 2.6.4
*/
// /themes/<name>/assets/...
if (preg_match('#^/themes/([^/]+)/assets/(.+)$#', $path, $m)) {
$themeName = $m[1];
@@ -32,12 +49,22 @@ if (preg_match('#^/themes/([^/]+)/assets/(.+)$#', $path, $m)) {
$assetFile = $basePath . '/themes/' . $themeName . '/assets/' . $assetRel;
$realBase = realpath($basePath . '/themes/' . $themeName . '/assets');
}
/**
* /admin/assets/...: admin-theme-asset matchen (default admin theme).
*
* @since 2.6.4
*/
// /admin/assets/...
elseif (preg_match('#^/admin/assets/(.+)$#', $path, $m)) {
$assetRel = $m[1];
$assetFile = $basePath . '/admin/theme/default/assets/' . $assetRel;
$realBase = realpath($basePath . '/admin/theme/default/assets');
}
/**
* /plugins/<naam>/assets/...: plugin-asset matchen en pad oplossen.
*
* @since 2.6.4
*/
// /plugins/<name>/assets/...
elseif (preg_match('#^/plugins/([^/]+)/assets/(.+)$#', $path, $m)) {
$pluginName = $m[1];
@@ -46,6 +73,14 @@ elseif (preg_match('#^/plugins/([^/]+)/assets/(.+)$#', $path, $m)) {
$realBase = realpath($basePath . '/plugins/' . $pluginName . '/assets');
}
/**
* Matchende asset uitleveren met juiste MIME en cache-headers.
*
* Controleert dat het opgeloste pad binnen de asset-map blijft en een
* regulier bestand is; anders volgt een 404.
*
* @since 2.6.4
*/
if ($assetFile && $realBase) {
$realFile = realpath($assetFile);
if ($realFile && strpos($realFile, $realBase) === 0 && is_file($realFile)) {
+102 -7
View File
@@ -1,12 +1,33 @@
<?php
/**
* Front-end entry point van CodePress CMS.
*
* Bootstrap de CMS-core, laadt de configuratie, stuurt security headers uit,
* serveert media-/asset-/robots-requests, voert BotGuard- en rate-limit-
* controles uit, registreert analytics- en logvermeldingen en render
* uiteindelijk de pagina via CodePressCMS::render().
*
* @since 2.6.4
* @package CodePress
*/
require_once __DIR__ . '/../cms/core/index.php';
$config = include __DIR__ . '/../cms/core/config.php';
/**
* Log-systeem initialiseren op basis van de logging-configuratie.
*
* @since 2.6.4
*/
// Initialize dynamic logging
LogManager::init($config['logging'] ?? []);
/**
* Security headers instellen voor alle front-end responses.
*
* @since 2.6.4
*/
// Security headers
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
@@ -15,6 +36,15 @@ header('Referrer-Policy: strict-origin-when-cross-origin');
header('Content-Security-Policy: default-src \'self\'; script-src \'self\' \'unsafe-inline\'; style-src \'self\' \'unsafe-inline\'; img-src \'self\' data:; font-src \'self\';');
header_remove('X-Powered-By');
/**
* Media- en asset-serving via /-media/ en /-assets/ routes.
*
* Bepaalt het verzoekpad en de toegestane MIME-types, resolveert de
* content-map dynamisch op basis van config['content_dir'] en serveert
* bestanden buiten public/ met een pad-controle om traversal te blokkeren.
*
* @since 2.6.4
*/
// Serve media files from any content/ subdirectory via /-media/
// Serve files from content/-assets/ via /-assets/ (backward compatible)
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
@@ -31,11 +61,15 @@ $mimeTypes = [
'css' => 'text/css', 'js' => 'application/javascript',
];
// Resolve the content directory once (absolute, from config) so media
// serving follows a customised content_dir instead of a hardcoded path.
$contentBase = realpath($config['content_dir']);
$contentBase = $contentBase !== false ? $contentBase : rtrim($config['content_dir'], '/');
if (strpos($path, '/-media/') === 0) {
$relative = ltrim(substr($path, 7), '/');
$root = realpath(__DIR__ . '/..');
$filePath = $root . '/content/' . $relative;
if (strpos($filePath, $root . '/content') === 0) {
$filePath = $contentBase . '/' . $relative;
if ($contentBase !== '' && strpos($filePath, $contentBase) === 0) {
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
if (in_array($ext, $allowedExt) && file_exists($filePath) && !is_dir($filePath)) {
if (isset($mimeTypes[$ext])) {
@@ -50,18 +84,23 @@ if (strpos($path, '/-media/') === 0) {
exit;
}
/**
* /-assets/ route: serveert bestanden uit content/-assets/ of de content-
* root, met backward-compatibiliteit en path-traversal-controle.
*
* @since 2.6.4
*/
if (strpos($path, '/-assets/') === 0) {
$relative = ltrim(substr($path, 8), '/');
$root = realpath(__DIR__ . '/..');
$servePath = null;
$candidates = [
$root . '/content/-assets/' . $relative,
$root . '/content/' . $relative,
$contentBase . '/-assets/' . $relative,
$contentBase . '/' . $relative,
];
foreach ($candidates as $candidate) {
if (strpos($candidate, $root . '/content') !== 0) continue;
if ($contentBase === '' || strpos($candidate, $contentBase) !== 0) continue;
if (file_exists($candidate) && !is_dir($candidate)) {
$ext = strtolower(pathinfo($candidate, PATHINFO_EXTENSION));
if (in_array($ext, $allowedExt)) {
@@ -92,6 +131,13 @@ if ($path === '/robots.txt') {
exit;
}
/**
* Directe toegang tot /content/ blokkeren.
*
* Voorkomt dat ruwe content-bestanden via het web opgevraagd worden.
*
* @since 2.6.4
*/
// Block direct access to content files
if (strpos($path, '/content/') === 0) {
http_response_code(403);
@@ -99,6 +145,16 @@ if (strpos($path, '/content/') === 0) {
exit;
}
/**
* Security-checks en rate limiting uitvoeren.
*
* Bepaalt client-IP, BotGuard-controle, IP-blocklist en rate-limiter op
* basis van de security-config. IP's op de allowed/uitgesloten lijsten
* slaan de controles over. Het requestStatus wordt bijgehouden voor
* latere logging en blocking.
*
* @since 2.6.4
*/
// Load RequestLogger for IP & request logging
require_once __DIR__ . '/../cms/core/class/RequestLogger.php';
@@ -149,9 +205,22 @@ if (!$isAllowedIp) {
}
}
/**
* CMS-instantie aanmaken met de geladen configuratie.
*
* @since 2.6.4
*/
// Instantiate CMS instance
$cms = new CodePressCMS($config);
/**
* Analytics en GeoIP-lookup uitvoeren.
*
* Land, IP-anonimisatie en analytics-instellingen worden bepaald op basis
* van de analytics-configuratie.
*
* @since 2.6.4
*/
// Analytics & GeoIP settings
$analyticsSettings = $config['analytics'] ?? [];
$geoCountry = null;
@@ -160,11 +229,25 @@ if (!empty($analyticsSettings['enabled'])) {
$geoCountry = $geoIp->lookupCountry($clientIp);
}
/**
* IP-anonimisatie toepassen voor opslag indien ingeschakeld.
*
* @since 2.6.4
*/
// Apply IP anonymization for storage if enabled
$storedIp = !empty($analyticsSettings['anonymize_ip'])
? RequestLogger::anonymizeIp($clientIp)
: $clientIp;
/**
* Pagina-view loggen (niet voor media-/assets-requests).
*
* Start sessie indien nodig, stelt huidige pagina/referrer vast, schrijft
* een request-logregel, vuurt een LogManager-requestevent en registreert
* geaggregeerde analytics (mits ingeschakeld en IP niet uitgesloten).
*
* @since 2.6.4
*/
// Log page view (not for media/assets)
if (!str_starts_with($path, '/-media/') && !str_starts_with($path, '/-assets/')) {
if (session_status() === PHP_SESSION_NONE) {
@@ -203,6 +286,13 @@ if (!str_starts_with($path, '/-media/') && !str_starts_with($path, '/-assets/'))
}
}
/**
* Geblokkeerde verzoeken afhandelen.
*
* Geeft 429 bij rate-limit en 403 bij andere blokkades, met noindex-headers.
*
* @since 2.6.4
*/
// Block request if status is not ok
if ($requestStatus !== 'ok') {
if ($requestStatus === 'blocked:ratelimit') {
@@ -216,4 +306,9 @@ if ($requestStatus !== 'ok') {
exit;
}
/**
* Pagina renderen via de CodePressCMS-instantie.
*
* @since 2.6.4
*/
$cms->render();