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で表示したものです。
では、このスクリーンショットではどうでしょうか。

上記のソースコードの問題に気づきましたか?気づかなかった場合は、コードのスニペットをもう少し詳しく見てみてください。
ここでは何が起きているのでしょうか。これはStretched String型の攻撃です。3行目のコードは、条件式がaccessLevel変数とuserの値が等しいかどうかをチェックしているように見えます。行末にはロジックチェックについてのコメントがあり、無害に思えるかもしれませんが、実際はまったく異なります。
実際には、3行目でUnicodeの双方向制御文字が使われており、accessLevel変数をチェックする文字列の実際の値が隠されています。コンパイラが実行する3行目の実際の内容は次のとおりです。
この論文では、双方向制御文字を悪用してソースコードに悪意あるコードを挿入する手法として、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を使ってファイルをスキャンできます。
JavaScriptプロジェクトでライブラリとして使う場合は、次のようにします。
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設定例を紹介します。
脆弱なコードスニペットがコードベースに入り込んだ場合の出力例です。
エコシステムでは、Trojan Source攻撃にどのように対処しているのでしょうか。
VS CodeなどのIDEは、これらのUnicode文字を強調表示するバージョンをリリースしました。これにより、プログラマーはコードのレビューや編集時に文字に気づき、適切な文脈で対応できます。同様に、GitHubも警告を公開し、双方向文字が使われている場合、GitHub上で表示されるコードベース内の危険なTrojan Sourceの使用箇所が強調表示されるようになりました。

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

上のJavaScriptコードスニペットからわかるように、このコードをレビューしてもGitHubから警告は表示されません。実際には何が起きているのでしょうか。
7行目の関数宣言には、U200Bとして識別されるゼロ幅スペースのUnicode制御文字が使われています。そのため、正当なfunction isAdmin()関数のように見えます。
UNIXのcatツールをクローンし、より優れた構文ハイライトとGit連携を備えたbatなどのツールを使ってコードを出力すれば、これを確認できます。

コンパイラやランタイムはTrojan Source攻撃を軽減すべきですか?
コンパイラや言語ランタイムについてはどうでしょうか。ほとんどの言語(Node.jsを含む)では、Unicode文字を拒否するようコンパイラを更新しない方針が採られています。その結果、リスクは実質的にコードエディターや、コードの読み取りやコードレビューを行う際により注意を払う必要がある人々に移っています。
一方、Zigなどの一部の言語ランタイムでは、ソースコード内でUnicodeの双方向文字を検出した場合にコンパイラエラーを出すことが前向きに検討されています。また、明示的なコメントを記述すれば、エラーを回避できます。
Trojan Source攻撃に関する参考資料
この記事が、Trojan Source攻撃とJavaScriptエコシステムでの発生例について理解する一助となれば幸いです。攻撃についてさらに詳しく知りたい方は、次の資料をご覧ください。
Snyk CodeでTrojan Source攻撃を防ぐ方法に関するブログ記事
Trojan Source公式サイト:https://www.trojansource.codes
コード例と概念実証を含むTrojan Source公式リポジトリ:https://github.com/nickboucher/trojan-source
Trojan Sourceの公式発表ブログ記事: https://www.lightbluetouchpaper.org/2021/11/01/trojan-source-invisible-vulnerabilities
最先端のインテリジェンスでコードを保護
わずか30分で、Snyk CodeのSAST機能を幅広くご紹介します。


