フォームデータの送信と取得

by 2 contributors:

多くの場合、HTML フォームの用途は、データをサーバへ送信することです。サーバはそのデータを処理して、レスポンスをユーザへ返します。これはシンプルであるように見えますが、データがサーバにダメージを与えたりユーザ側でトラブルを起こしたりしないようにするため、覚えておくべきことがいくつかあります。

データはどこへ行くのか?

クライアント/サーバアーキテクチャについて

Web は以下のように要約できる、ごく基本的なクライアント/サーバアーキテクチャに基づいています: クライアント (通常は Web ブラウザ) は HTTP プロトコルを使用して、サーバ (ほとんどの場合、Web サーバは ApacheNginxIISTomcat など) にリクエストを送ります。サーバは同じプロトコルを使用して、リクエストに応答します。

A basic schema of the Web client/server architecture

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

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

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

action 属性

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

最初の例では、データは http://foo.com に送信されます。

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

次の例ではフォームを持つページを提供するサーバにデータが送信されますが、送信先はサーバ上の別の URL になります:

<form action="/somewhere_else">

以下のように属性を指定しない場合は、<form> 要素はフォームを含むページに対してデータを送信します:

<form>

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

<form action="#">

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

method 属性

この属性は、どのようにデータを送信するかを定義します。HTTP プロトコルはリクエストを実行する方法をいくつか提供します。HTML フォームのデータは少なくともそのうち 2 つを使用して送信できます: GET メソッドと POST メソッドです。

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

GET メソッド

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

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

<form action="http://foo.com" method="get">
  <input name="say" value="Hi">
  <input name="to" value="Mom">
  <button>Send my greetings</button>
</form>

GET メソッドでは、HTTP リクエストが以下のようになります:

GET /?say=Hi&to=Mom HTTP/1.1
Host: foo.com
POST メソッド

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

以下のフォームについて考えてみましょう (前出の例と同じです):

<form action="http://foo.com" method="post">
  <input name="say" value="Hi">
  <input name="to" value="Mom">
  <button>Send my greetings</button>
</form>

POST メソッドで送信するとき、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 ヘッダはサーバに送信するリソースの種類を表します。これらのヘッダについて少し説明しましょう。

当然ながら HTTP リクエストはユーザには見えません (見たいのでしたら、Firefox の Web コンソールChrome のデベロッパー ツール などのツールが必要です)。ユーザに表示されのは、呼び出された 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;

この例では、送信されたデータとともにページを表示します。前出のフォーム例によれば、出力は以下のようになります:

Hi Mom

例: Python

この例は、同じこと (与えられたデータを Web ページに表示する) を Python で行います。ここではフォームデータへのアクセスに CGI Python package を使用します。

#!/usr/bin/env python
import html
import cgi
import cgitb; cgitb.enable()     # トラブルシューティング用

print("Content-Type: text/html") # 後に HTML があることを表す HTTP ヘッダ
print()                          # 空行、ヘッダの終わり

form = cgi.FieldStorage()
say  = html.escape(form["say"].value);
to   = html.escape(form["to"].value);

print(say, " ", to)

結果は PHP と同じになります:

Hi Mom

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

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

これらのフレームワークを使用したとしても、必ずしもフォームでの作業が容易にはならないことは知っておくべきでしょう。しかしそれらはずっとよく、また多くの時間を節約できるでしょう。

特別なケース: ファイル送信

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

enctype 属性

この属性で Content-Type HTTP ヘッダの値を指定できます。このヘッダはサーバに対して送信するデータの種類を伝えることから、とても重要です。デフォルト値は application/x-www-form-urlencoded です。人の言い方では以下のようになります: "これは URL 形式でエンコードされたフォームデータです。"

しかしファイルを送信したい場合は、2 つのことが必要です:

  • フォームを使用してファイルコンテンツを URL パラメータに収めることはできませんので、method 属性を POST に設定します。
  • データはファイル 1 つずつおよび合わせて送られるであろうフォーム本体のテキストによって複数に分けられるので、enctype の値を multipart/form-data に設定します。

例:

<form method="post" enctype="multipart/form-data>
  <input type="file" name="myFile">
  <button>Send the file</button>
</form>

注意: 一部のブラウザは、ひとつの input 要素で複数のファイルを送信するために、<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 サイトに対する主要な攻撃のひとつです

データの損失から権限昇格によるインフラ全体へのアクセスまで、大きな影響が発生する可能性があります。これは重大な脅威であり、ユーザから送信されたデータをサニタイズ (例えば、PHP/MySQL 基盤の mysql_real_escape_string() を使用する) せずに保管してはいけません。

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

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

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

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

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

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

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

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

おわりに

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

関連情報

Web アプリケーションのセキュア化についてさらに学びたいのでしたら、以下のリソースをご覧いただくとよいでしょう:

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

タグ: 
Contributors to this page: ethertank, yyss
最終更新者: ethertank,