Глава 10 Атака через WWW

We use cookies. Read the Privacy and Cookie Policy

Глава 10 Атака через WWW

Мы – работники КОМКОНа-2. Нам разрешается слыть невеждами, мистиками, суеверными дураками. Нам одно не разрешается: недооценить опасность. И если в нашем доме вдруг завоняло серой, мы просто обязаны предположить, что где-то рядом объявился черт с рогами, и принять соответствующие меры вплоть до организации производства святой воды в промышленных масштабах.

А. Стругацкий, Б. Стругацкий. Волны гасят ветер

Вопросы атаки через WWW мы решили рассмотреть в отдельной главе по нескольким причинам. В настоящее время World Wide Web – пожалуй, самая популярная служба Internet, то, с чем в первую очередь сталкивается большинство людей, подключающихся к Сети. Атака через Web является едва ли не самым частым способом взлома хостов и самым наглядным проявлением кракерства.

Атака на клиента

Далеко не все пользователи Internet осознают, что, подключившись к Сети, они не только получают доступ ко всему информационному богатству, но и открывают свой компьютер для доступа извне, а следовательно, подвергают его угрозам, характерным для хостов Сети: угрозе раскрытия, нарушения целостности системы и отказа в обслуживании. Здесь также появляется и четвертый тип атаки, который можно с некоторой натяжкой рассматривать как частный случай отказа в обслуживании с точки зрения системы человек – компьютер, – атака на самого пользователя, выражающаяся в создании условий, неблагоприятных для работы (раздражающие звуки, моргание экрана и т. п.).

Безопасность браузеров

Популярные браузеры в своем развитии уже вышли далеко за рамки простых средств отображения гипертекстовых документов. HTML (HyperText Markup Language – язык разметки гипертекстовых документов) изначально был ориентирован исключительно на отображение структурированного текста. Имелась также возможность включить в документ отдельные управляющие элементы для передачи информации на сервер, который после этого мог вернуть клиенту результаты обработки, то есть чисто клиент-серверное решение. Этим и исчерпывались интерактивные возможности Web. По мере развития Сети выяснилось, что ее выразительных средств становится недостаточно для удовлетворения растущих запросов пользователей, и HTML стал включать в себя средства для работы с таблицами, графикой, звуком.

Кроме того, не все устраивало и разработчиков Web-приложений – иногда им хотелось бы иметь дело с более интеллектуальным клиентом, способным не только передавать на сервер заполненную форму. Возникла потребность во вспомогательных приложениях клиентской стороны. При разработке этих приложений немедленно всплывают многочисленные проблемы, и безопасность здесь стоит далеко не на последнем месте. Достаточно сказать, что многие прорехи в системе безопасности браузеров, обнаруженные в последнее время, связаны именно с элементами, расширяющими функциональность клиентов.

Наибольшую популярность завоевали следующие подходы к реализации вспомогательных приложений для клиентской стороны:

• подключаемые модули (plug-ins);

• элементы ActiveX;

• средства подготовки сценариев JavaScript, VBScript, Dynamic HTML;

• приложения Java.

Рассмотрим их подробнее.

Использование подключаемых модулей получило в свое время широкое распространение в связи с популярностью браузера Netscape Navigator, предоставляющего такую возможность. С точки зрения безопасности этот подход не выдерживает никакой критики: не обеспечивается ни защита от сбоев, ни защита от злонамеренных действий, предпринимаемых модулем, который имеет полный доступ ко всем ресурсам системы пользователя. Все строится исключительно на доверии к автору модуля.

Управляющие элементы ActiveX – решение компании Microsoft, основанное на вездесущей технологии COM (Component Object Model – модель компонентных объектов), перенесенной на этот раз в Internet. Проблема безопасности решается с помощью введения института сертификатов – объекты ActiveX подписываются цифровой подписью автора, заверенной независимой организацией (например, VeriSign Inc.). Таким образом, работа с ActiveX отличается от работы с подключаемыми модулями Netscape только тем, что доверие к автору управляющего элемента может быть подкреплено авторитетом солидной организации. В то же время эта подпись гарантирует лишь возможность определения авторства объекта, а вовсе не его благонадежности.

При загрузке объекта ActiveX поведение браузера зависит от настроек его системы безопасности – как подписанные, так и неподписанные (либо заверенные неизвестной организацией) объекты могут быть либо автоматически загружены или отвергнуты, либо предъявлены пользователю, с тем чтобы дальнейшее решение принимал он. В Internet Explorer также можно задать разные настройки для различных зон безопасности – для локальной сети, Internet, отдельных подозрительных хостов, и наоборот, достойных особого доверия.

JavaScript, VBScript и т. п. представляют собой упрощенные языки подготовки сценариев, код которых встраивается непосредственно в html-файл и выполняется браузером. Они непригодны для реализации серьезных приложений, в них отсутствуют средства для работы с файлами, сетевого взаимодействия и т. д. Но они широко используются во вспомогательных целях, в качестве средства первоначальной обработки результатов, для оформления, «оживления» html-документов и т. д. Казалось бы, что ограничения, присущие этим языкам, делают их абсолютно безопасными, в действительности же львиная доля ошибок в браузерах связана именно с реализацией этих простейших средств разработки.

Безопасность Java-приложений

Java – язык, разработанный Sun Microsystems изначально для приложений бытовой электроники и позднее перенесенный в Internet, что стало для него вторым рождением. Различают обычные Java-приложения и апплеты, предназначенные для загрузки по сети и выполнения в окне браузера.

Вопросы безопасности Java-апплетов заслуживают того, чтобы о них говорить отдельно, поскольку это единственное распространенное средство разработки клиентских приложений, в котором решение проблемы безопасности предлагается уже на уровне архитектуры.

Основным достоинством Java-приложений является независимость от клиентской платформы. В отличие от традиционных приложений, транслирующихся в исполняемые коды процессора, Java-приложения транслируются в так называемый байт-код, интерпретируемый в дальнейшем виртуальной Java-машиной. При этом байт-код независим от платформы, на которой он в дальнейшем будет выполняться, – достаточно, чтобы для этой платформы существовала Java-машина. Поскольку большинство основных функций реализовано на уровне виртуальной Java-машины, это приводит к существенному уменьшению размеров байт-кода, что является как достоинством, так и недостатком Java-приложений. Так как байт-код интерпретируется виртуальной машиной, производительность Java-приложений уступает производительности традиционных откомпилированных программ. Частично с этим удается бороться, применяя компиляторы времени исполнения (JIT – just in time compilers), осуществляющие компиляцию приложения при его загрузке в «родной» для данного процессора код. Также возможен вызов функций, реализованных на других языках программирования (С, С++) и откомпилированных для данной платформы, – так называемый native code (родной код). Он применяется при реализации наиболее критичных ко времени исполнения фрагментов кода.

Другим достоинством Java-приложений является защищенность. Во-первых, сам язык способствует написанию более надежных и устойчивых к сбоям программ. Помимо строгой типизации, управления доступом, работы с исключениями, знакомых программистам и по С++, в Java добавлена автоматическая «сборка мусора» (освобождение неиспользуемой памяти), проверка на выход за границы массива, возможность указать, что данный метод или объект не может быть изменен или переопределен. В языке нет указателей и переопределенных операторов.

Все эти нововведения помогают создавать более безопасный код. Рассмотрим теперь особенности Java, вынуждающие писать безопасный код.

По мере развития Java развивалась и система безопасности. В JDK 1.0 (Java Development Kit) основу системы безопасности составляли три компонента – Verifier (верификатор), ClassLoader (загрузчик классов) и SecurityManager (менеджер безопасности). Эта модель известна под названием sandbox (песочница), в ней выполняются Java-приложения, загруженные из сети (рис. 10.1).

Рис. 10.1. Модель безопасности JDK 1.0

Для полноценного функционирования модели безопасности каждый ее компонент должен работать безошибочно, поскольку только четкая совместная работа компонентов обеспечивает контроль над приложением во время загрузки и исполнения кода.

Первый рубеж обороны – верификатор, проверяющий загружаемый байт-код на корректность, так как у нас нет никакой гарантии, что загружаемый код был получен в результате работы компилятора Java, а не подправлен вручную или не сгенерирован специальным «враждебным» компилятором. После того как код прошел верификацию, гарантируется, что файл класса имеет корректный формат, параметры всех операций имеют правильный тип, в коде не происходит некорректных преобразований типов (например, целого числа в указатель), нет нарушений доступа и т. п. Таким образом, проверяется все, что только можно проверить до начала исполнения программы. Верификатор встроен в виртуальную машину и недоступен из Java-программы.

Загрузчики классов определяют, когда и каким образом классы могут быть добавлены в работающую систему. Частью их работы является защита важных составляющих системы, например запрет на загрузку поддельного менеджера безопасности. Они выполняют две основные функции – собственно загрузку байт-кода (с локального диска, по сети, из области памяти и т. д.), определение namespaces (пространства имен) для различных классов и способов их взаимодействия (отделяя, к примеру, локальные классы от загруженных по сети).

Есть два вида загрузчиков – primordial (первичный) и Class Loader Object (реализованный в виде объекта). Первичный существует в единственном экземпляре и является составной частью виртуальной машины. Он занимается загрузкой доверенных классов (обычно находящихся на локальном диске). Загрузчик второго типа представляет собой экземпляр обычного Java-класса, унаследованного от абстрактного класса java.lang.ClassLoader. С его помощью можно осуществить загрузку класса по сети либо динамическое конструирование класса приложением. Метод defineClass преобразует массив байтов в экземпляр класса Class, а экземпляры нового класса создаются с помощью метода newInstance класса Class. Если метод созданного класса ссылается на другие классы, виртуальная машина вызывает метод loadClass его загрузчика, передавая ему имя запрашиваемого класса. При создании экземпляра класса или вызове любого из его методов потребуется также загрузка его предка и других используемых им классов, за это отвечает функция resolveClass.

Примерная реализация класса NetworkClassLoader может выглядеть следующим образом:

class NetworkClassLoader

{

String host;

int port;

Hashtable cache = new Hashtable();

NetworkClassLoader(String aHost, int aPort)

{

host = aHost;

port = aPort;

}

private byte loadClassData(String name)[]

{

// собственно загрузка класса

. . .

}

public synchronized Class loadClass(String name, boolean resolve)

{

Class c = cache.get(name);

// Хэш-таблица используется для исключения

if (c == null)

// повторной загрузки класса

{

// и формирования пространства имен

byte data[] = loadClassData(name);

c = defineClass(data, 0, data.length);

cache.put(name, c);

}

if (resolve)

resolveClass(c);

return c;

}

}

ClassLoader loader = new NetworkClassLoader(host, port);

Object main = loader.loadClass("Main", true).newInstance();

На самом деле загрузчик сначала должен обратиться к пространству имен первичного загрузчика и затем, лишь не обнаружив там запрашиваемого класса, продолжить поиск по пространству имен ссылающегося класса, чтобы предотвратить подделку встроенных классов. Причем загрузчик работает совместно с менеджером безопасности, определяющим, можно ли загружать данный класс. Весь алгоритм действий загрузчика обычно выглядит следующим образом:

1. Определить, не был ли загружен этот класс раньше, и, если да, вернуть его.

2. Проконсультироваться с первичным загрузчиком на предмет существования внутреннего класса с этим именем.

3. Запросить у менеджера безопасности разрешение на загрузку данного класса.

4. Считать файл класса в виде массива байтов – по сети, с диска и т. п.

5. Создать экземпляр класса Class.

6. Загрузить используемые классы.

7. Передать класс верификатору на проверку.

Загрузчик является существенным элементом модели безопасности, поэтому писать загрузчики следует особо осторожно – малейшая ошибка может привести к полному краху всей системы безопасности. В нормальной ситуации апплеты не имеют возможности устанавливать свои загрузчики.

Класс SecurityManager отвечает за политику безопасности приложения. Он позволяет приложению перед выполнением потенциально опасной операции выяснить, выполняется ли она классом, загруженным первичным загрузчиком, либо с помощью некоторого ClassLoader (к последнему, особенно при загрузке из сети, доверия должно быть гораздо меньше). Далее менеджер безопасности может определить, разрешить ли эту операцию или наложить на нее вето. Класс SecurityManager выявляет ряд методов, начинающихся со слова «check» (checkDelete, checkExec, checkConnect и т. п.), которые вызываются всеми методами стандартной библиотеки, выполняющими потенциально опасные действия (работа с файлами, сетевыми соединениями и т. п.). Выглядит это обычно следующим образом:

SecurityManager security = System.getSecurityManager();

if (security != null)

{

security.checkXXX(argument, . . . );

}

При разрешении операции функция check просто возвращает управление, при запрещении – возбуждает исключение SecurityException. Реализация по умолчанию для любого метода check* предполагает, что вызываемый метод не имеет права на выполнение данной операции. В обязанности менеджера безопасности, работающего с апплетами, входит защита от загрузки новых загрузчиков классов, защита потоков и групп потоков друг от друга, контроль за обращением к системным ресурсам, к ресурсам виртуальной машины, к сетевым соединениям и т. п.

Текущий менеджер безопасности устанавливается с помощью функции System.setSecurityManager, причем, если менеджер безопасности уже был установлен, эта функция также вызывает SecurityException.

В JDK 1.1 система безопасности получила дальнейшее развитие (рис. 10.2). Принципиально ничего не изменилось, но была добавлена возможность цифровой подписи классов – аналог сертификатов в ActiveX. Теперь можно решить, заслуживает ли подписанный удаленный код полного доверия, и не накладывать на него стандартные ограничения.

Рис. 10.2. Модель безопасности JDK 1.1

Побочным эффектом введения этого механизма стало появление в составе стандартной библиотеки Java криптографических функций – Crypto API.

Модель безопасности JDK 1.1 отличалась черно-белым взглядом на мир – мы либо полностью доверяем загруженному коду, либо нет. В Java 2 (JDK 1.2), вышедшей в декабре 1998, была представлена новая гибкая модель безопасности, основанная на привилегиях и правах доступа (рис. 10.3).

Рис. 10.3. Модель безопасности JDK 1.2

Дополнительно к существующим классам в Java 2 добавились:

• новый загрузчик классов java.security.SecureClassLoader;

• класс java.security.Permission, наследники которого используются для определения прав доступа к различным ресурсам, и класс java.security.PermissionCollection, позволяющий группировать права доступа. За доступ к файлам отвечает java.io.FilePermission, к сети – java.net.SocketPermission, к графическим ресурсам – java.awt.AWTPermission и т. д.;

• класс java.security.AccessController, используемый для контроля доступа FilePermission p = new FilePermission("/tmp/junk", "read"), и AccessController.checkPermission(p);

• класс java.security.ProtectionDomain, позволяющий объединить классы, которым предоставляются одинаковые права доступа;

• класс java.security.Policy, отвечающий за политику безопасности. В каждый момент активен только один объект Policy, считывающий настройки из файла конфигурации. В этом файле можно описать, какие права доступа связаны с той или иной подписью и/или местом расположения файлов:

grant codeBase «http://www.somehost.com/*», signedBy «Signer»

{

permission java.io.FilePermission "/tmp/*", "read";

permission java.io.SocketPermission "*", "connect";

};

Новая модель безопасности имеет следующие особенности:

• настраиваемый контроль доступа, дающий возможность указать, что код, обладающий определенными правами доступа, имеет право на определенные действия. Этого можно было добиться и раньше, с помощью создания специализированных менеджеров безопасности и загрузчиков классов, новая же архитектура значительно упростила такую процедуру;

• легко конфигурируемая политика безопасности. Опять же ключевое слово здесь – «легко»;

• возможность легкого расширения множества прав доступа, то есть фактически контролируемых действий. Если раньше это требовало введения новых методов check* в SecurityManager, теперь достаточно описать еще одного наследника класса java.security.Permission;

• усиление контроля за всеми Java-приложениями: в настоящее время никакой код не считается безопасным априори. Локальный код может быть подвержен тем же проверкам, что и код апплета, хотя, конечно, никто не мешает ослабить этот контроль с помощью настроек.

Таким образом, схема работы довольно гибкая и позволяет эффективно реализовать любую политику безопасности.

Данный текст является ознакомительным фрагментом.