JavaScript と Node.js のコードインジェクションを防止する 5 つの方法
2021年4月6日
0 分で読めます安全な JavaScript コードを記述してコードインジェクションを防ぐことは、一見普通の作業に思えますが、多くの落とし穴があります。たとえば、あなた (開発者) がセキュリティのベストプラクティスに従うからといって、他の人も同じことをするとは限りません。アプリケーションでオープンソースのパッケージをお使いになっているかもしれません。安全に開発されているかどうかは、どのように判断できますか?そこに eval()
のような安全でないコードが存在したらどうでしょうか?その点について考えましょう。
コードインジェクションとは?
コードインジェクションは、ブロードインジェクション攻撃の特殊な形態で、ハッカーはブラウザーまたは Node.js ランタイムによって解釈される JavaScript または Node.js コードを送信できます。セキュリティの脆弱性は、インタープリターが、開発者が意図した信頼できるコードと、攻撃者が入力として注入したコードを区別できない場合に顕在化します。
コードインジェクションを防ぐには
第一に、安全なコーディングのためには、アプリケーションで動的なコードの実行を許可しないでください。つまり、eval のような言語構造や、setTimeout()
または Function
コンストラクターに渡されるコード文字列の使用は避けてください。第二に、シリアライズを避けてください。シリアライズ処理でコードを実行するインジェクション攻撃を受ける可能性があるためです。最後に、依存関係をスキャンし、アプリケーションのサードパーティ製のオープンソースコンポーネントが原因でこの攻撃を受けることがないことを確認します。さらに、Snyk Code のような静的コード解析ツールを使うと、自分や同僚のコードに潜むこうしたコードインジェクションのセキュリティ脆弱性を発見することができます。
この記事ではコードインジェクションを防ぐための 5 つの方法をご紹介しています。
eval()
、setTimeout()
およびsetInterval()
を避けるnew Function()
を避けるJavaScript のコードのシリアライズを避ける
Node.js のセキュリティリンターを使用する
静的コード解析 (SCA) ツールを使用してコードインジェクションの問題を発見して修正する
1\.eval()
、setTimeout()
および setInterval()
を避ける
このことに対する反応は想像できますが、eval
の使用を避けるもう一つの理由があります。eval
(または他の形式のコード構造) を利用した他の有名なライブラリーの現実世界の事例をご紹介し、それが結果的に裏目に出て、深刻なセキュリティ脆弱性につながったことについてお知らせしたいと思います。
ただし、脆弱なサードパーティ製パッケージについて説明させていただく前に、まずは eval
と付随する関数についてご説明します。ブラウザーやサーバーサイドの Node.js プラットフォームなどの JavaScript 実行環境では、ランタイムでコードを評価し実行できます。実例として、次のようなものがあります。
const getElementForm = getElementType == “id” ? “getElementById” : “getElementByName”;
const priceTagValue = eval(“document.”+getElementForm+”(“+elementId+”).value”);
これにより、プログラマーは DOM 上のデータに動的にアクセスできるようにしています。この例では、elementId
変数と同様、getElementform
でもユーザーが制御できることが前提となっています。eval
を使うことなくこのタスクを実行できるより良い方法があるため、このような動的コードの使用は避けてください。
Node.js サイドでは、動的な評価に基づいて、アプリケーションの特定のデータポイントにアクセスできるようにしたいと思うかもしれません。以下はその一例です。
const db = "./db.json"
const dataPoints = eval("require('"+db+"')");
この例では、必要なファイルが動的で、ユーザーが制御できることを前提としています。この場合も、コードインジェクションのセキュリティの脆弱性が発生するかもしれません。
Dustjs のコードインジェクションは安全でない eval の使い方の実例
LinkedIn の npm パッケージ dustjs は、ブラウザーおよびサーバーサイド Node.js 向け非同期テンプレートプロジェクトで、コードインジェクションの脆弱性の重大度を示しています。
このパッケージはもうあまりメンテナンスされていませんが、それでも毎月約 72,000 件ダウンロードされており、コードインジェクションのセキュリティ脆弱性に対処する必要がありました。
dustjs のメンテナンス担当者は、eval()
関数のような安全でないコード構成に流れる可能性のある潜在的に危険なユーザー入力を回避するために最善を尽くしましたが、escapeHtml
関数自体にセキュリティ上の欠陥があり、配列などの他のタイプもチェックする必要があったものの、文字列タイプのみがチェックされ、入力がエスケープされていました。このプルリクエストでコードインジェクションのセキュリティ脆弱性が修正されました:
eval()
はどのように使用したらいいですか?
dustjs を使用している場合、npm パッケージ dustjs-helpers を使用して数学演算や論理演算などの追加のテンプレートヘルパーを取得できます。その追加のヘルパーの一つに if
条件があり、ご自身の dust テンプレートファイルで次のように使用することができます。
ご納得いただけましたか?
問題は、そのクエリーパラメーター device
で制御できないユーザー入力がそのまま eval を使用する if 条件ヘルパーに流れ込み、227 行目に示されているように、動的に条件が評価されてしまうことです。
これでお分かりになると思いますが、いくつかのセキュリティ問題が予期せず発生することがあります。
オープンソースのパッケージの dustjs-linkedin には、
escapeHtml
関数で入力文字列を誤ってサニタイズしてしまうセキュリティ上の欠陥があります。オープンソースのパッケージの dustjs-helpers では、ランタイムでコードを動的に評価する
eval()
関数のような安全でないコーディング規約が使用されています。
この脆弱性を悪用して、実際に動作しているアプリケーションをハッキングした例をぜひご確認ください。
setTimeout()
および setInterval()
も避ける
最後に、eval()
の使用を避けるためのベストプラクティスとして、JavaScript 開発者であれば必ず一度は耳にしたり、アプリケーションで使ったりしたことのある関数をご紹介します。setTimeout()
と setInterval()
です。
あまり知られていませんが、これらの関数ではコード文字列も受け取ることができます。たとえば、以下のように使用できます。
setTimeout(“console.log(1+1)”, 1000);
幸いなことに、Node.js 環境では文字列リテラルは許可されていません。
2\.new Function()
を避ける
上記の eval()
、setTimeout()
、setInterval()
に似たもう一つの言語構造は、文字列リテラルを基に関数を動的に定義できる Function
コンストラクターです。
次のよくある例について考えてみましょう。
const addition = new Function(‘a’, ‘b’, ‘return a+b’);
addition(1, 1)
ここまでの内容をよく見てみると、このような関数にユーザーの入力のフローがある場合、セキュリティ上の問題が発生する可能性があることにお気づきになるかと思います。
3\.JavaScript のコードのシリアライズを避ける
Java のエコシステムでは、シリアライズは重大事項です。相棒のブライアン・フェルメール (Brian Vermeer) が、安全でないシリアライズ操作が原因でセキュリティの脆弱性が Java アプリケーションに影響を与えるというブログ記事を投稿しています。ぜひお読みください。Java におけるシリアライズとデシリアライズ: Java のデシリアライズ脆弱性を説明する。
JavaScript の世界に戻っても明らかにシリアライズは重大事項です。
ただし、シリアライズやデシリアライズのロジックをご自身でコーディングしていないなら、そして npm のすばらしい世界にいて 1,500,000 以上のオープンソースパッケージを自由に使えるなら、それを使ってみるのはどうでしょうか。
js-yaml は、1 週間で 2800 万ダウンロードを達成し、Snyk Advisor によるとパッケージ全体の健全性は良好と診断されています。
とはいえ、前述の npm パッケージ js-yaml のスクリーンショットから、以前のバージョンにはセキュリティ脆弱性があることがお分かりいただけると思います。どこが問題なのでしょうか?
js-yaml のバージョンにはデシリアライズによるコード実行の脆弱性があることが判明しています。この脆弱性が顕在化するのは、新しい Function
() コンストラクターを以下のように使用した場合です。
function resolveJavascriptFunction(object /*, explicit*/) {
/*jslint evil:true*/ var func;
try {
func = new Function('return ' + object);
return func();
} catch (error) {
return NIL;
}
}
この脆弱性実証コードのエクスプロイトは次のようになります。
var yaml = require('js-yaml');
x = "test: !!js/function > \n \
function f() { \n \
console.log(1); \n \
}();"
yaml.load(x);
このため、上記の脆弱性実証コードでハッカーが x
変数を作成するデータやその一部を入力できる場合、潜在的な脆弱性が現実の脅威となります。
上記の脆弱性は 2013 年のものですが、2019 年のセキュリティ脆弱性レポートでは、js-yaml での恣意的なコードの実行の事例が見つかっています。そこで注意深くなることが必要です。または、もっと実用的で実際的なアドバイスとしては、新しい Function()
の使用を避け、実際にサードパーティのオープンソースパッケージをスキャンして、脆弱性がないこと、もし脆弱性があったとしても修正リクエストにより自動的に修正できることを確認してください。
4\.Node.js のセキュリティリンターを使用する
このガイドのツールのセクションでは、リンターについて説明したいと思います。JavaScript の開発者たちは、リンターを好んで使用しています。コードスタイルの導入に standardjs を使う場合や、eslint を使う場合において、JavaScript や Node.js プロジェクトでもかなり一般的なツールになっています。
優れたセキュリティ対策の導入を希望しますか?その場合には、eslint-plugin-security が役立ちます。README の説明のとおり、このプラグインの使い方は非常に簡単です。以下の eslint プラグイン設定を追加するだけで、推奨される設定を有効にできます。
"plugins": [
"security"
],
"extends": [
"plugin:security/recommended"
リンターを使うメリットは何ですか?
detect-eval-with-expression のような安全でないコーディング規則を検出するルールにより、eval()
で式や文字列リテラルが使用されていることを検出できます。リンターには child_process Node.js API の使用など、他のいくつかのルールもあります。
eslint-plugin-security の最終公開日は 4 年以上前であることに注意してください。機能的にはまだうまく動作するかもしれませんが、 eslint-plugin-security-node のような他の後継パッケージの検討をお勧めします。
5\.静的コード解析ツールを使用してコードインジェクションの問題を発見して修正する
ESLint で使用されている基本的な形式の静的コード解析 (SCA) リンターは、良い出発点となります。ただし、Node.js のセキュリティリンターで検討したように、実際のセキュリティ問題の対応に必要とされる柔軟性は持ち合わせていません。
eslint-plugin-security のような Node.js のセキュリティリンターについて開発者たちは次のような点を懸念しています。
誤検出: リンターのルールは非常に基本的なものなので、多くの誤検出があり、開発者の不満と混乱を招くことになります。たとえば、以下の
RegExp(matchEmailRegEx)
では、RegExp 関数に非リテラルを使用しているため、Node.js セキュリティリンターでエラーが発生します。matchEmailRegEx
は shared/variables.js ファイルの定数でしょうか?リンターの機能は、それを判別できるほど高度ではありません。厳格すぎるルール: 前述のポイントの続きで、ルールが厳しすぎるという問題点があります。
child_process.exec(someCommand, \[])
は使うか、使わないかにしてください。リンターで使用される、このような静的コード解析処理は、someCommand
がハードコードされた定数かどうか見分けられるほど賢くはありません。child_process.exec() を非リテラルで使っただけでリンターのエラーが発生し、結局開発者たちの不満がたまり、ルールはオフになってしまいます。基本的すぎるルール: ルールの集合が小さすぎて、結果も基本的なものばかりという問題点があります。これらは基本的に、特定のユーザー入力から、コマンド実行、SQL クエリーなどの潜在的な機密コードに実際にどのようにデータが流れるかについて、多くのコンテキストがない場合はオールオアナッシングになってしまいます。
繰り返しになりますが、eslint-plugin-security-node などのセキュリティリンターは良いスタート地点になります。何もないよりは良いといえます。
ただし、コードを書きながら、自分のコードのセキュリティ問題を発見するためのもっと良い方法があります。
開発者のために作られた静的アプリケーションセキュリティテストツール (SAST)、Snyk Code をご紹介します。
NODE.JS アプリケーションでコマンドインジェクションを見つける
Snyk Code はもうすぐ発売されますが、機能について少しご説明します。
まず、GitHub アカウントでSnyk に接続し、GitHub のリポジトリをインポートします。ここで、Add project (プロジェクトの追加) をクリックして、GitHub のアイコンをクリックします。
次に、リポジトリのリストから探すか、検索ボックスを使って入力し、リポジトリをトグルしてスキャンを開始します。
次に、Snyk により GitHub のリポジトリがインポートされ、すばやくスキャンされます。
既知の脆弱性を持つオープンソースの依存関係を使用している場合や、Docker イメージに多数のセキュリティの脆弱性が含まれている場合など、潜在的なセキュリティ問題に関連する他のマニフェストファイルが自動的に検出されます。
この Node.js アプリケーションのコードについて、Code analysis (コード解析) をクリックして分かったことを見てみましょう。
Snyk Code で、複数の脆弱性が発見されました。そのうちの 1 つは以下のようなコマンドインジェクションの脆弱性です。
このコードの行に見られるセキュリティ上の問題点の説明では、次のような懸念が示されています。
“Unsanitized input from the HTTP request body flows into child_process.exec, where it is used to build a shell command. This may result in a Command Injection vulnerability.”
ただし、データは、その url
パラメーターから安全でない exec()
関数にどのように流れるのでしょうか?full details (詳細) ボタンをクリックすると、データフローの詳細が表示され、コンテキストを追加できます。
ここでは、Snyk Code で分析する方法について、全体像を明確に把握できます。
url
パラメーターは、item
配列から生成され、それ自体がユーザー制御入力ソースとなり、メッセージ本文入力として変数 req.body.content
に流れます。
コマンドインジェクションを修正する
ここで、次のようなセキュリティの問題に対処するためにさらに手順を進めることができます。
安全でない
exec()
を使う代わりに、API の安全なバージョンexecFile()
を使用できます。これにより、配列関数の引数の形で提供された引数のエスケープを処理できます。システムプロセスの実行などの機密性の高いコードに流れる前に、ユーザー入力からアイテム変数を検証、エスケープ、またはサニタイズすることができ、これが推奨されます。
まとめ
最後までお読みいただきありがとうございました。
コードインジェクションの脆弱性がもたらす問題について、深く理解していただけたことと思います。脆弱性は独自のコードに由来するものもあれば、アプリケーションにインポートするサードパーティの依存関係に由来するものもあります。
この記事が役に立ったと思われる方は、Snyk の同僚のフォローアップの記事もお読みください。
Go の開発にご関心をお持ちの方は、Go セキュリティチートシートをご確認ください。Go 開発者向けセキュリティのベストプラクティス 8 選
特に Java や Spring MVC で開発を行っている方は興味深くお読みいただけることと思います。Spring MVC アプリケーションで Java セキュリティ問題を解決し、Snyk Code のセキュリティ問題も検出