こんにちは。
今日のお題はコードインジェクションについてです。
JapaScriptのコーディングには自信がある方にも、お役にたちそうな記事内容を見つけました。
コードインジェクション攻撃で、注射針を思い出すのは私だけかと思っていたところ、こちらのブログ記事にも注射アイコンを発見。万国共通のイメージなんですね。
コーディングの段階からセキュリティを取り入れるという考え方が理にかなっているとはわかるけど、やっぱりちょっと面倒、、、という方も、是非、最後まで読んでみてください。
##JavaScriptとNode.jsでコードインジェクションを防ぐ5つの方法
https://snyk.io/blog/5-ways-to-prevent-code-injection-in-javascript-and-node-js/
JavaScriptとNode.jsでコードインジェクションを防ぐ5つの方法
Liran Tal (リラン・タル)
2021年4月5日
コードインジェクションを防ぐ安全なコードを書くのは簡単なようで実は、多くの落とし穴があります。たとえば、あなた自身がデベロッパとしてセキュリティのベストプラクティスに従っているからといって、他のデベロッパも同じことをしているとは限りません。普段あなたもオープンソースパッケージをアプリケーションで使用していると思いますが、それらが安全に開発されたかどうかまでどのように知ることができるでしょうか。そこにeval()
のような安全でないコードが存在するとしたらどうしますか?では、実際に考えていきましょう。
###コードインジェクションとは?
コードインジェクションは、ブラウザまたはNode.jsのランタイム時に、JavaScriptまたはNode.jsコードを送信する広範なインジェクション攻撃の手法です。デベロッパが意図する信頼できるコードと攻撃者により挿入されたコードが区別なく読み込まれ、セキュリティの脆弱性となります。
###コードインジェクションを防ぐ方法
安全なコーディングの基本として重要なのは、動的なコードの実行をアプリケーションで許可をしないことです。例えば、eval
、setTimeout()
やFunction
に渡されるコード文字列などの言語コンストラクトを避ける必要があり、シリアライズプロセスでコードを実行するインジェクション攻撃に対して脆弱な可能性のあるシリアライズも回避する必要があります。また、サードパーティのオープンソースコンポーネントが原因でアプリケーションがコードインジェクションの攻撃を受けないように、依存関係のスキャンを実行します。Snyk Codeなどの静的コード分析ツールを使用して、自分やチーム内のコードインジェクションに関するセキュリティの脆弱性を確認することが重要です。
ここでは、コードインジェクションを防ぐための5つの方法を検討します。
-
eval()
、setTimeout()
、setInterval()
を回避
-
new Function()
を回避 - JavaScriptでコードのシリアライズを回避
- Node.jsセキュリティlinterの使用
- 静的コード分析(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上のデータにアクセスするための動的な方法を作成しようとしています。この例ではgetElementform
、elementId
変数だけでなく、ユーザーによる制御があると想定しています。しかし、evalを使わずにこのタスクを実行するためのより良い方法があるので、この例のような動的コードは絶対に避けてください。
Node.jsでは、動的評価に基づいて、アプリケーション内の特定のデータポイントへのアクセスを許可させたい場合を考えましょう。
例:
const db = "./db.json"
const dataPoints = eval("require('"+db+"')");
こちらは、必要なファイルが動的で、ユーザーの制御も可能にしたいと一般的に考えられる例です。この場合にも、コードインジェクションによるセキュリティの脆弱性が発生する可能性があります。
Dustjsコードインジェクションの、安全でないevalの使用法の実例
LinkedInのnpmパッケージdustjs(ブラウザーとサーバー側のNode.js用の非同期テンプレートプロジェクト)の例は、コードインジェクションの脆弱性がどれほど深刻になりえるということを教えてくれます。
このパッケージは、長らく十分なメンテナンスがされていないにもかかわらず、月間約72,000のダウンロードをキャプチャしており、コードインジェクションによるセキュリティの脆弱性に対処する必要がありました。
dustjsのメンテナは、eval()
関数のように、安全が確保されないコード構造に対する潜在的な危険があるユーザー入力を回避するための対策を行うことにしました。しかし、escapeHtml
関数にも文字列タイプのみをチェックしてから入力をエスケープするというセキュリティ上の欠陥があり、配列など他の要素についてもチェックする必要がありました。
このようなpullリクエストにより、コードインジェクションのセキュリティの脆弱性を修正しました。
eval()
の扱いについて気になりますよね?
dustjsを使用する場合は、数学演算や論理演算などの追加のテンプレートヘルパーを取得するnpmパッケージのdustjs-helpersを導入しました。これらの追加のヘルパーの1つはif条件であり、これを独自のdustテンプレートファイルで次のように使用しました。
ここまでは、理にかなっていますね。
問題は、そのクエリパラメータでは、制御されていないユーザー入力がdeviceif
条件ヘルパーに直接流れ込むことです。227行目にあるように、if条件ヘルパーがevalを使用して条件を動的に評価しています:
この例で、いくつかのセキュリティ問題が予期しない方法でどのように組み合わされるのかをわかっていただけると思います。
オープンソースパッケージであるdustjs-linkedinには、escapeHtml
関数内の入力文字列が誤ってサニタイズされるというセキュリティ上の欠陥がある。
オープンソースパッケージであるdustjs-helpersは、eval()
関数などの安全でないコーディング規則を使用して、実行時にコードを動的に評価する。
では実際に、私がこの脆弱性をどのように悪用し、この正確な脆弱性に基づいて実際に動作するアプリケーションをハッキングしたかをご紹介します。こちらをご覧ください:
setTimeout()
とsetInterval()
も回避する。
eval()回避のベストプラクティスについて締めくくる前に、JavaScriptのデベロッパとしてよく知られており、アプリケーションで少なくとも一度はあなたが使用したであろうsetTimeout()
とsetInterval()
関数についても触れさせてください。
これらの関数についてあまり知られていない事実は、それらがコード文字列も受け取るということです。たとえば、次のように使えてしまうということです:
setTimeout(“console.log(1+1)”, 1000);
幸いなことに、Node.js環境では文字列リテラルが使用できません。
#####EEE2. new Function()
を回避
既に述べたeval()
、setTimeout()
、setInterval()
と同様の言語コンストラクトとしては、文字列リテラルに基づいて定義できる動的な関数コンストラクタがあげられます。
よくある例を考えてみましょう:
const addition = new Function(‘a’, ‘b’, ‘return a+b’);addition(1, 1)
気をつけて読み進めていただいたデベロッパの皆さんであれば、そのような関数にユーザー入力で流れ込みで発生する可能性がある潜在的なセキュリティの問題についてお気づきでしょう。
3.JavaScriptでのコードのシリアライズを回避
シリアライズは、Javaエコシステムにおいて重要です。同僚であるBrianVermeerが、安全でないシリアライズ操作によるJavaアプリケーションへのセキュリティの脆弱性についてブログに投稿しています。是非、読んでみてください:Javaでのシリアライズとデシリアライズ:Javaのデシリアライズの脆弱性の説明
JavaScriptでは、シリアライズはとても重要です。
独自のシリアライズおよびデシリアライズのロジックを自分でコーディングすることは稀ですが、npmと150万を超えるオープンソースパッケージを自由に使えるのに利用しない手はありませんよね。
js-yamlは、週に2,800万回以上ダウンロードされる大人気のパッケージです。Snyk Advisorによると、パッケージ全体の状態が良好であることがわかります。
しかしながら、上記のnpmパッケージjs-yamlのスクリーンショットから、以前のバージョンにはセキュリティの脆弱性があったことがわかります。この場合、どうすればいいのでしょう?
js-yamlのバージョンは、デシリアライズのためのコード実行に対して脆弱であることがわかりました。脆弱性は、new 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で任意のコードが実行される別のケースも見つかりましたので、注意は怠らないようにしてください。
具体的には、new Function()
を避けること。さらに、サードパーティのオープンソースパッケージをスキャンし、これらの脆弱性がないことを確認してください。脆弱性がある場合は、プルリクエストにより自動的に修正が可能となります。
###4.Node.jsセキュリティlinterの使用
本ガイドのツールに関する分野として、linterについて説明します。JavaScriptデベロッパはlinterを好んで使います。standardsやeslintのどちらのコードスタイルを適用するかという問題ではなく、これらはJavaScriptまたはNode.jsプロジェクトで非常に一般的なツールです。
そこで、ぜひ、良いセキュリティプラクティスを取り入れてみませんか?早速、eslint-plugin-securityを使ってみましょう。READMEの手順に従えば、簡単にプラグインが使えます。次のeslintプラグイン構成を追加するだけで、推奨の構成が有効になります。
"plugins": [
"security"
],
"extends": [
"plugin:security/recommended"
linterはどのように役立つのでしょう?
次のような、安全でないコーディング規則を検出するルールがあります。例えば、detect-eval-with-expression– eval()with
式または文字列リテラルの使用を検出してくれます。child_process Node.js APIの使用なども可能です。
ただ、eslint-plugin-securityの最終公開日は4年以上前ですので、機能的には正常に機能しますが、eslint-plugin-security-nodeなどの後続のパッケージを検討することを推奨します。
###5. 静的コード分析(SCA)ツールの使用によるコードインジェクションの発見と修正
ESLintで使用される基本的な形式の静的コード分析(SCA)linterは、出発点としては良いのですが、コードスタイルを適用するのに十分なコンテキストを提供するだけで、Node.js security linterで確認したように、セキュリティの問題に必要な対処が実際にできるほど柔軟ではありません。
#####Node.js security linterに関してデベロッパが抱く懸念事項として:
誤検知が多すぎる:linterルールは非常に基本的なものであり、誤検知のアラートが多すぎる。結果として、デベロッパの不満と混乱を助長するだけに終わってしまうという可能性があるということ。例えば、RegExp(matchEmailRegEx)場合、RegExp関数がリテラル以外で使用されているため、Node.jsセキュリティlinterでエラーが発生。単にmatchEmailRegEx、shared / variables.jsファイルの定数だとしても、linterはそれを知るのに十分なほど賢くはありません。
#####ルールが厳し過ぎる:前述と重複しますがルールが厳しすぎます。例えば、child_process.exec(someCommand, [])
を使うか、使わないかの選択しかありません。linterを使用した静的コード分析プロセスは、someCommandがハードコーディングした定数であるとわかるほど賢くはありません。リテラル以外でchild_process.exec()
を使用しているということだけで、linterエラーをトリガーしてしまうため、結局、デベロッパは嫌気がさしてルールを無視するという結果となりがちです。
#####ルールが基本的すぎる:ルールのコレクションが少なすぎて、調査結果があまりに基本的です。例えば、データが特定のユーザー入力から、コマンド実行、SQLクエリなどの潜在的な機密コードに実際にどのように流れるかについての多くのコンテキストがなく、All or Nothingの選択しかありません。
このような懸念があるものの、eslint-plugin-security-nodeなどのセキュリティlinterは出発点としては良いと言えます。なぜならセキュリティ対策をしないよりは、間違いなく良いからです。
ただ、コーディング中に自分のコードのセキュリティ問題を見つけるもっと良い方法があります。
それは、デベロッパ向けに構築された静的アプリケーションセキュリティテストツール(SAST)であるSnykCodeを使うことです。
#####Node.jsアプリケーションでのコマンドインジェクションの検索
Snyk Codeはまもなくリリースされる予定ですが、その仕組みについての予告編をご紹介します。
まず、GitHubアカウントでSnykに接続し、GiitHubリポジトリのインポートを行います。プロジェクトの追加[Add project] をクリックし、次にGitHubアイコンをクリックします。
次に、リポジトリのリストからリポジトリを見つけるか、検索ボックスでリポジトリを入力し、リポジトリをオンにしてスキャンを開始します。
SnykはGitHubリポジトリをインポート、迅速にスキャンします。
既知の脆弱性を持つオープンソースの依存関係を使用している場合や、Dockerイメージに多数のセキュリティの脆弱性が発生している場合など、潜在的なセキュリティ問題に関連するマニフェストファイルが自動的に検出されます。
実際に、コード分析をクリックして、何が見つかるか、このNode.jsアプリケーションの独自のコードで試してみましょう。
Snyk Codeはいくつかの脆弱性を発見しました。
そのうちの1つは、ここに示すようにコマンドインジェクションです。
このコード行にある問題のあるセキュリティに関する、懸念事項を説明しています。
「HTTPリクエスト本文からのサニタイズされていない入力はchild_process.execに流れ込み、そこでシェルコマンドを構築するために使用されます。これにより、コマンドインジェクションの脆弱性が発生する可能性があります。」
“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()関数にどのように流れてくるのでしょう? 詳細ボタンをクリックすると、コンテキストを追加するためのデータフローについて知ることが可能です:
SnykCodeで分析すると、脆弱性について全体像をはっきりと確認することができます。
urlパラメータがitem配列から作成されて、それ自体がユーザ制御入力のソースとなり、req.body.contentのメッセージボディ入力として流れてきています。
コマンドインジェクションの修正
セキュリティ問題に対処するため、さらなる手順を次のように実行します。
安全でないexec()
を使用する代わりに、そのAPIの安全なバージョンを使用する。execFile()
は、配列関数の引数の形式で提供された引数をエスケープの処理をする。
システムプロセスの実行など機密性の高いコードへの影響がないように、ユーザー入力からアイテム変数を検証、エスケープ、またはサニタイズすべきである。
まとめ
最後まで読んでいただきありがとうございます!
コードインジェクションを引き起こす可能性のある脆弱性についての問題について、より理解を深めていただけたとしたら幸いです。
独自のコードにおいても、アプリケーションにインポートするサードパーティの依存関係においても、コードインジェクションの脆弱性が存在するのです。
Contents provided by:
Jesse Casman, Fumiko Doi, Content Strategists for Snyk, Japan, and Randell Degges, Community Manager for Snyk Global