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
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Anwendungsdaten ändern - Angreifer können Log-Einträge fälschen oder bösartige Inhalte einfügen, die legitim erscheinen. |
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Phishing-Angriffe mit Homograph-Domains können Anmeldedaten und sensible Informationen stehlen. |
| Sonstiges | Bereich: 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
-
Homoglyph Attack Generator - Generiert homoglyphische Varianten.
-
Unicode Security Analyzer - Unicode Confusables-Prüfung.
-
IDN Checker - Punycode-Konvertierung und IDN-Analyse.
CVE-Beispiele
-
CVE-2013-7236 - IDN-Homograph-Angriff in Browser-URL-Anzeige.
-
CVE-2012-0584 - Browser anfällig für URL-Spoofing über IDN.
-
CVE-2005-0233 - Browser zeigt IDN-Domains ohne Warnung an.
-
CVE-2017-5015 - Chrome-Adressleisten-Spoofing mit RTL-Zeichen.
Referenzen
-
MITRE Corporation. "CWE-1007: Insufficient Visual Distinction of Homoglyphs Presented to User." https://cwe.mitre.org/data/definitions/1007.html
-
Unicode Technical Report #36. "Unicode Security Considerations." https://www.unicode.org/reports/tr36/
-
Unicode Technical Standard #39. "Unicode Security Mechanisms." https://www.unicode.org/reports/tr39/