Origin-Validierungsfehler
Beschreibung
Origin-Validierungsfehler tritt auf, wenn Software den Ursprung einer Anfrage, Nachricht oder Daten nicht ordnungsgemäß verifiziert. Dies ermöglicht Angreifern von nicht vertrauenswürdigen Ursprüngen, mit der Anwendung zu interagieren, als kämen sie von einer vertrauenswürdigen Quelle. Häufige Manifestationen umfassen unsachgemäße CORS-Konfiguration, fehlende oder fehlerhafte Origin-Header-Validierung, Vertrauen auf den Referer-Header für Sicherheitsentscheidungen und das Akzeptieren von WebSocket-Verbindungen von beliebigen Ursprüngen.
Risiko
Origin-Validierungsfehler ermöglichen Cross-Site-Angriffe, die Same-Origin-Policy-Schutzmaßnahmen umgehen. Angreifer können authentifizierte Anfragen von bösartigen Websites stellen, Daten durch CORS-Fehlkonfigurationen stehlen oder WebSocket-Verbindungen herstellen, um Echtzeit-Kommunikation zu kapern. Das Risiko wird verstärkt, wenn es mit Anmeldedaten kombiniert wird - reflektierte Origins mit aktivierten Anmeldedaten sind besonders gefährlich. API-Endpunkte mit defekter Origin-Validierung setzen alle authentifizierten Benutzer Cross-Site-Angriffen aus.
Lösung
Implementieren Sie strenge Origin-Validierung unter Verwendung von Allowlists. Reflektieren Sie niemals beliebige Origin-Header, wenn Anmeldedaten beteiligt sind. Verwenden Sie den Origin-Header (nicht Referer) für Sicherheitsentscheidungen. Validieren Sie WebSocket-Verbindungs-Origins. Implementieren Sie ordnungsgemäße CORS-Richtlinien mit spezifischen erlaubten Origins. Verwenden Sie SameSite-Cookies als zusätzlichen Schutz. Für sensible Operationen erfordern Sie zusätzliche Verifizierung über die Origin-Prüfung hinaus. Validieren Sie den gesamten Origin einschließlich Schema und Port.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Datendiebstahl Angreifer können Antworten von Cross-Origin-Anfragen lesen und sensible Daten stehlen. |
| Integrität | Umfang: Unbefugte Aktionen CSRF-ähnliche Angriffe können Daten unter Verwendung der Anmeldedaten des Opfers modifizieren. |
| Authentifizierung | Umfang: Session-Missbrauch Authentifizierte Sessions werden von angreiferkontrollierten Websites missbraucht. |
Beispielcode + Korrigierter Code
Anfälliger Code
# ANFÄLLIG: Reflektieren eines beliebigen Origin
from flask import Flask, request
@app.after_request
def add_cors_headers(response):
# Reflektiert JEDEN Origin - gefährlich mit Anmeldedaten!
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
# ANFÄLLIG: Referer für Origin-Validierung verwenden
@app.route('/api/transfer', methods=['POST'])
def transfer_funds():
referer = request.headers.get('Referer')
# Referer kann gefälscht oder weggelassen werden!
if referer and 'example.com' in referer:
# Überweisung verarbeiten...
return jsonify({'status': 'success'})
return jsonify({'error': 'Ungültige Anfrage'}), 403
# ANFÄLLIG: Partielle Origin-Übereinstimmung
@app.after_request
def weak_origin_check(response):
origin = request.headers.get('Origin', '')
# Angreifer verwendet: evil-example.com oder example.com.evil.com
if 'example.com' in origin:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
# ANFÄLLIG: WebSocket ohne Origin-Prüfung
from flask_socketio import SocketIO
socketio = SocketIO(app, cors_allowed_origins='*') # Erlaubt JEDEN Origin!
@socketio.on('connect')
def handle_connect():
# Keine Origin-Validierung!
pass
// ANFÄLLIG: Origin in Java reflektieren
@WebFilter("/*")
public class VulnerableCorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// Reflektiert jeden Origin!
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(req, res);
}
}
// ANFÄLLIG: Regex-Bypass
@Component
public class WeakOriginValidator {
private static final Pattern ORIGIN_PATTERN = Pattern.compile(".*\\.example\\.com");
public boolean isValidOrigin(String origin) {
// Matched: evil.example.com aber auch evilexample.com
return origin != null && ORIGIN_PATTERN.matcher(origin).matches();
}
}
// ANFÄLLIG: Spring CORS mit Wildcard und Anmeldedaten
@Configuration
public class VulnerableCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // Jeder Origin!
.allowCredentials(true); // Mit Anmeldedaten!
}
}
// ANFÄLLIG: Express mit reflektiertem Origin
app.use((req, res, next) => {
// Reflektiert jeden Origin!
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
// ANFÄLLIG: WebSocket ohne Origin-Validierung
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, req) => {
// Keine Origin-Prüfung!
ws.on('message', (message) => {
// Nachricht verarbeiten...
});
});
// ANFÄLLIG: PostMessage ohne Origin-Validierung
window.addEventListener('message', (event) => {
// Akzeptiert Nachrichten von JEDEM Origin!
const data = event.data;
processData(data);
});
// ANFÄLLIG: Substring-Origin-Prüfung
function checkOrigin(origin) {
// Angreifer verwendet: https://example.com.evil.com
return origin && origin.includes('example.com');
}
Korrigierter Code
# SICHER: Strikte Origin-Allowlist
from flask import Flask, request
ALLOWED_ORIGINS = {
'https://app.example.com',
'https://www.example.com',
'https://admin.example.com'
}
@app.after_request
def add_cors_headers_safe(response):
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Access-Control-Max-Age'] = '86400'
return response
# SICHER: Ordnungsgemäße Origin-Validierung
from urllib.parse import urlparse
def is_valid_origin(origin):
"""Validiert Origin gegen strikte Allowlist."""
if not origin:
return False
try:
parsed = urlparse(origin)
# Muss HTTPS sein
if parsed.scheme != 'https':
return False
# Exakte Domain-Übereinstimmung (einschließlich Port falls vorhanden)
allowed_hosts = {'app.example.com', 'www.example.com'}
return parsed.netloc in allowed_hosts
except Exception:
return False
@app.route('/api/data')
def get_data():
origin = request.headers.get('Origin')
if not is_valid_origin(origin):
return jsonify({'error': 'Ungültiger Origin'}), 403
# Anfrage verarbeiten...
return jsonify({'data': 'sensitive'})
# SICHER: WebSocket mit Origin-Validierung
from flask_socketio import SocketIO, disconnect
ALLOWED_WS_ORIGINS = ['https://app.example.com', 'https://www.example.com']
socketio = SocketIO(app, cors_allowed_origins=ALLOWED_WS_ORIGINS)
@socketio.on('connect')
def handle_connect():
# Zusätzliche Validierung falls erforderlich
origin = request.headers.get('Origin')
if origin not in ALLOWED_WS_ORIGINS:
disconnect()
return False
# SICHER: Subdomain-Validierung mit ordnungsgemäßen Prüfungen
def is_valid_subdomain(origin):
"""Validiert dass Origin eine gültige Subdomain ist."""
if not origin:
return False
try:
parsed = urlparse(origin)
if parsed.scheme != 'https':
return False
host = parsed.netloc.lower()
# Muss mit .example.com enden oder genau example.com sein
if host == 'example.com':
return True
if host.endswith('.example.com'):
# Sicherstellen dass es eine echte Subdomain ist, nicht evil-example.com
subdomain = host[:-len('.example.com')]
# Subdomain-Format validieren
if subdomain and '.' not in subdomain and subdomain.isalnum():
return True
return False
except Exception:
return False
// SICHER: Java mit strikter Origin-Validierung
@WebFilter("/*")
public class SecureCorsFilter implements Filter {
private static final Set<String> ALLOWED_ORIGINS = Set.of(
"https://app.example.com",
"https://www.example.com",
"https://admin.example.com"
);
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String origin = request.getHeader("Origin");
if (origin != null && ALLOWED_ORIGINS.contains(origin)) {
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
response.setHeader("Access-Control-Max-Age", "86400");
}
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(req, res);
}
}
// SICHER: Spring Security CORS-Konfiguration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Nur spezifische Origins
configuration.setAllowedOrigins(List.of(
"https://app.example.com",
"https://www.example.com"
));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(86400L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
// SICHER: WebSocket Origin-Validierung
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins(
"https://app.example.com",
"https://www.example.com"
)
.withSockJS();
}
}
// SICHER: Express mit strikter Origin-Validierung
const ALLOWED_ORIGINS = new Set([
'https://app.example.com',
'https://www.example.com',
'https://admin.example.com'
]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (ALLOWED_ORIGINS.has(origin)) {
res.header('Access-Control-Allow-Origin', origin);
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Max-Age', '86400');
}
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
// SICHER: WebSocket mit Origin-Prüfung
const WebSocket = require('ws');
const ALLOWED_WS_ORIGINS = new Set([
'https://app.example.com',
'https://www.example.com'
]);
const wss = new WebSocket.Server({
port: 8080,
verifyClient: (info, callback) => {
const origin = info.origin;
if (ALLOWED_WS_ORIGINS.has(origin)) {
callback(true);
} else {
callback(false, 403, 'Origin nicht erlaubt');
}
}
});
// SICHER: PostMessage mit Origin-Validierung
window.addEventListener('message', (event) => {
// Origin validieren
if (event.origin !== 'https://trusted.example.com') {
console.warn('Nachricht von nicht vertrautem Origin:', event.origin);
return;
}
// Sicher zu verarbeiten
processData(event.data);
});
// SICHER: Hilfsfunktion für Subdomain-Validierung
function isValidOrigin(origin) {
if (!origin) return false;
try {
const url = new URL(origin);
// Muss HTTPS sein
if (url.protocol !== 'https:') return false;
const hostname = url.hostname.toLowerCase();
// Exakte Übereinstimmung oder gültige Subdomain
if (hostname === 'example.com') return true;
if (hostname.endsWith('.example.com')) {
// Subdomain holen
const subdomain = hostname.slice(0, -('.example.com'.length));
// Validieren: einzelnes Label, alphanumerisch + Bindestrich
return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(subdomain);
}
return false;
} catch {
return false;
}
}
Ausgenutzt in der Praxis
Facebook CORS-Schwachstelle
Eine CORS-Fehlkonfiguration ermöglichte Angreifern, private Daten von Facebook-APIs zu lesen, wenn Benutzer bösartige Seiten besuchten.
Mehrere API-Breaches
Viele Organisationen haben Datenschutzverletzungen erlitten, weil sie beliebige Origins mit Anmeldedaten reflektierten, was jeder Website ermöglichte, Benutzerdaten zu stehlen.
WebSocket-Hijacking
Anwendungen, die WebSocket-Verbindungen von beliebigen Origins akzeptieren, wurden ausgenutzt, um Echtzeit-Kommunikation zu kapern.
Tools zum Testen/Ausnutzen
-
Burp Suite — Testet CORS-Konfigurationen.
-
CORScanner — Automatisierter CORS-Fehlkonfigurations-Scanner.
-
Browser DevTools — Inspiziert CORS-Header.
-
Postman — Testet API mit verschiedenen Origin-Headern.
CVE-Beispiele
-
CVE-2021-22893 — Pulse Secure CORS-Schwachstelle.
-
CVE-2019-9787 — WordPress Origin-Validierungs-Bypass.
-
CVE-2020-5741 — Plex CORS-Fehlkonfiguration.
Referenzen
-
MITRE. "CWE-346: Origin Validation Error." https://cwe.mitre.org/data/definitions/346.html
-
OWASP. "CORS Security." https://owasp.org/www-community/attacks/CORS_OriginHeaderScrutiny