StackOverflowからのコピペをやめろ。今すぐにだ。

**Original article:https://dev.to/dotnetsafer/rip-copy-and-paste-from-stackoverflow-trojan-source-solution-4p8f**

その昔[コピペできない文章](https://nlab.itmedia.co.jp/nl/articles/1204/24/news103.html)というものがありました。
実際は単にフォントを変えているだけというものですが、人間の目に見える文字と実際の文字が異なることを利用した攻撃の一種と見ることもできます。

さて、最近になって[似たような攻撃に関する論文が公開されました](https://arxiv.org/abs/2111.00169)。
人間には見えない文字を織り交ぜることによって、一見問題ないコードが実は脆弱になってしまうというものです。

ただ[論文](https://trojansource.codes/trojan-source.pdf)は堅苦しいうえに長くて読むのがつらいので、具体的に何がどうなのかよくわかりません。
平易に解説している記事があったので紹介してみます。

以下はDotnetsafer( [Twitter](https://twitter.com/dotnetsafer) / [GitHub](https://github.com/dotnetsafer) / [Webサイト](https://dotnetsafer.com/) )による記事、[RIP Copy and Paste from Stackoverflow! 🚨 (+Trojan Source Solution)](https://dev.to/dotnetsafer/rip-copy-and-paste-from-stackoverflow-trojan-source-solution-4p8f)の日本語訳です。

# RIP Copy and Paste from Stackoverflow! 🚨 (+Trojan Source Solution)

ケンブリッジ大学のNicholas BoucherとRoss Andersonにより、ほとんどのコンパイラに影響を与える[2つの脆弱性が存在する](https://arxiv.org/abs/2111.00169)ことが判明しました。

この脆弱性は、ソフトウェアのサプライチェーンに悪影響を与えます。
たとえば攻撃者が人間のレビュアーを欺いて一度でもコードインジェクションに成功した場合、そのコードはその後も継承され続ける可能性が高くなります。

どのようなテクニックが使われているかを見てみましょう。

・ **拡張文字列** 文字列リテラルをコードのように見せかける。
・ **コメントアウト** コメントをコードとして実行させる。
・ **早期return** コメント内にあるreturnを実行させ、関数をパスする。

## What happens next?

それでなにが起こる？

コンパイラは、一部の目に見えない文字をサポートしているため、アプリケーションをコンパイルしたときに、IDEで人間が見るコードとは異なる内容に解釈してしまうことがある。


## How Trojan Source works?

私(Juan)は攻撃者であり、[dev.to](https://dev.to/dotnetsafer)や[ブログ](https://dotnetsafer.com/blog/)などで何らかの記事を公開することにします。
ここでは、あるユーザが管理者であるか否かをチェックする方法を教えることにしましょう。
もしくは、あなたやあなたの会社が何らかのオープンソースプロジェクトを持っているとします。

私は、ある特定のアクションは管理者のみが行えるようにするようなチェックを追加し、それを公開するかプルリクエストを送信しました。
あなたはそれをマージしました。

```dotnet
string access_level = "user";
if (access_level != "user"⁦) //Check if admin 
{    
  Console.WriteLine("You are an admin."); 
}
```

ところがこのコードには、IDEには表示されないUnicode文字が含まれています。
テキストエディタで表示してみましょう。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/3c1c06b0-785f-194f-c53a-4659e904988b.png)

この文字は、文字の位置や順序、方向を変更する制御文字です。
すなわち、IDEで見えている文字は決して正しいものではありません。

※訳注：実際はコピペしてもLRIがありません。元記事に貼られた参考コードの時点で既にLRIは消えているようです。

この制御文字の詳細は[こちら](https://www.compart.com/en/unicode/U+2066)で見ることができます。

さて、私のプルリクを受け入れたあなたのプロジェクトはこうなりました。

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/72ae5a49-c3af-22a6-a0ac-f1e7386067a8.png)

うまくマージされたようですね。

それではアプリを実行してみましょう。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/7159b2ef-52b7-8c53-a246-a757e2d8d908.png)

おや？

`access_level`を`"user"`と定義した直後に`"user"`と等しいかを調べているのに、異なると言われてしまいました。

どうして…

いったい"user"と"user"は何がちがうんだっていうんだ？

理由はもちろん、制御文字を解釈したうえでコンパイルされたからです。

アプリをデコンパイルしてILコードを見てみましょう。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/9b2690ed-fb30-4a54-301e-889c7e9f2662.png)


コンパイル後のコードでは、比較部分がこんなことになっていました。

`"user" != "user // check if admin"`

当然これは等しくないので、以下のコードが実行されます。

```dotnet
Console.WriteLine("You are an admin.");
```

今度はC#でコンパイルした結果を見てみましょう。

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/6f78d7a4-7fb8-70d4-c817-ff490ab67b4c.png)

元のソースコードとは全く異なる実行コードになりました。
私は管理者権限無しに管理者用コードを利用することが可能になり、そしてあなたがこれに気付くことはありません。

これは極めて単純な例ですが、同様に私は、目に見えない命令を大量にあなたのコードに埋め込むことが可能であり、これは**非常に危険な事態**といえます。

## How to solve it?

幸いなことに、GitHubでは制御文字が含まれている場合に警告が表示されます。

![06.jfif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/c01c23de-1c66-c23a-48d6-c548c52a4381.jpeg)


## What should I do?

どうすればよい？

インタプリタ、Unicode対応コンパイラ等は、文字列リテラルやコメントに、終端の無い文字順序制御文字が含まれていた場合に**警告やエラーを出さなければなりません(MUST)**。

終端の無い文字順序制御文字は、**言語要件で明示的に禁止されるべき(SHOULD)**です。

コードエディタ等では、制御文字を**視覚的に強調表示すべき(SHOULD)**です。


## How to check if I have been affected by this vulnerability?

確認する方法は？

GitHubが通知してくれるのはありがたいことです。
しかし、既に脆弱性が入り込んでいた場合はどうすれば？

我々のチームで検討した結果、ベストではないかもしれないけど最もシンプルな方法に辿り着きました。

要するに、この脆弱性はコードに隠し文字を入れるというだけのことなので、自分のコードに隠し文字があるかどうかをチェックすればいいわけです。

あなたは.NET開発者なので、.NETで小さなツールを作ることにしました。

### ✅ Solution

隠し文字があるかどうかを調べましょう。

```dotnet
var nonRenderingCategories = new UnicodeCategory[] {
  UnicodeCategory.Control, 
  UnicodeCategory.OtherNotAssigned, 
  UnicodeCategory.Format, 
  UnicodeCategory.Surrogate
};
```

まずは、検出したい文字を`UnicodeCategory`列挙型から取り出します。
`UnicodeCategory`の詳細については[MSDN](https://docs.microsoft.com/dotnet/api/system.globalization.unicodecategory?view=net-5.0)を参照してください。

我々の調べたところ、この脆弱性に関連しそうなUnicode文字は以下となりました。

・ **Control** U+007F、U+0000からU+001F、U+0080からU+009F
・ **OtherNotAssigned** どのカテゴリにも割り当てられていない文字
・ **Format** テキストのレイアウトや処理動作に影響を与える文字
・ **Surrogate** 上位サロゲート・下位サロゲート

以下は、これらの文字が存在する`.cs`ファイルを探し出すコードです。

```cs
var nonRenderingCategories = new UnicodeCategory[] {
UnicodeCategory.Control,
UnicodeCategory.OtherNotAssigned,
UnicodeCategory.Format,
UnicodeCategory.Surrogate };

using StreamReader sr = new StreamReader(dotnetFile);

while (sr.Peek() >= 0)
{
  var c = (char)sr.Read();
  var category = Char.GetUnicodeCategory(c);

  var isPrintable = Char.IsWhiteSpace(c) ||
    !nonRenderingCategories.Contains(category);

  if (!isPrintable
  {
    alert(dotnetFile);
    issuesCount++;
    break;     
    }
  } 

sr.Close();
sr.Dispose();
```

制御文字が含まれている場合に、そのファイルを書き出します。
なお、範囲が厳密ではないため、以下のような文字を誤検出する可能性があります。

・アラビア文字など特殊なアルファベット： أعطني 3 تصفيق
・絵文字など： 🦄

従って、これは完璧な対策ではありませんが、脆弱性を見つけるための第一段階として使用することは十分に可能でしょう。

これらの診断をまとめたツールを[TrojanSourceDetector4Dotnet](https://github.com/dotnetsafer/TrojanSourceDetector4Dotnet)において公開しています。
このツールでは、ひとつ以上の.NETプロジェクトにおいて、この脆弱性の危険がないかチェックすることができます。

我々Dotnetsaferは、あなたが如何に忙しいかを知っているので、あなたが何もしなくても全てのプロジェクトを数秒でチェックしてくれるように、このツールをインストールしておくことをお勧めします。
インストールは、`cmd`もしくは`PowerShell`上で以下のコマンドを打つだけです。

```bash
dotnet tool install --global TrojanSourceDetector --version 1.0.1
```

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/f78391dc-e0b2-b05b-feea-d77a3ab59e0a.png)


これで、あなたの.NETプロジェクトをいつでもスキャンできるようになりました。
TrojanSourceDetectorを実行し、パスを尋ねられたらフルパスを入力してください。

以下は、プロジェクトのディレクトリで`cmd`を実行した例です。

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/c06e2764-7d69-2d13-1f59-f2487ddeb9c4.png)

スキャンが完了すると、疑わしい制御文字が含まれているファイルを教えてくれます。

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/5ef4054e-b1c3-aced-5ae3-93e096a04626.png)

以上！おわり！



```bib
@article{boucher_trojansource_2021,
    title = {Trojan {Source}: {Invisible} {Vulnerabilities}},
    author = {Nicholas Boucher and Ross Anderson},
    year = {2021},
    journal = {Preprint},
    eprint = {2111.00169},
    archivePrefix = {arXiv},
    primaryClass = {cs.CR},
    url = {https://arxiv.org/abs/2111.00169}
}
```

# コメント欄

「素晴らしい、助かった！」
「コードサンプルが動かなかった。`error CS1026: ) expected`って言われる。」
「VSCodeで[Gremlins](https://marketplace.visualstudio.com/items?itemName=nhoizey.gremlins)を使っている理由のひとつ。[不可視文字を検出してくれる](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lyqrldvcxs1igzx2zejx.png)。」
「[このポスト](https://research.swtch.com/trojan)を読もう。コンパイラや言語仕様で修正するべきではなく、開発者とツールで修正されるべきもの。[Goの開発者もそう言ってる](https://github.com/golang/go/issues/20209#issuecomment-301681364)。StackOverflowは制御文字をはじくようにするべき。」
「Slackからコピペを辞めて、Discordの信頼できる人のコードをコピペするのがよいね。」「信頼できる人であれば問題ないですね。」


# 感想

目に見えない制御文字を使うことで、見た目のソースコードと実行されるソースコードを違うものにしてしまうということです。
普通のプログラマであれば、コピペ時にさっと目視チェックくらいは行うと思いますが、この制御文字は目に見えないので目視チェックをすり抜けてしまうわけですね。

本文にはStackOverflowが全く出てこないのですが、StackOverflowは広く使われているにもかかわらず制御文字を張り付けることができてしまうため、この問題の代表例として出したといったところでしょうか。
実際、StackOverflowだけではなく、あらゆる個人サイトや多くのSNS等でも可能な攻撃方法です。

なお、手元のVSCodeでは制御文字が強調表示され、制御文字が入っていることが一目瞭然でわかるようになっていました。

![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/27d948aa-ba5f-e2c6-bf57-fa50e01019db.png)

私はGremlinsを入れていないので、 ~~これが何によるものなのかはよくわかりません~~ → [2021/11にVSCode本体に入った機能](https://qiita.com/rana_kualu/items/3b03961deb003a8a2f1d#comment-64ce6828d85fe4c0fba0)だそうです。

このように制御文字への対策がなされていれば、コピペをしても安全性にはさほど問題ないでしょう。
もちろん何にせよ目視チェックは必要ですが。


# その他

・[Stack Overflowからのコピペで脆弱なプロジェクトがGitHubに多数あり](https://news.mynavi.jp/techplus/article/20191011-907711/)
・[https://arxiv.org/abs/1910.01321](https://arxiv.org/abs/1910.01321)

一見似たような、このような論文もあります。
しかし、こちらは単に目視でもわかる脆弱なコードをコピペしたというだけの話であり、今回のように悪意を持って脆弱性を隠したコードとは意図が異なります。
こちらの脆弱性についてはQiitaだろうがどこであろうが発生する可能性はあるし、前述のツールでも見つけることはできません。
単に脆弱性のある普通のコードですからね。