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

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

データはどこへ行くのか

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

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

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

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

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

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

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

<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 の動作についてみていきましょう。ウェブ上のリソースにたどり着こうとするたびに、ブラウザーは 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 のウェブアドレスが終了した後、疑問符 (?) に続いて、名前/値の組が、それぞれアンパサンド (&) で区切られて入ります。この場合、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 の例

この例は、同じこと (与えられたデータをウェブページに表示する) を 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 ですが、波括弧の中にウェブサーバーで実行されている 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 は、攻撃者がユーザーが見ているウェブページにクライアント側スクリプトを注入できるようになっていることです。クロスサイトスクリプティングの脆弱性は、攻撃者が同一オリジンポリシー等のアクセス制限を回避するために使用されることがあります。この攻撃の影響は、ささいな迷惑行為から重大なセキュリティ問題にまで及びます。

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

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

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

SQL インジェクション

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

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

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

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

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

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

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

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

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

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

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

おわりに

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

関連情報

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

このモジュール内の文書

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

このページの貢献者: mfuji09, chrisdavidmills, yyss, ethertank
最終更新者: mfuji09,