Skip to main content

コマンドインジェクション:仕組み、リスク、対策

著者

2020年11月25日

0 分で読めます

OSコマンドインジェクションとも呼ばれるコマンドインジェクション攻撃は、プログラムの不備を悪用し、入力の適切な検証、エスケープ、サニタイズを行わずにシステムコマンドを実行します。その結果、悪意のある攻撃者が任意のコマンドを実行できる可能性があります。

コマンドインジェクションにはどのようなリスクがあるのでしょうか?

アプリケーションの設定や実行プロセスの構成によっては、コマンドインジェクションの脆弱性により、プロセスの権限昇格が起きたり、悪意のある第三者が完全に操作できるリモートのリバースシェルが生成されたりする可能性があります。

コマンドインジェクション攻撃の仕組み

OSコマンドインジェクション攻撃につながるプログラムの不備を理解するために、Node.jsベースのアプリケーションで発見されたさまざまなコマンドインジェクションの脆弱性を見ていきましょう。

systeminformationは、週に50万回以上ダウンロードされ、定期的にメンテナンス(コミット)されているOS(オペレーティングシステム)情報ライブラリです。Snyk Advisorの依存関係ヘルスページを見ると、コミュニティも存在することがわかります。

健全性スコア75/100、週間ダウンロード数の推移、メンテナンスのコミット頻度を示すnpm systeminformationパッケージのページ

しかし、バージョン4.27.11より前のすべてのバージョンのsystem informationには、OSコマンドインジェクションの脆弱性があります。これは、ライブラリが公開している複数のメソッドに脆弱性があるためです。

脆弱性のあるinetChecksite()関数のコードを見てみましょう。このコードにある、コマンドインジェクションの例を示すセキュリティ上の脆弱性を見つけられますか?

      let urlSanitized = util.sanitizeShellString(url).toLowerCase();
      if (urlSanitized) {
        let t = Date.now();
        if (_linux || _freebsd || _openbsd || _netbsd || _darwin || _sunos) {
          let args = ' -I --connect-timeout 5 -m 5 ' + urlSanitized + ' 2>/dev/null | head -n 1 | cut -d " " -f2';
          let cmd = 'curl';
          exec(cmd + args, function (error, stdout) {
          …

このコードスニペットから、いくつかのことがわかります。

  • urlパラメーターはサニタイズされています。もしかすると、作者が想定していなかった方法でサニタイズ処理を回避できるかもしれません。

  • 安全でないOSプロセス起動APIのexec()が使われています。このAPIはコマンドと引数を連結して1つのシェルコマンドとして実行します。そのため、作者は独自のコマンドライン引数のサニタイズ処理を実装せざるを得ませんでした。

urlSanitized変数は、CLIに渡す引数の一部として連結されています。

このセキュリティ上の脆弱性は、最後に述べたurlSanitizedにあります。cmd変数で参照されるコマンドは、ネットワークユーティリティのcurlです。URLとしてcurlにコマンド引数を渡せたらどうなるでしょうか?

脆弱な関数に渡すURLを使って、次の概念実証によるコマンドインジェクションを試すことができます。URLは、inetChecksite()関数に渡される最初の引数のとおりです。以下はコマンドインジェクションの例です。

 const si = require('systeminformation');
si.inetChecksite("https://snyk.io; touch /tmp/abc", (result) => console.log(result));

ただし、この場合は成功しません。npmパッケージが、セミコロン(;)などの危険なユーザー入力をサニタイズしているためです。

しかし通常、コマンドライン引数に特殊文字は必要ありません。ユーザー入力としてhttps://snyk.io -o hihiのようなURLを渡すことができます。スペースが含まれているため、curlの位置引数-oが連結され、Webサイトの結果が攻撃者の制御するファイルに出力されます。

脆弱なバージョンのsysteminformation@4.27.10で想定どおりに動作する、悪用方法を示すもう1つのコマンドインジェクションの例を紹介します。

const si = require('systeminformation');
si.inetChecksite("https://snyk.io -o hihi", (result) => console.log(result));

このコミットではサニタイズ処理が修正され、この種のコマンドインジェクションの脆弱性をさらに抑制することを目指しています。

lib/internet.jsのコード差分を左右に並べて表示。文字置換の追加により、URLのサニタイズ処理が拡張されています。

コマンドインジェクション対策チートシート

他のコマンドインジェクションの例からも多くを学べます。オープンソースパッケージでセキュリティ上の脆弱性を特定し、どのように対処したかを確認してみましょう。たとえば、こちらとこちらの脆弱性が挙げられます。

コマンドインジェクション攻撃を防ぐには、次の対策を検討してください。

  1. アプリケーションが実行するコマンドに、ユーザー入力を一切渡さないでください。

  2. コマンドの実行には、execFile()などの安全なAPIのみを使用してください。他のAPIと異なり、最初のパラメーターとしてコマンドを、2番目の関数パラメーターとしてコマンドライン引数の配列を受け取ります。この動作により、コマンド自体が有効なプログラムである必要があり、危険な入力とは切り離されます。

  3. execFile()を使用する場合、ユーザーがプログラム名を制御できないようにしてください。また、ユーザー入力をコマンド引数に割り当て、入力をそのままプログラムの実行に渡さないようにしてください。

  4. さらに予防策として、ユーザー入力が想定される形式に従っていることを検証してください。たとえば、関数のパラメーターがURLを想定している場合、受け取った入力が有効なURLであることを検証します。これにより、前述のnpmパッケージsysteminformationのコマンドインジェクション脆弱性を防げたはずです。

OWASP Top 10に見るコマンドインジェクション攻撃

OSコマンドインジェクション攻撃がいかに一般的かを示すため、Snyk Security Teamが2020年10月に公開したすべての既知のセキュリティ脆弱性を調べ、npmのJavaScriptエコシステムに絞り込みました。ご覧のとおり、10月最終週だけでも、npmパッケージでCommand injectionの脆弱性がかなりの数見つかっています。

脆弱性の検索、パッケージの絞り込み、深刻度と公開日を含むnpm脆弱性の一覧を表示するSnyk Vulnerability DBのページ。

まとめると、コマンドインジェクションの脆弱性は想像以上によく見られます。インジェクション攻撃がOWASP Top 10のWebセキュリティリスクに取り上げられてきたのには理由があります。広く発生し、SQLインジェクション攻撃など、さまざまな形を取るためです。コマンドインジェクションの脆弱性を検査して修正するために、GitリポジトリをSnykに接続することを強くおすすめします。Snykなら、ほかの脆弱性も見つけられるかもしれません。Snykは無料で、プロジェクトの監視もすぐに始められます。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップで、CTFチャレンジの解き方を学びましょう。