いままでやったtomcat事はここにまとめてあるんご
tomcat再開 その6 tomcatやjavaアプリに出てくる用語の仕分け その1のつづき
■ tomcatはじめの6歩 クッキー情報の活用
・予備知識
新規リクエストに対してset cookieヘッダとsession cookieを付与してレスポンスするのはtomcatの機能。ていうかtomcatが備えるセッション管理機能。
・/jsp10/day02/cookie.jsp
このcookie.jspにアクセスすると、「cnt」というクッキー名のクッキーにアクセス回数を表す整数をクッキー値としたクッキーをレスポンスにaddCookieする。すなわちSet-Cookieヘッダをくっつける。
一方、tomcatには、デフォルトでセッション管理する仕様になっていて、セッションクッキーを持ってないクライアントからの初回アクセス時に、「JSESSIONID」というクッキー名の"セッションクッキー"にセッションIDを振り出してSet-Cookieヘッダをくっつける。
・http://192.168.2.70:8080/jsp10/day02/cookie.jspに初回接続時のレスポンスヘッダ
Set-Cookie: JSESSIONID=398306C39C60111E4C396B02DF5654D8; Path=/jsp10; HttpOnly ←tomcatがくっつけたセッションクッキー
Set-Cookie: cnt=1; Max-Age=15552000; Expires=Thu, 20-Dec-2018 15:38:25 GMT ←cookie.jspアプリがくっつけた有効期限つきクッキー(値=1)
Content-Type: text/html;charset=Windows-31J
Content-Length: 145
・http://192.168.2.70:8080/jsp10/day02/cookie.jspに2回目接続時のレスポンスヘッダ
Set-Cookie: cnt=2; Max-Age=15552000; Expires=Thu, 20-Dec-2018 15:54:13 GMT ←cookie.jspアプリがくっつけた有効期限つきクッキー(値=2)
Content-Type: text/html;charset=Windows-31J
Content-Length: 145
→2回目以降のアクセスの際にはtomcatはセッションクッキーをくっつけない
6行目: Cookie[] cookies = request.getCookies();
・Cookieクラス
せ70> for i in $CATALINA_HOME/lib/*.jar;do unzip -l $i;done | cat -n |egrep "Archive:|Cookie.class"
・・・前略・・・
2464 Archive: /usr/local/tomcat8/lib/postgresql-42.2.2.jar
2867 Archive: /usr/local/tomcat8/lib/servlet-api.jar
2937 3501 02-06-2018 23:10 javax/servlet/http/Cookie.class ←暗黙のimportされてるクラスなのでこっち
2999 Archive: /usr/local/tomcat8/lib/tomcat-api.jar
3022 Archive: /usr/local/tomcat8/lib/tomcat-coyote.jar
3283 11194 02-06-2018 23:10 org/apache/tomcat/util/http/parser/Cookie.class
3454 Archive: /usr/local/tomcat8/lib/tomcat-dbcp.jar
・・・後略・・・
・getCookies()メソッド
HttpServletRequest.classのインスタンスであるrequestに適用しているのでHttpServletRequest.class。
つまり、servlet-api.jarファイルに含まれる。
・7行目~23行目:
if(cookies != null){
for(int i=0; i < cookies.length; i++){ ←上述のようにリクエストにはセッションクッキーとcntクッキーの2個ついてるのでcookies.lengthは2
if(cookies[i].getName().equals("cnt") == true){
count = Integer.parseInt(cookies[i].getValue()) +1;
cook = new Cookie("cnt", (new Integer(count)).toString());
cook.setMaxAge(60 * 60 * 24 * 180);
response.addCookie(cook);
flag=true;
break;
}
}
}
if(!flag){
cook = new Cookie("cnt","1");
cook.setMaxAge(60*60*24*180);
response.addCookie(cook);
}
%>
→JSPの暗黙のオブジェクト(request、response、・・・)をいじってるメソッドはすべて javax/servlet/http/*.classクラス(servlet-api.jarファイル内)に含まれる。
■ tomcatはじめの7歩 セッション機能の活用
・/jsp10/day02/session.jsp
・http://192.168.2.70:8080/jsp10/day02/session.jspに初回接続時のレスポンスヘッダ
Set-Cookie: JSESSIONID=A41D91ADEA06C682D370D9249CDF0456; Path=/jsp10; HttpOnly
Content-Type: text/html;charset=Windows-31J
Content-Length: 129
・http://192.168.2.70:8080/jsp10/day02/session.jspに2回目接続時のレスポンスヘッダ
Content-Type: text/html;charset=Windows-31J
Content-Length: 129
上記のように、cookieに書くのではなく、tomcatが管理してる「セッション情報」にアクセス回数のような情報を書くみたい。
3行目~10行目:
Integer count = (Integer)session.getAttribute("cnt");
if(count == null){
session.setAttribute("cnt",1);
count = 1;
}else{
count++;
session.setAttribute("cnt",count);
}
・sessionオブジェクト
暗黙のオブジェクトなのでservlet-api.jarに含まれる。
・getAttributeとsetAttributeメソッド
sessionオブジェクトに作用してるからsessionと同じクラスに含まれてるのでservlet-api.jarに含まれる。
sessionのattributeには、上記のキー名「cnt」のように自分で何個でも任意の「キー名」を定義できて、キー名に対する「値」も書き込んだり変更できる
・/jsp10/day02/p_session1.jsp、/jsp10/day02/p_session2.jsp、/jsp10/day02/session.20180624.jsp
・コードは下記の通り
せ70> cat p_session1.jsp
<%@ page contentType="text/html;charset=Windows-31J" pageEncoding="UTF-8" %>
<%
String[] names = {"name","address"};
String[] values = {"",""};
for(int i=0;i<names.length;i++){
values[i] = (String)session.getAttribute(names[i]);
values[i] = (values[i]==null) ? "" : values[i];
}
%>
<html>
<head>
<title>セッションで情報を保持する</title>
</head>
<body>
<h1 style="background:#cccccc">セッションで情報を保持する</h1>
<form method="POST" action="p_session2.jsp">
<table border="0">
<tr>
<th>名前:</th>
<td><input type="text" name="name" size="20"
value="<%=values[0] %>" /></td>
</tr><tr>
<th>E-Mail:</th>
<td><input type="text" name="address" size="40"
value="<%=values[1] %>" /></td>
</tr><tr>
<td colspan="2">
<input type="checkbox" name="rec" value="true" checked />
情報を記録する
</td>
</tr><tr>
<td colspan="2"><input type="submit" value="登録" /></td>
</tr>
</table>
</form>
</body>
</html>
せ70> cat p_session2.jsp
<%@ page contentType="text/html;charset=Windows-31J" pageEncoding="UTF-8" %>
<%
String[] names = {"name","address"};
if(request.getParameter("rec") != null){
for(int i=0;i<names.length;i++){
session.setAttribute(names[i],request.getParameter(names[i]));
}
out.println("セッションに情報が保存されました");
}else{
session.invalidate();
out.println("セッションから情報を破棄しました");
}
%>
せ70> cat session.20180624.jsp
<%@ page contentType="text/html;charset=Windows-31J" pageEncoding="UTF-8" %>
<%
Integer count = (Integer)session.getAttribute("cnt");
String name = (String)session.getAttribute("name");
String address = (String)session.getAttribute("address");
if(count == null){
session.setAttribute("cnt",1);
count = 1;
}else{
count++;
session.setAttribute("cnt",count);
}
%>
<html>
<head>
<title>セッションで簡易アクセスカウンタ</title>
</head>
<body>
<h3>you accessed for <%=count %>times </h3>
<h3>your name is <%=name %> </h3>
<h3>your address is <%=address %> </h3>
</body>
</html>
・できること
最初に、/jsp10/day02/p_session1.jspにアクセス
→response headerに
Set-Cookie:JSESSIONID=61C7E19D270E9D77CB042784CB9A2EDD; Path=/jsp10; HttpOnly
が付与されて、下記のresponse bodyを受信する
「名前」欄と「E-Mail」欄に適当な文字列、「情報を記録する」チェックぽっくすにチェックを付けて「登録」ボタンを押すと、
→下記のようにrequestのcookieヘッダにsessionIDを付与し、POST bodyに上記で指定したform dataをつけてrequestされ、下記のようなresponseが返ってくる。
この後、下記のように、/jsp10/day02/session.20180624.jspにアクセスすると、下記のようなresponseが返ってくる
上記のように、sessionIDに紐づけられたセッション情報はクライアントからのsessionIDのみとなる。
この後、ブラウザを終了しないで再度/jsp10/day02/p_session1.jspにアクセスして、
「名前」欄と「E-Mail」欄に別の文字列、「情報を記録する」チェックボックスにチェックを付けて「登録」ボタンを押すと、
のようにセッション情報は更新されるが、sessionIDは変わらない。
【検証1】 セッションハイジャック
ちなみに、別のクライアントが上記のsessionIDを詐称したら下記のようにsessionハイジャックできる
せ6> nc 192.168.2.70 8080
GET /jsp10/day02/session.20180624.jsp HTTP/1.1
Host: 192.168.2.70:8080
Cookie: JSESSIONID=61C7E19D270E9D77CB042784CB9A2EDD ←セッションを詐称
HTTP/1.1 200
Content-Type: text/html;charset=Windows-31J
Content-Length: 212
Date: Sun, 24 Jun 2018 07:56:41 GMT
<html>
<head>
<title>ZbV???ANZXJE^</title>
</head>
<body>
<h3>you accessed for 3times </h3>
<h3>your name is chinko-man </h3>
<h3>your address is chinko-man@yahoo.co.jp </h3>
</body>
</html>
【分かったこと】
・sessionやcookieを読んだり書き換えるメソッドはすべてtomcat付属のservlet-api.jarファイルに含まれるclassライブラリ。
・sessionやcookieなどのオブジェクトはJSPでは暗黙のオブジェクトとして利用できる
・servretやjspアプリケーションで自由にcookieをくっつけたり読んだり値を書き換えることができる
・tomcatはデフォルトでsession管理機能をもっていて、任意のキー名をsession情報内に定義して同一セッション中に自由に読んだり書き換えたりできる
・同一session中にsessionIDさえ盗聴できれば簡単にセッションハイジャックできる
【検証2】 クロスサイトリクエストフォージェリ
・もし、/jsp10/day02/p_session1.jspがログイン画面で、ログイン認証がパスした場合だけ、
セッション情報が記録されてセッションクッキーが発行される仕組みだった場合で、/jsp10/day02/p_session1.jspで認証に通ったユーザがセッションクッキーを保持した状態で、第三者が設置した偽リンク先を誤って踏んでしまい、そのリンク先が、
「悪しきformデータ」をくっつけて/jsp10/day02/p_session2.jspに送信するURLだった場合、ログイン完了済み正規ユーザが偽リンクを踏んでしまったことにより、formデータに「悪しきデータ」をくっつけてリクエストされてしまう。
たとえば、これがクレジットカード決済やネットバンキングのサイトの場合は意図せぬ決済をさせられて金品を盗まれてしまいかねない。
・正規ユーザがログイン後、ブラウザがサーバの発行した正規セッションクッキーを持ってる状況で「偽リンク先」を踏んだ時の動作をシミュレーションしてみる。
まず、正規ユーザ(goodman)が/jsp10/day02/p_session1.jspにアクセスしてサーバ側にセッションが作られ、ブラウザは最初のレスポンスのset cookieでセッションクッキーを受け取る。
次に、http://192.168.2.70:8080/jsp10/day02/p_session2.jspのURLに、
name=badman&address=badman%40dag.domain&rec=true
のようなPOSTフォームデータを付けて送信するURLを正規ユーザが踏んでしまったら、すでにブラウザには正規に発行されたセッションクッキーが保持されているので、下記のようにセッションクッキーをつけた偽リクエストを送信させられてしまう。
せ70> nc 192.168.2.70 8080
POST /jsp10/day02/p_session2.jsp HTTP/1.1
Host: 192.168.2.70:8080
Content-Length: 46
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=E49199CB6B1FBE7127E266595658A7A5
name=badman&address=badman%40dag.domain&rec=true
HTTP/1.1 200
Content-Type: text/html;charset=Windows-31J
Content-Length: 35
Date: Sat, 30 Jun 2018 11:06:28 GMT
ZbV?鉐・恭袍欺
HTTP/1.1 400
Transfer-Encoding: chunked
Date: Sat, 30 Jun 2018 11:06:28 GMT
Connection: close
0
Ncat: Broken pipe.
せ70>
上記のように、クロスサイトリクエストフォージェリを模擬することができた。
※クロスサイトリクエストフォージェリを防ぐためにspling securityでは下記のように、formを送信するアプリケーションにtype="hidden"でリクエストごとにランダムになるトークンを付与してレスポンスすることにより、ブラウザからのフォームデータの中にこのトークンが含まれるようになることで、ちゃんとformページからアクセスしてフォーデータを送信したユーザとそうでないユーザを区別することができるようになる。
せ70> cat p_session1_csrf.jsp
<%@ page contentType="text/html;charset=Windows-31J" pageEncoding="UTF-8" %>
<%
String[] names = {"name","address"};
String[] values = {"",""};
for(int i=0;i<names.length;i++){
values[i] = (String)session.getAttribute(names[i]);
values[i] = (values[i]==null) ? "" : values[i];
}
%>
<html>
<head>
<title>セッションで情報を保持する</title>
</head>
<body>
<h1 style="background:#cccccc">セッションで情報を保持する</h1>
<form method="POST" action="p_session2.jsp">
<table border="0">
<tr>
<th>名前:</th>
<td><input type="text" name="name" size="20"
value="<%=values[0] %>" /></td>
</tr><tr>
<th>E-Mail:</th>
<td><input type="text" name="address" size="40"
value="<%=values[1] %>" /></td>
</tr><tr>
<td colspan="2">
<input type="checkbox" name="rec" value="true" checked />
情報を記録する
</td>
</tr><tr>
<input type="hidden" name="csrf" value="1234567" /> ←hidden型formデータの値(csrfトークン)をリクエストごとに変える
<td colspan="2"><input type="submit" value="登録" /></td>
</tr>
</table>
</form>
</body>
</html>
・ブラウザからhttp://192.168.2.70:8080/jsp10/day02/p_session1_csrf.jspにアクセス
「登録」を押すと、
のようにcsrfトークンが付与される
■ tomcatはじめの8歩 HTML予約文字のエンコード
・予備知識
JavaSEのクラス/メソッドを調べるには下記のAPI仕様が鉄板
https://docs.oracle.com/javase/jp/8/docs/api/ ←①
API仕様サイト(上記の①)から「索引」をクリックし、メソッド名の頭文字のアルファベットを選択してCtrl + Fでクラス名で検索すると、該当メソッドを検索できる ←②
・/jsp10/day03/escape2.jsp
・3行目:
private String htmlEscape(String value){
Stringクラスは下記のように、JavaSEのrt.jarの中にあるjava.langパッケージとcom.sun.org.apache.xpath.internal.operationsパッケージ内に同名のクラスが存在する。たぶんjava.langパッケージ内の方
せ70> for i in $JAVA_HOME/lib/*.jar;do unzip -l $i;done | cat -n | egrep "Archive:|/String.class"
1 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/charsets.jar
231 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/jce.jar
309 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/jsse.jar
474 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/management-agent.jar
481 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/resources.jar
1181 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/rt.jar
5955 943 01-17-2018 16:36 com/sun/org/apache/xpath/internal/operations/String.class
20907 25000 01-17-2018 16:36 java/lang/String.class
・4行目:
StringBuffer result = new StringBuffer();
StringBufferクラスは下記のように、JavaSEのrt.jarの中にあるjava.langパッケージ内のクラス
せ70> for i in $JAVA_HOME/lib/*.jar;do unzip -l $i;done | cat -n | egrep "Archive:|/StringBuffer.class"
1 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/charsets.jar
231 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/jce.jar
309 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/jsse.jar
474 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/management-agent.jar
481 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/resources.jar
1181 Archive: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre//lib/rt.jar
20831 13527 01-17-2018 16:36 java/lang/StringBuffer.class
・6行目~21行目:
for(int i=0;i<value.length();i++){
switch(value.charAt(i)){
case '&' :
result.append("&");
break;
case '<' :
result.append("<");
break;
case '>' :
result.append(">");
break;
default :
result.append(value.charAt(i));
break;
}
}
return result.toString();
lengthメソッドは上記②の方法でStringBufferクラスを検索すると、
|
|
となる。
charATメソッドは上記②の方法でStringBufferクラスを検索すると、
charAt(int index)メソッドはString型オブジェクトに作用してシ
ーケンス内の指定されたインデックスのchar
値を返します。
appendメソッドは上記②の方法でStringBufferクラスを検索すると、
append(boolean b)メソッドは
StringBuffer型オブジェクトに作用してboolean
引数の文字列表現をシーケンスに追加します。
【考え方】
1.JSPやサーブレットコード内に登場する各コトバ(メソッド、オブジェクト)が、どのクラスやjarファイルに含まれているか?
2.そのクラスのマニュアルやリファレンスを見て意味や用法を調べる
メソッドの場合は
①どのオブジェクトに作用するか
②どのクラスや型のオブジェクトを引数に取るか
③どのクラスや型を返すか
④結局なにをやっているか
⑤制約はあるか?どんな例外が考えられるか?
そして展望としては
3.javaSE、javaEE、tomcat、splingではどんな処理を書けるのか?どんなことができるのか?