こんにちは。セキュリティの世界へようこそ。
現場でバリバリコードを書いていると、「ちょっとこのファイル操作、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(関数)に渡す前に検証する」という習慣をつけましょう。
セキュリティは、一度の完璧な対策で終わりではありません。こうした小さな設計の積み重ねが、あなたの開発するシステムを、そして何よりユーザーの大切な情報を守る「強固な鍵」になるんです。
「便利さ」よりも「安全性」を優先する設計。それが、一流のエンジニアへの第一歩です。一歩ずつ、一緒に学んでいきましょう!
コメント