説明
コマンドインジェクションは、アプリケーションがユーザー入力を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シェルでの安全性は保証しません。シェルの引用処理に関係する文字には次のものがあります。&|;$><`\!
参考資料
- OWASP: コマンドインジェクション
- CWE: CWE-77: コマンド内の特殊要素の無害化不備
- Unixシェルの引用処理:
shlex.quote