Files
michaelschiemer/src/Framework/Exception/Security/PathTraversalAttemptException.php
Michael Schiemer 55a330b223 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
2025-08-11 20:13:26 +02:00

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',
];
}
}