Skip to main content

ESLintでJavaScriptコードベースのTrojan Source攻撃を効果的に検出・緩和する方法

著者

2021年11月10日

0 分で読めます

2021年11月1日、「Trojan Source: Invisible Vulnerabilities」と題した論文が公開されました。この論文では、攻撃者がUnicodeの双方向制御文字を利用して、一見無害なコードベースに悪意あるソースコードを紛れ込ませる手口が説明されています。この攻撃では、難読化された悪意あるソースコードをレビュー担当者にコメントと誤認させます。

トロイの木馬ソース攻撃とは何ですか?

従来のコードエディターやコードレビューの手法では、ソースコードに含まれる双方向制御文字を検出できません。そのため、攻撃者は無害に見える悪意あるコードを埋め込むことができます。この脆弱性は2021年11月1日に公表され、CVE-2021-42574が割り当てられました。

以下は、JavaScriptのソースコードでTrojan Source攻撃が使われた例をVS Codeで表示したものです。

// running internal logic for privileged users:
var accessLevel = "user";
if (accessLevel != "user‮ ⁦// Check if admin⁩ ⁦") {
    console.log("You are an admin.");
}

では、このスクリーンショットではどうでしょうか。

accessLevelが「user」と等しくないかを確認し、管理者向けメッセージをログに出力するJavaScriptコードスニペット

上記のソースコードの問題に気づきましたか?気づかなかった場合は、コードのスニペットをもう少し詳しく見てみてください。

ここでは何が起きているのでしょうか。これはStretched String型の攻撃です。3行目のコードは、条件式がaccessLevel変数とuserの値が等しいかどうかをチェックしているように見えます。行末にはロジックチェックについてのコメントがあり、無害に思えるかもしれませんが、実際はまったく異なります。

実際には、3行目でUnicodeの双方向制御文字が使われており、accessLevel変数をチェックする文字列の実際の値が隠されています。コンパイラが実行する3行目の実際の内容は次のとおりです。

If (accessLevel != "user // Check if admin") {

この論文では、双方向制御文字を悪用してソースコードに悪意あるコードを挿入する手法として、Commenting-Out、Stretched String、Invisible Functions、Homoglyph Functionの各タイプを紹介しています。研究者たちは、これらすべての攻撃をJavaScriptで実行する例を、GitHubのtrojan-sourceリポジトリで公開しています。

双方向制御文字の利用は新しい手法ですが、この種の攻撃自体は新しいものではなく、過去のメーリングリストや掲示板でも取り上げられています。たとえば、RTL/LTR文字の使用禁止に関する2017年のGolangのIssueや、2011年のBugzillaの投稿「[BiDi] Misleading display of bidirectional strings when RLO, LRO or PDF is used」があります(閲覧にはGoogle Cacheを利用してください)。

Trojan Source攻撃を修正するにはどうすればよいですか?

学術論文の著者は、この問題はコードエディターやIDEソフトウェア側にあり、こうしたUnicode文字を視覚的に識別できるよう修正する必要があると述べています。また、コンパイラーはこうした文字についてユーザーに警告するべきだとしています。

ソースコード内のTrojan Source攻撃を検出する

コード編集やコードレビューに使っているプラットフォームやツールでは、危険な双方向Unicode文字を強調表示できない場合があります。つまり、すでにコードベースに双方向制御文字が含まれている可能性があります。

では、ソースコードに双方向Unicode文字が含まれているかどうか、どうすれば確認できるでしょうか。そのために、ディレクトリをスキャンするか、標準入力(STDIN)から読み込んでテキスト内に含まれるUnicode文字を検出するanti-trojan-sourceというnpmパッケージを作成しました。

次のように、npxを使ってファイルをスキャンできます。

npx anti-trojan-source --files='src/**/*.js'

JavaScriptプロジェクトでライブラリとして使う場合は、次のようにします。

import { hasTrojanSource } from 'anti-trojan-source'
const isDangerous = hasTrojanSource({
  sourceText: 'if (accessLevel != "user) {' // Check if admin
})

ESLintでJavaScriptのTrojan Source攻撃を防ぐ

編集者注: この記事の初公開後、Trojan Sourceに関するルールがSnyk Codeに追加されました。詳しくは、Snyk CodeでTrojan Source攻撃を防ぐ方法に関するブログ記事をご覧ください。

すでに存在する問題を見つけるだけでなく、Trojan Source攻撃がソースコードに入り込まないよう、事前にコードベースを保護することが重要です。JavaScriptコミュニティでは、コード品質やスタイルの基準を適用するために、ESLintやさまざまなプラグインをよく利用しています。

そこで、eslint-plugin-anti-trojan-sourceを使えば、ESLintプラグインを導入して、双方向Unicode文字が原因で悪意ある可能性のあるコードを開発者や継続的インテグレーション/ビルドシステムが誤ってマージしないようにできます。

JavaScriptプロジェクトでのESLint設定例を紹介します。

"eslintConfig": {
    "plugins": [
        "anti-trojan-source"
    ],
    "rules": {
        "anti-trojan-source/no-bidi": "error"
    }
}

脆弱なコードスニペットがコードベースに入り込んだ場合の出力例です。

$ npm run lint

/Users/lirantal/projects/repos/@gigsboat/cli/index.js
  1:1  error  Detected potential trojan source attack with unicode bidi introduced in this comment: ' begin admins only '  anti-trojan-source/no-bidi if (isAdmin) {
  1:1  error  Detected potential trojan source attack with unicode bidi introduced in this comment: ' end admin only    anti-trojan-source/no-bidi }

/Users/lirantal/projects/repos/@gigsboat/cli/lib/helper.js
  2:1  error  Detected potential trojan source attack with unicode bidi introduced in this code: '"user" // Check if admin

エコシステムでは、Trojan Source攻撃にどのように対処しているのでしょうか。

VS CodeなどのIDEは、これらのUnicode文字を強調表示するバージョンをリリースしました。これにより、プログラマーはコードのレビューや編集時に文字に気づき、適切な文脈で対応できます。同様に、GitHubも警告を公開し、双方向文字が使われている場合、GitHub上で表示されるコードベース内の危険なTrojan Sourceの使用箇所が強調表示されるようになりました。

JavaScriptファイルに双方向Unicodeテキストが含まれているという警告が表示されたGitHubのコードビュー。管理者ユーザーかどうかを判定する条件式が表示されています。
出典: https://github.com/nickboucher/trojan-source/blob/main/JavaScript/stretched-string.js

ただし、GitHubが強調表示するのは、あらゆる種類のトロイの木馬型攻撃ではありません。たとえば、論文で取り上げられ、Invisible Functionsと名付けられた次のケースを見てみましょう。

名前がよく似た2つのisAdmin関数がそれぞれfalseとtrueを返し、その後に管理者アクセスを判定する条件文が表示されたコードエディター。

上のJavaScriptコードスニペットからわかるように、このコードをレビューしてもGitHubから警告は表示されません。実際には何が起きているのでしょうか。

7行目の関数宣言には、U200Bとして識別されるゼロ幅スペースのUnicode制御文字が使われています。そのため、正当なfunction isAdmin()関数のように見えます。

UNIXのcatツールをクローンし、より優れた構文ハイライトとGit連携を備えたbatなどのツールを使ってコードを出力すれば、これを確認できます。

不可視のUnicode文字を使った、見た目がよく似た関数名を示すJavaScriptコード。一方はfalseを返し、もう一方はtrueを返します。

コンパイラやランタイムはTrojan Source攻撃を軽減すべきですか?

コンパイラや言語ランタイムについてはどうでしょうか。ほとんどの言語(Node.jsを含む)では、Unicode文字を拒否するようコンパイラを更新しない方針が採られています。その結果、リスクは実質的にコードエディターや、コードの読み取りやコードレビューを行う際により注意を払う必要がある人々に移っています。

一方、Zigなどの一部の言語ランタイムでは、ソースコード内でUnicodeの双方向文字を検出した場合にコンパイラエラーを出すことが前向きに検討されています。また、明示的なコメントを記述すれば、エラーを回避できます。

Trojan Source攻撃に関する参考資料

この記事が、Trojan Source攻撃とJavaScriptエコシステムでの発生例について理解する一助となれば幸いです。攻撃についてさらに詳しく知りたい方は、次の資料をご覧ください。

  1. Snyk CodeでTrojan Source攻撃を防ぐ方法に関するブログ記事

  2. Trojan Source公式サイト:https://www.trojansource.codes

  3. コード例と概念実証を含むTrojan Source公式リポジトリ:https://github.com/nickboucher/trojan-source

  4. Trojan Sourceの公式発表ブログ記事: https://www.lightbluetouchpaper.org/2021/11/01/trojan-source-invisible-vulnerabilities

最先端のインテリジェンスでコードを保護

わずか30分で、Snyk CodeのSAST機能を幅広くご紹介します。

続きを読む

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

Blog

AIコーディングエージェントが不適切なアクセス制御を繰り返し実装する理由

AIコーディングエージェントは、コンパイルが通りレビューも通過する一方で、あるテナントのデータを別のテナントに公開してしまう認可ロジックを生成することがあります。不適切なアクセス制御が検出しにくい理由と、その防止策をご紹介します。