fix: all code review security findings

- CORS: replace open regex with explicit hostname allowlist + port whitelist
- Exception handler: only expose RuntimeException/InvalidArgumentException
  messages; PDOException and others return generic 'internal error'
- Auth::portalUrl(): allowlist-validate HTTP_HOST before using it in
  redirect URL — prevents open redirect via Host header injection
- _branding.php custom_css: strip HTML tags, js: URLs, @import, expression()
  instead of just </style> which was trivially bypassable
- accounts create: check accounts table as well as users for username
  uniqueness (TOCTOU fix); wrap user INSERT + provisioning in single
  transaction so rollback is atomic on failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
This commit is contained in:
2026-06-21 16:03:25 +00:00
parent 21dd846508
commit 956defc34b
4 changed files with 53 additions and 15 deletions
+24 -7
View File
@@ -150,18 +150,35 @@ class Auth {
* Used by login redirect so each role lands on the right port
*/
public static function portalUrl(string $role, string $path = '/'): string {
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
// No port in HTTP_HOST means the request came through a reverse proxy on 443 — stay on same host
if (!preg_match('/:\d+$/', $host)) {
return "https://{$host}{$path}";
}
// Direct access — redirect to the correct panel port
$host = $_SERVER['HTTP_HOST'] ?? '';
// Allowlist of trusted hostnames — prevents open redirect via Host header injection
$allowed = [
'novacpx.orbishosting.com',
'admin.novacpx.orbishosting.com',
'reseller.novacpx.orbishosting.com',
'panel.novacpx.orbishosting.com',
'web.orbishosting.com',
];
$hostname = preg_replace('/:\d+$/', '', $host);
// Trusted proxy (no port) — stay on same host
if ($host && !preg_match('/:\d+$/', $host) && in_array($hostname, $allowed, true)) {
return "https://{$hostname}{$path}";
}
// Direct port access — validate hostname is a known server IP or allowed host
$port = match($role) {
'admin' => PORT_ADMIN,
'reseller' => PORT_RESELLER,
default => PORT_USER,
};
return "https://{$hostname}:{$port}{$path}";
// Only redirect to localhost/LAN IPs on direct panel access
if ($hostname && (filter_var($hostname, FILTER_VALIDATE_IP) || in_array($hostname, $allowed, true))) {
return "https://{$hostname}:{$port}{$path}";
}
// Fallback — safe relative path with no host (works for same-origin redirects)
return $path;
}
}