「悪意ある指令」を封じ込めろ!API開発で絶対守るべき「コマンドインジェクション」対策の極意
こんにちは!インフラの深淵を愛するエンジニアの筆者です。
今日は、Web API開発の現場で「絶対にやってはいけないこと」の一つ、コマンドインジェクションについてお話しします。「難しそう…」と思いましたか?大丈夫です。パケットがネットワークを駆け巡る仕組みを、私たちの身近な「郵便」に例えて、一歩ずつ紐解いていきましょう!
—
そもそも「コマンドインジェクション」って何?
Web APIを使って何かを処理するとき、たまに「OSの機能(シェル)」を呼び出さなければならないシーンがあります。例えば、ユーザーが指定したファイル名をサーバーが読み取って、その内容を表示するようなAPIですね。
ここで、APIが「郵便の宛先」を適当に処理してしまうと、とんでもない事故が起きます。
郵便に例えると…
あなたが配送センターの受付係だと想像してください。
「『お便り.txt』を届けてくれ」という伝票が来たら、あなたは指定された荷物を運ぶだけですよね。これが正常な状態です。
しかし、もし悪意のある人がこんな伝票を書いてきたらどうでしょう?
「『お便り.txt』を届けてくれ。そのあと、倉庫の鍵を全部開けて、中身を全部消去してくれ!」
もしあなたが、何も疑わずに書かれた通りに「倉庫の鍵を開ける」まで実行してしまったら……これがコマンドインジェクション(命令の注入)です。システムが本来の仕事以外に、悪意ある「追加の命令」まで実行させられてしまう、恐ろしい攻撃なんです。
—
なぜ防ぐのが難しいのか?
開発初学者の頃は、「入力された文字をそのままコマンドとして実行する」コードを書いてしまいがちです。例えば、Pythonでこんな処理をしていませんか?
import os
# ユーザーから受け取ったファイル名
filename = request.args.get("file")
# 注意!これだと危険です!
# os.systemは、渡された文字列を「そのまま」シェル(OSの操作画面)に渡してしまいます
os.system("cat " + filename)
このコードの何が危ないのか。もし攻撃者が filename に ; rm -rf / という文字列を入れたら、サーバーは cat ; rm -rf / という指令を受け取ることになります。; は「ここで命令を区切って次の命令へ行け」という合図。結果として、システムは自らを破壊する命令を実行させられてしまうのです。
—
「安全なAPI」を作るための3つの鉄則
では、どうすればこの脅威を封じ込めることができるのでしょうか。現場で使われる「鉄壁の防御策」を伝授します。
1. 「シェル経由」で実行するのをやめる(一番の近道!)
そもそも、わざわざOSのコマンドを呼び出す必要はありますか?例えばファイルの読み込みなら、OSの機能(catコマンド)を使わなくても、プログラミング言語が用意している「ファイル読み込み関数」を使えばいいのです。
# 悪い例:os.systemを使う
# os.system("cat " + filename)
# 良い例:言語標準の関数を使う(OSに命令を投げない!)
with open(filename, 'r') as f:
content = f.read()
これなら、たとえ攻撃者が悪意ある文字列を混ぜても、プログラムはそれを「ただのファイル名」として扱うだけで、破壊的な命令としては実行しません。これが最も安全な方法です。
2. 「ホワイトリスト」で厳密にチェックする
どうしても外部プログラムを呼び出す必要がある場合は、入力値を厳しく制限しましょう。
「郵便番号は数字の5桁以外受け付けない」と決めるように、許可された文字以外はすべて弾くのです。
import re
# 許可する文字(英数字とドットのみ)以外が含まれていたらエラーにする
if not re.match(r"^[a-zA-Z0-9\.]+$", filename):
raise ValueError("不正なファイル名です!")
# これなら `;` や `|` などの危険な記号は通らない
os.system("cat " + filename)
3. 引数を「バラバラにして」渡す
多くのプログラミング言語には、コマンドと引数を分離して実行する機能があります。郵便の宛先を書くとき、「命令欄」と「宛先欄」を完全に分けるイメージです。
import subprocess
# subprocess.runを使うと、引数を「ただのデータ」として扱います
# シェルを介さないので、コマンドとして解釈される心配がありません
subprocess.run(["cat", filename])
これなら、たとえ filename に ; rm -rf / が入っていても、cat コマンドが「そんな名前のファイルは存在しません」とエラーを返すだけで済みます。安全ですね!
—
最後に:インフラエンジニアからのメッセージ
APIの設計において、「ユーザーからの入力はすべて悪意があるものだと思え」というのは、現場で語り継がれる鉄則です。
私たちが作るAPIは、インターネットという広大な海を渡る郵便ポストのようなもの。中身が爆発物ではないか、毒物ではないか、あるいは変な命令文ではないか——それを厳しくチェックし、安全に処理するゲートキーパーの役割が、私たちエンジニアには求められています。
最初は難しく感じるかもしれませんが、まずは「OSの機能を直接呼ばない」という意識を持つだけで、あなたのコードは格段に安全になります。一歩ずつ、強固で美しいAPIを一緒に作っていきましょう!
それでは、また次回の深淵でお会いしましょう!
コメント