この記事は、ユーザがフォームを送信したときに何が起こるか、つまりデータがどこへ行くのか、そこに来たときにどう扱うのかを見ます。また、フォームデータの送信に関連するセキュリティの考慮事項のいくつかも見てみます。

前提知識: 基本的なコンピュータリテラシー, HTML の理解, HTTP 及び サーバサイドプログラミングの基本的な知識。
目標: フォームデータが送信されたら何が起こるかを、データがサーバ上でどのように処理されるかの基本的な考えも含めて理解すること。

データはどこへ行くのか

ここでは、フォームが送信されたときにデータに何が起こるかを考えてみましょう。

クライアント/サーバ構成について

Web はごく基本的なクライアント/サーバ構成に基づいており、簡単に言うと次のようになります。クライアント (通常はWeb ブラウザ) は HTTP プロトコルを使用して、サーバ (ほとんどの場合、Web サーバは ApacheNginxIISTomcat など) にリクエストを送ります。サーバは同じプロトコルを使用して、リクエストに応答します。

基本的なクライアント/サーバー構成

クライアント側において、 HTML フォームはサーバへデータを送信するために HTTP リクエストを組み立てるのために、便利でユーザに使いやすい手段でしかありません。フォームによって、ユーザが HTTP リクエストで渡す情報を提供することができるようになります。

メモ: どのようにクライアント/サーバ構成が動作するかについてもっと知りたい場合は、サーバサイド Web サイトプログラミング入門モジュールをお読みください。

クライアント側: データ送信方法の定義

<form> 要素で、データを送信する方法を定義します。その属性すべてが、ユーザが送信ボタンを押すと送信されるリクエストを調整できるように設計されています。もっとも重要な属性は actionmethod の二つです。

action 属性

この属性は、どこにデータを送信するかを定義します。値は妥当な URL でなければなりません。この属性が与えられなかった場合は、フォームが含まれているページの URL にデータが送信されます。

このの例では、データを絶対 URL の http://foo.com に送信します。

<form action="http://foo.com">

こちらは、相対 URL を使用しています。データはサーバ上の別の URL に送信されます。

<form action="/somewhere_else">

以下のように属性を指定しない場合は、 <form> 要素はフォームが表示されているページ自身に対してデータを送信します。

<form>

多くの古いページでは、フォームを含むページ自身にデータを送信することを示すために、以下の表記を使用しています。これは、 HTML5 より前は action 属性が必須であったためです。現在は必須ではありません。

<form action="#">

メモ: HTTPS (secure HTTP) プロトコルを使用して URL を指定することができます。このようにすると、フォーム自体が HTTP でアクセスされる安全ではないページで提供される場合でも、データはリクエストの残りの部分とともに暗号化されます。一方、フォームが安全なページで提供されていても、 action 属性で安全ではない HTTP の URL を指定すると、どのブラウザでもデータを送信する際にユーザに対してセキュリティの警告を表示します。これは、データが暗号化されないためです。

method 属性

この属性は、どのようにデータを送信するかを定義します。 HTTP プロトコルはリクエストを実行するための方法をいくつか提供しています。 HTML フォームのデータは複数の方法で送信することができます。もっとも一般的なものは GET メソッドと POST メソッドです。

これら二つのメソッドの違いを理解するために、一歩戻って HTTP の動作についてみていきましょう。Web上のリソースにたどり着こうとするたびに、ブラウザは URL へリクエストを送信します。 HTTP リクエストは2つの部分で構成されます。ブラウザの機能に関する包括的なメタデータのセットを持つヘッダーと、指定されたリクエストをサーバが処理するために必要な情報を持つ本文です。

GET メソッド

GET メソッドは、サーバに対して指定したリソースを返すよう求めるためにブラウザが使用するメソッドです。 "やあサーバ、このリソースをくれよ。" この場合、ブラウザは空の本文を送信します。本文が空であるため、フォームをこのメソッドで送信する場合はデータを URL に付加します。

以下のフォームについて考えてみましょう。

<form action="http://foo.com" method="get">
  <div>
    <label for="say">What greeting do you want to say?</label>
    <input name="say" id="say" value="Hi">
  </div>
  <div>
    <label for="to">Who do you want to say it to?</label>
    <input name="to" id="to" value="Mom">
  </div>
  <div>
    <button>Send my greetings</button>
  </div>
</form>

GET メソッドが使用されているので、フォームを送信するときにブラウザのアドレスバーに www.foo.com/?say=Hi&to=Mom という URL が見えるでしょう。

URL に追加されたデータは名前/値の組の連続です。 URL のWebアドレスが終了した後、疑問符 (?) に続いて、名前/値の組が、それぞれアンパサンド (&) で区切られて入ります。この場合、2つのデータの断片がサーバに渡されます。

  • say の値は Hi
  • to の値は Mom

HTTP リクエストは次のようになります。

GET /?say=Hi&to=Mom HTTP/1.1
Host: foo.com

メモ: この例は GitHub にあります。 — get-method.html を参照してください (ライブはこちら).

POST メソッド

POST メソッドは少し異なります。これは、 HTTP リクエストの本文で提供したデータを考慮したレスポンスの要求を、ブラウザがサーバに送信するためのメソッドです。 "やあサーバ、このデータを見て適切な結果を返してよ。" このメソッドを使用してフォームを送信する場合は、データが HTTP リクエストの本文に追加されます。

例を見てみましょう。 — これは前述の GET の節で見たものと同じフォームですが、 method 属性が post に設定されています。

<form action="http://foo.com" method="post">
  <div>
    <label for="say">What greeting do you want to say?</label>
    <input name="say" id="say" value="Hi">
  </div>
  <div>
    <label for="to">Who do you want to say it to?</label>
    <input name="to" id="to" value="Mom">
  </div>
  <div>
    <button>Send my greetings</button>
  </div>
</form>

フォームをが POST メソッドで送信されると、URL にはデータが追加されず、 HTTP リクエストは次のように、リクエスト本文にデータが含まれた形になります。

POST / HTTP/1.1
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

say=Hi&to=Mom

Content-Length ヘッダーは本文の長さを、また Content-Type ヘッダーはサーバに送信するリソースの種類を表します。これらのヘッダーについて少し説明しましょう。

メモ: この例は GitHub で見つけることができます。 — post-method.html を参照してください (ライブ版も見てください)。

HTTP リクエストの表示

当然ながら HTTP リクエストはユーザには表示されません (見たいのであれば、 Firefox ネットワークモニターChrome デベロッパー ツールなどのツールが必要です)。例のように、フォームのデータは Chrome の Network タブに以下のように表示されます。フォームの送信後に、以下のように操作してください。

  1. F12 を押す
  2. "Network" を選択
  3. "All" を選択
  4. "Name" タブから "foo.com" を選択
  5. "Headers" を選択

これで下の画像にあるように、フォームデータを取得することができます。

ユーザに表示されるのは呼び出された URL のみです。前述のように、 GET リクエストはユーザが URL バーの中でデータを見ることができますが、 POST リクエストではそうではありません。これは2つの理由でとても重要です。

  1. パスワード (あるいは何らかの機密データ) を送信する必要がある場合は、 GET メソッドを使用してはいけません。データが URL バーに表示されるリスクがあり、とても危険です。
  2. 大量のデータを送信する必要があるなら、 POST が好ましいメソッドです。これは、URL の長さ制限があるブラウザが存在するためです。加えて、多くのサーバは受け入れる URL の長さを制限しています。

サーバ側: データの取得

どちらの HTTP メソッドを選択しても、サーバが受け取る文字列は、キー/値の組のリストとしてデータを取得するために解析されます。このリストにアクセスする方法は、使用する開発プラットフォームや、使用するであろう特定のフレームワークに依存します。使用する技術により、重複するキーの扱いも決まります。たいてい、指定されたキーについてもっとも直近に受け取った値を優先します。

PHP の例

PHP は、データにアクセスするためのグローバルオブジェクトを提供します。 POST メソッドを使用したと仮定すると、データを取得してユーザに表示する例は以下のとおりです。もちろん、データに対して何をするかはあなた次第です。データを表示したり、データベースに保管したり、メールで送信したり、他の手段で処理したりするでしょう。

<?php
  // $_POST グローバル変数は、 POST メソッドで送信されたデータへ名前でアクセスを可能にする
  // GET メソッドで送信されたデータにアクセスするには、$_GET が使用できる
  $say = htmlspecialchars($_POST['say']);
  $to  = htmlspecialchars($_POST['to']);

  echo  $say, ' ', $to;
?>

この例では送信されたデータを含むページを表示します。これはサンプルの php-example.html ファイル、つまり以前 methodpostactionphp-example.php の時に見たサンプルフォームを含むファイルアクションの中で見ることができます。送信されると、フォームデータは上記のブロックの PHP コードを含む php-example.php へ送信されます。コードが実行されると、ブラウザの出力は Hi Mom になります。

メモ: この例はブラウザにローカルに読み込んだ時には動作しません。 — ブラウザは PHP コードを解釈できないので、フォームがブラウザに送信されると、 PHP ファイルをダウンロードしようとするでしょう。動作させるためには、この例を何らかの PHP サーバ経由で実行する必要があります。ローカルの PHP のテストには、 MAMP (Mac 及び Windows) や AMPPS (Mac, Windows, Linux) がいいでしょう。

Python の例

この例は、同じこと (与えられたデータをWebページに表示する) を Python で行います。 これはテンプレートの表示やフォームデータの受付などのために Flask フレームワークを使用しています (python-example.py を参照してください)。

from flask import Flask, render_template, request
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def form():
    return render_template('form.html')

@app.route('/hello', methods=['GET', 'POST'])
def hello():
    return render_template('greeting.html', say=request.form['say'], to=request.form['to'])

if __name__ == "__main__":
    app.run()

次のように、上記のコードでは2つのテンプレートが参照されます。

  • form.html: 以前に POST メソッドの節で見たフォームと同じですが、 action{{ url_for('hello') }} に設定されています (これは Jinja2 テンプレートで、基本的に HTML ですが、波括弧の中にWebサーバで実行されている Python のコードの呼び出しを含めることができます。 url_for('hello') は基本的に、「フォームが送信されたら /hello にリダイレクトしてください」と言っています。)
  • greeting.html: このテンプレートは、表示時に渡された2つの小さいデータを表示する行だけを含みます。 /hello の URL が呼び出されるときに実行される、前述の hello() 関数によって行われます。

メモ: 繰り返しますが、このコードはブラウザに直接読み込もうとしても動作しません。 Python は PHP とは若干異なる動作をします。 — ローカルでこのコードを実行するには、 Python/PIP をインストールする必要があり、それから pip3 install flask を使用して Flask をインストールしてください。この時点で python3 python-example.py を実行し、ブラウザで localhost:5000 に移動することで実行することができるでしょう。

その他の言語やフレームワーク

フォームの操作に使用できるサーバ側の技術は、Perl、 Java、 .Net、 Ruby などたくさんあります。もっとも好きなものを選びましょう。しかしそれらの技術を直接使用することは、扱いにくいため一般的ではないことが特筆に値します。以下のような、フォームをより簡単に扱えるようにする多くのフレームワークのひとつを使用する方がより一般的です:

言うまでもなく、これらのフレームワークを使用したとしても、フォームでの作業が容易なるとは限りません。しかし、すべての機能を自分で1から書こうとするよりずっと簡単で、また多くの時間を節約できるでしょう。

メモ: サーバ側言語やフレームワークまで説明することはこの記事の範囲を超えます。上記のリンクが参考になりますので、学習してみてください。

特別な場合: ファイル送信

ファイルは HTML フォームで特別なケースです。他のデータがすべてテキストデータである中、ファイルはバイナリデータ (あるいはそのように考えられるデータ) です。 HTTP はテキストのプロトコルであるため、バイナリデータを扱うための特別な要件があります。

enctype 属性

この属性で Content-Type HTTP ヘッダーの値を指定できます。このヘッダーはサーバに対して送信するデータの種類を伝えることから、とても重要です。既定値は application/x-www-form-urlencoded です。人間の言葉では、「これは URL 形式でエンコードされたフォームデータです。」という意味です。

しかしファイルを送信したい場合は、さらに2つのステップを踏む必要があります。

  • ファイルの内容は URL 引数に収めることができないので、 method 属性を POST に設定してください。
  • データは複数の部分に分かれ、それぞれのファイルや文字列データがフォームがフォーム本体に含められているので、 enctype の値を multipart/form-data に設定ください。
  • ユーザがアップロードするファイルを選択できるように、1つ以上のファイル選択ウィジェットを含めてください。

例:

<form method="post" enctype="multipart/form-data">
  <div>
    <label for="file">Choose a file</label>
    <input type="file" id="file" name="myFile">
  </div>
  <div>
    <button>Send the file</button>
  </div>
</form>

メモ: ブラウザによっては、ひとつの <input> 要素で複数のファイルを送信するために、multiple 属性に対応しています。送信されたファイルをサーバで処理する方法は、サーバ側で使用する技術に強く依存します。先に述べたように、フレームワークを使用すると作業がとても容易になるでしょう。

警告: 多くのサーバは悪用を防ぐために、ファイルや HTTP リクエストの長さを制限しています。ファイルを送信する前に、この制限をサーバ管理者に確認することが重要です。

一般的なセキュリティへの配慮

サーバにデータを送信するたびに、セキュリティについて考える必要があります。 HTML フォームはサーバに対するもっともよくある攻撃の入口 (攻撃が行われる場所) になります。問題が HTML フォーム自身から発生することはありません — サーバがどのようにデータを扱うかによります。

何をしているかによりますが、遭遇する可能性のある有名なセキュリティの問題がいくつかあります。

XSS と CSRF

クロスサイトスクリプティング (XSS) とクロスサイトリクエストフォージェリ (CSRF) はよくある種類の攻撃で、ユーザが送信してユーザに戻ってきたデータや、他のユーザに送信されたデータを表示するときに発生します。

XSS は、攻撃者がユーザが見ているWebページにクライアント側スクリプトを注入できるようになっていることです。クロスサイトスクリプティングの脆弱性は、攻撃者が同一オリジンポリシー等のアクセス制限を回避するために使用されることがあります。この攻撃の影響は、ささいな迷惑行為から重大なセキュリティ問題にまで及びます。

CSRF は同じ方法 (Webページにクライアント側スクリプトを注入する) で攻撃が始まる点は XSS 攻撃と似ていますが、攻撃目標が異なります。 CSRF の攻撃者は実施できない操作 (データを認証されていないユーザに送信するなど) を実行するために、高い権限のユーザ (サイト管理者など) への権限昇格を試みます。

XSS 攻撃はユーザからのWebサイトへの信頼を悪用するのに対して、CSRF 攻撃はWebサイトからのユーザへの信頼を悪用します。

これらの攻撃を防ぐには、サーバにユーザが送信するデータを常に確認するべきであるとともに、 (データを表示することが必要であれば) ユーザから提供された HTML コンテンツをそのまま表示しないようにしましょう。代わりに、ユーザが提供したデータをそのまま表示しないように処理するべきです。現在出回っているフレームワークのほぼすべてが、ユーザが送信したデータから HTML の <script>, <iframe>, <object> 要素を取り除く最低限のフィルタを実装しています。これは危険性の軽減に有用ですが、それを必ずしも根絶できるものではありません。

SQL インジェクション

SQL インジェクションは攻撃の一種で、対象のWebサイトで使用されるデータベースに対して操作を行おうと試みるものです。これはふつう、 (ふつうはユーザが送信したデータをアプリケーションサーバが格納しようとするときに) サーバで実行されることを期待して SQL リクエストを含めるものです。実際これは、Webサイトに対する主要な攻撃のひとつです

その結果は重大で、データの損失から権限昇格を使用したWebサイトのインフラ全体の制御権の取得までが発生するおそれがあります。これは重大な脅威であり、ユーザから送信されたデータを無害化 (例えば、 mysqli_real_escape_string() を使用) せずに保管してはいけません。

HTTP ヘッダーインジェクションと電子メールインジェクション

この種類の攻撃は、アプリケーションが HTTP ヘッダーを組み立てたりユーザがフォームに入力したデータに基づいて電子メールを作成したりするときに発生する可能性があります。サーバやユーザに対して直接被害を与えることはありませんが、セッションハイジャックやフィッシング攻撃といった、より深刻な攻撃につながります。

これらの攻撃はたいてい静かに行われ、サーバがゾンビ (日本語版) と化す可能性があります。

疑い深くあれ: ユーザを信用してはいけません

さて、これらの脅威に対してどう対抗するのでしょうか? これは本ガイドの内容を超える話題です。それでも、覚えておくとよいルールがいくつかあります。もっとも重要なルールは、自分自身も含めユーザを決して信用してはならないことです。信頼されているユーザでさえハイジャックされるかもしれません。

サーバに来るすべてのデータを確認およびサニタイズしなければなりません。いつでもです。例外はありません。

  • 潜在的に危険な文字をエスケープします。注意すべき具体的な文字は、データが使用される状況や使用するサーバ基盤に大きく依存しますが、どのサーバ側言語もそのための機能を持っています。
  • 入力データの量を、必要なサイズまでしか受け入れないように制限します。
  • アップロードされたファイルをサンドボックス化します (ファイルを別のサーバに保管して、別のサブドメインまたはよりよい方法としてまったく別のドメインを通してのみアクセスを許可します)。

これら3つのルールに従うと、多くのあるいはほとんどの問題を避けられるでしょう。ただし、適格の第三者によるセキュリティレビューを受けることもよい考えです。発生し得る問題のすべてを見いだしたとは考えないようにしてください。

メモ: サーバ側の学習トピックにおけるWebサイトセキュリティの記事は、上記の脅威や解決の可能性についてより詳しく説明しています。

おわりに

ご覧いただいたように、フォームデータの送信は簡単ですが、アプリケーションを安全にするのは容易ではありません。フロントエンドの開発者はデータのセキュリティモデルを定義すべき者ではないことを忘れないようにしてください。今後見ていくようにクライアント側でのデータ検証も可能ですが、クライアント側で実際に何が起きているかを知ることはできませんので、サーバ側でその検証内容を信用することはできません。

関連情報

Webアプリケーションのセキュア化についてさらに学びたいのでしたら、次のリソースをよく読んでください。

このモジュール

ドキュメントのタグと貢献者

最終更新者: silverskyvicto,