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

AuswirkungDetails
VertraulichkeitUmfang: Datendiebstahl

Angreifer können Antworten von Cross-Origin-Anfragen lesen und sensible Daten stehlen.
IntegritätUmfang: Unbefugte Aktionen

CSRF-ähnliche Angriffe können Daten unter Verwendung der Anmeldedaten des Opfers modifizieren.
AuthentifizierungUmfang: 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


Referenzen

  1. MITRE. "CWE-346: Origin Validation Error." https://cwe.mitre.org/data/definitions/346.html

  2. OWASP. "CORS Security." https://owasp.org/www-community/attacks/CORS_OriginHeaderScrutiny