- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
343 lines
11 KiB
PHP
343 lines
11 KiB
PHP
<?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',
|
|
];
|
|
}
|
|
}
|