Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Exception\Security;
|
||||
|
||||
use App\Framework\Exception\ErrorCode;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
/**
|
||||
* Ausnahme für Path-Traversal-Angriffs-Versuche
|
||||
*
|
||||
* Verwendet OWASP-konforme Nachrichten für Path-Traversal-Detection
|
||||
*/
|
||||
final class PathTraversalAttemptException extends FrameworkException
|
||||
{
|
||||
/**
|
||||
* @param string $clientIp Client-IP für Security-Tracking
|
||||
* @param string $requestedPath Angeforderte Pfad
|
||||
* @param string $pattern Erkanntes Path-Traversal-Pattern
|
||||
* @param string $attackContext Kontext (file_access, url_parameter, etc.)
|
||||
* @param \Throwable|null $previous Vorherige Ausnahme
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $clientIp,
|
||||
public readonly string $requestedPath,
|
||||
public readonly string $pattern,
|
||||
public readonly string $attackContext = 'file_access',
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
// OWASP-konforme Nachricht mit Platzhaltern
|
||||
$message = "Client {$this->clientIp} attempted path traversal: {$this->requestedPath}";
|
||||
|
||||
$context = ExceptionContext::forOperation('security.path_traversal_detection', 'Security')
|
||||
->withData([
|
||||
'client_ip' => $this->clientIp,
|
||||
'requested_path' => $this->requestedPath,
|
||||
'pattern' => $this->pattern,
|
||||
'context' => $this->attackContext,
|
||||
'path_depth' => $this->calculateTraversalDepth(),
|
||||
'event_identifier' => "security_path_traversal:{$this->clientIp}",
|
||||
'category' => 'file_access',
|
||||
'requires_alert' => true, // Path-Traversal-Versuche erfordern immer Alerts
|
||||
])
|
||||
->withMetadata([
|
||||
'security_event' => true,
|
||||
'owasp_compliant' => true,
|
||||
'log_level' => 'ERROR',
|
||||
'attack_type' => 'path_traversal',
|
||||
'critical_security_event' => true,
|
||||
]);
|
||||
|
||||
parent::__construct(
|
||||
message: $message,
|
||||
context: $context,
|
||||
code: 400, // Bad Request
|
||||
previous: $previous,
|
||||
errorCode: ErrorCode::SECURITY_PATH_TRAVERSAL
|
||||
);
|
||||
}
|
||||
|
||||
// === Factory Methods für verschiedene Path-Traversal-Patterns ===
|
||||
|
||||
public static function dotDotSlash(string $clientIp, string $path): self
|
||||
{
|
||||
return new self($clientIp, $path, '../ pattern', 'file_access');
|
||||
}
|
||||
|
||||
public static function dotDotBackslash(string $clientIp, string $path): self
|
||||
{
|
||||
return new self($clientIp, $path, '..\ pattern', 'file_access');
|
||||
}
|
||||
|
||||
public static function encodedTraversal(string $clientIp, string $path): self
|
||||
{
|
||||
return new self($clientIp, $path, 'URL-encoded traversal', 'file_access');
|
||||
}
|
||||
|
||||
public static function unicodeTraversal(string $clientIp, string $path): self
|
||||
{
|
||||
return new self($clientIp, $path, 'Unicode-encoded traversal', 'file_access');
|
||||
}
|
||||
|
||||
public static function absolutePath(string $clientIp, string $path): self
|
||||
{
|
||||
return new self($clientIp, $path, 'Absolute path access', 'file_access');
|
||||
}
|
||||
|
||||
public static function systemPath(string $clientIp, string $path): self
|
||||
{
|
||||
return new self($clientIp, $path, 'System directory access', 'system_access');
|
||||
}
|
||||
|
||||
public static function configFileAccess(string $clientIp, string $path): self
|
||||
{
|
||||
return new self($clientIp, $path, 'Configuration file access', 'config_access');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt OWASP-konforme Event-Daten zurück
|
||||
*/
|
||||
public function getSecurityEventData(): array
|
||||
{
|
||||
return [
|
||||
'event_identifier' => "security_path_traversal:{$this->clientIp}",
|
||||
'description' => "Client {$this->clientIp} attempted path traversal: {$this->requestedPath}",
|
||||
'category' => 'file_access',
|
||||
'log_level' => 'ERROR',
|
||||
'requires_alert' => true,
|
||||
'client_ip' => $this->clientIp,
|
||||
'requested_path' => $this->requestedPath,
|
||||
'pattern' => $this->pattern,
|
||||
'context' => $this->attackContext,
|
||||
'attack_severity' => $this->getAttackSeverity(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt Schweregrad des Path-Traversal-Angriffs
|
||||
*/
|
||||
public function getAttackSeverity(): string
|
||||
{
|
||||
// Systemverzeichnisse sind kritisch
|
||||
$criticalPaths = ['/etc/', '/var/', '/usr/', '/sys/', '/proc/', 'C:\Windows', 'C:\System'];
|
||||
foreach ($criticalPaths as $critical) {
|
||||
if (str_contains($this->requestedPath, $critical)) {
|
||||
return 'CRITICAL';
|
||||
}
|
||||
}
|
||||
|
||||
// Konfigurationsdateien sind hochriskant
|
||||
$sensitiveFiles = ['.env', 'config', 'passwd', 'shadow', 'hosts', 'web.config'];
|
||||
foreach ($sensitiveFiles as $sensitive) {
|
||||
if (str_contains($this->requestedPath, $sensitive)) {
|
||||
return 'HIGH';
|
||||
}
|
||||
}
|
||||
|
||||
// Deep traversal ist verdächtig
|
||||
if ($this->calculateTraversalDepth() > 3) {
|
||||
return 'HIGH';
|
||||
}
|
||||
|
||||
return 'MEDIUM';
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Tiefe des Traversal-Versuchs
|
||||
*/
|
||||
public function calculateTraversalDepth(): int
|
||||
{
|
||||
return substr_count($this->requestedPath, '../') + substr_count($this->requestedPath, '..\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Path-Traversal-Versuche erfordern immer Alerts
|
||||
*/
|
||||
public function requiresAlert(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob es ein automatisierter Angriff ist
|
||||
*/
|
||||
public function isAutomatedAttack(): bool
|
||||
{
|
||||
// Typische automatisierte Scanner-Patterns
|
||||
$automatedPatterns = [
|
||||
'../../../../../../../../etc/passwd',
|
||||
'..\\..\\..\\..\\windows\\system32',
|
||||
'%2e%2e%2f', // URL-encoded ../
|
||||
'\x2e\x2e\x2f', // Hex-encoded
|
||||
];
|
||||
|
||||
$lowerPath = strtolower($this->requestedPath);
|
||||
foreach ($automatedPatterns as $pattern) {
|
||||
if (str_contains($lowerPath, strtolower($pattern))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert das Path-Traversal-Pattern detailliert
|
||||
*/
|
||||
public function analyzePattern(): array
|
||||
{
|
||||
$analysis = [
|
||||
'encoding_type' => 'none',
|
||||
'target_os' => 'unknown',
|
||||
'target_files' => [],
|
||||
'traversal_depth' => $this->calculateTraversalDepth(),
|
||||
'automation_detected' => $this->isAutomatedAttack(),
|
||||
'evasion_techniques' => [],
|
||||
];
|
||||
|
||||
// Encoding-Erkennung
|
||||
if (str_contains($this->requestedPath, '%')) {
|
||||
$analysis['encoding_type'] = 'url_encoded';
|
||||
} elseif (str_contains($this->requestedPath, '\x')) {
|
||||
$analysis['encoding_type'] = 'hex_encoded';
|
||||
} elseif (preg_match('/\\u[0-9a-f]{4}/i', $this->requestedPath)) {
|
||||
$analysis['encoding_type'] = 'unicode_encoded';
|
||||
}
|
||||
|
||||
// OS-Erkennung
|
||||
if (str_contains($this->requestedPath, '\\')) {
|
||||
$analysis['target_os'] = 'windows';
|
||||
} elseif (str_contains($this->requestedPath, '/')) {
|
||||
$analysis['target_os'] = 'unix';
|
||||
}
|
||||
|
||||
// Zieldateien identifizieren
|
||||
$targetFiles = [
|
||||
'passwd' => 'unix_password_file',
|
||||
'shadow' => 'unix_shadow_file',
|
||||
'hosts' => 'hosts_file',
|
||||
'.env' => 'environment_file',
|
||||
'config' => 'configuration_file',
|
||||
'web.config' => 'iis_config',
|
||||
'httpd.conf' => 'apache_config',
|
||||
'nginx.conf' => 'nginx_config',
|
||||
];
|
||||
|
||||
foreach ($targetFiles as $file => $description) {
|
||||
if (str_contains(strtolower($this->requestedPath), $file)) {
|
||||
$analysis['target_files'][] = $description;
|
||||
}
|
||||
}
|
||||
|
||||
// Evasion-Techniken
|
||||
if (str_contains($this->requestedPath, './')) {
|
||||
$analysis['evasion_techniques'][] = 'current_directory_reference';
|
||||
}
|
||||
|
||||
if (preg_match('/\.{3,}/', $this->requestedPath)) {
|
||||
$analysis['evasion_techniques'][] = 'multiple_dots';
|
||||
}
|
||||
|
||||
if (str_contains($this->requestedPath, '//')) {
|
||||
$analysis['evasion_techniques'][] = 'double_slash';
|
||||
}
|
||||
|
||||
return $analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt benutzerfreundliche Fehlermeldung zurück (ohne Details zu verraten)
|
||||
*/
|
||||
public function getUserMessage(): string
|
||||
{
|
||||
return "Invalid file path. Access denied.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt spezifische Verteidigungsempfehlung zurück
|
||||
*/
|
||||
public function getDefenseRecommendation(): string
|
||||
{
|
||||
return match ($this->attackContext) {
|
||||
'file_access' => 'Implement proper path validation, use allowlists, and restrict file access to specific directories.',
|
||||
'system_access' => 'Block access to system directories, implement strict path validation, and use security contexts.',
|
||||
'config_access' => 'Secure configuration files, implement access controls, and use environment variables.',
|
||||
default => 'Implement comprehensive path validation and access controls.',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert IOC (Indicator of Compromise) für Security-Teams
|
||||
*/
|
||||
public function generateIOC(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'path_traversal_attempt',
|
||||
'source_ip' => $this->clientIp,
|
||||
'requested_path' => $this->requestedPath,
|
||||
'pattern' => $this->pattern,
|
||||
'context' => $this->attackContext,
|
||||
'severity' => $this->getAttackSeverity(),
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'automated' => $this->isAutomatedAttack(),
|
||||
'analysis' => $this->analyzePattern(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob sofortige Gegenmaßnahmen erforderlich sind
|
||||
*/
|
||||
public function requiresImmediateAction(): bool
|
||||
{
|
||||
return in_array($this->getAttackSeverity(), ['HIGH', 'CRITICAL']) ||
|
||||
$this->isAutomatedAttack() ||
|
||||
$this->calculateTraversalDepth() > 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt WAF-Regel-Vorschläge zurück
|
||||
*/
|
||||
public function getWafRuleSuggestions(): array
|
||||
{
|
||||
$rules = [
|
||||
'Block requests containing ../ or ..\\ patterns',
|
||||
'Block requests with URL-encoded path traversal sequences',
|
||||
'Block access to sensitive file extensions (.env, .config, etc.)',
|
||||
];
|
||||
|
||||
$analysis = $this->analyzePattern();
|
||||
|
||||
if ($analysis['encoding_type'] !== 'none') {
|
||||
$rules[] = 'Implement URL decoding before path traversal detection';
|
||||
}
|
||||
|
||||
if (! empty($analysis['target_files'])) {
|
||||
$rules[] = 'Block access to system configuration files and password files';
|
||||
}
|
||||
|
||||
if ($analysis['traversal_depth'] > 3) {
|
||||
$rules[] = 'Limit maximum directory traversal depth';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt sichere Alternative für File-Access vor
|
||||
*/
|
||||
public function getSecureAlternatives(): array
|
||||
{
|
||||
return [
|
||||
'Use file IDs instead of file paths in URLs',
|
||||
'Implement a file mapping table',
|
||||
'Restrict file access to a specific directory',
|
||||
'Use symbolic links instead of direct paths',
|
||||
'Implement role-based file access controls',
|
||||
'Validate file paths against an allowlist',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user