いままでやったtomcat事はここにまとめてあるんご

tomcat再開 その5 課題リスト

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("&amp;");
                                break;
                        case '<' :
                                result.append("&lt;");
                                break;
                        case '>' :
                                result.append("&gt;");
                                break;
                        default :
                                result.append(value.charAt(i));
                                break;
                }
        }
        return result.toString();

lengthメソッドは上記②の方法でStringBufferクラスを検索すると、

 

 

length()メソッドはString型オブジェクトに作用してint型で長さ(文字数)を返します。

となる。

charATメソッドは上記②の方法でStringBufferクラスを検索すると、

charAt(int index)メソッドはString型オブジェクトに作用してシーケンス内の指定されたインデックスのchar値を返します。

appendメソッドは上記②の方法でStringBufferクラスを検索すると、

append(boolean b)メソッドはStringBuffer型オブジェクトに作用してboolean引数の文字列表現をシーケンスに追加します。

 

【考え方】 

1.JSPやサーブレットコード内に登場する各コトバ(メソッド、オブジェクト)が、どのクラスやjarファイルに含まれているか?

2.そのクラスのマニュアルやリファレンスを見て意味や用法を調べる

 メソッドの場合は

 ①どのオブジェクトに作用するか

 ②どのクラスや型のオブジェクトを引数に取るか

 ③どのクラスや型を返すか

 ④結局なにをやっているか

 ⑤制約はあるか?どんな例外が考えられるか?

そして展望としては

3.javaSE、javaEE、tomcat、splingではどんな処理を書けるのか?どんなことができるのか?