Webアプリケーションの開発や運用において、最も重要視すべき課題のひとつが「入力値の安全な取り扱い」です。ユーザーからの入力は、開発者の想定を超えた動作を引き起こし、システム全体を危険にさらすセキュリティホールとなり得ます。
本記事では、Webアプリケーションにおける代表的な脆弱性「ディレクトリトラバーサル」と「OSコマンドインジェクション」について詳しく解説します。これらは仕組み自体はシンプルですが、悪用されれば情報漏洩やシステム破壊に直結する極めて危険な攻撃手法です。
なぜこれらの攻撃が成立するのか、サーバー内部で何が起きているのか、堅牢なシステムを構築するためにどのようなコードを書くべきなのか。攻撃者の視点と防御の鉄則を体系的に解説していきます。
サーバーのディレクトリ構造と「魔の文字列」の正体
Webアプリケーションは、サーバー上の特定ディレクトリに配置されたファイルを提供・処理することで動作します。通常、ユーザーがアクセスできる範囲は厳格に制限されていますが、設計の不備を突かれることで、その制限を突破される危険性があります。
ディレクトリトラバーサルとは何か
ディレクトリトラバーサル(Directory Traversal)は「パストラバーサル」とも呼ばれ、ディレクトリを横断して本来アクセス権限のないファイルへアクセスする攻撃手法です。
Webサーバーの公開ディレクトリ外側には、システム設定ファイル、パスワードファイル、顧客データなど重要情報が保存されています。これらは通常、外部から見えないよう保護されています。しかし、アプリケーションがファイルパス指定にユーザー入力をそのまま使用している場合、攻撃者は特殊な文字列でディレクトリ階層を遡り、重要ファイルを取得できてしまいます。

「../」が引き起こす階層の移動
この攻撃の核心は、「親ディレクトリ」を表す特殊文字列 ../(ドット・ドット・スラッシュ)です。Windows環境では ..\ が該当します。
コンピュータのファイルシステムにおいて、.(ドット1つ)はカレントディレクトリ(現在の場所)、..(ドット2つ)は一つ上の階層(親ディレクトリ)を意味します。
例えば、アプリケーションが /var/www/html/user_data/ ディレクトリ内のファイルを読み込む設計だったとします。ユーザーが report.txt を指定すれば、システムは /var/www/html/user_data/report.txt を読み込みます。
しかし、攻撃者が ../../../../etc/passwd を送信した場合はどうでしょうか。システムがこれを単純結合すると、パスは次のようになります。
/var/www/html/user_data/../../../../etc/passwd
ファイルシステムはこのパスを解釈し、「user_dataから一つ上がり、htmlから一つ上がり...」と遡ります。結果、ルートディレクトリまで到達し、そこから etc/passwd へと降りていきます。こうして、本来見えてはいけないOSのユーザーアカウント情報が盗み出されてしまうのです。
想定される被害の甚大さ
ディレクトリトラバーサルによる被害は、単なるファイル閲覧に留まりません。
1. 機密情報の漏洩
OSの設定ファイル、アプリケーションのソースコード、データベース接続情報が記載されたファイルなどが盗まれることで、さらなる攻撃の手がかりを与えてしまいます。特にソースコード流出は、新たな脆弱性発見につながる深刻な事態です。
2. データの改ざん・削除
ファイルの読み込みだけでなく、書き込みや削除が可能な機能にこの脆弱性が存在した場合、Webサイト改ざん、重要データ消失、バックドア設置などに繋がります。
3. サーバーの不正操作
設定ファイルの書き換えが可能であれば、サーバーの動作そのものを変更し、攻撃者の意図通りに制御することが可能になります。
OSコマンドインジェクションによるシステム乗っ取り
ディレクトリトラバーサルが「ファイルへの不正アクセス」であるのに対し、OSコマンドインジェクションは「サーバーそのものの不正操作」を目的とした、より攻撃的な手法です。
シェルを直接操作される恐怖
OSコマンドインジェクション(OS Command Injection)は、Webアプリケーションを経由してWebサーバーのOS上で任意のコマンドを実行させる攻撃です。
Webアプリケーションの中には、処理の一部としてOSのコマンドや外部プログラムを呼び出すものがあります。メール送信に sendmail コマンドを使用したり、画像処理に ImageMagick を使用したり、ネットワーク診断のために ping コマンドを使用したりするケースです。
この際、コマンドの引数(パラメータ)としてユーザー入力値を不用意に使用すると、攻撃者はそこに「別のコマンド」を紛れ込ませることができます。

コマンドがつながる仕組み
OS(シェル)には、複数のコマンドを一行で続けて実行するための「区切り文字」が存在します。
;(セミコロン):前のコマンド終了後、次のコマンドを実行する(Linux等)&(アンパサンド):バックグラウンドで実行する、または前のコマンドと一緒に実行する|(パイプ):前のコマンドの出力を次のコマンドの入力にする&&(ダブルアンパサンド):前のコマンドが成功したら次のコマンドを実行する
例えば、入力されたIPアドレスに対して ping を打つだけの単純なスクリプトがあったとします。
内部での処理:system("ping -c 1 " + $address);
正常なユーザーは 192.168.1.1 と入力します。
実行されるコマンド:ping -c 1 192.168.1.1
しかし、攻撃者が 192.168.1.1; cat /etc/passwd と入力したらどうなるでしょうか。
実行されるコマンド:ping -c 1 192.168.1.1; cat /etc/passwd
シェルはこれを「まずpingを実行し、それが終わったらcatコマンドでパスワードファイルを表示せよ」と解釈します。結果、Webブラウザ画面上に、pingの結果と共にパスワードファイルの内容が表示されてしまうのです。
ブラインドOSコマンドインジェクション
攻撃結果が画面に表示されない場合でも安心はできません。「ブラインドOSコマンドインジェクション」では、コマンド実行結果を外部サーバーへ送信させたり、sleep コマンドで応答時間を意図的に遅らせることで、コマンド成功の有無を推測したりします。
目に見えるエラーが出ないからといって、脆弱性が存在しないとは限りません。攻撃者はあらゆる手段を使ってサーバーの制御権を奪おうと画策します。
ディレクトリトラバーサルとOSコマンドインジェクションへの防御策
これらの脆弱性は、どちらも「ユーザー入力を信頼しすぎている」ことに起因します。しかし、単に「入力をチェックすれば良い」というほど単純ではありません。攻撃者はチェック漏れや想定外のエンコーディング(文字コード変換)を使ってフィルターをすり抜けようとします。
ここでは、根本的な解決策と多層防御の観点からの対策を詳しく解説します。
外部からの入力でパスを直接指定しない
ディレクトリトラバーサル対策の最も効果的な方法は、**「ユーザー入力をファイル名として使わない」**ことです。
可能な限り、ファイル名そのものではなく、IDやトークンを使用するよう設計を変更します。
例:download.php?file=report.txt ではなく、download.php?id=123 とする
サーバー側では、ID 123 が report.txt に対応するというマッピング情報をデータベースや連想配列で管理し、その対応表に基づいてファイルにアクセスします。これならば、ユーザーがどんな文字列を入力しても、対応表にないIDであればエラーになるだけです。ファイルシステムへの直接的なパス操作を完全に遮断できます。
ファイル名を利用せざるを得ない場合のサニタイズ
どうしてもファイル名を入力として受け取る必要がある場合は、ファイル名のみ(ベースネーム)を取得する処理を行います。
多くのプログラミング言語には、パスからディレクトリ部分を取り除き、ファイル名だけを抽出する関数が用意されています。
- PHP:
basename() - Python:
os.path.basename() - Java:
Paths.get().getFileName()
これらを使用することで、../../etc/passwd という入力があっても、ディレクトリを表す ../../etc/ 部分が削除され、単なる passwd というファイル名として扱われます。その上で、そのファイルが許可されたディレクトリ内に存在するかどうかをチェックすれば安全です。
さらに、使用可能な文字種を英数字のみに限定する(ホワイトリスト方式)バリデーションを併用することで、より強固な対策となります。
OSコマンド呼び出し機能の使用を避ける
OSコマンドインジェクションへの最良の対策は、シェルを呼び出す関数を使わないことです。
多くの機能は、OSコマンドを使わずともプログラミング言語標準のライブラリで代替可能です。
- メール送信:
sendmailコマンドではなく、言語標準のメール送信ライブラリやSMTPライブラリを使用する - ファイル操作:
cpやrmコマンドではなく、ファイルシステム操作用のAPIを使用する
ライブラリを使用すれば、そもそもシェルが起動しないため、コマンドインジェクションが発生する余地がなくなります。言語自体の機能で完結させることは、セキュリティだけでなく移植性(Windows/Linux間の互換性など)の観点からも推奨されます。
シェル呼び出しが必須な場合の安全な実装
どうしても外部コマンド呼び出しが必要な場合でも、system() や exec() のようなシェルを経由する関数ではなく、シェルを経由せずにプロセスを直接起動する関数を使用すべきです。
例えば、PerlやRuby、Pythonなど多くの言語では、引数をリスト(配列)として渡すことで、シェルを経由せずにコマンドを実行するモードを持っています。
exec(["/bin/ping", "-c", "1", address])
このように、コマンドと引数を明確に分けて渡すことで、入力値 address がどんな文字列であっても、それは単なる「引数の一部」として扱われます。区切り文字が含まれていても、それは区切り文字としての意味を持たず、ただの文字列として処理されるため、インジェクションは成立しません。

WAF(Web Application Firewall)による防御
根本的な対策はソースコードレベルで行うべきですが、多層防御の一環としてWAFの導入も有効です。
WAFは、Webアプリケーションへの通信を監視し、攻撃の特徴的なパターン(シグネチャ)が含まれているリクエストを検知・遮断します。../ や /etc/passwd、cat、| といった怪しい文字列を含むリクエストをWAFで弾くことで、万が一アプリケーションに脆弱性が残っていた場合でも、攻撃到達を防ぐことができます。
ただし、WAFはあくまで「保険」であり、すり抜けの可能性もゼロではないため、コード改修を優先すべきであることは変わりません。
開発ライフサイクルにおけるセキュリティ意識
脆弱性を作り込まないためには、設計段階からセキュリティを考慮する「セキュリティ・バイ・デザイン」の考え方が不可欠です。
入力値検証(バリデーション)の徹底
すべての入力値に対して、「正しい形式か」「想定された範囲内か」「許可された文字種か」を検証することは、セキュリティの基本中の基本です。
クライアントサイド(JavaScript)でのチェックだけでは不十分です。攻撃者はブラウザのチェックを無効化したり、Proxyツールを使って直接HTTPリクエストを送信したりできるため、必ずサーバーサイドでの厳密なチェックが必要です。
バリデーションでは、ブラックリスト方式(危険な文字を禁止する)よりもホワイトリスト方式(安全な文字のみ許可する)を優先すべきです。ブラックリストは想定外の攻撃パターンに対して脆弱ですが、ホワイトリストは明示的に許可したもの以外をすべて拒否するため、より堅牢です。
最小権限の原則
Webサーバーを実行するOSのユーザー権限を最小限に絞ることも重要です。
万が一、コマンドインジェクションによって任意のコマンドが実行されたとしても、Webサーバーの実行ユーザーが root(管理者)権限を持っていなければ、被害を最小限に抑えることができます。Webサーバーがアクセスできるディレクトリや実行できるコマンドを必要最低限に制限しておく「最小権限の原則」を徹底しましょう。
具体的には、Webサーバープロセスを専用の権限制限されたユーザーで実行し、アクセス可能なディレクトリを公開ディレクトリとアップロードディレクトリのみに限定します。また、システムコマンドの実行権限も必要最小限に絞り込むことで、たとえ攻撃を受けても影響範囲を限定できます。
セキュアコーディング標準の策定
開発チーム全体でセキュアコーディングの標準を策定し、共有することも重要です。具体的なコーディング規約、チェックリスト、レビュープロセスを確立することで、個々の開発者の知識レベルに依存しない一定品質のセキュリティを保てます。
コードレビューの際には、特にユーザー入力を扱う部分、ファイル操作を行う部分、外部コマンドを実行する部分を重点的にチェックします。これらは脆弱性が混入しやすい箇所であり、慎重な確認が必要です。
定期的な脆弱性診断とペネトレーションテスト
開発段階だけでなく、リリース後も定期的に脆弱性診断やペネトレーションテスト(侵入テスト)を実施することで、見落としていた脆弱性を発見できます。
自動化されたセキュリティスキャンツールの活用に加え、専門のセキュリティエンジニアによる手動診断を組み合わせることで、より精度の高い診断が可能になります。特に大規模なアップデートや新機能追加の際には、必ず脆弱性診断を実施すべきです。
実際の攻撃事例から学ぶ
これまで説明した脆弱性は、決して机上の理論ではありません。実際に多くのWebサイトやアプリケーションがこれらの攻撃を受け、深刻な被害を受けています。
大規模情報漏洩につながったディレクトリトラバーサル
過去には、大手企業のWebサイトにおいてディレクトリトラバーサルの脆弱性が発見され、数十万件の個人情報が漏洩した事例があります。攻撃者は画像表示機能の脆弱性を突き、データベースのバックアップファイルや設定ファイルにアクセスし、そこから顧客情報を取得しました。
この事例では、ユーザーが指定した画像ファイル名をそのままファイルシステムに渡していたため、../ を含むパスを指定することで公開ディレクトリ外のファイルにアクセスできてしまいました。適切な入力検証とパスの正規化処理があれば防げた事故です。
OSコマンドインジェクションによるサーバー乗っ取り
ネットワーク機器の管理画面において、pingコマンド実行機能にOSコマンドインジェクションの脆弱性が存在し、攻撃者がルート権限でシステムを制御できた事例もあります。この脆弱性により、攻撃者はバックドアを設置し、長期間にわたって不正アクセスを続けました。
この事例では、ping対象のIPアドレスをユーザーから受け取り、シェル経由でpingコマンドを実行していました。攻撃者はIPアドレスの後にセミコロンと任意のコマンドを追加することで、システム上で好きなコマンドを実行できました。引数を配列として渡す安全な実装方法を採用していれば、この攻撃は成立しませんでした。
フレームワークとライブラリの活用
現代のWeb開発では、セキュリティ機能が組み込まれたフレームワークやライブラリを活用することが一般的です。これらのツールは、多くの脆弱性対策を自動的に行ってくれます。
主要フレームワークのセキュリティ機能
多くのWebフレームワークには、入力値の自動エスケープ、パラメータのバリデーション、安全なファイル操作用のAPIなどが標準で備わっています。
例えば、Ruby on RailsやDjango(Python)、Laravel(PHP)などのフレームワークは、デフォルトで多くのセキュリティ対策が有効になっており、開発者が意識的に無効化しない限り安全な実装が維持されます。
ただし、フレームワークを使用していても、その機能を正しく理解し適切に使用しなければ意味がありません。フレームワークのセキュリティ機能を過信せず、常に入力値の検証とエスケープ処理を意識することが重要です。
セキュリティライブラリの導入
ファイルアップロード処理、画像処理、データベースアクセスなど、特定の機能に特化したセキュリティライブラリも多数公開されています。これらのライブラリは、専門家によって脆弱性対策が施されており、自前で実装するよりも安全性が高いことが多いです。
ただし、ライブラリ自体にも脆弱性が発見されることがあるため、定期的なアップデートと脆弱性情報のチェックが欠かせません。使用しているライブラリのバージョン管理を徹底し、脆弱性が報告されたらすぐに対応できる体制を整えましょう。
ログ監視とインシデント対応
どれだけ対策を施しても、脆弱性をゼロにすることは困難です。そのため、攻撃を早期に検知し、被害を最小限に抑えるための仕組みも重要です。
アクセスログの監視
Webサーバーのアクセスログを定期的に監視し、不審なリクエストパターンを検知することで、攻撃の兆候を早期に発見できます。
特に注目すべきパターンは次のとおりです。
../や..%2F(URLエンコードされた形式)を含むリクエスト/etc/passwdや/windows/system32/などのシステムファイルへのアクセス試行;、|、&&などのコマンド区切り文字を含むパラメータ- 異常に長いパラメータ値や特殊文字の連続
これらのパターンを検知したら、即座にアラートを発し、該当するIPアドレスをブロックするなどの対応を取ります。自動化されたログ分析ツールを導入することで、リアルタイムでの監視と対応が可能になります。
インシデント発生時の対応手順
万が一、攻撃が成功してしまった場合に備えて、インシデント対応手順を事前に策定しておくことが重要です。
対応手順には、次のような項目を含めるべきです。
- 攻撃の検知と初動対応(サービス停止、該当機能の無効化など)
- 被害範囲の調査(どの情報が漏洩したか、どのファイルが改ざんされたかなど)
- 脆弱性の修正とパッチ適用
- システムの復旧とテスト
- 影響を受けたユーザーへの通知
- 再発防止策の策定と実施
これらの手順を文書化し、定期的に訓練を実施することで、実際のインシデント発生時に迅速かつ適切な対応が可能になります。
【理解度チェック】ディレクトリトラバーサルとOSコマンドインジェクション演習問題
本記事で解説したWebアプリケーションの代表的な脆弱性、「ディレクトリトラバーサル」と「OSコマンドインジェクション」。 攻撃の仕組みや根本的な対策について、正しく理解できているでしょうか?
知識の定着を確認するために、全10問の演習問題を用意しました。 攻撃者の視点と防御の鉄則、そしてセキュアな実装方法について、クイズ形式で総復習しましょう。全問正解を目指してチャレンジしてください。
まとめ
ディレクトリトラバーサルとOSコマンドインジェクションは、Webアプリケーションの黎明期から存在する古典的な脆弱性ですが、その脅威は現代でも全く色褪せていません。むしろ、クラウドサービスの利用拡大やAPI連携の複雑化に伴い、一つの脆弱性が引き起こす連鎖的な被害のリスクは高まっています。
ディレクトリトラバーサルの要点
- パスを遡る
../によって非公開ファイルへアクセスされる - ファイル名を直接指定させない、または
basename()でディレクトリ部分を除去する - IDベースのアクセス制御を採用し、ファイルパスを直接扱わない設計にする
OSコマンドインジェクションの要点
- 入力値に紛れ込んだ区切り文字によって任意のOSコマンドが実行される
- シェル経由の関数を使わず、言語標準のライブラリやAPIを使用する
- やむを得ずコマンドを実行する場合は、引数を配列として渡してシェルを経由しない
総合的な防御策
- すべての入力値に対して厳密なバリデーションを実施する
- ホワイトリスト方式の入力検証を優先する
- 最小権限の原則に基づいてシステムを設計する
- WAFなどの多層防御を組み合わせる
- 定期的な脆弱性診断とペネトレーションテストを実施する
- ログ監視とインシデント対応手順を整備する
これらの脆弱性は、適切な知識と実装方法を知っていれば確実に防ぐことができます。「動けばいい」というコードではなく、「攻撃されても大丈夫」なコードを書くこと。その意識一つが、あなた自身とあなたのシステムを使うユーザーを守る盾となります。
日々のコーディングにおいて、ファイル操作や外部コマンド実行を行う際は、一度立ち止まって「この入力値は本当に安全か?」「ここからシステム内部に入り込まれないか?」と自問する習慣をつけましょう。セキュリティは魔法ではなく、地道な確認と正しい実装の積み重ねによってのみ実現されるのです。
開発者一人ひとりがセキュリティ意識を持ち、チーム全体で知識を共有し、組織として継続的に改善していくこと。それこそが、安全なWebアプリケーションを提供し続けるための唯一の道です。