Unzureichende visuelle Unterscheidung von Homoglyphen für Benutzer

Beschreibung

Unzureichende visuelle Unterscheidung von Homoglyphen für Benutzer tritt auf, wenn visuell ähnliche oder identische Zeichen (Homoglyphen) bei der Anzeige für Benutzer nicht klar unterschieden werden. Verschiedene Unicode-Zeichen können in vielen Schriftarten nahezu identisch oder vollständig identisch erscheinen - zum Beispiel der lateinische Buchstabe "a" und das kyrillische "а", oder das lateinische "o" und das griechische "ο". Angreifer nutzen dies aus, indem sie Domainnamen registrieren, Benutzernamen erstellen oder Inhalte mit Zeichen verfassen, die identisch mit legitimen Zeichen aussehen, aber semantisch unterschiedlich sind, wodurch Phishing-, Identitätsbetrugs- und Spoofing-Angriffe ermöglicht werden.

Risiko

Diese Schwachstelle ermöglicht verschiedene betrügerische Angriffe. Internationalized Domain Name (IDN) Homograph-Angriffe verwenden ähnlich aussehende Domains für Phishing (z.B. "аpple.com" mit kyrillischem Zeichen). Benutzernamen-Spoofing ermöglicht Identitätsbetrug in Chat-Systemen, Foren oder sozialen Medien. Log-Fälschung wird möglich, wenn bösartige Einträge von legitimen Benutzern zu stammen scheinen. Quellcodedateien können bösartigen Code enthalten, der durch ähnlich aussehende Variablen- oder Funktionsnamen versteckt wird. E-Mail-Adressen können gefälscht werden, um legitim zu erscheinen. Das Risiko ist besonders hoch, weil Menschen viele Homoglyphen-Paare visuell nicht unterscheiden können, selbst bei sorgfältiger Prüfung.

Lösung

Implementieren Sie visuelle Disambiguierung für angezeigte Inhalte in sicherheitskritischen Kontexten. Verwenden Sie Schriftarten, die Homoglyphen wo möglich klar unterscheiden. Für Domainnamen verwenden Sie Punycode-Anzeige (z.B. "xn--...") oder zeigen Sie eine Warnung für IDN-Domains an. Implementieren Sie Normalisierung und Erkennung verwechselbarer Zeichen. Beschränken Sie Zeichensätze in Benutzernamen und Bezeichnern, um das Mischen von Skripten zu verhindern. Zeigen Sie Unicode-Skript-Informationen neben Text in Sicherheitskontexten an. Verwenden Sie Syntaxhervorhebung, die Zeichenskripte unterscheidet. Implementieren Sie Browser-Schutzmaßnahmen gegen IDN-Homograph-Angriffe. Warnen Sie Benutzer, wenn verwechselbare Zeichen erkannt werden.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Anwendungsdaten ändern - Angreifer können Log-Einträge fälschen oder bösartige Inhalte einfügen, die legitim erscheinen.
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Phishing-Angriffe mit Homograph-Domains können Anmeldedaten und sensible Informationen stehlen.
SonstigesBereich: Sonstige

Benutzer können getäuscht werden, bösartigen Inhalten, URLs oder Identitäten zu vertrauen.

Beispielcode und Lösung

Verwundbarer Code

<!-- Verwundbar: Browser zeigt IDN ohne Warnung an -->
<!-- Benutzer sieht: https://www.аpple.com (sieht aus wie apple.com) -->
<!-- Tatsächliche Domain verwendet kyrillisches 'а' (U+0430) statt lateinischem 'a' (U+0061) -->

<!-- Verwundbar: Link mit Homograph-Domain -->
<a href="https://www.аpple.com/login">Bei Apple anmelden</a>
<!-- Benutzer denkt, er geht zu Apple, aber es ist eine Phishing-Seite -->
# Verwundbar: Chat-Anwendung ohne Homoglyphen-Erkennung
class VerwundbareChat:

    def nachricht_anzeigen(self, benutzername, nachricht):
        # Verwundbar: Keine Homoglyphen-Erkennung
        # Angreifer verwendet "аdmin" (kyrillisches 'а') statt "admin"
        return f"{benutzername}: {nachricht}"

    def benutzerliste_anzeigen(self, benutzer):
        # Benutzer "admin" und "аdmin" erscheinen identisch
        # sind aber unterschiedliche Konten
        for user in benutzer:
            print(user.name)
# Verwundbar: Log-Anzeige ohne Zeichenunterscheidung
class VerwundbarerLogViewer:

    def log_eintrag_anzeigen(self, eintrag):
        # Verwundbar: Kann Homoglyphen in Logs nicht unterscheiden
        # Eintrag von "аdmin" sieht aus wie von "admin"
        print(f"[{eintrag.timestamp}] {eintrag.benutzer}: {eintrag.aktion}")

    # Angreifer erstellt Konto "аdmin" (kyrillisch)
    # Führt bösartige Aktionen aus, die in Logs als "admin" erscheinen
// Verwundbar: URL-Validierung erkennt Homoglyphen nicht
function verwundbareGültigeDomain(domain) {
    // Verwundbar: Prüft nur Muster, nicht Zeichenskripte
    const pattern = /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    return pattern.test(domain);

    // "аpple.com" besteht, weil kyrillisches 'а' nicht erkannt wird
}
// Verwundbar: Quellcode mit versteckten Homoglyphen
// Dieser Code scheint String korrekt zu vergleichen, tut es aber nicht

int vulnerableCheckAdmin(const char *username) {
    // Dies sieht aus wie "admin", verwendet aber kyrillische Zeichen
    // Compiler behandelt sie als unterschiedliche Strings
    if (strcmp(username, "аdmin") == 0) {  // Kyrillisches 'а'
        return 1;  // Admin-Zugriff gewähren
    }

    // Echte Admin-Prüfung mit lateinischem "admin"
    if (strcmp(username, "admin") == 0) {
        return 1;
    }

    return 0;
}
// Angreifer registriert "аdmin"-Konto und umgeht Prüfungen

Sichere Lösung

// Sicher: Browser-Erweiterung für IDN-Homograph-Erkennung
class HomoglyphenDetektor {

    constructor() {
        // Verwechselbare Zeichen-Zuordnungen
        this.verwechselbare = {
            '\u0430': 'a',  // Kyrillisch а -> Lateinisch a
            '\u0435': 'e',  // Kyrillisch е -> Lateinisch e
            '\u043E': 'o',  // Kyrillisch о -> Lateinisch o
            '\u0440': 'p',  // Kyrillisch р -> Lateinisch p
            '\u0441': 'c',  // Kyrillisch с -> Lateinisch c
            '\u0445': 'x',  // Kyrillisch х -> Lateinisch x
            '\u0391': 'A',  // Griechisch Alpha -> Lateinisch A
            '\u0392': 'B',  // Griechisch Beta -> Lateinisch B
            '\u0395': 'E',  // Griechisch Epsilon -> Lateinisch E
            // ... umfangreiche Zuordnung
        };
    }

    homoglyphenErkennen(text) {
        const erkannt = [];
        for (let i = 0; i < text.length; i++) {
            const zeichen = text[i];
            if (this.verwechselbare[zeichen]) {
                erkannt.push({
                    position: i,
                    zeichen: zeichen,
                    siehtAusWie: this.verwechselbare[zeichen],
                    codePoint: zeichen.codePointAt(0).toString(16)
                });
            }
        }
        return erkannt;
    }

    getSkriptMischWarnung(domain) {
        const skripte = new Set();
        for (const zeichen of domain) {
            const skript = this.getZeichenSkript(zeichen);
            if (skript !== 'Common' && skript !== 'Unknown') {
                skripte.add(skript);
            }
        }

        if (skripte.size > 1) {
            return `Warnung: Domain verwendet gemischte Skripte (${Array.from(skripte).join(', ')})`;
        }
        return null;
    }

    domainSicherAnzeigen(domain) {
        const homoglyphen = this.homoglyphenErkennen(domain);
        const gemischtesSkript = this.getSkriptMischWarnung(domain);

        if (homoglyphen.length > 0 || gemischtesSkript) {
            // Punycode-Version stattdessen anzeigen
            return {
                anzeige: this.zuPunycode(domain),
                warnung: gemischtesSkript || 'Domain enthält verwechselbare Zeichen'
            };
        }

        return { anzeige: domain, warnung: null };
    }
}
# Sicher: Chat-Anwendung mit Homoglyphen-Erkennung
import unicodedata

class SichereChat:

    def __init__(self):
        # Zuordnung verwechselbarer Zeichen zu ASCII-Äquivalenten
        self.verwechselbare = self._lade_verwechselbare()

    def normalisieren_fuer_vergleich(self, text):
        """Normalisiere Text zur Homoglyphen-Erkennung"""
        # NFKC-Normalisierung konvertiert kompatible Zeichen
        normalisiert = unicodedata.normalize('NFKC', text)

        # Bekannte Verwechselbare ersetzen
        ergebnis = []
        for zeichen in normalisiert:
            if zeichen in self.verwechselbare:
                ergebnis.append(self.verwechselbare[zeichen])
            else:
                ergebnis.append(zeichen)

        return ''.join(ergebnis).lower()

    def benutzernamen_kollision_prüfen(self, neuer_benutzername, existierende_benutzer):
        """Prüfe, ob Benutzername mit existierenden Benutzern verwechselbar ist"""
        normalisiert_neu = self.normalisieren_fuer_vergleich(neuer_benutzername)

        for existierend in existierende_benutzer:
            normalisiert_existierend = self.normalisieren_fuer_vergleich(existierend)
            if normalisiert_neu == normalisiert_existierend:
                return True, existierend

        return False, None

    def nachricht_anzeigen(self, benutzername, nachricht):
        # Sicher: Verdächtige Benutzernamen erkennen und markieren
        if self.enthält_homoglyphen(benutzername):
            # Mit Skript-Indikatoren anzeigen
            annotiert = self.skripte_annotieren(benutzername)
            return f"{annotiert} [Gemischte Skripte]: {nachricht}"

        return f"{benutzername}: {nachricht}"

    def enthält_homoglyphen(self, text):
        """Prüfe, ob Text gemischte Unicode-Skripte enthält"""
        skripte = set()
        for zeichen in text:
            if zeichen.isalpha():
                skript = self.get_skript(zeichen)
                if skript not in ('Common', 'Inherited'):
                    skripte.add(skript)

        return len(skripte) > 1
# Sicher: Log-Viewer mit Homoglyphen-Hervorhebung
from colorama import Fore, Style

class SichererLogViewer:

    def __init__(self):
        self.bekannte_admins = {'admin', 'root', 'system'}
        self.detektor = HomoglyphenDetektor()

    def log_eintrag_anzeigen(self, eintrag):
        benutzer = eintrag.user

        # Sicher: Erkennen ob Benutzername mit bekannten Admins verwechselbar ist
        normalisiert = self.detektor.normalisieren(benutzer)

        if normalisiert in self.bekannte_admins and benutzer not in self.bekannte_admins:
            # Verdächtig: sieht aus wie Admin, ist es aber nicht
            hervorgehoben = self._verdaechtig_hervorheben(benutzer)
            print(f"{Fore.RED}[VERDÄCHTIG]{Style.RESET_ALL} "
                  f"[{eintrag.timestamp}] {hervorgehoben}: {eintrag.aktion}")
            print(f"    Hinweis: Benutzername '{benutzer}' ähnelt '{normalisiert}' "
                  f"verwendet aber andere Zeichen")

            # Zeichencodes anzeigen
            self._zeichen_details_anzeigen(benutzer)
        else:
            print(f"[{eintrag.timestamp}] {benutzer}: {eintrag.aktion}")

    def _verdaechtig_hervorheben(self, text):
        ergebnis = []
        for zeichen in text:
            if not zeichen.isascii() or self.detektor.ist_verwechselbar(zeichen):
                ergebnis.append(f"{Fore.RED}{zeichen}{Style.RESET_ALL}")
            else:
                ergebnis.append(zeichen)
        return ''.join(ergebnis)

    def _zeichen_details_anzeigen(self, text):
        for i, zeichen in enumerate(text):
            code = ord(zeichen)
            name = unicodedata.name(zeichen, 'UNBEKANNT')
            print(f"    Position {i}: '{zeichen}' U+{code:04X} ({name})")
// Sicher: Domain-Validierung mit Homoglyphen-Erkennung
const punycode = require('punycode');

class SichereDomainValidierung {

    constructor() {
        this.verwechselbarMap = this.ladeVerwechselbare();
        this.vertrauteDomains = new Set(['apple.com', 'google.com', 'microsoft.com']);
    }

    domainValidieren(domain) {
        const ergebnis = {
            isValid: true,
            warnungen: [],
            sichereAnzeige: domain
        };

        // Prüfen ob IDN (internationalisierter Domainname)
        if (this.hatNichtASCII(domain)) {
            ergebnis.warnungen.push('Domain enthält Nicht-ASCII-Zeichen');

            // Auf gemischte Skripte prüfen
            if (this.hatGemischteSkripte(domain)) {
                ergebnis.warnungen.push('Domain verwendet gemischte Unicode-Skripte');
                ergebnis.sichereAnzeige = punycode.toASCII(domain);
            }

            // Prüfen ob verwechselbar mit vertrauenswürdiger Domain
            const normalisiert = this.fuerVergleichNormalisieren(domain);
            if (this.vertrauteDomains.has(normalisiert)) {
                ergebnis.isValid = false;
                ergebnis.warnungen.push(
                    `Domain ist verwechselbar mit vertrauenswürdiger Domain: ${normalisiert}`
                );
                ergebnis.sichereAnzeige = `VERDÄCHTIG: ${punycode.toASCII(domain)}`;
            }
        }

        return ergebnis;
    }

    hatNichtASCII(str) {
        return /[^\x00-\x7F]/.test(str);
    }

    hatGemischteSkripte(str) {
        const skripte = new Set();
        for (const zeichen of str) {
            const skript = this.getSkript(zeichen);
            if (skript !== 'Common') {
                skripte.add(skript);
            }
        }
        return skripte.size > 1;
    }

    fuerVergleichNormalisieren(domain) {
        let normalisiert = domain.toLowerCase();
        for (const [verwechselbar, ascii] of Object.entries(this.verwechselbarMap)) {
            normalisiert = normalisiert.replace(new RegExp(verwechselbar, 'g'), ascii);
        }
        return normalisiert;
    }
}

// Verwendung im Browser
function linkSicherheitPruefen(url) {
    const validator = new SichereDomainValidierung();
    const domain = new URL(url).hostname;
    const ergebnis = validator.domainValidieren(domain);

    if (!ergebnis.isValid) {
        warnungAnzeigen(`Verdächtige Domain erkannt: ${ergebnis.sichereAnzeige}`);
        return false;
    }

    if (ergebnis.warnungen.length > 0) {
        infoAnzeigen(ergebnis.warnungen.join('\n'));
    }

    return true;
}
# Sicher: Benutzernamen-Registrierung mit Homoglyphen-Prävention
import unicodedata
import re

class SichererBenutzernamenValidator:

    def __init__(self):
        # Nur einfache lateinische Zeichen erlauben
        self.erlaubtes_muster = re.compile(r'^[a-zA-Z0-9_-]+$')

        # Oder erlaubte Unicode-Kategorien/Skripte definieren
        self.erlaubte_kategorien = {'Ll', 'Lu', 'Nd'}  # Kleinbuchstaben, Großbuchstaben, Ziffern

    def benutzername_validieren(self, benutzername):
        """Prüfe, ob Benutzername Homoglyphen enthält"""

        fehler = []

        # Option 1: Auf ASCII beschränken
        if not self.erlaubtes_muster.match(benutzername):
            fehler.append("Benutzername darf nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten")

        # Option 2: Auf gemischte Skripte prüfen
        if self.hat_gemischte_skripte(benutzername):
            fehler.append("Benutzername darf nicht verschiedene Schriftsysteme mischen")

        # Option 3: Auf bekannte verwechselbare Zeichen prüfen
        verwechselbare = self.verwechselbare_finden(benutzername)
        if verwechselbare:
            zeichen = ', '.join(f"'{c}'" for c in verwechselbare)
            fehler.append(f"Benutzername enthält verwechselbare Zeichen: {zeichen}")

        # Kollision mit existierenden Benutzernamen prüfen
        normalisiert = self.skelett(benutzername)
        # Datenbank auf Kollision abfragen...

        return len(fehler) == 0, fehler

    def hat_gemischte_skripte(self, text):
        skripte = set()
        for zeichen in text:
            if zeichen.isalpha():
                skript = self.get_skript(zeichen)
                if skript not in ('Common', 'Inherited'):
                    skripte.add(skript)
        return len(skripte) > 1

    def skelett(self, text):
        """Skelett für Verwechselbarkeitserkennung generieren (UTS39)"""
        # NFKD-Normalisierung, dann Verwechselbare zuordnen, dann Kleinbuchstaben
        normalisiert = unicodedata.normalize('NFKD', text)
        # Verwechselbare-Zuordnung anwenden...
        return normalisiert.lower()

    def get_skript(self, zeichen):
        """Unicode-Skript für Zeichen ermitteln"""
        # Vereinfacht - unicodedata oder ICU für vollständige Implementierung verwenden
        code = ord(zeichen)
        if 0x0400 <= code <= 0x04FF:
            return 'Cyrillic'
        if 0x0370 <= code <= 0x03FF:
            return 'Greek'
        if 0x0041 <= code <= 0x007A:
            return 'Latin'
        return 'Unknown'

Ausgenutzt in der Praxis

PayPal-IDN-Homograph-Phishing (2017)

Sicherheitsforscher demonstrierten, wie Angreifer eine perfekt aussehende PayPal-Phishing-Domain mit kyrillischen Zeichen erstellen könnten. Browser zeigten die Domain als "paypal.com" an, obwohl sie eine völlig andere Adresse war.

Unicode-Domain-Betrug im Finanzsektor (2018)

Mehrere Banken waren von Phishing-Angriffen betroffen, bei denen Angreifer Domains mit homoglyphischen Zeichen registrierten, um Kunden zu täuschen und Anmeldedaten zu stehlen.

GitHub-Benutzernamen-Spoofing (2019)

Forscher zeigten, wie durch Unicode-Homoglyphen Benutzernamen auf GitHub erstellt werden könnten, die identisch mit vertrauenswürdigen Entwicklern aussahen, was Social-Engineering-Angriffe auf Open-Source-Projekte ermöglichte.


Tools zum Testen und Ausnutzen


CVE-Beispiele


Referenzen

  1. MITRE Corporation. "CWE-1007: Insufficient Visual Distinction of Homoglyphs Presented to User." https://cwe.mitre.org/data/definitions/1007.html

  2. Unicode Technical Report #36. "Unicode Security Considerations." https://www.unicode.org/reports/tr36/

  3. Unicode Technical Standard #39. "Unicode Security Mechanisms." https://www.unicode.org/reports/tr39/