多くの場合、HTML フォームの用途は、データをサーバへ送信することです。サーバはそのデータを処理して、レスポンスをユーザへ返します。これはシンプルであるように見えますが、データがサーバにダメージを与えたりユーザ側でトラブルを起こしたりしないようにするため、覚えておくべきことがいくつかあります。
データはどこへ行くのか?
クライアント/サーバアーキテクチャについて
Web は以下のように要約できる、ごく基本的なクライアント/サーバアーキテクチャに基づいています: クライアント (通常は Web ブラウザ) は HTTP プロトコルを使用して、サーバ (ほとんどの場合、Web サーバは Apache、Nginx、IIS、Tomcat など) にリクエストを送ります。サーバは同じプロトコルを使用して、リクエストに応答します。
クライアント側において HTML フォームは、サーバへデータを送信するために HTML リクエストを組み立てるのに便利で使いやすい手段でしかありません。フォームによって、ユーザが HTTP リクエストで渡す情報を提供することが可能になります。
クライアントサイド: データの送信方法を定義する
<form>
要素で、データを送信する方法を定義します。その属性すべてが、ユーザが送信ボタンを押すと送信されるリクエストを調整できるように設計されています。もっとも重要な属性は action
と method
の 2 つです。
action
属性
この属性は、どこにデータを送信するかを定義します。値は正当な URL でなければなりません。この属性が与えられない場合は、フォームが含まれているページの URL にデータが送信されるでしょう。
例
以下の例では、データを https://foo.com に送信します:
<form action="https://foo.com">
次の例ではフォームを持つページを提供するサーバにデータを送信しますが、送信先はサーバ上の別の URL になります:
<form action="/somewhere_else">
以下のように属性を指定しない場合は、<form>
要素はフォームを含むページに対してデータを送信します:
<form>
多くの古いページでは、フォームを含むページにデータを送信することを示すために以下の表記を使用しています。これは、HTML5 より前は action
属性が必須であったためです。現在は必須ではありません。
<form action="#">
注記: HTTPS (secure HTTP) プロトコルを指定できます。このようにすると、フォーム自体が HTTP でアクセスされる安全ではないページで提供される場合でも、データはリクエストの残りの部分とともに暗号化されます。一方、フォームが安全なページで提供されていても action
属性で安全ではない HTTP の URL を指定すると、どのブラウザでもデータを送信する際にユーザに対してセキュリティの警告を表示します。これは、データが暗号化されないためです。
method
属性
この属性は、どのようにデータを送信するかを定義します。HTTP プロトコルはリクエストを実行する方法をいくつか提供します。HTML フォームのデータは少なくともそのうち 2 つを使用して送信できます: GET
メソッドと POST
メソッドです。
これら 2 つのメソッドの違いを理解するために、一歩後退して HTTP の動作についてみていきましょう。Web 上のリソースにたどり着こうとするたびに、ブラウザは URL へリクエストを送信します。HTTP リクエストは 2 つの部分で構成されます。ブラウザの機能に関する包括的なメタデータのセットを持つヘッダと、指定されたリクエストをサーバが処理するために必要な情報を持つボディです。
GET メソッド
GET
メソッドは、サーバに対して指定したリソースを返すよう求めるためにブラウザが使用するメソッドです: "サーバさん、私はこのリソースが欲しいのです。" この場合、ブラウザは空のボディを送信します。ボディが空であるため、フォームをこのメソッドで送信する場合はデータを URL に付加します。
例
以下のフォームについて考えてみましょう:
<form action="https://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="https://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 つの理由でとても重要です:
- パスワード (あるいは何らかの機密データ) を送信しなければならないなら、
GET
メソッドを使用してはいけません。データが URL バーに表示される危険性があります。 - 大量のデータの送信が必要であるなら、
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
その他の言語やフレームワーク
フォームの操作に使用できるサーバサイドの技術は Perl、Java、.Net、Ruby などたくさんあります。もっとも好きなものを選びましょう。しかしそれらの技術を直接使用することは、扱いにくいため一般的ではないことが特筆に値します。以下のような、フォームをより簡単に扱えるようにする多くのフレームワークのひとつを使用する方がより一般的です:
- PHP 用 Symfony
- Python 用 Django
- Ruby 用 Ruby On Rails
- Java 用 Grails
- 等々
これらのフレームワークを使用したとしても、必ずしもフォームでの作業が容易にはならないことは知っておくべきでしょう。しかしそれらはずっとよく、また多くの時間を節約できるでしょう。
特別なケース: ファイル送信
ファイルは 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>
要素で複数のファイルを送信するために、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 アプリケーションのセキュア化についてさらに学びたいのでしたら、以下のリソースをご覧いただくとよいでしょう: