安全な 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 実行環境では、 ランタイムで コードを 評価し実行できます。 実例と して、 次のような ものが あります。
これに より、 プログラマーは DOM 上の データに 動的に アクセスできるようにしています。 この 例では、 elementId 変数と 同様、 getElementform でも ユーザーが 制御できる ことが 前提と なっています。 eval を 使う ことなく この タスクを 実行できるより 良い 方法が ある ため、 このような 動的コードの 使用は 避けて ください。
Node.js サイドでは、 動的な 評価に 基づいて、 アプリケーションの 特定の データポイントに アクセスできるように したいと 思うかもしれません。 以下は その 一例です。
この 例では、 必要な ファイルが 動的で、 ユーザーが 制御できる ことを 前提と しています。 この 場合も、 コードインジェクションの セキュリティの 脆弱性が 発生するかもしれません。
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() 関数のような 安全でない コーディング規約が 使用されています。
この 脆弱性を 悪用して、 実際に 動作している アプリケーションを ハッキングした 例を ぜひ ご確認ください。
Play Video: Liran Tal - Stranger Danger: Finding Security Vulnerabilities Before They Find You!
setTimeout() および setInterval() も避ける 最後に、 eval() の 使用を 避ける ための ベストプラクティスと して、 JavaScript 開発者であれば 必ず 一度は 耳に したり、 アプリケーションで 使ったりした ことの ある 関数を ご紹介します。 setTimeout() と setInterval() です。
あまり 知られていませんが、 これらの 関数では コード文字列も 受け取ることができます。 たとえば、 以下のように 使用できます。
幸いなことに、 Node.js 環境では 文字列リテラルは 許可されていません。
2. new Function() を避ける 上記の eval()、setTimeout()、setInterval() に 似た もう 一つの 言語構造は、 文字列リテラルを 基に 関数を 動的に 定義できる Function コンストラクターです。
次の よく ある 例に ついて 考えてみましょう。
ここまでの 内容を よく 見てみると、 このような 関数に ユーザーの 入力の フローが ある 場合、 セキュリティ上の 問題が 発生する 可能性が ある ことに お気づきに なるかと 思います。
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 () コンストラクターを 以下のように 使用した 場合です。
この 脆弱性実証コードの エクスプロイトは 次のようになります。
この ため、 上記の 脆弱性実証コードで ハッカーが x 変数を 作成する データや その 一部を 入力できる 場合、 潜在的な 脆弱性が 現実の 脅威と なります。
上記の 脆弱性は 2013 年の ものですが、 2019 年の セキュリティ脆弱性レポートでは、 js-yaml での 恣意的な コードの 実行 の 事例が 見つかっています。 そこで 注意深くなることが 必要です。 または、 もっと 実用的で 実際的な アドバイスと しては、 新しい Function () の 使用を 避け、 実際に サードパーティの オープンソースパッケージを スキャン して、 脆弱性が ない こと、もし脆弱性が あったとしても 修正リクエストに より 自動的に 修正できる ことを 確認してください。
4\.Node.js のセキュリティリンターを使用する この ガイドの ツールの セクションでは、 リンターに ついて 説明したいと 思います。 JavaScript の 開発者たちは、 リンターを 好んで 使用しています。 コードスタイルの 導入に standardjs を 使う 場合や、 eslint を 使う 場合に おいて、 JavaScript や Node.js プロジェクトでも かなり 一般的な ツールに なっています。
優れた セキュリティ対策の 導入を 希望しますか? その 場合には、 eslint-plugin-security が 役立ちます。 README の 説明の とおり、 この プラグインの 使い方は 非常に 簡単です。 以下の eslint プラグイン設定を 追加するだけで、 推奨される 設定を 有効に できます。
リンターを 使う メリットは 何ですか?
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 つは 以下のような コマンドインジェクションの 脆弱性です。
この コードの 行に 見られる セキュリティ上の 問題点の 説明では、 次のような 懸念が 示されています。
ただし、 データは、 その url パラメーターから 安全でない exec() 関数に どのように 流れるのでしょうか? full details (詳細) ボタンを クリックすると、 データフローの 詳細が 表示され、 コンテキストを 追加できます。
ここでは、 Snyk Code で 分析する 方法に ついて、 全体像を 明確に 把握できます。
url パラメーターは、 item 配列から 生成され、 それ自体が ユーザー制御入力ソースと なり、 メッセージ本文入力と して 変数 req.body.content に 流れます。
コマンドインジェクションを修正する ここで、 次のような セキュリティの 問題に 対処する ために さらに 手順を 進める ことができます。
まとめ 最後まで お読みいただき ありがとう ございました。
コードインジェクションの 脆弱性が もたら す 問題に ついて、 深く 理解していただけた ことと 思います。 脆弱性は 独自の コードに 由来する ものも あれば、 アプリケーションに インポートする サードパーティの 依存関係に 由来する ものも あります。
この 記事が 役に 立ったと 思われる方は、 Snyk の 同僚の フォローアップの 記事も お読みください。
Capture the Flagを 始めよう オンデマンドの バーチャル入門ワークショップを 見て、 Capture the Flagの 課題の 解き方を 学びましょう。