【入門編】OSコマンドインジェクションの防御:シェル呼び出しの回避とAPI利用 – アプリケーションセキュリティ & 安全な開発防御ガイド

こんにちは。セキュリティの世界へようこそ。

現場でバリバリコードを書いていると、「ちょっとこのファイル操作、OSのコマンドを直接叩いちゃったほうが楽じゃない?」なんて誘惑に駆られること、ありますよね。でも、その「楽」が、実はあなたのシステムに「泥棒への招待状」を置いてしまうことになるんです。

今日は、そんなOSコマンドインジェクションという「玄関の鍵を自分から壊すような攻撃」について、一緒に紐解いていきましょう。

—

1. OSコマンドインジェクションって、何が怖いの?

まずは身近な例で考えてみましょう。

あなたの家の玄関には、強固な鍵がかかっていますよね。でも、もしあなたが「家の外から『おーい、中に入って掃除してくれ!』と叫んだら、自動でドアが開く仕組み」を作ったらどうなるでしょう? もちろん、泥棒も「掃除してくれ」と叫べば、堂々と中に入ってきてしまいます。

OSコマンドインジェクションは、まさにこれです。プログラムがOS(WindowsやLinuxなど)に命令を出す際に、「悪意のある命令」を混ぜ込むことで、サーバーの全権限を奪い取る攻撃です。

攻撃のメカニズム:& や ; という「魔法の記号」

例えば、ユーザーが入力したファイル名を使って、サーバー側で ls コマンド(ファイル一覧を表示するコマンド)を実行するプログラムがあるとします。

正常な動き:ユーザーが「data.txt」と入力
ls data.txt

ここで攻撃者は、入力欄にこう書き込みます。
data.txt; rm -rf /

すると、サーバーで実行される命令はこうなってしまいます。
ls data.txt; rm -rf /

この ; は「前の命令が終わったら、続けて次の命令を実行せよ」という意味です。つまり、「ファイルを表示したあと、サーバーの全データを消せ」という命令にすり替わってしまうわけです。これが、コマンドインジェクションの恐ろしさです。

—

2. 「シェル」を呼び出すことが、最大の盲点

なぜこんなことが起きるのか。それは、プログラムが「シェル(OSのコマンドを解釈する黒い画面のようなもの)」を介して命令を実行しているからです。

シェルは非常に便利で多機能ですが、その分、「何でも実行できてしまう」という危うさがあります。プログラムが「シェルを呼び出して命令を実行する」という設計になっている限り、私たちは常に「入力値に不正な記号が混ざっていないか?」を一生懸命チェックし続けなければなりません。

しかし、人間が作るチェックには必ず限界があります。だからこそ、「シェルを呼び出さない」という根本的な解決策をとるべきなんです。

—

3. 解決策:API(標準ライブラリ)を使おう!

「シェルを呼び出す」のではなく、プログラミング言語が用意している「標準API」を使いましょう。

例えば、Pythonでファイル一覧を取得したい場合、os.system("ls") のようにシェルに頼るのではなく、標準ライブラリの os.listdir() を使います。

悪い例(シェル呼び出し):

import os

ユーザーの入力をそのままシェルに渡してしまう(絶対ダメ!)
filename = input(“ファイル名を入力: “)
os.system(f”ls {filename}”)

良い例(API利用):

import os

APIを使えば、もし入力値に “; rm -rf” が混ざっていても、
単に「そんな名前のファイルはないよ」というエラーになるだけです。
filename = input(“ファイル名を入力: “)
try:
# 直接OSの機能(API)を呼び出すので、コマンドとして解釈されません
files = os.listdir(f”./{filename}”)
print(files)
except FileNotFoundError:
print(“ファイルが見つかりません”)

APIを使う最大のメリットは、「入力された文字列がコマンドとして解釈される余地がないこと」です。これこそが、セキュリティの「防御」の本質。泥棒が叫んでも、ドアが開かない仕組みを作るのです。

—

4. 今日からできる「守りの姿勢」

最後に、現場で意識してほしい3つのポイントをまとめました。

1. 「シェルを呼び出す」メソッドは極力使わない

  • Pythonなら os.system や subprocess.call(..., shell=True) を避けましょう。
  • PHPなら exec や system、passthru は要注意です。

2. どうしても必要な場合でも、入力を直接渡さない

  • どうしてもOSコマンドが必要なら、引数を「リスト」や「配列」として渡し、シェルを経由しない呼び出し方(subprocess.run(["ls", filename]) など)を選択してください。

3. 入力値は「敵」だと思う

  • どんなに信頼できるユーザーでも、入力値には不正な記号が含まれている前提で、「API(関数)に渡す前に検証する」という習慣をつけましょう。

セキュリティは、一度の完璧な対策で終わりではありません。こうした小さな設計の積み重ねが、あなたの開発するシステムを、そして何よりユーザーの大切な情報を守る「強固な鍵」になるんです。

「便利さ」よりも「安全性」を優先する設計。それが、一流のエンジニアへの第一歩です。一歩ずつ、一緒に学んでいきましょう!

コメント

タイトルとURLをコピーしました