コマンドインジェクション

コマンドインジェクション

説明

コマンドインジェクションは、アプリケーションがユーザー入力をOSコマンドとして実行する場合に発生します。攻撃者は細工した入力によってアプリケーションプロセスの権限でコマンドを実行し、データを漏えいさせたり、システムを損傷させたりする可能性があります。

想定される影響

  • プロセス権限の悪用: プロセスがアクセスできるファイルやサービスが悪用される可能性があります。管理者権限が自動的に与えられるわけではありません。
  • データ漏えい: 機密データが攻撃者に開示される可能性があります。
  • サービス拒否: リソースの消費や重要なプロセスの終了によってサービスが停止する可能性があります。

対処方法

  • 入力を検証してください。コマンドに使うユーザー入力を厳密に検証してください。
  • コマンドと引数を分離してください。可能ならsubprocess.run([...], shell=False)のように引数をリストで渡し、シェルを使わないでください。
  • シェルの使用を最小限にしてください。shell=Trueは必要な場合に限り、POSIXシェルの引数をshlex.quote()やshlex.join()で明示的にエスケープしてください。
  • プラットフォームの違いを考慮してください。shlexはUnix/POSIXシェル向けであり、Windowsシェルでも安全とは限りません。shell=Trueでリストやタプルの後方にユーザー入力を置く場合も、動作がプラットフォームによって異なるため、安全とは一概に判断できません。

例

OS system

変更前

python
# 危険なos.systemの例
import os
from flask import request

@app.route('/command')
def unsafe_command():
    command = request.args.get('command')
    os.system(command)
    return "Command executed"

変更後

python
# subprocessを使った対策例
import subprocess
from flask import request, abort

@app.route('/command')
def safe_command():
    command = request.args.get('command')
    if command not in ['ls', 'pwd']:  # 許可したコマンドだけを実行
        return abort(400, description="Invalid command")
    result = subprocess.run([command], shell=False, capture_output=True, text=True)
    return result.stdout

説明:

  • 変更前: 入力を検証せずOSコマンドとして実行するため、攻撃者が任意のコマンドを実行できます。
  • 変更後: 定義済みのコマンドだけを許可し、shell=Falseで引数リストを渡して、シェルの特殊文字の解釈を避けます。

Paramiko exec_command

FlaskアプリケーションとSSH認証情報の設定を省略した抜粋です。SSHサーバーのホスト鍵を検証し、リモートのシェルがPOSIX互換であることを確認してください。shlex.quote()はシェル構文をエスケープするだけで、接続先を認可しません。hostも許可した接続先であることを検証してください。

変更前

python
# 危険なParamikoの例
from flask import request
from paramiko.client import SSHClient

@app.route('/paramiko')
def paramiko():
    client = SSHClient()
    client.load_system_host_keys()
    client.connect("example.com", username=USER, password=PASS)
    client.exec_command(request.args.get("cmd")) # 信頼できないコマンド
    return "Command sent"

変更後

python
# Paramikoの対策例
import subprocess
import shlex
from flask import request, abort
from paramiko.client import SSHClient

@app.route('/paramiko')
def paramiko():
    client = SSHClient()
    client.load_system_host_keys()
    client.connect("xeize.com", username=USER, password=PASS)
    
    health_cmd= "/bin/ping -c 3 -- %s"
    cmd = health_cmd % shlex.quote(request.args.get('host'))
    client.exec_command(cmd)
    return "Command sent"

説明:

  • 変更前: 未検証のユーザー入力をリモートコマンドに渡すため、任意のコマンドを実行できます。
  • 変更後: shlex.quote()によって引数をPOSIXシェルの単一トークンとして渡します。可能な場合は、実行できるコマンドも明示的な許可リストで制限してください。
  • shlex.quote()はUnixシェル向けであり、Windowsなどの非POSIXシェルでの安全性は保証しません。シェルの引用処理に関係する文字には次のものがあります。
    • &
    • |
    • ;
    • $
    • >
    • <
    • `
    • \
    • !

参考資料