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:
365
src/Framework/Exception/Security/XssAttemptException.php
Normal file
365
src/Framework/Exception/Security/XssAttemptException.php
Normal file
@@ -0,0 +1,365 @@
|
||||
<?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 XSS-Angriffs-Versuche
|
||||
*
|
||||
* Verwendet OWASP-konforme Nachrichten für XSS-Detection
|
||||
*/
|
||||
final class XssAttemptException extends FrameworkException
|
||||
{
|
||||
/**
|
||||
* @param string $clientIp Client-IP für Security-Tracking
|
||||
* @param string $field Betroffenes Feld/Parameter
|
||||
* @param string $pattern Erkanntes XSS-Pattern
|
||||
* @param string $originalValue Ursprünglicher Wert (sanitized)
|
||||
* @param string $xssType Art des XSS (reflected, stored, dom)
|
||||
* @param \Throwable|null $previous Vorherige Ausnahme
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $clientIp,
|
||||
public readonly string $field,
|
||||
public readonly string $pattern,
|
||||
public readonly string $originalValue,
|
||||
public readonly string $xssType = 'reflected',
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
// OWASP-konforme Nachricht mit Platzhaltern
|
||||
$message = "Client {$this->clientIp} attempted XSS attack in field {$this->field}";
|
||||
|
||||
$context = ExceptionContext::forOperation('security.xss_detection', 'Security')
|
||||
->withData([
|
||||
'client_ip' => $this->clientIp,
|
||||
'field' => $this->field,
|
||||
'pattern' => $this->pattern,
|
||||
'xss_type' => $this->xssType,
|
||||
'value_length' => strlen($this->originalValue),
|
||||
'event_identifier' => "security_xss_attempt:{$this->clientIp},{$this->field}",
|
||||
'category' => 'input_validation',
|
||||
'requires_alert' => true, // XSS-Versuche erfordern immer Alerts
|
||||
])
|
||||
->withDebug([
|
||||
'sanitized_value' => $this->sanitizeValueForLog($this->originalValue),
|
||||
])
|
||||
->withMetadata([
|
||||
'security_event' => true,
|
||||
'owasp_compliant' => true,
|
||||
'log_level' => 'ERROR',
|
||||
'attack_type' => 'xss',
|
||||
'critical_security_event' => true,
|
||||
]);
|
||||
|
||||
parent::__construct(
|
||||
message: $message,
|
||||
context: $context,
|
||||
code: 400, // Bad Request
|
||||
previous: $previous,
|
||||
errorCode: ErrorCode::SECURITY_XSS_ATTEMPT
|
||||
);
|
||||
}
|
||||
|
||||
// === Factory Methods für verschiedene XSS-Patterns ===
|
||||
|
||||
public static function scriptTag(string $clientIp, string $field, string $value): self
|
||||
{
|
||||
return new self($clientIp, $field, '<script> tag injection', $value, 'reflected');
|
||||
}
|
||||
|
||||
public static function onEventHandler(string $clientIp, string $field, string $value): self
|
||||
{
|
||||
return new self($clientIp, $field, 'Event handler injection (onclick, onload, etc.)', $value, 'reflected');
|
||||
}
|
||||
|
||||
public static function javascriptProtocol(string $clientIp, string $field, string $value): self
|
||||
{
|
||||
return new self($clientIp, $field, 'javascript: protocol injection', $value, 'reflected');
|
||||
}
|
||||
|
||||
public static function htmlInjection(string $clientIp, string $field, string $value): self
|
||||
{
|
||||
return new self($clientIp, $field, 'HTML tag injection', $value, 'reflected');
|
||||
}
|
||||
|
||||
public static function domBasedXss(string $clientIp, string $field, string $value): self
|
||||
{
|
||||
return new self($clientIp, $field, 'DOM-based XSS pattern', $value, 'dom');
|
||||
}
|
||||
|
||||
public static function storedXss(string $clientIp, string $field, string $value): self
|
||||
{
|
||||
return new self($clientIp, $field, 'Stored XSS pattern', $value, 'stored');
|
||||
}
|
||||
|
||||
public static function cssInjection(string $clientIp, string $field, string $value): self
|
||||
{
|
||||
return new self($clientIp, $field, 'CSS injection with expression()', $value, 'reflected');
|
||||
}
|
||||
|
||||
public static function svgXss(string $clientIp, string $field, string $value): self
|
||||
{
|
||||
return new self($clientIp, $field, 'SVG-based XSS injection', $value, 'reflected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt OWASP-konforme Event-Daten zurück
|
||||
*/
|
||||
public function getSecurityEventData(): array
|
||||
{
|
||||
return [
|
||||
'event_identifier' => "security_xss_attempt:{$this->clientIp},{$this->field}",
|
||||
'description' => "Client {$this->clientIp} attempted XSS attack in field {$this->field}",
|
||||
'category' => 'input_validation',
|
||||
'log_level' => 'ERROR',
|
||||
'requires_alert' => true,
|
||||
'client_ip' => $this->clientIp,
|
||||
'field' => $this->field,
|
||||
'pattern' => $this->pattern,
|
||||
'xss_type' => $this->xssType,
|
||||
'attack_severity' => $this->getAttackSeverity(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt Schweregrad des XSS-Angriffs
|
||||
*/
|
||||
public function getAttackSeverity(): string
|
||||
{
|
||||
return match ($this->xssType) {
|
||||
'stored' => 'CRITICAL', // Stored XSS ist am gefährlichsten
|
||||
'dom' => 'HIGH', // DOM-based XSS ist schwer zu erkennen
|
||||
'reflected' => 'MEDIUM', // Reflected XSS ist häufig aber weniger persistent
|
||||
default => 'MEDIUM',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* XSS-Versuche erfordern immer Alerts
|
||||
*/
|
||||
public function requiresAlert(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob es ein automatisierter Angriff ist
|
||||
*/
|
||||
public function isAutomatedAttack(): bool
|
||||
{
|
||||
$automatedPatterns = [
|
||||
'alert(',
|
||||
'prompt(',
|
||||
'confirm(',
|
||||
'document.cookie',
|
||||
'xss',
|
||||
'<svg',
|
||||
'javascript:',
|
||||
'onerror=',
|
||||
'onload=',
|
||||
];
|
||||
|
||||
$lowerValue = strtolower($this->originalValue);
|
||||
foreach ($automatedPatterns as $pattern) {
|
||||
if (str_contains($lowerValue, $pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert das XSS-Pattern detailliert
|
||||
*/
|
||||
public function analyzePattern(): array
|
||||
{
|
||||
$analysis = [
|
||||
'vector_type' => 'unknown',
|
||||
'payload_complexity' => 'low',
|
||||
'encoding_used' => false,
|
||||
'obfuscation_detected' => false,
|
||||
'automation_detected' => $this->isAutomatedAttack(),
|
||||
'potential_impact' => 'low',
|
||||
];
|
||||
|
||||
$lowerValue = strtolower($this->originalValue);
|
||||
|
||||
// Vector-Typ-Erkennung
|
||||
if (str_contains($lowerValue, '<script')) {
|
||||
$analysis['vector_type'] = 'script_tag';
|
||||
} elseif (str_contains($lowerValue, 'on') && preg_match('/on\w+\s*=/', $lowerValue)) {
|
||||
$analysis['vector_type'] = 'event_handler';
|
||||
} elseif (str_contains($lowerValue, 'javascript:')) {
|
||||
$analysis['vector_type'] = 'javascript_protocol';
|
||||
} elseif (str_contains($lowerValue, '<svg') || str_contains($lowerValue, '<embed')) {
|
||||
$analysis['vector_type'] = 'svg_embed';
|
||||
} elseif (str_contains($lowerValue, 'expression(')) {
|
||||
$analysis['vector_type'] = 'css_expression';
|
||||
}
|
||||
|
||||
// Komplexität bewerten
|
||||
if (strlen($this->originalValue) > 100) {
|
||||
$analysis['payload_complexity'] = 'high';
|
||||
} elseif (strlen($this->originalValue) > 50) {
|
||||
$analysis['payload_complexity'] = 'medium';
|
||||
}
|
||||
|
||||
// Encoding-Erkennung
|
||||
if (str_contains($this->originalValue, '%') || str_contains($this->originalValue, '&#')) {
|
||||
$analysis['encoding_used'] = true;
|
||||
}
|
||||
|
||||
// Obfuskierung-Erkennung
|
||||
if (preg_match('/String\.fromCharCode|eval\(|unescape\(/', $this->originalValue)) {
|
||||
$analysis['obfuscation_detected'] = true;
|
||||
$analysis['payload_complexity'] = 'high';
|
||||
}
|
||||
|
||||
// Impact-Bewertung
|
||||
if (str_contains($lowerValue, 'cookie') || str_contains($lowerValue, 'document')) {
|
||||
$analysis['potential_impact'] = 'high';
|
||||
} elseif (str_contains($lowerValue, 'alert') || str_contains($lowerValue, 'prompt')) {
|
||||
$analysis['potential_impact'] = 'medium';
|
||||
}
|
||||
|
||||
return $analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt benutzerfreundliche Fehlermeldung zurück (ohne Details zu verraten)
|
||||
*/
|
||||
public function getUserMessage(): string
|
||||
{
|
||||
return "Invalid input detected. Please check your data and try again.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Bereinigt Wert für sicheres Logging
|
||||
*/
|
||||
private function sanitizeValueForLog(string $value): string
|
||||
{
|
||||
// HTML-Entities kodieren und auf 100 Zeichen begrenzen
|
||||
$sanitized = htmlspecialchars(substr($value, 0, 100), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// Zusätzliche Bereinigung für Logs
|
||||
$sanitized = preg_replace('/\s+/', ' ', $sanitized);
|
||||
|
||||
return trim($sanitized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt spezifische Verteidigungsempfehlung zurück
|
||||
*/
|
||||
public function getDefenseRecommendation(): string
|
||||
{
|
||||
return match ($this->xssType) {
|
||||
'stored' => 'Implement output encoding, Content Security Policy, and input validation.',
|
||||
'dom' => 'Use safe JavaScript APIs, validate DOM manipulation, implement CSP.',
|
||||
'reflected' => 'Implement output encoding, input validation, and Content Security Policy.',
|
||||
default => 'Use output encoding, input validation, and Content Security Policy.',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert Content Security Policy Empfehlungen
|
||||
*/
|
||||
public function getCspRecommendations(): array
|
||||
{
|
||||
$baseCSP = [
|
||||
"default-src 'self'",
|
||||
"script-src 'self'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data:",
|
||||
"connect-src 'self'",
|
||||
"font-src 'self'",
|
||||
"object-src 'none'",
|
||||
"frame-src 'none'",
|
||||
];
|
||||
|
||||
$analysis = $this->analyzePattern();
|
||||
|
||||
// Verschärfungen basierend auf Angriffsmuster
|
||||
if ($analysis['vector_type'] === 'script_tag') {
|
||||
$baseCSP[] = "script-src 'self' 'nonce-{random}'"; // Nonce-basierte Scripts
|
||||
}
|
||||
|
||||
if ($analysis['vector_type'] === 'css_expression') {
|
||||
$baseCSP[] = "style-src 'self'"; // Entferne unsafe-inline
|
||||
}
|
||||
|
||||
return $baseCSP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert IOC (Indicator of Compromise) für Security-Teams
|
||||
*/
|
||||
public function generateIOC(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'xss_attempt',
|
||||
'source_ip' => $this->clientIp,
|
||||
'target_field' => $this->field,
|
||||
'pattern' => $this->pattern,
|
||||
'xss_type' => $this->xssType,
|
||||
'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 $this->xssType === 'stored' ||
|
||||
$this->getAttackSeverity() === 'CRITICAL' ||
|
||||
$this->isAutomatedAttack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt WAF (Web Application Firewall) Regel-Vorschläge zurück
|
||||
*/
|
||||
public function getWafRuleSuggestions(): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
$analysis = $this->analyzePattern();
|
||||
|
||||
switch ($analysis['vector_type']) {
|
||||
case 'script_tag':
|
||||
$rules[] = 'Block requests containing <script tags in input fields';
|
||||
|
||||
break;
|
||||
case 'event_handler':
|
||||
$rules[] = 'Block requests containing on* event handlers in input fields';
|
||||
|
||||
break;
|
||||
case 'javascript_protocol':
|
||||
$rules[] = 'Block requests containing javascript: protocol in input fields';
|
||||
|
||||
break;
|
||||
case 'svg_embed':
|
||||
$rules[] = 'Block or sanitize SVG/embed tags in user input';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($analysis['encoding_used']) {
|
||||
$rules[] = 'Implement URL decoding before XSS detection';
|
||||
}
|
||||
|
||||
if ($analysis['obfuscation_detected']) {
|
||||
$rules[] = 'Implement advanced obfuscation detection rules';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user