方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

【Webセキュリティ】HTTPとセッション管理, GET/POST, 認証と認可, Basic認証, Cookie, 同一オリジンポリシー

この記事は約22分で読めます。

はじめに

Webセキュリティに関しても備忘録まとめていく。

2018年に購入していたにも関わらず、途中までで読みきれていなかった「安全なWebアプリケーションの作り方」より。

脆弱性が生まれる理由

主に2種類

1)バグによるもの

  • SQLインジェクション
  • XSS(クロスサイト・スクリプティング)

2)チェック機能不足

  • ディレクトリ・トラバーサル

HTTPとセッション管理

HTTP通信はクライアント(通常はウェブブラウザ)とサーバー間のリクエストとレスポンスのプロセスで構成される。ステートレス(サーバー側では状態を保持しない)

ウェブブラウザがウェブページをリクエストすると、サーバーは適切なレスポンスを返す。

GETメソッド

リクエストメッセージ

ShellScript
# リクエストライン
GET / HTTP/1.1
# ヘッダ-
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Host: www.example.com

リクエストライン:リクエストメソッド(GET)・パス・プロトコルバージョンからなる。

  • リクエストメソッド: この例では GET。サーバーに特定のリソースを要求する。
  • パス(URL): /。これはルートディレクトリを指し、ウェブサイトのホームページを要求している。通常はホスト名(今回だとwww.example.com)を含まない
  • プロトコルバージョン: HTTP/1.1

ヘッダー

  • リクエストメッセージの2行目以降。名前と値をコロン「:」で区切った形
  • リクエストに関する追加情報。 Host はリクエストが送られるサーバーのドメインを示し、User-Agent はリクエストを行っているクライアントの情報を提供する。
  • 必須なのはHostだけ。

HTTPレスポンス

ShellScript
HTTP/1.1 200 OK
Date: Mon, 23 May 2024 22:38:34 GMT
Server: Apache/2.4.1 (Unix)
Content-Type: text/html; charset=UTF-8
Content-Length: 155
Last-Modified: Wed, 20 May 2024 22:11:15 GMT
Connection: close
<html>
<head>
<title>An Example Page</title>
</head>
<body>
Hello World, this is a very simple HTML document.
</body>
</html>

ステータスライン

  • HTTP/1.1 200 OK。これはプロトコルバージョン、ステータスコード(200)、ステータスメッセージ(OK)を示す。200はリクエストが成功したことを意味する。
    安全なWebアプリケーションの作り方

レスポンスヘッダー:

  • レスポンスメッセージの2行目以降
  • サーバーに関する情報とレスポンスのメタデータ。 Content-Type はレスポンスのMIMEタイプを示し、Content-Length はレスポンスのコンテンツのサイズをバイトで示す。
  • MIME(Multipurpose Internet Mail Extensions)タイプは、データの種類や形式を識別するための標準化された識別子。MIMEタイプは通常、データの種類とサブタイプの組み合わせで表される。テキスト文書のMIMEタイプは text/plain であり、JPEG画像のMIMEタイプは image/jpeg
    安全なWebアプリケーションの作り方

ボディ

  • 実際のコンテンツ。この例ではHTMLドキュメント

POSTリクエスト:Referer / パーセントエンコーディング

  • クライアントからサーバーへデータを送信するために使用される。
  • 通常、フォームの送信、ファイルのアップロード、またはウェブアプリケーションに対する命令を送る際に使われる

例)ユーザーの名前が「田中 太郎」で年齢が「30」、そして送信ボタンをクリックした場合

ShellScript
POST /submit-form HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 58
Referer: http://www.example.com/form.html

name=%E7%94%B0%E4%B8%AD%20%E5%A4%AA%E9%83%8E&age=30&submit=Submit

リクエストライン

  • POST /submit-form HTTP/1.1 は、/submit-formパスにPOSTメソッドを使用し、HTTPバージョン1.1プロトコルを通じてリソースを要求することを示す。

ヘッダー

  • Content-Type: application/x-www-form-urlencoded は、フォームデータがURLエンコードされていることを意味する。Content-Length はボディの長さを示しす。

Referer

  • http://www.example.com/form.html は、リクエストが発生した元のページ(リファラー)を示す。これはサーバーがどのページからリクエストが来たかを知るのに役立つ。
  • refererがセキュリティ上の問題になるのは、URLが秘密情報を含んでいる場合。典型的にはURLがセッションIDを含んでいる場合、Referer経由で外部に漏洩して、成りすましに悪用される可能性がある。

パーセントエンコーディング:

  • URL内で使用できない文字や、特別な意味を持つ文字をエンコードするためのメカニズム
  • メッセージボディの中で、田中 太郎%E7%94%B0%E4%B8%AD%20%E5%A4%AA%E9%83%8E にパーセントエンコーディングされている。ここで「田」は %E7%94%B0、「中」は %E4%B8%AD、「太」は %E5%A4%AA、「郎」は %E9%83%8E に、そしてスペースは %20 に変換されている。これにより、日本語や他の特殊文字が含まれるデータでも、HTTPリクエストを介して正確に送信することが可能になる。

GETとPOSTの使い分け

  • データ更新など副作用を伴うリクエストの場合→POST
  • 秘密情報を送信する場合→POST(GETの場合、URL上に指定されたパラメーターが流出する危険があるため)
  • 送信するデータの総量が多い場合→ORT
  • 参照(リソースの取得)のみ→GET

hiddenパラメーター

利用者自身により書き換えできる。情報漏洩や第三者からの書き換えに対しては堅牢

HTMLフォームのhiddenパラメーターは、クライアントサイドで書き換え可能。これらのパラメーターは、フォームが表示されているページのHTMLソースに存在するため、ユーザーがブラウザの開発者ツールを使用すると見たり、変更したりできる。

HTML
<form action="submit.php" method="post">
    <input type="hidden" name="user_id" value="12345">
    <input type="text" name="comments">
    <input type="submit" value="Submit">
</form>

認証と認可の違い

認証(authentication):利用者が確かに本人であることを何らかの手段で確認すること

認可(authorization):認証ずみの利用者に権限を与えること。具体的には、データの参照・更新・削除や物品の購入など

Basic認証

HTTPプロトコルで定義された、最もシンプルな認証の形式の1つ。ステートレス

ユーザー名とパスワードを組み合わせてサーバーに送信することで、ユーザーの身元を確認する方法

安全なWebアプリケーションの作り方

具体的な動作の流れ

  1. ユーザーが保護されたウェブリソースにアクセスしようとする。
  2. サーバーは認証が必要であることを示すレスポンス(ステータスコード 401 Unauthorized)をブラウザに送信し、WWW-Authenticate ヘッダーに Basic 認証を要求する。
  3. ブラウザはユーザーにユーザー名とパスワードの入力を求めるダイアログボックスを表示する。
  4. ユーザーが認証情報を入力すると、ブラウザはその情報を username:password の形式で結合し、この文字列をBase64エンコードして、再度サーバーにリクエストを送信する。このエンコードされた文字列は、Authorization ヘッダーに Basic の後に続けて設定される。

例えば、ユーザー名が user 、パスワードが pass の場合、これらを user:pass という形に結合し、Base64でエンコードする。Base64エンコードされた user:passdXNlcjpwYXNz になる。

したがって、HTTPリクエストのヘッダーには以下のように記載される:

ShellScript
Authorization: Basic dXNlcjpwYXNz

Base64エンコードは、情報を隠すためのものではなく、バイナリデータ(ここではuser:password という文字列はバイナリデータとして扱われ(内部的には文字列もバイナリデータとしてコンピュータに保存されている)、Base64エンコードによって安全に転送できるテキスト形式に変換される)をテキストフォーマットにエンコードするためのもの。そのため、誰でもこの文字列をデコードして元のユーザー名とパスワードを取得することが可能。Base64エンコードされた dXNlcjpwYXNz をデコードすると、user:pass に戻る。

このため、Basic認証はHTTP通信が暗号化されていない場合、中間者攻撃によってユーザー名とパスワードが盗まれる危険性がある。そのため、通常はHTTPSと組み合わせて使用し、通信内容を暗号化することが推奨される。

セッション管理

HTTP認証(Basic認証やDigest認証)を使えば、ブラウザ側でIDとパスワードを記憶してくれる(ブラウザがユーザーの認証情報をメモリに一時的に保持するため)が、HTTP認証を使わない場合は、サーバー側で認証状態を覚えておく必要がある

→セッション管理という概念が登場

ブラウザが認証情報を記憶するのは通常、そのブラウザウィンドウが開いている間、つまりセッション中のみ。ブラウザを完全に閉じて新しいセッションを開始すると、再度認証情報の入力が求められる。

クッキー(cookie)

  • サーバー側からブラウザ側に対して「名前=変数」の組を覚えておくように指示するもの。ブラウザはクッキーデータをそのユーザーのデバイス上の特定の場所にファイルとして保存する(HTTP認証のみだと、ブラウザ上にしかユーザー認証情報は保存されないので、ユーザーがブラウザを閉じると認証情報も消去される)

例えばGoogle ChromeブラウザをmacOSで使用している場合、クッキーデータは以下の場所に保存される。

ShellScript
~/Library/ApplicationSupport/Google/Chrome/Default/Cookies

ただし、これらのファイルは通常のテキストエディタでは読むことができず、クッキーを閲覧・管理するにはブラウザの設定メニューや専用のツールを使用する必要がある。

  • セッション管理をHTTPで実現する目的で導入された。
  • クッキーは主に、ユーザーのセッション情報や設定、ログイン状態などを記録し、ユーザーがWebサイトを訪れるたびにこれらの情報をサーバーに送り返すことで、パーソナライズされた体験やセッションの維持を可能にする。
  • クッキーはブラウザごとに管理される。つまり、同じコンピュータで異なるブラウザを使用している場合、各ブラウザはそれぞれ独立したクッキーを保持している。

ログイン時のHTTPレスポンス例:

ユーザーがIDとパスワードを入力してログインボタンをクリックすると、サーバーは以下のようなHTTPレスポンスを返す。

ShellScript
HTTP/1.1 200 OK
Content-Type: text/html
<span style="background-color: rgb(251, 243, 219);">Set-Cookie: session_id=abc123; Path=/; HttpOnly</span>

このレスポンスに含まれるSet-Cookieヘッダーは、ブラウザにsession_idという名前のクッキーを設定するように指示している。このクッキーにはabc123という値が格納され、これによりユーザーセッションが一意に識別される。Path=/はこのクッキーがウェブサイトのすべてのページで有効であることを示し、HttpOnly属性はクッキーがJavaScriptからアクセスできないようにするためのもの。

再訪問時のHTTPリクエスト例:

ユーザーがログイン後、または別の日に同じサイトを訪れると、ブラウザは以下のようなHTTPリクエストをサーバーに送信する。

ShellScript
GET /index.html HTTP/1.1
Host: www.example.com
<span style="background-color: rgb(251, 243, 219);"><strong>Cookie: session_id=abc123</strong></span>

このリクエストに含まれるCookieヘッダーは、前回の訪問時に設定されたクッキーをサーバーに送り返している。サーバーはこのsession_idクッキーを使用して、ユーザーのセッションを識別し、ユーザーが以前に行った操作や設定を復元することができる。これにより、ユーザーは再度ログインすることなく、サイトをパーソナライズされた形で利用できるようになる。

クッキーの属性:Domain, Secure, HttpOnly

Set-Cookie - HTTP | MDN
The Set-Cookie HTTP response header is used to send a cookie from the server to the user agent, so that the user agent c...

クッキーを発行する際には、様々なオプションの属性を設定できる。

セキュリティ上重要な属性は、Domain, Secure, HttpOnlyの3つ

安全なWebアプリケーションの作り方

Domain属性:デフォルトON

  • 異なるドメインに対するクッキーが設定できると、セッションIDの固定化攻撃の手段として利用されるので、通常は異なるドメインに対するクッキー設定はしない。
  • デフォルトもOFF(つまり、現在のドキュメントのドメインにのみ関連付けられる)になっているので、そのままにしておく

Secure属性:デフォルトOFF

  • Secure属性をつけたクッキーは、HTTP通信の場合のみサーバーに送信される。基本的につける
  • この属性が設定されていない場合、クッキーはHTTPおよびHTTPSの両方のリクエストで送信される

HttpOnly属性:デフォルトOFF

  • XSS(クロスサイト・スクリプティング)攻撃により、JavaScriptを悪用してクッキーを盗み出すリスクを減らすために、基本的につける

その他

Path属性

  • この属性が設定されていない場合、クッキーはクッキーが設定されたウェブページのパスとサブパスに対して有効になる。パスを指定すると、そのパス以下のページでのみクッキーが利用可能になる。

Expires属性

  • この属性が設定されていない場合、クッキーは「セッションクッキー」となり、ブラウザが閉じられると削除される。ExpiresまたはMax-Ageを設定することで、クッキーに有効期限を設けることができる。
  • Expires 属性を使用してクッキーの有効期限を設定する場合、特定の日時をGMT(グリニッジ標準時)で指定する指定する必要がある。
ShellScript
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure
  • Expires属性を指定しない場合、クッキーはブラウザが閉じられると削除されるので、ユーザーの視点から見た場合(ユーザー体験)には、HTTP認証のみのブラウザでの認証保存と変わらない。

受動的攻撃と同一オリジンポリシー

同一オリジンポリシー(same origin policy)

  • ブラウザのサンドボックスに用意された制限の一つ
  • JavaScriptなどのクライアントスクリプトからサイトを跨ったアクセスを禁止する

攻撃のシナリオとして、サイトA(罠サイト)にユーザーを誘導し、そこに埋め込まれた<iframe>を介してサイトB(例えば、ユーザーがログインしている銀行のウェブサイトなど)の情報を読み取ろうとするケースを考えてみる

具体的な攻撃シナリオ:

ユーザーを罠サイトに誘導する:

  • 攻撃者はユーザーを罠サイト(サイトA)に誘導する。フィッシングメール、ソーシャルメディアを通じたリンク共有、あるいは検索エンジン最適化(SEO)の悪用などの手法を使用。

<iframe>を利用した攻撃:

  • 罠サイトには、サイトBのコンテンツを表示する<iframe>が含まれている。攻撃者は、JavaScriptを使用してこの<iframe>内のデータにアクセスしようと試みる。

サイトAのHTMLコードの例:

HTML
<html>
<body>
  <h1>罠サイトにようこそ!</h1>
  <iframe src="http://siteB.com" id="targetFrame"></iframe>
  <script>
    // JavaScriptによる攻撃コード
    var iframe = document.getElementById('targetFrame');
    try {
      var innerDoc = iframe.contentDocument || iframe.contentWindow.document;
      // ここでiframe内のデータにアクセスしようと試みる
      console.log(innerDoc.body.innerHTML);
    } catch (e) {
      console.error('アクセスできません: ', e);
    }
  </script>
</body>
</html>

しかし、同一オリジンポリシーにより、異なるオリジン(サイトAとサイトB)間でのこのようなアクセスはブロックされ、攻撃は失敗する。

ただし、XSSはJavaScriptを送り込むことで、同一オリジンポリシー下でJavaScriptを実行し、コンテンツを盗み出すことができてしまう。

同一オリジンポリシーにおいて、「オリジン」はスキーム(プロトコル)、ホスト(ドメイン名)、ポート番号の3つがすべて一致する場合に、2つのリソースは「同一オリジン」とみなされる。

例1:同一オリジンの場合

  • リソース1: http://example.com:80/page1.html
  • リソース2: http://example.com:80/page2.html

これらはプロトコル(http)、ホスト(example.com)、ポート番号(80)がすべて一致しているため、同一オリジン。

例2:ホストが異なる場合(異なるオリジン)

  • リソース1: http://example.com/page1.html
  • リソース2: http://sub.example.com/page2.html

サブドメインもホストの一部として扱われるので、example.comsub.example.comは異なるホストのため、異なるオリジンとみなされる。

例3:プロトコルが異なる場合(異なるオリジン)

  • リソース1: http://example.com/page1.html
  • リソース2: https://example.com/page2.html

プロトコルが一つはhttpで、もう一つはhttpsなので、これらは異なるオリジン。

例4:ポート番号が異なる場合(異なるオリジン)

  • リソース1: http://example.com:80/page1.html
  • リソース2: http://example.com:8080/page2.html

ポート番号が一つは80で、もう一つは8080なので、これらは異なるオリジン。

CORS(Cross-Origin Resource Sharing)

ウェブページが別のオリジン(ドメイン、スキーム、ポート)からリソースをリクエストできるようにするためのメカニズム。

通常、ブラウザの同一オリジンポリシーは異なるオリジン間でのリソース共有を制限するが、CORSは特定の条件下でこれを緩和する。

CORSで中心的な役割を果たすのがAccess-Control-Allow-Originレスポンスヘッダー。このヘッダーは、サーバーからクライアントに送られ、特定のオリジンがリソースにアクセスすることを許可するかどうかをブラウザに指示する。

Access-Control-Allow-Originの動作

  • 特定のオリジンからのアクセスを許可する場合: サーバーはAccess-Control-Allow-Originヘッダーに許可するオリジンを明示的に指定する。例えば、Access-Control-Allow-Origin: http://example.comhttp://example.comからのリクエストを許可する。
  • すべてのオリジンからのアクセスを許可する場合: サーバーはAccess-Control-Allow-Originヘッダーにワイルドカード*を使用して、どのオリジンからのリクエストも許可することができる。例えば、Access-Control-Allow-Origin: *はすべてのオリジンからのリクエストを許可する。

例)クライアント(http://example.comのウェブページ)が異なるオリジン(http://api.example.com)にあるAPIからデータを取得しようとするシナリオ

HTTPリクエスト

  • Originヘッダー:クロスオリジンHTTPリクエストを行う際にブラウザによって自動的に設定されるヘッダー
ShellScript
GET /some-data HTTP/1.1
Host: api.example.com
Origin: http://example.com

サーバー(api.example.com)は次のようなレスポンスを返す。

ShellScript
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Content-Type: application/json

{"key":"value"}

このレスポンスでは、Access-Control-Allow-Origin: http://example.comヘッダーにより、http://example.comからのアクセスを許可している。これにより、example.comのページからapi.example.comへのAPIリクエストが可能になる。

クッキーなど認証用のヘッダを伴うクロスオリジンリクエスト:追加設定すべき2項目

withCredentials プロパティ

withCredentials プロパティは、クロスオリジンリクエストでクッキーや認証ヘッダー(例えば、HTTP認証やOAuthトークン)、TLSクライアント証明書などの認証情報を使用するかどうかを制御するために使用される。

クロスオリジンリクエストでは、これらの認証情報はデフォルトではセキュリティ上の理由から送信されない。具体的にはCSRF攻撃の防止、ユーザーのセッション情報の漏えいの防止など。

しかし、特定の場合には、クロスオリジンリクエストに認証情報を含める必要がある。たとえば、ユーザーがログインした状態で異なるオリジンのAPIからデータを安全に取得する場合など。このような場合には、XMLHttpRequestやFetch APIのwithCredentialsプロパティをtrueに設定する。

XMLHttpRequest (XHR)

  • JavaScriptを使用してHTTPを介してサーバーと非同期にデータを交換するためのWeb API
  • このオブジェクトを使用すると、Webページが全体を再読み込みすることなく、バックグラウンドでサーバーとのデータ交換が可能になる。これにより、ページの一部分のみを更新する動的なWebアプリケーションを作成できる

XMLHttpRequestの例:

ShellScript
var xhr = new XMLHttpRequest();
xhr.open('GET', '<http://example.com/data>', true);
xhr.withCredentials = true;
xhr.send(null);

Fetch APIの例:

ShellScript
fetch('<http://example.com/data>', {
  method: 'GET',
  credentials: 'include'
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

レスポンスヘッダー:Access-Control-Allow-Credentials: true

サーバー側では、クロスオリジンリクエストに認証情報を含めることを許可するために、適切なCORSヘッダーをレスポンスに含める必要がある。具体的には、Access-Control-Allow-Credentials ヘッダーを true に設定する。

ShellScript
Access-Control-Allow-Credentials: true

このヘッダーが設定されていない場合、ブラウザは認証情報を含むクロスオリジンリクエストをブロックする。また、Access-Control-Allow-Credentialstrue の場合、Access-Control-Allow-Origin ヘッダーはワイルドカード(*)を使用できない。具体的なオリジンを指定する必要がある。

ShellScript
Access-Control-Allow-Origin: <http://yourwebsite.com>

これらの設定により、クロスオリジンリクエストでセキュアに認証情報をやりとりできるようになる。

コメント

タイトルとURLをコピーしました