【入門編】OSコマンド実行を回避する代替APIの選定(exec系関数の禁止) – アプリケーションセキュリティ & 安全な開発防御ガイド

「システムを乗っ取らせない」ために。OSコマンドインジェクションの恐ろしさと、開発者が今すぐ捨てるべき習慣

こんにちは。日々、コードの裏側に潜む「穴」を探し続けているセキュリティエンジニアです。

今日は、開発現場でついやりがちな「ある実装」について、少し耳の痛いお話をしましょう。皆さんは、プログラムの中で外部コマンドを呼び出すとき、どんな書き方をしていますか?

「とりあえず動けばいいや」と、system() や exec() といった関数に、ユーザーからの入力をそのまま放り込んでいませんか? もしそうなら、それは「家の玄関の鍵を、道行く人全員に配っている」のと同じくらい危険な状態です。

今回は、なぜこれが危ないのか、そしてどうすれば安全に実装できるのか、専門用語を抜きにして紐解いていきましょう。

—

泥棒は「ついで」を狙っている:OSコマンドインジェクションの正体

OSコマンドインジェクションを一言で言うと、「プログラムに『ついでにこれもやっておいて』と悪意ある命令を紛れ込ませる攻撃」のことです。

想像してみてください。あなたは玄関のインターホン越しに、「届いた荷物を玄関の棚に置いておいて」と業者さんに頼みました。でも、その「業者さん」がもし、本当は泥棒だったらどうでしょう?

「荷物を置いておいて。あ、ついでにそこの金庫も開けておいてね」

こう言われて、本当に金庫を開けてしまったら……これが、セキュリティで言う「コマンドインジェクション」です。プログラムは、あなたが書いた命令と、泥棒が付け加えた命令の区別がつかなくなってしまうのです。

なぜ system() や exec() はダメなのか?

system("ls " + filename) のようなコードを書くと、プログラムはOSに対して丸投げをします。「この文字列をコマンドとして実行して!」という命令です。

もしユーザーが filename に ; rm -rf / なんて入力したらどうなるでしょうか?
OSは「あ、これ消せってことね」と解釈して、あなたのシステムを根こそぎ破壊してしまいます。プログラムが「ユーザーからの入力」と「本来やりたかったコマンド」を混同してしまうため、泥棒の言いなりになってしまうのです。

—

解決策:命令と引数を「切り離す」

では、どうすれば泥棒の付け入る隙をなくせるのでしょうか? 答えは簡単です。「命令(プログラム)」と「データ(引数)」を明確に分けることです。

家の防犯に例えるなら、玄関を開ける権限は「家主」にしか与えず、業者さんには「指定された小窓から荷物を滑り込ませる」という専用ルートだけを使わせるイメージですね。

Pythonでの安全な実装例

Pythonでは os.system() などの危険な関数は卒業し、subprocess モジュールを使うのが鉄則です。特にポイントは shell=False にすること。

import subprocess

ユーザーから受け取ったファイル名
user_input = “my_document.txt”

【NGな例】shell=True は絶対に使わない!
subprocess.run(“ls ” + user_input, shell=True)

【OKな例】命令と引数を「配列」で渡す
これにより、ユーザーの入力は「あくまでデータ」として扱われ、
悪意あるコマンド(例: ; rm -rf /)は単なるファイル名の一部とみなされます。
try:
subprocess.run([“ls”, “-l”, user_input], shell=False, check=True)
print(“ファイルを表示しました”)
except subprocess.CalledProcessError as e:
print(f”エラーが発生しました: {e}”)

なぜこれで防げるの?

subprocess.run(["ls", "-l", user_input], shell=False) と書くと、OSに対して「『ls』というプログラムを起動してね。引数はこのリストの中身だよ」と明確に伝えます。

これなら、user_input にどんな怪しい記号(; や | など)が含まれていても、OSは「lsというプログラムに渡す『変な名前のファイル』」としか認識しません。金庫を開けるための命令として解釈されることは決してないのです。

—

今日からできる「守りの第一歩」

セキュリティ対策と聞くと身構えてしまうかもしれませんが、結局のところ「信用しすぎないこと」が一番の近道です。

1. shell=True をコード全体から検索する:もし見つけたら、それは爆弾を抱えているのと同じです。今すぐ書き換えましょう。
2. 標準ライブラリを信頼する:自前で文字列を結合してコマンドを作るのはやめましょう。言語が用意している「引数を安全に渡す機能」を最大限活用してください。
3. 入力のバリデーション(検閲)を行う:そもそも、その入力値は期待通りの形(英数字だけか? 長さは適切か?)をしていますか? 玄関に来る客の身分証を確認するのと同じで、入ってくるデータは徹底的にチェックしましょう。

セキュリティは、一度やって終わりというものではありません。でも、こうした「安全な書き方」を習慣にするだけで、あなたのコードは格段に硬くなります。

明日からの開発で、ぜひ「この命令は、泥棒に悪用されないかな?」と、一度だけ立ち止まって考えてみてください。その小さな習慣が、あなたのシステムを、そしてあなたのエンジニアとしての価値を、強力に守ってくれるはずですよ。

一歩ずつ、一緒に強くなっていきましょう!

コメント

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