🔍

Unicode Homoglyph Attacks: When Characters Lie About Who They AreUnicodeホモグリフ攻撃: 見た目は同じ、中身は別物

How visually identical characters from different scripts enable phishing and spoofing, and how to detect them.異なるスクリプトの視覚的に同一な文字がフィッシングを可能にする仕組みと検出方法。

The Threat: Characters That Look Alike

Unicode contains over 150,000 characters from hundreds of scripts. Many of these characters are visually identical or nearly identical, despite being completely different code points from different writing systems. These are called homoglyphs.

The most notorious example: Latin a (U+0061) and Cyrillic а (U+0430). They are pixel-perfect identical in most fonts, but they are distinct Unicode characters from different scripts.

Looks likeLatinCyrillicGreek
AU+0041 AU+0410 АU+0391 Α
aU+0061 aU+0430 а
BU+0042 BU+0412 ВU+0392 Β
EU+0045 EU+0415 ЕU+0395 Ε
oU+006F oU+043E оU+03BF ο
pU+0070 pU+0440 рU+03C1 ρ
xU+0078 xU+0445 хU+03C7 χ

This is not a bug — these are legitimately different characters that happen to have converged on the same visual form. But attackers exploit this coincidence.

Multi-Script Homoglyphs in Depth

The problem extends far beyond Latin/Cyrillic. Unicode contains homoglyphs across many script pairs:

VisualScripts involvedCode points
1 / l / IDigit / Latin lower / Latin upperU+0031, U+006C, U+0049
0 / O / ОDigit / Latin / CyrillicU+0030, U+004F, U+041E
н / HCyrillic lower / Latin upperU+043D, U+0048
ν / vGreek lower / Latin lowerU+03BD, U+0076
ℓ / lScript small L / Latin LU+2113, U+006C
⁰ / ° / oSuperscript 0 / Degree / Latin oU+2070, U+00B0, U+006F
ー / — / ─Katakana / Em dash / Box drawingU+30FC, U+2014, U+2500

Fullwidth characters add another dimension: (U+FF21, FULLWIDTH LATIN CAPITAL LETTER A) looks similar to A (U+0041) in some contexts. Mathematical alphanumeric symbols (U+1D400–U+1D7FF) provide yet more lookalikes: 𝐀 (U+1D400, MATHEMATICAL BOLD CAPITAL A).

Real-World Attacks

Homoglyph attacks have caused real damage:

  • IDN homograph attacks: Registering domains like аpple.com (with Cyrillic а) that display identically to apple.com in browser address bars. This led to the development of IDN display policies in browsers.
  • Source code attacks (Trojan Source): Inserting Bidi override characters or homoglyphs in source code to make malicious logic appear benign during code review. A 2021 Cambridge paper demonstrated how this could inject vulnerabilities invisible to human reviewers.
  • Phishing emails: Spoofing sender names or URLs using mixed-script homoglyphs that pass visual inspection.
  • Social media impersonation: Creating usernames that look identical to legitimate accounts using Cyrillic or Greek substitutions.
// These strings look identical but are different:
const latin  = "apple";      // All Latin
const mixed  = "аpple";      // Cyrillic а + Latin pple

latin === mixed              // false
latin.length === mixed.length // true (both 5)

// Byte comparison reveals the difference:
latin.codePointAt(0)  // 97  (U+0061 Latin Small Letter A)
mixed.codePointAt(0)  // 1072 (U+0430 Cyrillic Small Letter A)

Detection Methods

Several approaches exist to detect homoglyph attacks:

  • Script mixing detection: Flag strings that contain characters from multiple scripts (e.g., Latin mixed with Cyrillic). Unicode TR#39 defines “mixed-script” detection algorithms.
  • Confusable detection: Unicode TR#39 also publishes a confusables.txt file that maps each character to its “skeleton” — a canonical form for comparison. Two strings with the same skeleton are confusable.
  • Single-script enforcement: Requiring that identifiers (usernames, domains) use characters from only one script.
  • Visual inspection tools: Using tools like this one to reveal the actual code points behind text that looks suspicious.
// Unicode TR#39 confusable skeleton (conceptual):
skeleton("аpple") → "apple"  // Cyrillic а maps to Latin a
skeleton("apple") → "apple"  // Already Latin

// If skeletons match, strings are confusable:
skeleton("аpple") === skeleton("apple")  // true → confusable!

// Script detection:
function getScripts(str) {
  return [...new Set(
    [...str].map(ch => {
      // Use Unicode script property
      // (simplified; real impl uses Unicode data)
      const cp = ch.codePointAt(0);
      if (cp >= 0x0400 && cp <= 0x04FF) return "Cyrillic";
      if (cp >= 0x0370 && cp <= 0x03FF) return "Greek";
      return "Latin";
    })
  )];
}

Normalization as a Partial Defense

Unicode normalization (especially NFKC) can collapse some homoglyphs but not all:

Homoglyph pairNFKC helps?Reason
A (U+FF21) vs A (U+0041)YesNFKC maps fullwidth to ASCII
ⅰ (U+2170) vs i (U+0069)YesNFKC decomposes Roman numerals
а (U+0430) vs a (U+0061)NoDifferent scripts, not compatibility equivalents
Α (U+0391) vs A (U+0041)NoGreek and Latin are distinct
𝐀 (U+1D400) vs A (U+0041)YesNFKC maps math alphanumerics

NFKC normalization is a useful first pass — it eliminates compatibility variants and fullwidth forms. But cross-script homoglyphs (Latin vs Cyrillic vs Greek) survive normalization because they are not compatibility equivalents. They are genuinely different characters that just happen to look the same.

For robust defense, you need both normalization and confusable detection. This tool lets you see exactly which code points are behind any text, making it easy to spot when characters are not what they appear to be.

脅威: 見た目が同じ文字たち

Unicode は数百のスクリプトから15万以上の文字を含んでいます。これらの多くは、完全に異なる書記体系の完全に異なるコードポイントでありながら、視覚的に同一またはほぼ同一です。これらはホモグリフ(同形異字)と呼ばれます。

最も悪名高い例: ラテン文字の a(U+0061)とキリル文字の а(U+0430)。ほとんどのフォントでピクセル単位で完全に同一ですが、異なるスクリプトの別個の Unicode 文字です。

見た目ラテンキリルギリシャ
AU+0041 AU+0410 АU+0391 Α
aU+0061 aU+0430 а
BU+0042 BU+0412 ВU+0392 Β
EU+0045 EU+0415 ЕU+0395 Ε
oU+006F oU+043E оU+03BF ο
pU+0070 pU+0440 рU+03C1 ρ
xU+0078 xU+0445 хU+03C7 χ

これはバグではありません — たまたま同じ視覚形式に収斂した正当に異なる文字です。しかし攻撃者はこの偶然を悪用します。

多スクリプトホモグリフの詳細

問題はラテン/キリルをはるかに超えて広がります。Unicode は多くのスクリプトペア間にホモグリフを含んでいます:

見た目関連スクリプトコードポイント
1 / l / I数字 / ラテン小文字 / ラテン大文字U+0031, U+006C, U+0049
0 / O / О数字 / ラテン / キリルU+0030, U+004F, U+041E
н / Hキリル小文字 / ラテン大文字U+043D, U+0048
ν / vギリシャ小文字 / ラテン小文字U+03BD, U+0076
ℓ / l筆記体小文字 L / ラテン LU+2113, U+006C
⁰ / ° / o上付き0 / 度 / ラテン oU+2070, U+00B0, U+006F
ー / — / ─カタカナ / Em ダッシュ / 罫線素片U+30FC, U+2014, U+2500

全角文字はさらに別の次元を加えます: (U+FF21、全角ラテン大文字A)は文脈によっては A(U+0041)と類似して見えます。数学用英数字記号(U+1D400〜U+1D7FF)もさらなる類似文字を提供します: 𝐀(U+1D400、数学用太字大文字A)。

実際の攻撃事例

ホモグリフ攻撃は実際に被害を引き起こしてきました:

  • IDN ホモグラフ攻撃: аpple.com(キリル文字のа)のようなドメインを登録し、ブラウザのアドレスバーで apple.com と同一に表示させる。これがブラウザにおける IDN 表示ポリシー策定のきっかけとなった。
  • ソースコード攻撃(Trojan Source): ソースコードに Bidi オーバーライド文字やホモグリフを挿入し、悪意あるロジックをコードレビュー時に無害に見せる。2021年のケンブリッジ大学の論文がこの手法で人間のレビュアーに見えない脆弱性を注入できることを実証。
  • フィッシングメール: 視覚的な検査をパスする混合スクリプトホモグリフを使って送信者名や URL を偽装。
  • SNS のなりすまし: キリル文字やギリシャ文字の置換を使用して正規アカウントと同一に見えるユーザー名を作成。
// これらの文字列は同一に見えるが異なる:
const latin  = "apple";      // すべてラテン文字
const mixed  = "аpple";      // キリル а + ラテン pple

latin === mixed              // false
latin.length === mixed.length // true(どちらも5)

// バイト比較で違いが判明:
latin.codePointAt(0)  // 97   (U+0061 Latin Small Letter A)
mixed.codePointAt(0)  // 1072 (U+0430 Cyrillic Small Letter A)

検出方法

ホモグリフ攻撃を検出するためのいくつかのアプローチがあります:

  • スクリプト混在検出: 複数のスクリプトの文字を含む文字列(例: ラテン文字とキリル文字の混在)をフラグ付け。Unicode TR#39 が「混合スクリプト」検出アルゴリズムを定義。
  • 紛らわしい文字の検出: Unicode TR#39 は各文字をその「スケルトン」— 比較用の正規形 — にマッピングする confusables.txt ファイルも公開。同じスケルトンを持つ2つの文字列は紛らわしい。
  • 単一スクリプト強制: 識別子(ユーザー名、ドメイン)が1つのスクリプトの文字のみを使用することを要求。
  • 視覚的検査ツール: このツールのように、疑わしいテキストの背後にある実際のコードポイントを明らかにする。
// Unicode TR#39 紛らわしい文字スケルトン(概念的):
skeleton("аpple") → "apple"  // キリル а がラテン a にマップ
skeleton("apple") → "apple"  // 元からラテン

// スケルトンが一致すれば紛らわしい:
skeleton("аpple") === skeleton("apple")  // true → 紛らわしい!

// スクリプト検出:
function getScripts(str) {
  return [...new Set(
    [...str].map(ch => {
      const cp = ch.codePointAt(0);
      if (cp >= 0x0400 && cp <= 0x04FF) return "Cyrillic";
      if (cp >= 0x0370 && cp <= 0x03FF) return "Greek";
      return "Latin";
    })
  )];
}

正規化による部分的防御

Unicode 正規化(特に NFKC)は一部のホモグリフを統合できますが、すべてではありません:

ホモグリフペアNFKC で解決?理由
A (U+FF21) vs A (U+0041)はいNFKC が全角を ASCII にマップ
ⅰ (U+2170) vs i (U+0069)はいNFKC がローマ数字を分解
а (U+0430) vs a (U+0061)いいえ異なるスクリプト、互換等価ではない
Α (U+0391) vs A (U+0041)いいえギリシャとラテンは別個
𝐀 (U+1D400) vs A (U+0041)はいNFKC が数学用英数字をマップ

NFKC 正規化は有用な第一段階です — 互換変種と全角形式を除去します。しかしクロススクリプトのホモグリフ(ラテン vs キリル vs ギリシャ)は互換等価ではないため正規化を生き残ります。これらはたまたま同じ外見を持つ、真に異なる文字です。

堅牢な防御には正規化紛らわしい文字検出の両方が必要です。このツールはテキストの背後にある正確なコードポイントを表示し、文字が見た目通りでない場合を簡単に発見できます。