インジェクションの防止

意図しないシステム実行を防止するために、すべての信頼できないデータとユーザー入力が妥当性検証、サニタイズ、出力エンコードされていることを確認します。オペレーティングシステム (OS) コマンドインジェクション、クロスサイトスクリプティング (JavaScript インジェクションなど) 、SQL インジェクション、XPath インジェクションなどのさまざまなインジェクション攻撃がアプリケーションセキュリティ内に存在します。しかし、組込みソフトウェア内のインジェクション攻撃の中で最もよくあるものは OS コマンドインジェクションに関するものです。アプリケーションが信頼できない入力やセキュアではない入力を受け入れ、それを妥当性検証や適切なエスケープなしで外部アプリケーションに (アプリケーション名自体または引数として) 渡します。

オペレーティングシステムコールを使用する 非準拠コード例 :

この非準拠コード例では、system() 関数を使用してホスト環境で any_cmd を実行します。コマンドプロセッサの呼び出しは必要ありません。

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

enum { BUFFERSIZE = 512 };

void func(const char *input) {
  char cmdbuf[BUFFERSIZE];
  int len_wanted = snprintf(cmdbuf, BUFFERSIZE,
                            "any_cmd '%s'", input);
  if (len_wanted >= BUFFERSIZE) {
    /* Handle error */
  } else if (len_wanted < 0) {
    /* Handle error */
  } else if (system(cmdbuf) == -1) {
    /* Handle error */
  }
}

このコードがコンパイルされ、Linux システム上にて特権で実行される場合、攻撃者は次の文字列を入力することでアカウントを作成できます。any_cmd 'happy'; useradd 'attacker' これは以下のように解釈されます。

any_cmd 'happy';
useradd 'attacker'

準拠例:この準拠策では、system() の呼び出しは execve() の呼び出しに置き換えられています。exec 系の関数は完全なシェルインタプリタを使用しませんので、非準拠コード例に示すようなコマンドインジェクション攻撃に対して脆弱ではありません。

execlp(), execvp(), および (標準的ではない) execvP() 関数は、指定されたファイル名の先頭にスラッシュ文字 (/) がない場合、シェルの動作を真似て実行可能ファイルを探します。結果として、先頭のスラッシュ文字 (/) なしで使用されるのは、PATH 環境変数が安全な値に設定されている場合にのみであるべきです。

execl(), execle(), execv(), および execve() 関数はパス名の置き換えを行いません。

さらに、予防措置を講じて外部実行可能ファイルが信頼できないユーザーにより改変されないようにすべきです。例えば、実行可能ファイルがユーザーにより書き込み可能でないことを保証します。この準拠策は前述の非準拠コード例とは大きく異なります。第一に、入力は args 配列に組み込まれ execve() に引数として渡されます。コマンド文字列の生成時にバッファオーバーフローや文字列トランケーションの問題がなくなります。第二に、この準拠策は子プロセスで /usr/bin/any_cmd を実行する前に新しいプロセスをフォークします。この方法は system() を呼び出すよりも複雑ですが、追加されたセキュリティは追加の労力の価値があります。

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

void func(char *input) {
  pid_t pid;
  int status;
  pid_t ret;
  char *const args[3] = {"any_exe", input, NULL};
  char **env;
  extern char **environ;

  /* ... Sanitize arguments ... */

  pid = fork();
  if (pid == -1) {
    /* Handle error */
  } else if (pid != 0) {
    while ((ret = waitpid(pid, &status, 0)) == -1) {
      if (errno != EINTR) {
        /* Handle error */
        break;
      }
    }
    if ((ret != -1) &&
      (!WIFEXITED(status) || !WEXITSTATUS(status)) ) {
      /* Report unexpected child status */
    }
  } else {
    /* ... Initialize env as a sanitized copy of environ ... */
    if (execve("/usr/bin/any_cmd", args, env) == -1) {
      /* Handle error */
      _Exit(127);
    }
  }
}

検討事項:

  • シェルコマンドラッパーを起動してはいけません。これは以下に限定されません。

    • PHP:system() exec(), passthru(), shell_exec()

    • C:system() popen(), exec(), execl(), execle(), execv(), execve()

    • C++:ShellExecute()

    • Lua:os.execute()

    • Perl:system(), exec()

    • Python:os.system() subprocess.call()

  • 可能であれば、オペレーションシステムコマンドにユーザーデータを使用することは避けます。

    • 必要に応じて、オペレーティングシステムに渡される可能性のあるユーザー駆動文字列に番号とコマンド文字列の参照マップを使用します。

  • 参照マップを介して受け入れられるコマンドの "許可リスト" を適用し、期待されるパラメータ値のみが処理されることを保証します。

  • エンコード文字のユーザーデータをコンテキストに合わせて出力することを保証します。 (例、 HTML, JavaScript, CSS, など)

    • HTML エンティティエンコーディング

      • < は以下のようにエンコードして出力します。

        • &lt;
      • > は以下のようにエンコードして出力します。

        • &gt;

その他の参考情報:

Last updated