0. はじめに
前提知識
- HTTP(リクエスト、レスポンス、ヘッダ、ボディ)
- Servlet, JSP
参考図書
目次
- HTTPはステートレスプロトコル
- Cookie
- セッションとは
- Servletでセッションの作成
環境構築
- Tomcat8(Servlet, JSP)
- Java8
- プロジェクト名: "SampleWeb"
- Firefox55
1. HTTPはステートレスプロトコル
[復習] HTTP通信の流れ
※ HTTP はステートレス 引用
- クライアントがサーバにリクエストを送る
- サーバがリクエストを受け取る
- サーバがクライアントにレスポンスを返す
- クライアントがレスポンス(HTMLなど)を表示する
HTTPはステートレスプロトコル
-
HTTPは1回のやりとり(リクエスト、レスポンス)で処理が完結しているので、「状態」を管理できない
-
ステートレスとは「サーバがクライアントのアプリケーション状態を保存しない」制約のこと。
-
ステートレスの逆は「ステートフル」
※ HTTP はステートレス 参考
ステートフルなやりとり(ハンバーガショップにて)
店員「いらっしゃい」
客「ハンバーガーセット1つ」
店員「OK サイドメニューはどうする?」
客「ポテトMで」
店員「OK ドリンクはどうする?」
客「コーラMで」
店員「OK かしこまりました」
※ 「Webを支える技術」を読みました とRESTのまとめ 引用
ステートレスなやりとり(ハンバーガショップにて)
店員「いらっしゃい」
客「ハンバーガーセットで」
店員「OK サイドメニューはどうする?」
客「ハンバーガーセットのポテトMで」
店員「OK ドリンクはどうする?」
客「ハンバーガーセットのポテトMとコーラMで」
店員「OK かしこまりました」
※ 「Webを支える技術」を読みました とRESTのまとめ 引用
ステートレス、ステートフルなやりとりの特徴
-
ステートフルなやりとりは、
- 簡潔
- サーバがクライアントのそれまでの注文を覚えている
-
ステートレスなやりとりは、
- 冗長
- クライアントは毎回すべての注文を繰り返している
※『Webを支える技術』P082 引用
[補足] FTPはステートフルなプロトコル
FTPは、ログインしてからログアウトするまで、そのクライアントがどのディレクトリにいるかといった、アプリケーション状態をサーバが管理する。
※『Webを支える技術』P082 参考
2. HTTP Cookie
ステートレスプロトコルは不便
- ログイン状態など保持できない
- ネットショッピングができない
⇒「Cookie」という仕組みで、疑似的に「ステートフル」を実現した
Cookieとは?
Cookie は、ステートレスな HTTP プロトコルのためにステートフルな情報を記憶したもの。
Cookie は主に、以下の 3 つの用途で使用される。
- セッション管理 (ユーザーのログイン、ショッピングカート)
- 個人設定 (ユーザーの設定)
- トラッキング (ユーザーの行動を分析する)
※ HTTP Cookie - MDN 引用
Cookieの具体的な流れ
- サーバーはクライアントへ、Cookie を保存するよう指定する(
Set-Cookie
ヘッダ使用)
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
...
-
クライアント(ブラウザ)はCookieを保存する。
-
クライアントはサーバにリクエストを送るときに、
Cookie
ヘッダでCookieを送る。
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
※ HTTP Cookie - MDN 参考
[Try] ServletでCookieを確認
Cookieを保存させるServletを作成
@WebServlet(urlPatterns = { "/Cookie" })
public class Cookie extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Set-Cookie", "yummy_cookie=choco");
PrintWriter out = response.getWriter();
out.println("/Cookie");
}
}
レスポンスヘッダのSet-Cookie
を確認
リクエストヘッダのCookie
を確認
- プロジェクト配下のURLにアクセス( http://localhost:8080/SampleWeb/Hello )
3. セッション
Cookieのみでログイン状態を維持できそうだけど…
下記の情報をCookieに保存すれば、ログイン状態を維持できそう…
- ユーザID
- パスワード
- ユーザ名
- 住所
- 電話番号
Cookieのみでログイン状態を維持したときの問題
- Cookieは確実に保存される訳ではない
- シークレットモード
- ユーザがブラウザの履歴を削除する
- 最大容量が4KBで小さい
- デフォルト(secureを付与しなければ)は平文で送るため、セキュリティに問題あり
※ 『Real World HTTP』P40 参考
Cookie の仕組み全体が本質的に安全ではないため、HTTP Cookie で機密情報を保存したり送信したりしてはいけません。
※ HTTP Cookie - MDN 引用
[補足] IPA試験対策:Cookieにsecure属性をつける
https: のページで使用するCookieには発行時に secure属性をつける。
secure属性が付けられたCookieは、http:のコンテンツ宛には送られない。
http: のページにおいてもCookieを使用する必要がある場合は、Cookie の値を第三者が傍受し得ることを十分に考慮した上で secure属性のつかない Cookieを発行する。
セッションとは?
本スライドでの「セッション」の意味:
ログインしてからログアウトするまでの一連の操作
先の問題を解決する対応案
- Cookieにはユーザを認識できるID(セッションID)のみを保存
- ユーザ名などの個人情報はサーバで管理
Cookieに個人情報は保存していないので、先の例よりセキュリティ的に良い(完全に問題が解決された訳ではない。後述のセッションハイジャック参照)
[Try] Servletでセッションを作成
request.getSession(true);
でセッションを作成する。
@WebServlet(urlPatterns = { "/CreateSession" })
public class CreateSession extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//Session取得。セッションがなければセッション作成。
HttpSession session = request.getSession(true);
PrintWriter out = response.getWriter();
out.println("CreateSession");
//セッションIDを表示
out.println("session_id=" + session.getId());
}
}
セッションIDを確認
CreateSession
session_id=5F4F3DF78E1BC8822CE970D9B3E3ECF7
- ServletのセッションIDは、32文字のランダムな値(0-9A-F)
- Cookieに保存されたセッションIDのキーは、"JSESSIONID"
[Try] セッションハイジャックを確認
セッションハイジャックは、利用者が Webアプリケーションにログインした際に発行されるセッション ID を、ネットワーク上で盗聴されたり、規則性から類推されることで、攻撃者が利用者になりすます攻撃です。
※ セッション管理の不備と対策 引用
ログイン用のサーブレットを作成
/** SessionにUser Nameを保存する */
@WebServlet(urlPatterns = { "/Login" })
public class Login extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
//Sessionにユーザ名を設定
HttpSession session = request.getSession(true);
String userName = request.getParameter("userName");
session.setAttribute("userName", userName);
PrintWriter out = response.getWriter();
out.println("Login Success");
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/SampleWeb/Login" method="POST">
User Name <input type="text" name="userName">
<input type="submit" value="Login">
</form>
</body>
</html>
/** Sessionに保存したUser Nameを表示 */
@WebServlet("/HelloUser")
public class HelloUser extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Sessionがなければnullを返す
HttpSession session = request.getSession(false);
PrintWriter out = response.getWriter();
out.println("HelloUser");
if (session == null) {
out.println("Session Does Not Exist");
} else {
out.println("userName = " + session.getAttribute("userName"));
}
}
}
一般ユーザAの動作
- http://localhost:8080/SampleWeb/form.html にアクセス
- User Nameに"Hoge"を入力して、Login
⇒ SessionにUser Nameが保存される -
http://localhost:8080/SampleWeb/HelloUser にアクセス
⇒ User Name"Hoge"が表示される
攻撃者Xの動作(セッションハイジャック)
- ユーザA:ログインする
- 攻撃者X:ユーザAのセッションIDをなんらかの手段で取得する(盗聴など)
- 攻撃者X:Cookieに取得したセッションIDを保存する
- 攻撃者X:http://localhost:8080/SampleWeb/HelloUser
⇒ User Name "Hoge"が表示される
[補足] セッション固定化攻撃
セッションID固定化攻撃は、Webサイトが生成する正規のセッションIDを含むURLに利用者をアクセスさせ、攻撃者が用意したセッションIDを使用する通信を意図的に確立させる攻撃です。攻撃者はその後同じセッションIDでWebサイトにアクセスし、ログイン中のセッションを乗っ取ります。
たとえセッションIDに推測が困難な乱数を使用していたとしても、以下に挙げるWebサイトでは攻撃者に正規のセッションIDを取得され、セッション固定化攻撃を受ける可能性があります。
- ログイン画面(URLなど)にセッションIDを表示する仕様になっており、ログイン後も同じセッションIDを使用する
- クッキーを使用できない場合に、セッション情報をURLに埋め込むURLリライティング機能が有効になっている
- セッションIDが利用者ごとに固定されているなど容易に推測が可能である
※ 情報処理安全確保支援士 平成29年春期 午前Ⅱ 問5 引用
[補足] Cookieに情報を格納するのは昔のやり方
ローカルストレージ用に考えられた新しい API を使うべき。
Web storage API (localStorage, sessionStorage)
※ Web Storage 引用
// sessionStorage にデータを保存する
sessionStorage.setItem("yummy", "choco");
// sessionStorage に保存したデータを取得する
var data = sessionStorage.getItem("yummy");
// sessionStorage に保存したデータを削除する
sessionStorage.removeItem("yummy");
IndexedDB
IndexedDB は、ユーザのブラウザ内にデータを永続的に保存する手段です。ネットワークの状態にかかわらず高度な問い合わせ機能を持つ Web アプリケーションを作成できますので、オンラインとオフラインの両方で動作するアプリケーションになります。
※ IndexedDB を使用する 引用
[補足] HTTPクッキーの命名例
- JSESSIONID (JSP)
- PHPSESSID (PHP)
- CGISESSID (CGI)
- ASPSESSIONID (ASP)
名前からプログラム言語が推測できる(確定ではない)。
[補足] セッションタイムアウトを設定する方法
<session-config>
<session-timeout>30</session-timeout>
</session-config>
[補足] JSPにアクセスするとセッションが作成される。
jspにアクセスすると、Cookieに"JSESSIONID"が保存される。
これは、JSPからコンパイルされたServletのpageContext.getSession()
によるものだと考えられる(未確認)。
public void _jspService(/* ... */) {
//...
final javax.servlet.jsp.PageContext pageContext;
//...
try {
response.setContentType("text/html; charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
//Sesssion 取得
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
....
}
}