WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 6 | 7 || 9 | 10 |   ...   | 12 |

«Michael Howard David LeBlank WRITING SECURE CODE Second Edition Microsoft Press ...»

-- [ Страница 8 ] --

Атака выполняется следующим образом: клиент-взломщик создает подключе ние и объявляет окно очень маленького размера (или равным нулю), заставляя сервер передавать данные чрезвычайно медленно и с очень большим дополни тельным расходом ресурсов. На каждую пару-тройку байт полезных данных при этом приходится около 40 байт заголовков TCP и IP. Неудачно написанное сер верное приложение блокирует ресурсы при попытке отправки информации, а на обслуживание дополнительных рабочих потоков и переключений контекста рас ходуется масса процессорного времени. До реализации поддержки окон приема не было нужды волноваться о подобных проблемах — стеки TCP/IP согласовали передачу данных без нашего вмешательства, и при стандартном использовании сокетов практически не приходилось менять принципы их работы. Но, к сожале нию, созданы специализированные приложения, доставившие всем массу неудобств.

Защита от такой атаки заключается в обязательной проверке откликов на зап росы отсылки данных. Подобный подход не только полезен, но считается при знаком хорошего стиля. Я очень часто сталкивался с ситуациями, в которых под ключения закрывались до начала передачи данных, но после создания. Иногда и в обычных условиях возможна ситуация, когда от сервера требуется передавать данные медленно. Например, передача данных системе с модемным доступом от высокопроизводительного Web-сервера, подключенного к гигабитному каналу связи. Если клиент слишком долго обрабатывает переданные ему данные, лучше всего закрыть сокет вызовами close и shutdown.

Выбор интерфейсов сервера При конфигурировании системы, которую предполагается публиковать в Интер нете, одна из первоочередных задач — свести к минимуму число сервисов, до ступных извне. Если у системы только один IP-адрес и один сетевой интерфейс, сделать это довольно просто: достаточно отключить ненужные сервисы, чтобы не Безопасность сокетов ГЛАВА прослушивать лишние порты. Если система входит в крупный Интернет-центр, га она, скорее всего, многоадресная (multihomed) — то есть имеет по крайней мере две сетевых карты. Это усложняет дело. Б большинстве случаев нельзя просто «вык лючить» сервис — он может оказаться необходимым для работы корпоративю >й сети. Если нельзя управлять тем, какие сетевые интерфейсы или IP-адреса про слушивает сервис, для обеспечения защиты придется применить какой-нибудь тип фильтрации на хосте или маршрутизаторе/брандмауэре. Но ведь возможны си туации, когда IP-фильтры настроены неправильно, маршрутизаторы по той и та иной причине вышли из строя;

хакер взломал соседнюю систему и атакует в об ход маршрутизатора. Вдобавок, если сервер даже в обычном состоянии сильно загружен, включение на нем фильтрации на основе хостов существенно увеличит нагрузку на него. Обеспечить необходимую защиту намного проще, если програ м мист предусмотрел возможность настройки сервиса. Желательно, чтобы в люб' >м IP-сервисе можно было настраивать один из следующих параметров:

• прослушиваемый сетевой интерфейс;

• прослушиваемый 1Р-адрес(а), предпочтительно с детализацией до отдельных портов;

• перечень клиентов, которым разрешено обращаться к сервису, Перечисление всех интерфейсов и привязка к ним IP-адресов довольно уто мительна в Windows NT. Приходится сперва просматривать реестр, чтобы найти адаптеры с привязкой, а затем обращаться к в другим разделам реестра, связан ным с конкретным адаптером.

Порядок обработки запросов на создание подключения API-интерфейс Windows Sockets 2.0 (Winsock) поддерживает несколько вариан"ов обработки данных, поступивших от того или иного клиента. При работе с про токолом, не поддерживающим подключений (например, UDP), все просто: опре деляем IP-адрес и порт клиента и на основании этой информации решаем, стоит ли обрабатывать запрос. Если вы не хотите принимать запрос, обычно пакет просто отбрасывается и клиенту никакого ответа не отправляется. На создание и отправ ление отклика расходуются ресурсы, и, вдобавок, атакующий получает дополни тельную информацию.

При работе с поддерживающим подключения протоколом (например, TCP) ситуация усложняется. Для начала я расскажу, как выглядит процесс создания TCP подключения с точки зрения сервера. На первом этапе клиент инициирует под ключение, направляя па сервер пакет SYN. Если тот готов «общаться» с этим кли ентом (предполагается, что порт прослушивается), он отправляет в ответ пакет SYN-ACK, после чего клиент завершает процесс создания подключения передачей пакета АСК. Теперь данные можно передавать в обоих направлениях. Чтобы зак рыть подключение, клиент отправляет на сервер пакет FIN. Сервер отвечает па кетом FIN-ACK и уведомляет приложение о закрытии подключения. Дальше сер вер обычно передает оставшиеся данные, отправляет клиенту пакет FIN и ждет ответа с FIN-ACK в течение интервала, равного двум максимальным периодам жи ?ни пакета в сегменте (maximum segment lifetime, MSL), Часть Ml Дополнительные методы создания защищенного кода Примечание MSL — это период времени, в течение которого пакету разреша ется существовать в сегменте, после чего он отбрасывается.

Вот как выглядел процесс создания подключения в старом стиле, с примене нием функции accept (полную версию приложения вы найдете в файле Ассерс Connection.cpp, папка Secureco2\Cbapterl5\AcceptConnectiori):

void 01dStyleListen(SOCKET sock) { // Привязка создана. Прослушиваем порт.

// Используем эту переменную как счетчик подключений.

int conns = 0;

whiled) // Будем поддерживать максимально разрешенное число подключений.

if(listen(sock, SOHAXCONN) == 0) { SOCKET sock2;

sockaddr_in from;

int size;

// Кто-то пытается подключиться - вызываем accept, // чтобы идентифицировать клиента.

conns++;

size = sizeof(sockaddr_in);

sock2 = accept(sock, (sockaddr*)&fcom, &size);

if(sock2 == INVALID^SOCKET) !

printf("Oum6Ka создания исключения - Jd\n", GetLastErrorO);

else // Примечание: в реальной жизни обычно // этот сокет передается рабочему потоку.

printf("npHHs)T запрос на подключение от X s \ n ", inet^ntoa(from.sin_addr});

// Теперь решаем, что делать с подключением;

// выберем простейший критерий обработки подключений:

// принимаем каждое второе подключение, iffconns % 2 == 0) !

printf("9TOT клиент нам понравился.\п");

// Здесь что-то делаем.

} else Безопасность сокетов ГЛАВА printfC'riuter вон!\п");

closesocket(sock2);

else < // Ошибка printf("Heyfla4Hafl попытка прослушивания код ошибки = Xd\n", GetLastErrorO);

break;

// Здесь размещается код, где принимается решение, // при каких условиях завершить работу серверного приложения.

if(conns > 10) { break;

• Я привел стандартный код для работы с сонетами, который проверен годами и выглядит довольно привлекательно. Что же в нем плохого? Во-первых, даже если немедленно сбросить подключение, взломщик узнает, что запрошенный порт прослушивается. Неважно, что сервис не отвечает на запросы взломщика, порт все таки «живой». Во-вторых, за время процедуры отказа в создании подключения удается обменяться в общей сложности семью пакетами. В конце концов, е< ли злоумышленник по-настоящему хочет нанести вред, он может изменить свой ЕР стек так, чтобы тот отправлял FIN-ACK в ответ на пакет FIN с сервера. В этом слу чае придется ждать отклика в течение двух MLS-интервалов. Даже если нормаль ный сервер обрабатывает несколько сотен подключений в секунду, несложно уви деть, каким образом злоумышленник)- удастся загрузить даже самый большой пул рабочих потоков. Частично проблема решается вызовом функции setsockport для присвоения параметру SO_LINGER нулевого или очень маленького значения пе ред вызовом dosesocket. В этом случае ОС будет быстрее освобождать сокеты Есть и другой способ установить подключение — средствами функции WSMccept.

Эта функция в сочетании с установкой параметра сокета SQ_CQNDITlONALj\CCHPT позволяет перед ответом принять решение, необходимо ли то или иное подклю чение.

int CALLBACK AcceptCondition( IN LPWSABUF IpCallerld, IN LPWSABUF IpCallerData, IN OUT LPQOS IpSQOS, IN OUT LPQOS IpGQOS, IN LPWSABUF IpCalleeld, OUT LPWSABUF IpCalleeData, OUT GROUP FAR *Q, 402 Дополнительные методы создания защищенного кода Часть III IN DWORD dwCallbackData i •;

sockaddr_in* pCaller;

sockaddr_in* pCallee;

pCaller = (sockaddr_in*)lpCallerld->buf ;

pCallee = (sockaddr_in*)lpCallee!d->buf ;

printf("flonbiTKa создать подключение с Sis\n", inet_ntoa(pCaller->sin_addr));

// Если требуется, чтобы програнма работала в Windows 98, // прочитайте статью Q1 93919.

ifUpSQOS l= NULL) I // Здесь согласуются условия использования QOS.

// Здесь принимается решение, что возвращать клиенту // подключения от самих себя принимать не будем.

if(pCaller->sin_addr.S_un.S_addr == inet_addr(My!pAddr)) ', return CF_REJECT;

I else { return CF_ACCEPT;

// Примечание: мы также можем вернуть CF_DEFER // эта функция должна выполняться в том же потоке, // что и вызывающая программа.

// Для этого сперва можно выяснить DNS-имя клиента, // а затем попытаться снова, уже зная, кто он, void NewStyleListen(SOCKET sock) • // Привязка создана. Прослушиваем порт.

// Используем эту переменную как счетчик подключений, int conns = 0;

// Сперва установим значение параметра.

BOOL val = TRUE;

if(setsockopt(sock, SOL.SOCKET, SO_CONDITIONAL_ACCEPT, (const cnar*)&val, sizeof(val)) != 0) ГЛАВА 15 Безопасность сокетов I printf("He удается установить SO_CONDITIONAL_ACCEPT код ошибки = Kd\n", GetLastErrorO);

return;

i while(1) { // Используем максимальное разрешенное число подключений.

if(listen(sock, SOMAXCONN) == 0) < SOCKET sock2;

sockaddr_in from;

int size;

// Кто-то пытается подключиться - вызываем accept, // чтобы идентифицировать клиента, conns++;

size = sizeof(sockaddr_in);

// Здесь начинаются отличия.

sock2 = WSAAccept(sock, (sockaddr*)ifrom, Ssize, AcceptCondition, conns);

// Используем conns для обратного вызова.

if(sock2 == INVALID_SOCKET) { printf("Ошибка приема подключения - Sd\n", GetLastErrorO);

:

else I // Примечание: в реальной жизни обычно // этот сокет передается рабочему потоку.

printf("Принят запрос на подключение от Xs\n", inet_ntoa(from.sin_addr));

// Выполняем какие-то операции.

closesocket(sock2);

} I else !

// Ошибка printf("Heyfla4Hafl попытка прослушивания код ошибки = Sd\n", GetLastErrorO);

Часть III Дополнительные методы создания защищенного кода break;

// Здесь размещается код, где принимается решение // при каких условиях завершить работу серверного приложения.

if{conns > 10) break;

!

!

Как вы видите, код почти совпадает с начальной версией;

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

[d:\ilPortScan.exe -v -p 8765 192.168.0. Port 192.168.0.1:8765:0 timed out Теперь взглянем на происходящее со стороны сервера:

[d:\JAcceptConnection.exe Привязка создана Попытка создать подключение с 192.168.0. Ошибка приема подключения - Попытка создать подключение с 192.168.0. Ошибка приема подключения - Попытка создать подключение с 192.168.0. Ошибка приема подключения - По умолчанию попытка установить TCP-подключение делается клиентским приложением трижды, хотя это зависит от того, как написано приложение. В обычных условиях клиент направляет пакет SYN и ждет ответа. Если отклика нет, отправляется еще один пакет SYR и клиент ожидает в два раза дольше. Если от вет снова не получен, попытка повторяется, причем время ожидания снова уве личивается вдвое. Если же клиент настроен на время ожидания меньшее стандар тного, то реализуется только две попытки подключения. Новый вариант программы хорош и с точки зрения безопасности: взломщик получит извещения о тайм-ауте и не сможет выяснить причину — на порту включена фильтрация или приложе ние не желает устанавливать подключение. Очевидный недостаток — дополнитель ные издержки на сервере на отклонение трех попыток подключения. Однако они невелики и зависят от объема работы, предусмотренной в функции обратного вызова.

Значительный недостаток функции WSAAccept в том, что она несовместима со встроенной в операционную систему защитой от переполнения SYN-запросами (SYN flood). Она также часто некорректно взаимодействует с высокопроизводительными приложениями (где обычно применяется AcceptEx) с перекрывающимися опера циями ввода/вывода.

ГЛАВА 15 Безопасность сокетов Создание приложений, поддерживающих взаимодействие через брандмауэры Разработчики часто жалуются, что брандмауэры препятствуют нормальной работе приложений, Да будет вам известно: брандмауэры обязаны «путаться под нога ми»*, В этом и заключается их задача. Если бы они пропускали все пакеты, то на зывались бы маршрутизаторами (хотя некоторые маршрутизаторы и выполняют функции брандмауэров). К тому же часто администраторы брандмауэров — уп рямые люди, которые не хотят ничего менять. По крайней мере именно бранд мауэры, управляемые подобными администраторами, реально защищают от зло умышленников. Прижимистый администратор очень не любит открывать порты для неизвестных ему приложений — тем более что количество открываемых портов удваивается, если в приложение порты работают в обоих направлениях. Если при ложение написано грамотно, брандмауэры очень редко создают помехи его ра боте. По моим прогнозам, в будущем количество брандмауэров возрастет. Они будут располагаться не только на границах сети, но и внутри нее. Поэтому проектиро вание приложений, поддерживающих взаимодействие через брандмауэры, будет иметь еще большее значение.

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

• используйте для работы только одно подключение;

• не создавайте обратных подключений от сервера к клиенту;

• применяйте протоколы с поддержкой подключений, так как их легче защищать;

• не используйте в приложениях передачу данных поверх другого протокол;

!:

• позаботьтесь, чтоб данные прикладного уровня не содержали IP-адреса хостов;

• выделите для взаимодействия клиента и сервера специальные порты.

А теперь я поясню каждое из перечисленных правил.

Используйте для работы только одно подключение Потребность приложения более чем в одном подключении — признак плохого проектирования. Архитектура сокетов обеспечивает двухсторонний обмен дан ными по одному подключению, поэтому редко возникает ситуация, когда необ ходимо создание нескольких подключений. Обычно это происходит, когда вцо бавок к каналу для передачи данных требуется отдельный канал управления, но такая возможность предусмотрена в протоколе TCP. К тому же необходимость во многих подключениях легко удовлетворить, правильно спроектировав протокол-, в большинстве протоколов в заголовке пакета передается информация о типе содержащихся данных. Если вам все же кажется, что без нескольких подключе ний не обойтись, тщательнее проанализируйте архитектуру своего приложения.

Учите, что IP-фильтры тем эффективнее, чем меньше правил фильтрации. Одно единственное подключение — это один набор правил и меньше возможности ошибиться при настройке брандмауэра.

406 Часть III Дополнительные методы создания защищенного кода Не создавайте обратных подключений от сервера к клиенту Показательный пример «недружественного по отношению к брандмауэру» при ложения — FTP-сервер. Он прослушивает порт 21 протокола ТОР, а при подклю чении клиент сообщает серверу о необходимости обратного подключения с TCP порта 20 на порт с более высоким номером (больше 1024). Если администратор брандмауэра достаточно безрассуден, чтобы разрешить такие действия, злоумыш ленник сможет установить на своей системе порт 20 источника и атаковать лю бой сервер, прослушивающий порт с более высоким номером. Самые известные серверы, доступные для атаки таким способом, — Microsoft SQL Server, работаю щий через порт 1433, Microsoft Terminal Server (порт 3389), клиенты X Window (в системе Х-Window роли клиента и сервера распределены с точностью до наобо рот относительно общепринятых) (порт 6000). Если администратор заблокирует на брандмауэре внешние подключения именно к этим серверам, впоследствии не избежно обнаружатся другие неохваченные серверы с другими портами, что при ведет к проблемам с безопасностью. Сервер никогда не должен сам подключать ся к клиенту. Не говоря уже о том, что это усложняет одноранговое взаимодей ствие между хостами. Если в системе, на которой выполняется приложение, запу щен персональный брандмауэр, сложно обеспечить двухстороннюю передачу данных. Лучше, когда приложение прослушивает единственный порт, к которому подключаются другие системы.

Используйте поддерживающие подключения протоколы Такие протоколы (например, TCP) проще защищать, чем те, что не поддержива ют подключения (в частности, UDP). В приличном брандмауэре или маршрути заторе можно задавать правила создания подключений, чтобы разрешить созда ние подключений из внутренних сетей во внешние, но никак не наоборот. Пра вило, позволяющее работать DNS-клиентам, на маршрутизаторе выглядит примерно так:

Allow internal UDP high port to external UDP port Allow external UDP port 53 to internal UDP high port Оно позволяет злоумышленнику установить на источнике порт под номером 53 и атаковать любые UDP-сервисы внутренней сети, работающие на непривиле гированных портах. Администратор брандмауэра может решить эту проблему двумя способами. Первый — настроить брандмауэр так, чтобы он выполнял функцию прокси для выбранного протокола, а второй — использовать брандмауэры с под держкой состояний. Как ясно из названия, брандмауэры такого типа отслежива ют состояния подключений. Обнаружив запрос, отправленный из внутренней сети, такой брандмауэр ожидает ответа от конкретного сервера и с конкретного порта и передает обратно во внутреннюю сеть только ожидаемые ответы. Конечно, иногда веские причины заставляют использовать не поддерживающие подключения про токолы (они обеспечивают большую производительность), но если есть выбор, то лучше применять протоколы с подключениями.

Безопасность сокетов ГЛАВА Не используйте в приложениях передачу данных поверх другого протокола Если приложение использует другой протокол для передачи данных, сложно го ворить о высоком уровне безопасности. В этом случае приложением трудно уп равлять, к тому же появляются проблемы с безопасностью (причем как на стор< ) не клиента, так и сервера), так как сильно усложняется взаимодействие приложе ния с существующим ПО. Обычно мультиплексирование применяют из-за того, что администраторы брандмауэра отказываются открывать дополнительные порты и разработчики пытаются обойти эти ограничения, пустив данные поверх како го-нибудь разрешенного протокола прикладного уровня. Что можно на это ска зать — прежде всего, хороший администратор все равно заблокирует подобное приложение при помощи фильтров информационного наполнения (content level filters). Скоро вы убедитесь в том, что брандмауэр в большинстве случаев не ме шает работе должным образом написанного приложения. Если следовать приве денным правилам, вам не придется использовать передачу данных поверх уже существующего протокола. Это не значит, что такой подход вообще неприемлем.

Например, для связи двух Web-серверов абсолютно естественно использовать порт 80 протокола TCP.

Не размещайте IP-адреса хостов в данных прикладного уровня Пока протокол IPv6 не получил широкого распространения, популярность пре образования сетевых адресов (network address translation, NAT) и прокси-серве ров не снизится, а даже вырастет, особенно из-за обострения проблемы нехват ки адресов. Если в приложении данные прикладного уровня содержат сведен ля об IP-адресах, оно не будет работать на системах, расположенных за пределами сегмента, обслуживаемого NAT-сервером или прокси. Вывод очевиден: данные не должны содержать информацию об IP-адресах хостов. Еще одна веская причина, по которой не стоит включать информацию транспортного уровня в данные прикладного уровня — приложение перестанет работать при переходе на IPv6.

Создавайте настраиваемые приложения Клиентам требуется изменить стандартный номер порта, на котором работает приложение, по различным причинам. Возможность настройки сервера и клиен та предоставляет вашим клиентам дополнительную гибкость при развертывании, Бывает и такое, что параметры порта по умолчанию конфликтуют с другими пр и ложениями. Некоторые полагают, что можно обеспечить защиту, утаивая особен ности реализации (хотя это обычно не приносит большой пользы — безопасность вкупе с утаиванием намного лучше);

они полагают, что, изменив порт на котором работает сервис, они повысят его безопасность.

Дополнительные методы создания защищенного кода 408 Часть III Подмена сетевых объектов и доверие хостам и портам Подмена сетевых объектов предусматривает участие трех хостов: атакующего, жертвы и безобидного стороннего. Атакующий заставляет жертву поверить в то, что подключение, информация или запрос поступает от стороннего сервера. В протоколах без создания подключения подмена реализуется без проблем;

взлом щику достаточно выбрать в качестве стороннего подходящий хост, подменить в пакетах поле с адресом источника и переправить пакеты жертве, Пример протокола, уязвимого для подмены сетевых объектов, — syslog. Этот стандартный протокол применяется в UNIX и UNIX-подобных системах и иногда встречается в Windows. Для передачи данных в этом протоколе применяется UDP кроме того, syslog поддерживает настройку на прием данных журналов только с заданных избранных хостов. Злоумышленнику достаточно узнать адрес хотя бы одного из их, чтобы подменить его и заполнить файлы журналов любой инфор мацией по своему выбору, Протоколы с поддержкой подключений также в некоторой степени подвержены подмене сетевых объектов. Широко известен пример, когда Кевин Митник (Kevin Mitnick) использовал утилиту rsh с подменой IP-адреса для вторжения на компь ютер Цутому Шимомуры (Tsutomu Shimomura). Несмотря на то, что большинство современных операционных систем намного лучше защищены от атак с TCP подменой, полностью доверять информации, ориентируясь только по адресу ис точника, не стоит. Еще один вариант подмены хостов — искажение базы данных DNS. Такую атаку не очень сложно организовать, но если информация DNS изме нена, может оказаться, что, подключаясь к «правильному» сайту somebost.nicwguys.org, приложение будет работать с хакерским хостом, реальный адрес которого destnic tion.evilbackers. org.

Внимание! Если приложению нужно точно знать, с кем оно имеет дело, пре дусмотрите проверку подлинности клиента с помощью общего секре та, сертификата или друтого надежного криптографического метода. Ни когда не полагайтесь на IP-адрес или DNS-имя в деле идентификации хоста.

С этим связана и проблема доверенных портов. Хорошая иллюстрация — ути лита rsh: она предполагает, что в UNIX-системах только привилегированные пользо ватели (обычно только root) вправе использовать порты с номерами ниже 1024.

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

К сожалению, подобные опасности грозят не только устаревшим и уже неис пользуемым протоколам. Во множестве современных приложений, работающих ГЛАВА 15 Безопасность сокетов с важными данными, возможны (и не в единственном числе) ошибки, описанные в этом разделе. Не наступайте на эти грабли! Важно знать, кем на самом деле яв ляются ваши клиенты или серверы, так что позаботьтесь о взаимной проверке подлинности в приложении, IPv6 наступает!

IPv6 — это новая версия протокола IP, в которой устранено большинство недо статков исходной реализации IP — IPv4. Самая заметная особенность IPv6 — 128 разрядная адресация, которая позволяет присвоить индивидуальные IP-адреса всем имеющимся ныне сетевым устройствам, при этом еще остается масса не занятых адресов. IPv6 предоставляет множество интересных возможностей, и этой теме посвящено достаточно книг, поэтому я расскажу лишь о самых интересных. В IPv так и не решены многие из описанных в этой главе проблем. Этот протокол пре доставляет адресное пространство, достаточно обширное для того, чтобы предо ставить глобальные IP-адреса всем имеющимся сетевым устройствам. Подробный рассказ о возможностях IPv6 выходит за рамки этой книги, поэтому если вы уже хорошо знакомы с IPv4, советую проштудировать книгу Кристиана Хютемы (Christian Huitema) «IPv6: The New Internet Protocol, Second Edition» (IPv6: НОВЕЛИ протокол Интернета) (Prentice Hall PTR, 1998). Кристиан занимал пост главы от деления Internet Activities Board в организации IETF, а теперь работает в компа нии Microsoft. Поддержка протокола IPv6 предусмотрена в Microsoft Windows.NET Server 2003 и уже включена в Service Pack 1 для Windows XP. А теперь немного о некоторых интересных особенностях IPv6.

У 1Ру6-системы может быть несколько IP-адресов. В IPv6 появилось понятие анонимных IP-адресов, которые вводятся на время, а затем удаляются. Таким об разом, в мире IPv6 не стоит основываться на доверенных IP-адресах.

Область действия IPv-6-адрсса ограничивается одной из трех областей: локаль ной (link local), локальной в пределах сайта (site local) или глобальной (global). В приложениях, предназначенных для доступа только из локальной подсети, при меняется локальный IP-адрес. IP-адреса, локальные в пределах сайта, предназна чены для маршрутизации только в данном сайте или корпоративной сети, но не для глобальной маршрутизации. Есть новинки и в сокетах: теперь разрешается устанавливать область действия сокета с привязкой;

и это действительно здорово.

Все реализации IPv6 обязаны поддерживать протокол IPSec (Internet Protocol Security). При работе с IPv6 всегда можно рассчитывать на его наличие. При реа лизации IPv6, как и прежде, придется решать организационные вопросы исполь зования имеющейся инфраструктуры (например, как договариваться о ключе), но вместо того чтобы создавать собственную систему обеспечения секретности и целостности пакетов, допустимо во время установки системы настроить должним образом протокол IPSec. Кристиан упоминает в своей книге, что производителям предоставлено право добавлять в протокол возможность включать IPv6 на сокете «на ходу*, в период исполнения. Я думаю, что это хорошая идея, но у меня нет сведений о каких-либо планах Microsoft или другой компании на этот счет — х< >тя все может и измениться.

IPv6 затрудняет задачу для злоумышленников. В настоящее время, проскапи ровать все устройства Интернета (на основе IPv4) — дело нескольких дней, даже Часть III Дополнительные методы создания защищенного кода если в вашем распоряжении немного систем. Просканировать даже нижнюю 64 битную «локальную» часть 1Руб-адресов за разумное время при нынешних скоро стях досту-па и других ограничениях на пакеты не представляется возможным.

Резюме Вы узнали, как создавать привязку сокетов так, чтобы избежать локальных атак подмены серверного приложения. Ожидается, что с появлением Windows.NET Server 2003 подобных проблем станет меньше. При проектировании серверных приложений тщательно подумайте, каким образом пользователи будут определять прослушиваемые сетевые интерфейсы и при каких условиях принимать запросы на создание подключений, Важнейшая тема этой главы — создание приложений, поддерживающих взаи модействие через брандмауэры. Мой прогноз: R ближайшее время ожидается ла винообразный рост числа установленных брандмауэров, особенно персональных.

Если у приложения не возникает проблем при работе через брандмауэры, значит, оно полностью готово к этому «нашествию брандмауэров».

ГЛАВА Защита RPC, ActiveX-элементов и объектов DCOM 1У1еханизм удаленного вызова процедур (Remote Procedure Call, RPC) является ос новным средством взаимодействия со времен выхода ОС Microsoft Windows NT 3. (то есть с 1993 г)- Существует две основных версии механизма RFC: DCE (Distribuled Computing Environment) RPC и ONC (Open Network Computing) RPC. Обе приья ты в качестве открытого стандарта и реализованы на ряде платформ. В операии онных системах корпорации Microsoft применяется DCE RPC, хотя во многих Windows-приложениях используется ONC RPC. Стоит заметить, что DCE RPC иногда называют «Microsoft RPC», a ONC RPC — «Sun RPC». В этой главе под термином RPC подразумевается вариант DCE RPC, реализованный Microsoft, хотя некоторые утвер ждения верны для обеих версий RPC.

Работа огромного числа приложений для Windows NT/2000/XP в значитель ной степени зависит от механизма RPC. Обеспечение безопасности предпола! а ет укрепление всех компонентов системы, поэтому жизненно важно создавать.ча щищенные и устойчивые к атакам RFC-приложения. Об этом и пойдет речь в эт )й главе. Кроме того, вы узнаете о DCOM-приложениях и ActiveX-элементах. Причи на в том, что RPC используется в модели DCOM (Distributed COM) в качестве ме ханизма взаимодействия СОМ-приложений, а технология ActiveX-элемептов пред ставляет собой особый вариант СОМ, При проектировании и создании защищенных приложений стоит учиться на прошлых ошибках, поэтому я познакомлю вам с тремя брешами в RPC, правда, уже устраненными компанией Microsoft. Первая обеспечивала успех атаки, в котор ж злоумышленник отправлял диспетчеру LSA некорректные данные, приводившие Дополнительные методы создания защищенного кода 412 Часть III к его зависанию. На первый взгляд казалось, что причина в API;

однако виновни ком оказалась API-функция LsaLookupSids, которая пересылала дефектные данных в LSA средствами RPC. Подробнее об этой дыре в защите рассказывается в бюлле тене «Malformed Security Identifier Request» («Некорректный запрос идентифика тора безопасности*} на странице http://iinwv.microsoft.CQm/tecbnet/security/bulletin/ m$99-057-asp.

Вторая брешь позволяла путем передачи «мусорных* данных на порт 135 ком пьютера под управлением Windows NT 3.51 /NT 4 заставить RFC-сервер, прослуши вающим на этом порту, вызвать 100-процентную загрузку процессора, практически заблокировав доступ пользователей к серверу. Чаще всего злоумышленник под ключался telnet-клиентов к порту 135, передавал десяток-другой случайных сим волов и обрывал связь. Эта брешь подробно описана в статье <• Telnet to Port Causes 100 Percent CPU Usage* («Сеанс Telnet к порту 135 вызывает 100-процент ную загрузку процессора*) в базе знаний Microsoft Knowledge Base (http://support microsoft.com/suppon/kb/articles/Ql 62/5/67-asp').

Последняя брешь, описанная в выпущенном Microsoft в июле 2001 г. бюллете не «Malformed RPC Request Can Cause Service Failure* («Некорректный RPC-запрос способен вызвать сбой службы»), связана с серверными RPC-заглушка ми (stubs), которые перед перенаправлением запросов другим сервисам не выполняли их не предмет корректности, делая систему уязвимой для DoS-атак. Статья опубликова на на странице bttp://wwu\microsoft.com/tecbnet/security/bulletin/ms01-Q4Lasp, Азы RPC Цель этого раздела — рассказать о ключевых понятиях и терминах RFC. Если вы хорошо знаете RPC, переходите к разделу «Проверенные методы обеспечения безопасности RPC». Однако новичкам не стоит его игнорировать — первое зна комство с RPC способно привести в замешательство, Что такое RPC RFC (Remote Procedure Call) — это механизм взаимодействия клиентского и сер верного приложений, в котором клиенты вызывают функции сервера. Для клиен та это выглядит как вызов локальной функции, но на самом деле вызов передает ся средой исполнения RPC (RFC runtime) серверу, который исполняет функцию и возвращает результаты клиенту.

Примечание RFC — основная технология для языков программирования С и C++. Несмотря на наличие соответствующих оболочек, в других языках (таких, как Perl, Microsoft JScript или Microsoft Visual Basic) вместо RPC проще использовать COM или DCOM.

Встроенный в системы Microsoft Windows вариант механизма RPC основан на OSF RPC (Open Software Foundation RPC), что позволяет ему взаимодействовать с другими операционными системами, например UNIX и Apple.

Большинство системных служб Windows [в том числе Print Spooler (Диспетчер очереди печати), Event Log (Журнал событий), Remote Registry (Служба удаленно ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM го управления реестром), Secondary Logon (Вторичный доступ)], как и сотни при ложений сторонних производителей, хотя бы в некоторой степени используют RFC. К тому же, многие приложения, располагающиеся на одном компьютере, взаимодействуют посредством локальной версии RFC — LRPC, Создание RFC-приложений Создание RFC-приложения поначалу может показаться сложной задачей. Исполь зование RFC рекомендуется запланировать уже на этапе проектирования при ю жения, а не добавлять поддержку RFC вдогонку. При создании RPC-приложения применяются следующие компоненты:

• код клиента;

• код сервера;

• файл на языке описания интерфейсов (с расширением.idl);

• необязательный файл с параметрами приложения (с расширением.acf).

Код клиента обычно пишется на C/C++. В нем вызываются различные функ ции: конфигурирования RFC, локальные, а также удаленного RPC. Код сервера также содержит код запуска RFC. но интереснее всего то, что он содержит реальные функции, к которым обращаются RFC-клиенты. Чрезвычайно важен IDL-файл. В нем определяются сигнатуры функций удаленного интерфейса (то есть имя, ар гументы и возвращаемые значения), что позволяет разработчику группировать функции по простым в управлении интерфейсам. ACF-файл позволяет менять поведение RPC-приложения, не вмешиваясь в сетевое представление данных.

Компиляция кода Компиляция RPC-приложения выполняется в несколько этапов.

1. Компиляция IDL- и ACF-файлов посредством Midl.exe. В результате получается три файла: код серверной и клиентской RFC-заглушек и заголовочный файл, 2. Компиляция клиентского кода и кода клиентской RPC-заглушки. Учтите: код клиента содержит также заголовочный файл, созданный на первом этапе, 3. Компоновка клиентского кода с необходимой RFC-библиотекой периода ис полнения (обычно Rpcrt4.Hb).

4. Компиляция серверного кода части и кода серверной заглушки RFC. Учтите, что код серверной части приложения содержит также созданный на нерюм этапе заголовочный файл.

5. Компоновка кода серверной части с соответствующей RFC-библиотекой псри> )да исполнения (обычно Rpcrt4.1ib).

Вот и все! Посмотрим на примере, как это все работает (рис. 16-1). Пусть при ложение (работающее по принципу телефона) называется Phone, код клиента содержится в файле Phonec.c, код сервера — в Fhones.c, a IDL- и ACF-файлы на !ы ваются соответственно Phone.idl и Phone.acf. В результате компиляции Phone.idl с помощью Midl.exe образуются три файлы: заголовочный файл Phone.h и клиент ская и серверная RFC-заглушки — Phone_c.c и Phone_s.c. Затем Phonec.c и Phone c.c компилируются и компонуются с библиотекой Ppcrt4.1ib, а в результате получает ся клиентская часть приложения — Phonec.exe. Наконец, путем компиляции Phones.c Дополнительные методы создания защищенного кода 414 Часть III и Phone_s.c и компоновки с библиотекой Ppcrt4.Hb создается серверная часть Phones.exe.

Phone.acf —ь, Phone_s.c -Tk —ц Ibk Phones.c Phonec.exe Phonec.c Phone, h Phones.exe Рис 16-1. Процесс разработки RFC-приложения Это не так сложно, как кажется на первый взгляд! Исходный код приложения Phone вы найдете в папке Secureco2\Chapterl6\RPC.

Как взаимодействуют RPC-приложения При взаимодействии клиента и сервера первый вызывает клиентскую заглушку RPC, которая, в свою очередь, выполняет маршалинг (marsalling) предназначенных серверу данных. Под маршалингом подразумевается упаковка информации о фун кции и ее параметров в формат, понятный любому КРС-серверу на любой плат форме. После упаковки клиентский запрос отправляется на сервер, где серверная заглушка распаковывает его и передает данные серверному коду. Выполнив за прошенные операции сервер передает данные обратно клиенту, также применив марш ал инг.

Для связи между клиентом и сервером в RPC-приложениях используются раз личные сетевые транспортные протоколы, в том числе именованные каналы и сокеты TCP/IP. Могу обрадовать: вам, как программисту', не нужно разбираться в деталях работы сетевых протоколов — всю работу выполнит механизм RPC, Для связи с сервером клиент создает привязку» (bind) к нему, что подразумева ет создание описателя привязки на основе строки привязки. Последняя состоит из нескольких частей, причем в первой содержится информация о последователь ности, протоколов (protocol sequences), то есть о применяемых протоколах. У каждого протокола свое характерное имя. В табл. 16-1 перечислены наиболее часто используемые последовательности протоколов.

ГЛАВА 16 Защита RFC, ActiveX-элементов и объектов DCOM Таблица 16-1. Примеры последовательностей протоколов Последовательность протоколов Примечание Именованные каналы псасп_пр Локальное (в отличие от удаленного) ncalrpc взаимодействие между процессами Протокол TCP/IP ncacn_ip_tcp Затем следует адрес сервера, обычно это имя сервера в формате, понятном для используемого протокола. Дальше указывается конечная точка, которая опреде ляет требуемый сетевой ресурс на хосте. Есть специальная функция для состав ления необходимой строки — RpcStringBindingCompose, Например, следующий код создает строку привязки ncacn_np:nortbwintraders[\\pipe\\phone]-.

LPBYTE pszUuid = (LPBYTE)NULL;

LPBYTE pszProtocolSequence = (LPBYTE)"ncacn_np";

LPBYTE pszNetworkAddress = (LPBYTE)"northwindtraders";

LPBYTE pszEndpoint = (LPBYTE)"\\pipe\\phone";

LPBYTE pszDptions = (LPBYTE)NULL;

LPBYTE pszStringBinding = (LPBYTE)NULL;

RPC_STATUS status = RpcStringBindingCompose(pszUuid, pszProtocolSequence, pszNetworkAddress, pszEndpoint, pszOptions, ipszStringBinding);

После создания описателя привязки клиент готов вызывать RFC-функции, Описатели контекста и состояние подключений С формальной точки зрения механизм RPC не поддерживает состояние подклю чения: RFC-сервер никакой информации о пользовательских подключениях не сохраняет. Однако некоторым приложениям необходимо, чтобы сервер сохранял состояние между клиентскими вызовами информации о клиентах;

в этом случае серверу приходится хранить информацию о состоянии всех и каждого клиента.

Для этого используются описатели контекста — сложные «непрозрачные» струк туры данных, выдаваемые клиенту сервером. При каждом запросе клиент посы лает серверу описатель контекста, роль которого в данной ситуации напоминает cookie-файлы в Web, Запомните: в RPC применяются два основных типа описателей: привязки и контекста. Первые необходимы для идентификации логического соединения между клиентом и сервером. Они схожи с описателями файлов. Вторые позволяют сер пе ру хранить информацию о состоянии клиентов в промежутке между вызовами функций.

Дополнительные методы создания защищенного кода 416 Часть Проверенные методы обеспечения безопасности RPC Л сейчас о ряде методов обеспечения безопасности — они проверены опытным путем и хорошо себя зарекомендовали. RPC грозит:

• опасность DoS-атак, во время которых злоумышленник направляет специаль ным образом измененные данные конечной точке RPC, a RFC-сервер не спо собен правильно обработать дефектные данные и «падает»;

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

• опасность модификации и подмены данных: пересылаемые по сети незащи щенные данные легко перехватить и изменить.

Так как же бороться с ними?

Параметр /robust MIDL-компилятора В Windows 2000 появилась возможность компиляции MIDL-данных (Microsoft Interface Definition Language) с параметром /robust. Она позволяет выполнить динамическую проверку данных, поступающих на вход службы маршалинга RPC сервера. Это увеличивает стабильность сервера, так как при этом отклоняется больше некорректных пакетов, чем в старых версиях RFC. Чрезвычайно важно, чтобы маршалер отклонял все некорректные пакеты.

Если приложение предназначено для работы в Windows 2000 и последующих ОС, обязательно применяйте этот параметр компилятора. При этом не придется ничего менять в исходном коде клиента или сервера. Единственный недостаток — такая возможность доступна только с Windows 2000. Если необходимо обеспечить совместимость RFC-сервера и с Windows NT 4, придется создать две версии сер вера: для работы в Windows NT 4 и для Windows 2000 и последующих ОС. Приме нить параметр проще простого: просто добавьте /robust в командную строку вызова MIDL-компилятора.

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

Атрибут [range] Он используется в IDL-файле для изменения значений важных параметров или полей, например необходимых для хранения размера или длины. Так, IDL-компи лятор позволяет описать размер большого двоичного объекта (blob) данных:

void Message([in] long lo, [in] long hi, [size_is(lo, hi)] char **ppData);

Теоретически злоумышленник может присвоить переменным lo и hi значения, не вписывающиеся в рамки разрешенного диапазона, и нарушить работу сервера или клиента. Для снижения вероятности реализации такой атаки и предусмотрен ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM атрибут I range}. В следующем примере значения переменных /о и Ы должны на ходиться в диапазоне 0 — 1023, а это означает, что длина данных, на которые указывает fjpData, не может превышать 1023 байта.

void Hessage([in, range(0,1023)] long lo, [in, range(0,1023)] long hi, [size_is(lo, hi)] char **ppData);

Заметьте: для создания кода заглушки, выполняющего такую проверку, при компиляции IDL-файла необходимо использовать параметр /robust. Кроме того, в этом случае проверка того, действительно ли hi больше 1о, полностью возложе на на сервер.

Применяйте аутентификацию подключений Предотвратить DoS-атаки очень просто — заставьте клиентов проходить аутенти фикацию. Представьте сервер, принимающий запросы клиентов и имеющий д.-ia режима работы. В первом он принимает данные от всех подряд без проверки подлинности, поэтому все поступающие от клиентов данные считаются аноним ными. Во втором режиме перед приемом данных проводится аутентификация пользователя, данные, поступающие от не прошедших аутентификацию клиентов, отбрасываются. В каком режиме риск успешной DoS-атаки выше? Ответ очевиден:

в первом, анонимном варианте, так как в этом случае нечего противопоставить действиям анонимного злоумышленника. Если атакующий знает, что для совер шения атаки придется представиться, маловероятно, что он станет предпринимать враждебные действия! Так что поддерживайте на своих RFC-серверах только аутеи тифицированные подключения.

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

При добавлении методов защиты RFC-клиентов на более поздних этапах (в отличие от встраивания их в приложение с самого начала) часто применяют следующий подход: в переходный период (па время обновления клиентской час ти) сервер принимает оба типа подключений. Затем постепенно поддержка не про ходящих аутентификацию подключений блокируется. Ясно, что наиболее безо пасный вариант — реализовать безопасность с самого начала.

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

status = RpcBindingSetAuthInfo( phone_Handle, Часть III Дополнительные методы создания защищенного кода szSPN, // Для поддержки Kerberos применяется SPN сервера.

RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE, NULL, 0);

Второй аргумент, szSPN, определяет основное имя службы (service principal name, SPN), о котором я расскажу чуть позднее. Третьему аргументу,AuthnLevel, присво ено значение RPC_C_AUTHN_LEVEL_PKT_PPIVACY— это означает, что данные, ко торыми обмениваются клиент и сервер шифруются, проходят аутентификацию и проверку на целостность и неизменность. В табл. 1б-2 указаны возможные зна чения уровня безопасности RPC.

Таблица 16-2. Уровни безопасности RPC Параметр Значение Примечание 0 Устанавливается уровень безопасности службы RPC_C_AVTHN_LEVEL по умолчанию. Лично я избегаю этого вариан DEFAULT та, ведь заранее неизвестно, какие параметры предусмотрены в службе. Возможно, это из-за того, что я слишком долго работаю в области информационной безопасности и предпочи таю понимать все происходящее полностью!

В настоящее время значение по умолчанию для RFC-приложений — RPC_C_AUTHN_LEVEL_CONNECT 1 Аутентификация не выполняется. Настоятель RPC_C_AUTHN_LEVEI._NONE но не рекомендую этот вариант 2 Аутентификация выполняется при первом RPC_C_AUTHN_LEVEL_ подключении клиента к серверу CONNECT 3 Аутентификация выполняется перед каждым RPC С AUTHN LE\^EL CALL RPC-вызоном. Заметьте: если протокол поддер живает подключения (то есть его название на чинается с псасп), это значение автоматичес ки превращается в RPC_C_ALTHN_LEVEL_PKT I Аутентификация выполняется с целью убе RPC_C_A UTHN'_LEVEJ._PKT диться в авторстве полученных данных RPC_C_A LrTHN_LEVEL_PKT_ 5 Действие параметра аналогично INTEGRITY RPC_C_AUTHN_LEVEL_PKT, но вдобавок прове ряется целостность данных RPC_C_AUTHN_LEVELJ>KT_ Аналогично RPCJ2_AUTHN_LEVEL_PKT_INTEG PRIVACY R1TY. но дополнительно данные шифруются Примечание Некоторые читатели заметят, что название аргумента — Authn Level— вводит в заблуждение, ведь этот аргумент отвечает не только за аутентификацию, но и за целостность и конфиденциальность.

В итоге на клиентской стороне получаем такую картину: функция RpcBinding SetAuthlnfo помещает идентификационную информацию клиента в описатель привязки, который при вызове удаленной процедуры передается серверу в каче стве первого параметра.

ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM Конфигурация сервера Для обеспечения подходящего уровня безопасности сперва создают серверный обработчик аутентификации, а затем конфигурация подключившегося клиента проверяется на соответствие заданному уровню безопасности.

Уровень аутентификации определяется функцией RpcServerRegisterAuthlnfo.

status = RpcServerRegisterAuthInfo( szSPN, RPC_CJWTHN_GSS_NEGOTEATE, NULL, NULL);

Для Windows-аутентификации наиболее важен второй аргумент, AutbnSvc, так как именно он определяет способ аутентификации клиента. Наиболее часто приме няется значениеRPC_C^UTHN_GSS_WINNT, которое соответствует NTLM-аутенти фикации. Однако, вместо этого значения в Windows 2000 и последующих ОС ре комендуется RPC_C_AUTHN_GSS_NEGQTIATE-. в этом случае способ аутентификации выбирается автоматически из двух вариантов: NTLM и Kerberos, Существует еще один вариант - RPC_C_AUTHN_GSS_KERBERQS, но ЯРС_С_ AUTHN_GSS_NEGOTIATE предоставляет приложению больше свободы, позволяя ра ботать и в «старых» ОС, например Windows NT 4. Конечно, и злоумышленник по лучает больший простор в своих действиях: он может заставить использовать менее защищенный аутентификационный протокол NTLM.

Сервер извлекает информацию для аутентификации из описателя привязки клиента, вызвав в удаленной процедуре функцию RpcBindinglnqAutbClient. При эт ж определяется используемый способ (NTLM или Kerberos) и желаемый уровень аутентификации (полное отсутствие аутентификации, аутентификация пакетов, проверка целостности данных и т.п.). Вот небольшой пример.

// Функция RPC-сервера со встроенным кодом проверки безопасности, void Message(handle_t hPhone, unsigned char -szMsg) { RPC_AUTHZ_HANDLE hPrivs;

DWORD dwAuthn;

RPC_STATUS status = RpcBindingInqAuthClient( hPhone, &hPrivs, NULL, SdwAuthn, NULL, NULL);

if (status != RPC_S_OK> { printf("*yHKUHfl RpcBindinglnqAuthClient вернула: OxXx\n", status);

RpcRaiseException(ERROR_ACCESS_DENIED);

I // Теперь проверим уровень аутентификации.

// Требуется как минимум аутентификация на уровне пакетов, if (dwAuthn < RPC_C_AUTHN_LEVEL_PKT) { Часть III Дополнительные методы создания защищенного кода printf( "Клиент запрашивает недостаточно надежный способ аутентификации. \п");

RpcRaiseException( ERROR_ACCESS_DENIED) ;

.

if (RpcImpersonateClient(hlfPtione) != RPC_S_OK) { printf("OnHLieTBopeHHe не состоялосьДп");

RpcRaiseException(ERROR_ACCESS_DENIED);

char szName[ 128+1];

DWORD dwNameLen = 128;

if (!Get_UserName(szName, AdwHameLen)) lstrcpy(szName, "Неизвестный пользователь");

printf( "Сообщение: Xs\n" "Us поддерживает уровень аутентификации Xd\n", szMsg, szName, dwAuthn);

RpcRevertToSelfO;

Здесь выполняется ряд действий. Функция Message вызывается удаленно из приложения-телефона. Сначала выясняется поддерживаемый уровень аутентифи кации вызовом функции RpcBindinglnqAuthClient и получением значения AuthnLevel Если функция возвращает ошибку' или же AutbnLevel оказывается ниже требуемо го уровня безопасности, вызов завершается ошибкой, а сервер инициирует исклю чение отказа в доступе, которое передается клиенту. При благоприятном исходе проверки производится олицетворение вызывающего пользователя и определя ется его имя. Наконец, после отображения соответствующего сообщения испол нение вызова возвращается в контекст процесса.

Заметьте также, что здесь проверяются значения, возвращаемые всеми функ циями олицетворения. До Windows.NET Server 2003 работа этих функций не вы зывала нареканий (обычно они завершались с ошибкой только из-за недостатка системной памяти или запрета олицетворения на системном уровне). Однако из за наличия в Windows.NET Server 2003 новой привилегии Impersonate a client after authentication (олицетворение клиента после аугентификации) такие ошибки будут возникать чаще, а именно в тех случаях, когда учетная запись процесса не обла дает этой привилегией.

Замечание о поддержке Kerberos Параметр szSPN, используемый при вызове RpcBindingSetAuthlnfo, определяет ос новное имя сервера, необходимое для нормальной работы протокола Kerberos.

Помните: протокол Kerberos поддерживает взаимную аутентификацию клиента и сервера, a NTLM — только клиента. Проверка подлинности сервера предотвраща ет его подмену. Чтобы отключить поддержку Kerberos, установите параметр szSPN в NULL.

Значение этого параметра на стороне клиента определяется вызовом функции DsMakeSPN. Она определена в Ntdsapi.h и поэтому компонуется с библиотекой ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM Ntdsapi.dll. В следующем фрагменте кода демонстрируется использование DsMa keSPN.

DWORD cbSPN = MAX_PATH;

char szSPN[MAX_PATH + 1];

status = DsMakeSpn("ldap", "blake-laptop.northwindtraders.coin", NULL, 0, NULL, &cbSPN, szSPN);

Необходимо убедиться в том, что в серверном приложении используется то же ИМЯ:

LPBYTE SzSPN = NULL;

status = flpcServerInqDefaultPrincName( П PC_C_AUTHN_GSS_N EGOTIATE, SszSPN);

if (status != RPC_S_OK) ErrorHandler(status);

// Регистрируем информацию об аутентификации клиента, status = RpcServerRegisterAuttiInfo( szSPN, RPC^C_AUTHN_GSS_NEGOTIATE, 0, 0);

if (status != RPC_S_OK) ErrorHandler(status);

if (szSPN) RpcStringFree(&szSPN);

Производительность при различных конфигурациях безопасности При анализе приложения в первую очередь интерес вызывает его производителт ность. Как же влияет обязательная аутентификация на производительность RPC серверов? В наборе инструментальных средств Microsoft Platform SDK есть при мер RFC-приложения под названием RPCSvc. оно предназначено специально дли проверки производительности в различных конфигурациях RPC. Это приложение я выполнял на двух компьютерах. Клиент работал на машине под управлением Windows XP Professional, а сервер — под управлением Windows.NET Server 2003 с процессором частотой 500 МГц и оперативной памятью в 25б Мб. Тест заключал ся в тысячекратном вызове одной-единственной удаленной функции, передающей серверу содержимое буфера размером 100 байт. В табл. 16-3 показаны усреднен ные результаты трех тестов, выполненных для двух транспортов: именованных каналов и TCP/IP.

422 Дополнительные методы создания защищенного кода Часть III Таблица 16-3. Время выполнения теста при различных конфигурациях RPC Именованные каналы TCP/IP AuthnLevel (псасп_пр), мс (ncacn_ip_tcp), мс 1926 RPC_C_A 1Л 'HN_LEVEL_NONE 2023 RPCjC_AUTHNJVEL_CON№CT 2044 RPC_C_AUTHN_LEVEL_PKT PRIVACY Как видите, принудительная аутентификация не оказывает существенного вли яния на производительность. Быстродействие падает примерно на 10%, но это с лихвой окупается значительным повышением уровня безопасности. Заметьте:

разница между уровнями RPC_Cj\.UTHNJVEL_CONNECT и RPC_C_AUTHN_LEVEL_ PK1'J?RIVACY минимальна. Поэтому если в приложении используется RPCjC_AUTHN_ LEVEL_CONNECT, то имеет смысл перейти к R1>C_C_AUTHN_LEVEL_PKT_PRIVACY.

Сейчас мы об этом поговорим поподробнее.

Обеспечьте поддержку конфиденциальности и целостности Если вы обеспечили поддержку аутентификации при RFC-вызовах, рекомендуем пойти дальше и реализовать конфиденциальность и целостность пакетов. Ничем плохим это не грозит! В январе 2000 г. во время анализа безопасности проекти руемого крупного приложения Microsoft я предложил разработчикам реализовать конфиденциальность и целостность административной информации, пересыла емой средствами RPC. Разработчики поначалу опасались падения производитель ности, но после проверки работы в таком режиме (для этого потребовалось все го лишь изменить флаг в RpcBindingSetAuthlnfo) была выбрана более защищенная конфигурация. Позже (примерно за полгода до выхода продукта) для аудита при ложения пригласили специалистов известной компании, занимающейся вопро сами безопасности. В выводах присутствовало следующее примечание, заставив шее меня улыбнуться: «Мы потратили массу времени, пытаясь взломать канал пере дачи администраторских данных, но ровным счетом ничего не добились. В условиях, когда многим компаниям не удается организовать надежную защиту данных та кой важности, нам остается лишь поаплодировать разработчикам за использова ние защищенных механизмов RPC и DCOM».

На рис. 16-2 показан результат применения na.p^M.ei'p3.RPC_C_AUTHN_LVEL_NO NE, а на рис. 16-3 - параметра RPC_C_AUTHN_LEVEL_PKT_PRIVACY.

Используйте строгие описатели контекста Если нет необходимости совместного использования описателей контекста не сколькими интерфейсами, применяйте строгие описатели контекста (strict handle). В противном случае останется риск некоторых тривиальных DoS-атак.

Кратко изложу суть дела. Обычно при вызове метода интерфейса создается опи сатель контекста, доступный другим интерфейсам. Наличие атрибута [strict_con texl_handle] в ACF-файле гарантирует, что методы интерфейса будут принимать только описатели контекста, созданные его же методами, ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM ВЁРРС: о/о ПК RecTiie.-t: m i l Ox - взвр: Wersmn - Б (0*1) ЯЗРР-;

: version (Итог- ' s i o x a :

я BSfiPO: f l a g a 1 - 3 [Ок HS№": ГсадаепЕ Lsnetiv - 61 (ОкЗЬ!

ЯЗЪ?':-. iuthencica-jDi, Length - 0.

Рис. 16-2. RPC-трафикпри наличии параметраRPC_C_AIJTH_LEVEL_NONE.

Обратите внимание на пересылку пароля открытым текстом Mterusufl MBl«u*.Monitor -ICaplure:

Рис. 16-3- RPC-трафик при наличии параметра RPC_C_AUTH LEVEL_PKT_PR1VACY. Полезная нагрузка, то есть секретное сообщение, зашифровано 15- 424 Часть III Дополнительные методы создания защищенного кода Далее приводится пример незащищенного кода, в котором не предусмотрена обязательность строгих описателей контекста. Вначале приведу код IDl-файла, в котором определяется RFC-приложение с двумя интерфейсами: для управления принтерами и управления файлами.

interface PrinterQpe rations { typedef context_handle void *PRINTER_CONTEXT;

void OpenPrinter([in. out] PRINTER.CONTEXT «ctx);

void UsePrinter([in] PRINTER_CONTEXT ctx);

void ClosePrinter([in, out] PRINTER_CONTEXT *ctx);

• interface FileOperations { typedef contextjiandle void *FILE_CONTEXT;

void OpenFile([in, out] FILE_CONTEXT *ctx);

void UseFile([in] FILE_CONTEXT ctx);

void CloseFile([in, out] FILE_CONTEXT *ctx) А вот отрезок кода на C++ соответствующего RFC-сервера:

void OpenPrinter(PRINTER_CONTEXT *ctx) { // Создаем экземпляр объекта-принтера, *ctx = new CPrinterManipulatorO;

if (*ctx == NULL) RpcRaiseException(ERROR_NOT_ENOUGH_MEMORY);

// Выполняем действия по открытию принтера.

void UseFile(FILE_CONTEXT ctx) { // Получаем экземпляр объекта пользовательского файла.

CFileManipulator cFile = {CFileManipulator*)ctx;

// Выполняем операции с файлами, Это вполне работоспособный RFC-сервер, но в нем присутствует одна не за метная на первый взгляд брешь. Если злоумышленник передаст принтерный кон текст файловому интерфейсу, процесс RFC-сервера может аварийно завершить ся, так как строка CFileManipulator cFile = (CFileManipulator *)ctx вызовет ошибку нарушения доступа. Например, подобный сбой инициирует такой злонамеренный код.

void *ctxAttacker;

OpenPrinter(&ctxAttacker);

UseFile(ctxAttacker);

В последней строке при вызове функции UseFile(ctxAttacker) передается не FILE_CONTEXT, a PRINTER _CONTEXT.

ГЛАВА 16 Защита RFC, ActiveX-элементов и объектов DCOM Для устранения недостатка достаточно изменить ACF-файл, включив в него атрибут [strict_context_handle]\ [explicit_handle, strict_context_handle] interface PrinterOperations{} interface FileOperations{} Это заставит среду исполнения RFC проверять, действительно ли функциям PrinterOperations и FileOperations передаются только созданные ими самими опи сатели контекста.

Не полагайтесь при проверке доступа на описатели контекста Никогда не используйте описатели контекстов в качестве замены проверки до ступа. Иногда злоумышленники в состоянии перехватить описатель контекста и применить его для олицетворения другого пользователя, при этом взломщику даже не требуется разбираться в содержимом описателя или данных RFC. Особенно это справедливо для случаев, когда данные не шифруются. При шифровании вероят ность реализации атаки существенно меньше, но ею все равно не стоит пренеб регать, В некоторых приложениях при открытии описателя контекста осуществляет ся проверка доступа, и при этом предполагается, что вызовы с одинаковым опи сателем контекста поступают с одного источника. Это может представлять или tie представлять угрозу безопасности — все определяется тем, какие действия выпо. г няются с описателем контекста, но в общем случае это недопустимо. Если в коде необходимо выполнить проверку доступа, выполняйте ее непосредственно /.о защищаемого действия, не полагаясь на содержащуюся в описателе контекс а информацию.

RFC-механизм прилагает усилия, чтобы вызовы с одинаковым описателем KOI r текста принадлежали одному сетевому сеансу (а это зависит от того, в СОСТОЯНР и ли сетевой транспорт гарантировать подлинность сеансов), но не гарантирует, что они принадлежат одному сеансу безопасности. Иначе говоря, RFC-сеансы иногда перехватываются.

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

426 Часть III Дополнительные методы создания защищенного кода Избегайте нулевых описателей контекста С формальной точки зрения работа с нулевым (NULL") описателем контекста свя зана проблемой устойчивости приложения (то есть корректностью обработки таких описателей), но если не предпринимать никаких мер, то они могут стать причиной уязвимости по отношению к DoS-атакам. Описатель контекста может указывать на NULL, как в этом примере:

void MyFunc(..., /* [другие параметры} */ CONTEXT_HANDLE_TYPE «hCtx) О Хотя bCtx не указывает на NULL, нулевым может оказаться указатель *bCtx. По этому при попытке использовать в коде указатель *bCtx произойдет сбой. RFC механизм заботится, чтобы все переданные функции описателя контекста были предварительно выделены сервером, но нулевой описатель — это особый случай и он всегда проходит такую проверку, Взгляните на следующий код.

short OpenFileByID(handle_t hBinding, PPCONTEXT_HANOLE_TYPE pphCtx, short sDevicelD) { short sErr = 0;

HANDLE hFile = NULL;

*pphCtx = NULL;

if (RpcImpersonateClient(hBinding) == RPC_S_OK) { hFile = OpenlDFile(sDevicelD);

if (hFile == INVALID_HANDLE_VALUE) { sErr = -1;

} else { // Выделяем клиенту серверную память для контекста, FILE_ID -pFid = midl_user_allocate(sizeof ( FILE_ID));

if (pFid) { pFid->hFile = hFile;

*pphCtx = (PCONTEXT_HANDLE_TYPE)pFid;

} else { SErr = ERROR_NOT_ENOUGH_MEMORY;

} RpcRevertToSelfO;

} return sErr;

' short ReadFileByID(handle_t hBinding, PCONTEXT_HANDLE_TYPE phCtx) { FILE_ID *pFid;

short sErr = 0;

if (RpcImpersonateClient(hBinding) == RPC_S_OK) { pFid = (FILE_ID *)phCtx;

ReadFileFromID(phCtx->hFile,...);

RpcRevertToSelfO;

} else { ГЛАВА 16 Защита RFC, ActiveX-элементов и объектов DCOM sErr = -1;

:

return sErr;

!

short CloseFileBylDfhandlej: hBinding, PPCONTEXT_HANDLE_TYPE pphCtx) { FILE_ID *pFid = (FILE_ID *)*PPhCtx;

f>Fid->hFile = NULL;

mtdl_user_free(pFid);

*pphCtx = NULL;

return 0;

} Этот код позволяет пользователю открывать файл с помощью идентификато ра файла вызовом OpenFileBylD. Если доступ к файлу разрешен, функция выделя ет динамическую память для хранения информации о файле, куда и указывает описатель контекста. Однако, если вызов функций RpclmpersonateClient или Ореп IDFile закончится неудачей, указатель *pphCtx окажется равным NULL. Когда вг.о следствии пользователь вызовет CloseFileBylD или ReadFileBylD, при попытке ра зыменовать нулевой указатель эти функции также потерпят крах.

Код RPC-сервера должен всегда проверять, что описатель контекста указывает не на NULL, а на действительно выделенную память.

if {*pphCtx == NULL) { // Попытка использовать нулевой описатель контекста.

Не доверяйте соседним процессам Это правило стоит применять не только для RPC, но и для всех сетевых техноло гий. RFC-вызовы из высокопривилегированного процесса в процессы с низкими привилегиями опасны, так как клиент в состоянии позаимствовать права приви легированного процесса, что чревато атакой повышения привилегий. Если все же необходимо выполнять RFC-сервер с повышенными привилегиями, при вызове другого процесса задействуйте анонимное подключение или установите уровень олицетворения в значение Identify (Определить). Этого можно добиться вызов< >м функции RpcBindingSetAuthlnfoEx:

({ Устанавливаем параметры качества обслуживания.

RPC_SECURITY_QOS qosSec;

qosSec. Version = RPC_C_SECURITY_QOS_VERSION;

qosSec. Capabilities = RPC_C_QOS_CAPABILITIES_DEFAULT;

qosSec. Identity-Tracking = RPC_C_QOS_IDENTITY_STATIC;

qosSec. ImpersonationType = RPC_C_IMP_LEVEL_IDENTIFY;

status = RpcBindingSetAuthInfoEx(..., &qosSec);

Параметр ImpersonationType принимает одно из четырех возможных значений:

RPC_C_1MP_LEVEL_ANONYMOUS (не позволяет получателю вызова знать, кем явля ется клиент), RPC_CJMP_ LEVEL IDENTIFY (разрешает получателю вызова иденти фицировать клиента), RPC_C_IMP_LEVEL_IMPERSONATE и RPC_CJMP_LEVEL_DELE 428 Часть III Дополнительные методы создания защищенного кода GATE (последние два позволяют получателю вызова узнавать, кто клиент, и дей ствовать от его лица).

Используйте безопасные функции обратного вызова Для защиты функций RFC-сервера предпочтительно использовать безопасные функции обратного вызова. Для этого при запуске RFC-сервера вместо RpcSewer Registertf нужно вызывать RpcServerRegisterIf2 или RpcServerRegisterlJEx. причем последний аргумент устанавливается так, чтобы указывать на функцию, которая вызывается средой исполнения RFC для определения того, разрешено ли клиенту вызывать функции этого интерфейса.

В следующем примере разрешены только клиентские подключения с парамет ром безопасности RPC_Cj\.UTHNJVEL_PKT и выше.

Л Phones.срр */ // Безопасная функция обратного вызова вызывается // при каждом вызове функции RPC-сервера, RPC_STATUS HPC.ENTRY SecurityCallBack(RPC_IF_HANDLE idlF, void *ctx) { RPC.AUTHZJWJDLE hPrivs;

DWORD dwAuthn;

RPC_STATUS status = RpcBindinglnqAuthClientC ctx, bhPrivs, NULL, SdwAuthn, NULL, NULL);

if (status != RPC_S_OK) { printf{"Функция RpcBindinglnqAuthClient вернула: ОхХх\ n", status);

return ERROfi_ACCESS_DENIED;

I // Проверяем уровень аутентификации.

// Требуется как минимум аутентификация на уровне пакетов.

if (dwAuthn < RPC_C_AUTHN_LEVEL_PKT) { printf("Клиент поддерживает слишком слабую аутентификацию.\п");

return ERROR_ACCESS_DENIED;

return RPC_S_OK;

void raainO { ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM status = RpcServerfiegisterIfEx(phone_v1_0_s_ifspec, NULL, NULL, О, RPC_C_LISTEN_MAX_CALLS_DEFAULT, SecurityCallBack);

Примечание В некоторых версиях библиотеки MSDN и набора Platform SDK содержится неправильное описание безопасной функции обратного вызова: вместо (RPCJF_ID nnterjace, void *context) дол жно быть (RPC_IF_HANDLE ^interface, void «context}.

Чтобы разрешить только защищенные подключения, при вызове RpcServer RegisterlfEx и RpcServerRegisterIf2 следует установить флаг RPC_IF_ALLOW_SECU RE_ONLY. Он сужает круг разрешенных клиентских подключений: разрешены только те, уровень безопасности которых выше RPC_C_AUTHN_LEVEL_NONE. He прошед шим проверку клиентам возвращается ошибка RPC_S_ACCESS_DENIED. Это суще ственная поправка. Если флаг не установлен, но разрешены только аутентифици рованные подключения, среда исполнения RPC все равно передаст для обработ ки клиентский запрос приложению, которое немедленно откажет в доступе. Ус тановка флага позволит среде исполнения отклонить запрос, не передавая егс л программу. И еще: применение этого флага в Windows NT 4/2000 позволяет кли ентам использовать нулевые (иначе — анонимные) сеансы, что в Windows ХР за i ретцено.

ФлагRPC_(F_ALLOW_SECURE_ONLYпредпочтительнее для защиты интерфейса, чем описатель безопасности при вызове RpcServerUseProtSeq, на то есть две при чины. Во-первых, описатели безопасности допустимы только, когда в качестве транспорта используются именованные каналы или LRPC. В реализации на осно ве протокола TCP/IP описатели безопасности бесполезны. Во-вторых, все консчнь it точки доступны через любой интерфейс, и этому посвящен следующий раздел.

Совместная работа нескольких RFC-серверов водномпроцессе Как вы наверное знаете, RPC-механизму все равно, по какому сетевому протоко лу работать. К RFC-серверу можно обратиться посредством любого поддерживае мого сетевого протокола. В связи с этой особенностью возникает интересный побочный эффект, который не затрагивает большинства пользователей, но о нем следует помнить, Если RPC-сервер располагается в одном процессе с другими серверами (напри мер, если сервис содержит несколько RFC-серверов), все серверы прослушивают каждый выбранный протокол. Например, когда один процесс поддерживает три RPC-сервера: RPC1, основанный на именованных каналах и LRPC, RPC2, исполь зующий сокеты, и RPC3, работающий только через LRPC, то все три сервера при нимают данные всех трех протоколов (именованных каналов. LRPC и сонетов) (рис. 16-4).

Часть III Дополнительные методы создания защищенного кода Теоретически В реальности MyServtce.exe i %Se rvice.exe RPC RPCa RPC, RPCj RPC RPC;

t 1! i :

'"t'=1JL_ i -t t.

++ Именованные Именованные Именованные Именованные * Сок мы LR С каналы и LRPC каналы, LRPC каналы, LRPC каналы, LRPC и сокеты и сокеты и сокеты Рис. 16-4. Все три RFC-сервиса прослушивают все множество затребованных сетевых протоколов Вы ошибетесь, предположив, что сервер, использующий только LRPC, находится в безопасности — размещенные в том же процессе RPC-серверы проосушивают именованные каналы или сокеты, вынуждая первый RPC-сервер использовать и эти протоколы!

Для проверки того, что клиентский запрос работает по определенному сете вому протоколу, нужно вызвать функцию RpcBindingToStringBinding, а затем с по мощью функции RpcStringBindingParse определить последовательность протоко лов. Следующий пример демонстрирует всю процедуру: в данном случае выясня ется, действует ли в контексте протокол LRPC.

• Phones,cpp BOOL IsLRPC(void *ctx> { BOOL flsLRPC = FALSE;

LPBYTE pBinding = NULL;

if (HpcBindingToStringBinding(ctx, &pBinding) == RPC_S_OK) LPBYTE pProtSeq = NULL;

// Нас интересует только последовательность протоколов, // поэтому присвоим остальным параметрам значение NULL, if (RpcStringBindingParse(pBinding, NULL, &pProtSeq, NULL, NULL, NULL) == RPC_S_OK) { рг1пт.1Ч"Используется Ks\n", pProtSeq);

// Убеждаемся, что клиентский запрос // выполнен средствами LRPC, if (lstrcmpi((LPCTSTR)pProtSeq, "ncalrpc") == 0) flsLRPC = TRUE;

if (pProtSeq) ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM RpcStringFree(&pProtSeq);

if (pBinding) RpcStringFree(&pBinding);

i return flsLRPC;

He забывайте добавлять аннотации к конечным точкам Аннотирование конечных точек — не вопрос безопасности, а просто признак хорошего стиля! При создании конечной точки RPC обязательно вызовите функ цию RpcEpRegister и добавьте аннотацию. В дальнейшем это облегчит отладку — инструменты анализа конечных точек (такие, как RPCDump.exe из Windows Resource Kit) будут указывать назначение конечной точки, RPC_BINDING_VECTOR *pBindings = NULL;

if (RpcServerlnqBindingsC&pBindings) == RPC_S_OK) { if (RpcEpRegister(prtone_v1_0_s_ifspec, pBindings, NULL, "Приложение-телефон") == RPC_S_OK) { // Класс! Аннотация добавлена!

I Я добавил этот совет лишь потому, что за свою жизнь потратил массу време ни, выясняя предназначение конечных точек, пока спецы по RPC не подсказали мне эту функцию.

Используйте популярные протоколы Используйте общеизвестные последовательности протоколов: ncacn_ip_tcp, ncacnj ip и ncalrpc. Их популярность вынуждает производителей тестировать свои прило жения тщательнее.

Примечание Иногда клиент или RFC-сервера «падает», и тогда вызов GetLastError или сама RFC-функция возвращает код ошибки. Не знаю, как вы, но я уже успел забыть все значения кодов, кроме ошибки номер 5 — <-огказ в до ступе»! Однако помощь всегда под рукой. В командной строке введите net helpmsg пппп, где пппп — десятичное представление номера ошиб ки, и ОС отобразит текстовую информацию о ней.

Дополнительные методы создания защищенного кода Часть III Проверенные методы обеспечения безопасности DCOM DCOM — на самом деле лишь оболочка вокруг RPC. позволяющая СОМ работать через сеть, так что многие понятия, описанные в предыдущем разделе об RPC, вам уже знакомы. Кроме имеющихся уже проблем, связанным с уровнем олицетворе ния и аутентификации, в DCOM появляются и другие, связанные с разрешениями на запуск и доступ и с контекстом пользователя, требующим объект. Вдобавок существует не меньше трех вариантов обеспечения безопасности. Итак, начнем!

Основы DCOM Сначала запустим приложение Dcomcnfg.exe. При этом в Windows NT 4 или Win dows 2000 открывается диалоговое окно Distributed COM Configuration Properties (Свойства: Настройка Distributed COM), а в Windows XP — окно ММС-оснастки.

позволяющее просматривать приложения СОМ+ и DCOM-объекты. На рис. 16- показана вкладка Default Properties (Свойства по умолчанию) диалогового окна Distributed COM Configuration Properties в Windows 2000.

тли див год ЯИ.ШПЯИР;

Г The AUher-fea)№ Level st'satie;

security at the packs' tewet ' deieifflhe whet is saltoig Итея andveieS'ia- She application ^a Рис. 16-5. Вкладка Default Properties диалогового окна Distributed Com Configuration Properties Во-первых, здесь можно разрешить или запретить DCOM на компьютере. Это сильнодействующее средство: будьте осторожны, иначе некоторые компоненты неожиданно перестанут работать. Во-вторых, есть возможность разрешить СОМ службы Интернета (COM Internet Services). Они поддерживают RPC поверх НТРР.

превращая Web-сервер в RPC- и DCOM-провайдера. Не рекомендую отмечать этот флажок, не выяснив заранее, какие административные интерфейсы станут в этом случае доступными по HTTP. И, наконец, можно указать заданные по умолчанию уровни проверки подлинности и олицетворения. Эти параметры один в один ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM соответствуют параметрам RPC. Уровень проверки подлинности по умолчанию — Connect (Подключение), что соответствует значению RPC_C_AUTHN_CONNECT, Уровень олицетворения по умолчанию — Identify (Определить), что соответству ет значению RPC_C_IMP_LEVELJDENTIFY.

Последний элемент вкладки Default Properties — Provide additional security for reference tracking (Повышенная безопасность для отслеживания ссылок). Тут по надобятся некоторые объяснения работы СОМ. При открытии объекта вызывагт ся метод IUnknown::AddRef, а при освобождении — JUnknown:.-Release. Если число освобождений совпадает с числом вызовов I{Jnknown::AddRef, объект самостоятелъ но решает, что в нем больше нет необходимости, и самоуничтожается. К сожале нию, в СОМ не предусмотрена проверка того, исходят ли вызовы методов от од ного процесса, поэтому, если метод !Unknown::AddRef несколько лишних раз вы зывает небрежно написанный клиент или злоумышленник, объект — к великому изумлению пользователя — уничтожается. Типичный отказ в обслуживании, не правда ли? Но подобной ситуации удается избежать, обеспечив повышенную бе зопасность при отслеживании ссылок, однако имейте в виду: на это потребуются дополнительные ресурсы. При установке приложения на чужую систему не стоит менять параметры безопасности для всех приложений — лучше добавить отсле живание ссылок вызовом функции CoInitializeSecurfty, присвоив значение EQAC_SECURE_REFS аргументу dwCapabilities.

На вкладке Default Security (Безопасность по умолчанию) задаются устанав;

и ваемые по умолчанию разрешения доступа, запуска и конфигурирования. Разре шения доступа определяют, кто имеет право доступа к текущему процессу, разре шения запуска — круг допущенных к созданию объекта, а разрешения конфи у рировапия — тех, кто имеет право на изменение параметров конфигурации. По следние особенно важны, так как DCOM-приложение можно сконфигурировать для выполнения с правами текущего пользователя. Следует четко понимать, ч го любой пользователь, способный изменять параметры DCOM, сможет выступать в роли любого интерактивного пользователя. По умолчанию правом изменения кон фигурации обладают только члены групп Administrators (Администраторы) и Power Users (Опытные пользователи). Учтите, что члены группы Power Users в Win dows 2000, в отличие от Windows NT, обладают правами, незначительно уступаю щими администраторским. Не стоит изменять эти разрешения в сторону расши рения прав, но, с другой стороны, желая сократить их, не забудьте убедиться, что это не нарушит работу других приложений. Хороший тест — проверить, в сос"'О янии ли обычные пользователи выполнять свои задачи. Если да, то можно выбрать из двух вариантов: сократить права группы Power Users или лишить всех пользо вателей дополнительных прав, превратив их в рядовых.

Появившаяся в Windows NT 4 SP 4 вкладка Default Protocols (Набор протоко лов) позволяет задавать протоколы, применяемые в DCOM-приложениях. Кроме этого на ней определяется диапазон портов транспортных протоколов TCP и UDP [в пользовательском интерфейсе им соответствуют названия Connection-Orienied TCP/IP (Ориентированный на подключение TCP/IP) и Datagram UDP/IP (Датаграм мы UDP/IP)]. При необходимости работы DCOM через брандмауэр администра торам брандмауэра предоставляется отрадная возможность точно указать порт или диапазон портов, а использование TCP позволит настроить брандмауэр таким образом, чтобы подключение создавалось только в одном направлении, Часть III Дополнительные методы создания защищенного кода Безопасность на уровне приложения Все параметры, доступные для всей системы, можно определять и для отдельных приложений. Для этого достаточно дважды щелкнуть значок приложения в спис ке на вкладке Applications (Приложения) диалогового окна Distributed COM Confi guration Properties или изменить параметры прямо в реестре, отыскав идентифи катор объекта в разделе HKEY_LOCAL_MACHINE\Software\Classes\AppId. Заметьте: если приложение содержит несколько объектов, их конфигурации должны совпадать.

Лучше всего проанализировать разрешения, необходимые для всех объектов, и выбрать «наименьший общий знаменатель», удовлетворяющий их все. Затем при менить управление безопасностью на уровне приложения для настройки различ ных параметров безопасности для отдельных объектов, но это слишком сложно и чревато ошибками. Для таких случаев есть хорошее правило: если у объектов сильно различаются требования к безопасности, их лучше разнести по разным приложениям или DLL-библиотекам. Помимо компонентов, использующих обще системные параметры, отдельные DCOМ-приложения могут выполняться в раз личных пользовательских контекстах. Это очень важно, и об этом пойдет речь в следующем разделе. Наконец, если для передачи выбран протокол TCP или UDP, разные объекты разрешается настроить на работу по разным портам. Возможность выполнения сложных транзакций средствами DCOM при всего лишь двух откры тых портах (порт 1 3 5 с одной стороны и выбранный порт — с другой) намного лучше, чем полная прозрачность брандмауэра между двумя системами. Заметьте;

начиная с Windows 2000. устранена поддержка протоколов, основанных на дей таграммах.

Некоторые параметры DCOM для отдельных приложений настраиваются только через реестр. Любые параметры, которые устанавливаются до запуска приложе ния, нельзя поменять в самом приложении. В частности, разрешения на запуск, информация о конечных точках и пользовательский контекст указываются толь ко в реестре.

Контексты пользователей в DCOM Подобно службе DCOM-объект выполняется в разных пользовательских контек стах. Возможны следующие варианты: олицетворение пользователя, вызвавшего объект, выполнение от имени интерактивного пользователя, учетной записи SYS TEM (доступно только DCOM-серверам, реализованным в виде системной служ бы) или конкретного пользователя. В отличие от большинства авторов, пишущих о безопасности DCOM, я (Дэвид) расскажу вам, что по этому поводу думает и ха кер, и администратор по безопасности. Такова моя работа — взламывать системы и находить способы защиты от атак извне. Выбор пользовательского контекста для DCOM-объекта оказывает огромное влияние на безопасность всей сети. Рас смотрим все возможные варианты со всеми их достоинствами и недостатками.

Выполнение в контексте пользователя, вызвавшего приложение В этом случае анализ последствий для безопасности довольно прост. Пользова тельские реквизиты нигде не хранятся, а управление доступом выполняется стан дартными средствами ОС. Правда, есть один существенный недостаток: в систе мах, предшествующих Windows 2000, невозможно делегировать вызовы в другую ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM систему. Если DCOM-объект должен иметь доступ к нелокальным ресурсам п.ж использовании Windows NT 4.0, выполнение в контексте вызвавшего приложение пользователя невозможно. Даже в Windows 2000 и последующих ОС администра торам безопасности стоит соблюдать осторожность при назначении систем, до веренных для делегирования. К тому же, при этом падает производительность, так как запущенным в различных пользовательских контекстах экземплярам объекта требуются отдельные экземпляры оконных станций (window station) — объект! >в, поддерживающих рабочий стол. Подробно об этом рассказано в Platform SDK Выполнение от имени интерактивного пользователя Это самый опасный вариант, и я настоятельно не рекомендую его применять.

исключение делается при разработке отладочных инструментов. Во-первых, если в данный момент в системе никто не работает, DCOM-объект не удастся запус тить, а если пользователь выйдет из системы во время работы приложения, при ложение завершится. Во-вторых, атаки с целью повышения привилегий не заста вят себя ждать. Существует множество API-функций и иных методов определения момента интерактивного входа пользователя в систему. Достаточно постоянно опрашивать систему, дождаться входа администратора, а затем запустить DCOM объект для совершения вредоносных действий. Если вы абсолютно уверены в том, что вам необходим DCOM-объект именно в контексте интерактивного пользова теля, не забудьте предупредить вошедшего в систему пользователя о запуске при ложения, а также жестко ограничить круг имеющих права доступа к объекту и его запуска. И поаккуратнее с предоставляемыми методами.

Выполнение в контексте локальной системы Работающие как служба DCOM-объекты иногда выполняются под учетной запи сью Local System или. в Windows XP, под непривилегированной учетной записью сетевой службы. Учетная запись Local System — самая «мощная* в системе, факти чески определяющая поведение ОС. Сетевая служба не обладает такими возмож ностями, но многие службы выполняются именно под ней, поэтому остерегайте,:ь, Тщательно проверяйте предоставляемые интерфейсы и будьте готовы олицетво рять клиента при выполнении проверок на доступ. Если DCOM-приложение яв ляется системной службой, установите уровень олицетворения (на всех проке и) в Identify. В противном случае ничто не помешает вызывающим пользователям обзавестись повышенными привилегиями. В DCOM по умолчанию установлен уровень олицетворения Identify, но программисты по привычке стараются выз вать функцию CoInitializeSecurity или API-функции для защиты прокси и поменять значение по умолчанию на Impersonate.

Примечание Стоит упомянуть о том, что в Windows.NET Server добавлена привилегия олицетворения, подробнее о которой рассказывается в главе 7.

Выполнение от имени конкретного пользователя Microsoft Transaction Server обычно выполняет объекты именно таким образом, и этот подход имеет свои преимущества. Будучи вызван пользователем домена, объе кт 436 Дополнительные методы создания защищенного кода Часть III способен выполнять операции на других системах от имени данного пользова теля. Кроме того, создается хотя бы по одной оконной станции на каждый объект, а не на каждого вызывающего. Любой учетной записи, используемой для выпол нения DCOM-объекта, требуется привилегия Log on as a batch job (Вход в каче стве службы). При назначении пользователя с помощью утилиты Dcornnfg.exe она присваивает нужные права, если же пользователь добавляется в самом приложе нии, необходимые привилегии придется предоставлять программно. И убедитесь в отсутствии доменных политик, переопределяющих установленные вами приви легии.

Стоит учесть и недостатки этого способа. При выполнении DCOM-объекта от имени определенного пользователя в реестр записывается информация о его учетной записи. Вроде нет причины для волнений — пароль в безопасности, не так ли? В некотором смысле вы правы: только администратору разрешается за пускать утилиту для считывания секретных данных LSA. А теперь представьте себе ситуацию: вы установили приложение на 3000 систем, и везде оно выполняется с правами администратора. То есть у вас имеется 3000 компьютеров, каждый из которых может стать причиной взлома всей группы. Предположим, что эти сис темы обслуживаются группой первоютассных системных администраторов, поэтому с точки зрения безопасности компьютеры отличаются надежностью 99,9% (то есть отдельно взятая система оказывается действительно уязвимой лишь один день на каждые 1000 дней). Общая вероятность того, что все 3000 компьютеров в данный wco момент невозможно взломать, равна (0.999), то есть примерно 0,05. Так что действия хакеров окажутся безуспешными всего 18 дней в году, а остальное вре мя система не защищена. Если же вы переоценили своих администраторов, то шансы взлома окажутся еще выше.

Первое «лекарство* от этой проблемы— запуск DCOM-объекты в контексте непривилегированного пользователя. Но даже в таком случае, когда в системах хра нятся секретные данные (например, личные данные персонала), получение рек визитов и такого пользователя проблематично. Второй способ — уменьшение количества систем, на которых выполняется объект: 20 компьютеров защитить проще, чем 3000. Третий вариант — разделение контекстов разных пользователей на разных группах систем. В этом случае взлом одной группы не повлечет за со бой моментальное проникновение в другие системы. Если объекту для выполне ния работы требуются полномочия привилегированного пользователя, заведите на каждой системе особую учетную запись (желательно локального пользовате ля). В текущем варианте службы клиента SMS (Systems Management Server) реали зован такой подход, что лишает смысла действия взломщика. Они взламывают систему, получают доступ с правами администратора, считывают секретные дан ные, а в результате получают уже имеющийся уровень доступа. Ну никакой радо сти для хакера! Если вы системный администратор, могу вас заверить, что, когда хакеры веселятся, это означает для вас только головную боль и массу неприятно стей. И, наконец, в Windows XP и Windows.NET Server доступны новые учетные записи — LocalService и NetworkService. Они не требуют управления паролями и не обладают высокими системными привилегиями.

Защита RPC, ActiveX-элементов и объектов DCOM ГЛАВА Программное управление безопасностью Технология DCOM позволяет устанавливать из программы параметры безопасно.гги как сервера, так и клиента. Это выполняется вызовом функции CoInitialtzeSecurity на стороне сервера или клиента, клиент также вправе изменить параметры безо пасности одного-единственного интерфейса, вызвав lClientSecurity::SetBlanket.

Похоже, у СОМ собственный язык описания своих возможностей, на котором набору параметров безопасности соответствует слово оболочка (blanket). Посмот рим, какие параметры передаются функции CoInitializeSecurity.

HRESULT CoInltializeSecurityC PSECURITY_DESCRIPTOR pVoid, // Указывает на описатель безопасности LONG cAuthSvc, // Число элементов в asAuthSvc SOLE_AUTHENTICATION_SERVICE * asAuthSvc, // Массив имен для регистрации void * pReservedl, // Зарезервировано для применения в будущем DWORD dwAuthnLevel, // Уровень аутентификации по умолчанию // для прокси DWORD dwImpLevel, // Уровень олицетворения по умолчанию // для прокси SOLE_AUTHENTICATION_LIST * pAuthList, // Аутентификационная информация для // каждой службы аутентификации DWORD dwCapabilities, // Дополнительные возможности // клиента и/или сервера void * pReservedS // Зарезервировано для применения в будущем );

Первый параметр — описатель безопасности. Существует целый ряд вариан тов его применения: указание на существующий описатель безопасности, на иден тификатор приложения (application ID, AppID) или на объект lAccessControl. Что конкретно означает содержимое PSECURITY_DESCRIPTQR, определяется флагом в аргументе dwCapabiiities. Если это AppID. вся информация берется из реестра, а другие аргументы игнорируются. Описатель определяет список тех, кому прс до ставляется доступ к объекту, причем после создания описателя безопасности на сервере его нельзя изменить. Этот параметр не применяется на клиенте и может быть нулевым (то есть указывать на NULL), В документации к набору Platform SDK мелким шрифтом (к сожалению, это обычная практика, когда речь идет о действи тельно важных вещах) указано, что, если сервер установил описатель на NULL, от ключается вся проверка доступа и остается аутентификация на основе параметра dwAutbnLevel. Так что не применяйте нулевой описатель безопасности.

Следующий шаг — выбор службы аутентификации. Во многих случаях это ос тавляется на усмотрение ОС, для этого параметру cAutbSvc передают значение -1, Перейдем к параметру dwAuthnLevel, отвечающему за определение необходимого уровня аутентификации. Как уже упоминалось, присвоение этому параметру зна чения RPC_C_AUTHN_LEVEL_PKT_PPJVACY позволяет изрядно укрепить безопасность за счет незначительного снижения быстродействия. Практически всегда стоит обеспечивать безопасность на уровне пакетов. В результате согласования пара метров безопасности между сервером и клиентом выбирается уровень безопас ности, наиболее высокий из затребованных клиентом и сервером, Дополнительные методы создания защищенного кода 438 Часть III Уровень олицетворения не согласовывается, а просто задается клиентом. Кли енту имеет смысл сообщать серверу, какие действия разрешается совершать с переданными ему реквизитами пользователя. Существует интересная возмож ность — смена ролей клиента и сервера, так что неплохо установить этот флаг и на сервере, который в этом случае превращается в клиента! Как уже говорилось ранее, следует указывать значения RPC_CJMP_LEVELJDENTfFYium KPC_C_IMP_LE VEL_ANONYMOUS, если только приложению не требуется более высокий уровень олицетворения.

А теперь о нескольких интересных значениях аргумента dwCapabilities. Два флага- EOAC_STATIC_CLOAKINGvi ЕОАС^DYNAMIC_CLOAKING - применяются в системах Windows 2000 и последующих для организации маскировки (cloaking).

Она позволяет промежуточному объекту получать доступ к низкоуровневому объек ту с правами пользователя, от имени которого делается вызов. При олицетворе нии вызывающего пользователя обычно лучше всего обращаться к другим объек там в контексте самого пользователя — иначе объектам окажутся доступными «лишние» ресурсы. Чтобы помешать освобождению злонамеренными пользова телями объектов других пользователей установите флаг EOAC_SECURE_REFS. Но помните, что он не совместим с анонимной аутентификацией.

Начиная с Windows 2000, стал доступным новый флаг — EOAC_NOjCUSTOM_MAR SHAL. Он повышает безопасность сервера при использовании DCOM за счет сни жения вероятности выполнения произвольных DLL-библиотек. При установлен ном флаге EOAC_NO_CUSTOM_MARSHAL допустим демаршалинг только классов с CLSID, представленных в Ole32.dll и службе компонентов (Component Services).

Идентификатор CLSID — это уникальное в глобальных масштабах число, однознач но определяющее СОМ-объект. В DCOM ссылки на объекты упаковываются путем создания объектных ссылок (object references, OBJREF), содержащих CLSID. Атака на идентификаторы CLSID в процессе демаршалинга позволяет вызвать загрузку произвольной DLL-библиотеки. Процессы, для объявления флага EOAC_NO_CUS TOM_MARSHAL, могут также посредством вызова CoInitializeSecurity обращаться к идентификаторам CLSID, содержащие CATID_Marshaler.

При установленном флаге EOAC_DISABLE_AAA любая операция, в которой пред принимается попытка запуска серверного процесса от лица пользователя, завер шается с ошибкой E_ACCESSDENIED. Этот флаг, доступный только при вызове фун кции CoInitializeSecurity, позволяет предотвратить запуск ненадежных компонен тов в контексте привилегированной учетной записи (например, Local System), в котором выполняется приложение. Такая возможность реализована в Windows и последующих ОС.

Специально для экспериментов с различными параметрами и проверкой ра боты их комбинаций я создал приложение проверки безопасности DCOM (см. папку Secureco2\Chaf>terl6\DCQM_Security). Сначала с помощью мастера ATL СОМ Арр Wizard из состава пакета Microsoft Visual C++ 6 я создал простенький стандартный DCOM-сервер, затем добавил в него интерфейс ISecurityExample, реализующий метод GetServerBlanket.

STDMETHODIMP CSecurityExample::GetServerBlanket(DWORD * AuthNSvc, DWORD * AuthZSvc, DWORD * AuthLevel, ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM DWORD * ImpLevel) IServerSecurity- pServerSecurity;

OLECHAR- PriName;

if(CoGetCallContext(IID_IServerSecurity, (vaid-*)&pServerSecurity) == S_OK) { HRESULT fir;

hr = pServerSecurity->QueryEUanket(AuthNSvc, AuthZSvc, iPriName, AuthLevel, rmpLevel, NULL, NULL);

if(hr == S_OK) { CoTaskMemFree(PriName);

I return hr;

I else return E_NOINTERFACE;

I Как вы видите, код довольно прост: сперва получается контекст текущего по тока, а затем с помощью объекта IserverSecurity запрашивается защитная оболоч ка (blanket). Полученные результаты возвращаются клиенту. Клиентская программ:!

TestClient выясняет текущие параметры безопасности клиента, выводит их на пе чать, вызывает метод ICUentSecurity::SetBlanket, который устанавливает на интер фейсе безопасность на уровне пакетов, а затем вызывает GetSemerBlanket на сер вере. Взглянем на результаты:

Initial client security settings:

Client Security Information:

Snego security support provider No authorization Principal name: DAVENET\david Auth level = Connect Impersonation level = Identify Set auth level to Packet Privacy Server Security Information:

Snego security support provider Часть III Дополнительные методы создания защищенного кода No authorization Auth level = Packet privacy Impersonation level = Anonymous После установки и сборки приложения скопируйте TestClient.exe и DCOM_Secu rity.exe на другую систему. Зарегистрируйте DCOM_Security.exe в ОС командой DCOM_Security.exe /regserver. Убедитесь в том, что все набрали правильно — построенное с помощью мастера приложение не выдает никаких сообщений об успехе или неудаче регистрации. Приложив минимальные усилия, вы включите этот тестовый код и в свое приложение, чтобы понаблюдать, что происходит при изменении параметров безопасности. Однако будьте начеку: проверка бесполез на, если клиент и сервер работают на одной машине.

Источники и приемники В DCOM существует интересный способ обработки асинхронных вызовов, хотя, начиная с Windows 2000, в ОС этого семейства поддерживаются настоящие асин хронные вызовы. DCOM позволяет клиенту запросить сервер с тем, чтобы тот после завершения текущего вызова выполнил обратный вызов указанного клиентом интерфейса. Это реализуется посредством стыкуемого объекта (connectable object), Точки соединения (connection points) детально описаны во многих книгах [напри мер книга Гая (Guy Eddon) и Хенри Эддонов (Henry Eddon) «Inside Distributed COM* (Microsoft Press, 1998)] и за подробностями лучше обратиться к ним. С точки зре ния безопасности интересен тот факт, что в такой ситуации сервер превращает ся в клиента. Если оболочка безопасности сервера не настроена на предотвраще ние полного олицетворения, клиент в состоянии повысить свои привилегии.

Представьте себе следующую последовательность событий, связанных с: выполня ющимся под учетной записью Local System сервером, который в обычном состо янии олицетворяет клиента. Сперва клиент сообщает серверу о своем объекте приемнике данных (sink) и просит завершить занимающий достаточно большое время вызов. Затем клиент принимает вызов, олицетворяет сервер и получает возможность манипулировать операционной системой. Исследуя эту проблему, я просмотрел три различные книги, посвященные DCOM, и только в одной из них.

и то мельком, упоминалось о проблемах безопасности, связанных со стыкуемы ми объектами. При разработке сервера с поддержкой стыкуемых объектов поста райтесь не попадать в эту ловушку.

Эта проблема проявляется и в том случае, если один из методов принимает указатель на интерфейс (то есть указатель на другой объект COM/DCOM). Необ ходимо считаться с возможностью возникновения проблемы и при запросе ме тода IDispatch:.-Invoke из самого объекта. Если злоумышленнику удастся подставить свой объект вместо целевого или, того хуже, ваш компонент принимает вызовы от любых объектов без разбора, хакер получит повышенные привилегии, олицет ворив ваш компонент.

Азы ActiveX Разработанная в Microsoft технология COM (Component Object Model) стала по пулярна благодаря независимости от языка программирования и возможности Защита RPC, ActiveX-элементов и объектов DCOM ГЛАВА повторного использования кода. Взаимодействие СОМ-компонентов происходит через интерфейсы, причем все компоненты обязаны поддерживать базовый ин терфейс lUnknown.

ActiveX-элемент — это саморегистрирующийся СОМ-объект, поддерживающий интерфейс lUnknown. Поддержка интерфейса IDispatch позволяет беспрепятственно обращаться из языков высокого уровня (например, Visual Basic и Perl) и языков сценариев (к примеру, VBScript и Jscript) к любым компонентами посредством механизма, который называется Automation. Архитектура ActiveX-элементов ши роко применяется для разработки программных компонентов, внедряемых в раз личные СОМ-контейнеры, в том числе в инструменты разработки приложений и клиентские приложения (такие, как Web-браузеры и почтовые клиенты).

Проверенные методы обеспечения безопасности ActiveX Неправильно спроектированные или «криво» написанные ActiveX-элементы при водят к серьезным проблемам с безопасностью контейнеров двух видов — Web браузеров и почтовых клиентов. Ведь Web-страницы способны вызывать Active X элементы средствами HTML или сценариев, а приложения для работы с электрон ной почтой часто отображают текст в виде HTML, поэтому (конечно, если это разрешено политиками безопасности) вызов ActiveX-элементы возможен и из электронных писем. Кстати, почтовый клиент Outlook 2002 (из состава Microsoft Office XP) по умолчанию не запускает содержащиеся в электронном письме Active X элементы, впрочем, так же, как и Outlook Express в Windows.NET Server 2003 и Windows XP, Если ActiveX-элемент содержит брешь, это грозит большими неприятностями, особенно если не предупредить пользователя о том. что HTML-страница (иш сообщение электронной почты с HTML-страницей) собирается запустить уязви мый ActiveX-элемент, Чтобы на HTML-странице (в Web-браузере или почтовом клиенте) запуск Acti veX-элемента происходил без оповещения пользователя, необходима особая кон фигурация политик безопасности. Стоит заметить, что, если код объявлен безо пасным в плане инициализации (safe for initialization, SFI) или исполнения сцена риев (safe for scripting, SFS), приложение может не предупреждать пользователя о потенциально опасном способе применения этого кода.

ActiveX-компоненты, безопасные в плане инициализации и исполнения сценариев При инициализации ActiveX-элемента локальные или удаленные данные удается открывать посредством различных СОМ-интерфейсов /Persist, В этом случае воз никает угроза безопасности, так как источники данных не всегда надежны. Безо пасными в плане инициализации считаются ActiveX-элементы, гарантирующее отсутствие проблем с безопасностью при загрузке постоянных данных инищ а лизации, причем независимо от их источника.

Безопасность в плане исполнения сценариев подразумевает, что автор ActiveX элемента выяснил, что элемент управления безопасен, так как в принципе не спо 442 Дополнительные методы создания защищенного кода Часть lit собен создать угрозу для безопасности. Однако то, что ActiveX-элемент безопа сен при вызове пользователями, отнюдь не означает, что он безопасен при авто матизированном запуске посредством ненадежных сценариев или Web-страниц.

Так, приложение Microsoft Excel является надежным инструментом, поступившим из заслуживающего доверия источника, но написанный со злым умыслом сцена рий будет использовать функции автоматизации этого приложения для удаления файлов и распространения вирусов, Думаю, стоит кратко описать возможности превращения ActiveX-элементов в небезопасные в плане инициализации и исполнения сценариев, Внимание! ActiveX-элементы — это исполняемые программы, и их можно за щищать цифровыми подписями по технологии Autbenticode. Хотя под пись гарантирует подлинность автора ActiveX-элемента и целостность самого элемента, она не предохраняет от ошибок или брешей безопас ности.

Вот пример ActiveX-элемента, небезопасного в плане исполнения сценариев.

В мае 2001 г. я занимался анализом безопасности Web-сайта, который требовал от пользователей загрузить и установить ActiveX-элемент. В первую очередь я поинтересовался, безопасен ли элемент управления для работы в сценариях, и получил от разработчиков утвердительный ответ. Затем я спросил, есть ли в нем методы, обладающие доступом к ресурсам (например, к файлам) на компьютере пользователя. Оказалось, что у ActiveX-элемента есть метод Print, позволяющий распечатать любой файл на любом принтере! Этого было достаточно, чтобы уве домить разработчика, что ActiveX-элемент нельзя считать безопасным для исполь зования в сценариях, так как при посещении хакерского Web-сайта, его хозяин злоумышленник сможет распечатать любой документ с жесткого диска пользова теля на своем принтере, причем пользователь не получит никакого предупреж дения.

Вы спросите, как же такое возможно? Запомните: любой Web-сайт в состоянии контролировать все загруженные в память компьютера ActiveX-элементы, если только не предпринять специальные шаги по предотвращения загрузки таких элементов управления. Описанная брешь возникает из-за того, что Web-сайт за пускает ActiveX-элемент на своей Web-странице, а затем вызывает метод Print для печати конфиденциального документа.

Рекомендую ознакомиться с некоторыми примерами того, как ActiveX-элементы с цифровой подписью, написанные квалифицированными разработчиками без малейшего злого умысла, становились причиной серьезных брешей в защите, Просмотрите статьи «Outlook View Control Exposes Unsafe Functionality» (Встро енный в Outlook элемент управления для просмотра предоставляет небезопасные возможности) (http://wwwmicrosojtC(mi/technet/security/bulletin/MS01-038^sp')^hc\\ ve Setup Control Vulnerability» (Уязвимость элемента управления для интерактив ной установки) (http://www.microsoft.com/technet/security/bulletin/MS99-048.asp) и «-Office HTML Script and IE Script Vulnerabilities* (Бреши, обусловленные уязвимо стью механизмом выполнения сценариев на HTML-страницах в Office и Internet Explorer) (http://www.microsoft.com/technet/security/bulleUn/MSOO-049-cisp) ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM Внимание! Угроза, исходящая от ActiveX, касается не только преднамеренно враждебных элементов — да таковых не так уж много. Настоящая опас ность в том, что злоумышленники используют дыры в безопасности вполне «легальных* ActiveX-элементов, изменяя их поведение, Если требуется объявить ActiveX-элемент как безопасный в плане инициали зации или исполнения сценариев, обратитесь на сайт msdn.microsoft.com и выпол ните поиск по фразе safe for scripting. Но до этого настоятельно рекомендую про читать следующий раздел!

Проверенные методы создания SFI- и SFS-элементов Первейшее правило создания ActiveX-элементов, безопасных в плане инициали зации и/или исполнения сценариев, звучит так ActiveX-элемент небезопасен в плане инициализации или исполнения сценариев!

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

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

Безопасен ли ActiveX-элемент?

Процесс выяснения, является ли безопасным ActiveX-элемент, довольно преет:

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

• доступа к данным на локальном компьютере или в сети, таким, как файлы или параметры реестра;

• раскрытия частной информации (например, частных ключей, конфиденциа ть ных паролей и документов);

• изменения или удаления информации, хранящейся на локальном компьюте ре или в сети;

• инициирования аварийного завершения приложения-контейнера;

• потребления слишком большого объема ресурсов (таких, как память и дис ;

о вое пространство);

• выполнения потенциально опасных системных вызовов, включая исполнение файлов.

444 Часть III Дополнительные методы создания защищенного кода Если хоть что-то из перечисленного имеет место, ActiveX-элемент нельзя считать безопасным в плане исполнения сценариев. Быстрый и простой способ заключа ется в просмотре всех названий в поиске глаголов, обращая особенное внимание на названия функции, например RunCode, PrintDoc, EraseFile, Shell, Call, Write, Read и т. п.

Заметьте: само по себе чтение файла или раздела реестра не обязательно вле чет за собой опасность для защиты. Но если атакующий в состоянии указать, ка кой конкретно ресурс считывать, а затем получить данные этого ресурса, то это действительно серьезная проблема.

Другой вариант — реализовать интерфейс lObjecfSafety. Это позволяет прило жению-контейнеру (обычно это Internet Explorer) опрашивать объект и выяснять, безопасен ли он в плане инициализации или исполнения сценариев.

Также следует проверять каждый метод и свойство на возможность возникно вения переполнения буфера (см. главу 19).

Ограничение области действия конкретным доменом Независимо от того, объявили ли вы ActiveX-элемент безопасным для сценариев или нет, вызывайте его только из определенного узкого круга доменов. Например, ограничьте действие ActiveX-элементов так, чтобы они вызывался только с Web страницы, относящейся к домену nortbwindtraders.com. Этого можно добиться следующим образом.

1. Реализуйте интерфейс lobjectWithSite с методом SetSite, который должен вызы вать контейнер (например, Internet Explorer), чтобы получить указатель на интерфейс It/known контейнера (для этого следует подключить заголовочный файл Oddity. lobjectWithSite обеспечивает простую связь между ActiveX-элемен том и контейнером, 2. Затем для получения имени сайта используйте следующий псевдокод:

pUnk->QueryInterface(IID_IServiceProvider, &pSP);

pSP->OueryService(IID_IWebBrowser2, &pWB);

pWB->g6tLocationURL(bstrURL);

3. Наконец, программа должна выяснить, относится ли значение bstrURL к дове ренным URL-адресам. Тут стоит немного подумать. Обычно совершают ошиб ку, проверяя, содержится ли строка northwindtraders.com (ну, или имя другого нужного сервера) в имени сервера. Но такую проверку легко обойти, создав имя типа ivww.northwindtraders.com.foo.com\ Поэтому поиск лучше выполнять вы зовом InternetCrackUrl из библиотеки Wininet.dll, эта функция получает имя хоста из URL-адреса (ему соответствует переменная lpUrlComponent~>lpszHostName), начиная с конца строки.

Следующий пример (см. папку Secureco2\Cbapter l6\Intemet€rackURL) демон стрирует реализацию последнего этапа.

Л InternetCrackURL cpp */ BOOL IsValidDomain(char «szURL, char *szValidDomain, BOOL fRequireHTTPS) { URL_COMPONENTS urlComp;

ГЛАВА 16 Защита RPC, ActiveX-элементов и объектов DCOM ZeroMemoryC&urlComp, sizeof(urlComp));

urlComp.dwStructSize = sizeof(urlComp);

// Нас интересует только имя хоста, char szHostName[128];

urlComp.lpszHostName = szHostName;

urlComp.dwHostNameLength = sizeof(szHostName);

BOOL fRet = InternetCrackUrUszURL, 0, 0, SurlComp) ;

if (fRet==FALSE) { printf("InternetCrackURL failed - > И", GetLastErrorO);

return FALSE;

II Проверяем на наличие HTTPS, если этот протокол необходим, if (fRequireHTTPS && urlComp.nScheme != INTERNET_SCHEHE_HTTPS) return FALSE;

// Сляпанный наспех чувствительный к регистру поиск, // начиная с крайней правой части строки.

int cbHostName = Istrlen(szHostName);

int cbValid = Istrlen(szValidDomain);

int cbSize = (cbHostName > cbValid) ? cbValid : cbHostName;

for (int 1=1;

i <= cbSize;

1++) if (szHostName[cbHostName - i] l= szValidDomain[cbValid - i]) return FALSE;

return TRUE;

void main() { char *szl)RL="https: //www. northwindtraders.com/foo/default. html";

char *szValidDomain = "northwindtraders.com";

BOOL fRequireHTTPS - TRUE;

if (IsValidDomain(szURL, szValidDomain, TRUE}) { printf("Bce нормально, Is - правомочный домен.", szURL) ;

Если вызов IsValidDomain завершится с ошибкой, ActiveX-элемент не загрузи г ся, так как он вызывается с Web-страницы, относящейся к неизвестному домену в данном случае не к домену nortbwindtraders.com.

Замечу, что подробную информацию относительно использованных в этом разделе интерфейсов и функций СОМ вы найдете на сайте msdn.microsoft.com, а в базе знаний Microsoft Knowledge Base, в статье «НСЖТО: Tie ActiveX Controls to a Specific Domain» (Как закрепить ActiveX-компонент за конкретным доменом) (support.microsojt.com/support/kb/articles/Ql 96/0/61 ASP), приводится пример ATL кода для ограничения области действия ActiveX-элемента определенным домено и.

Дополнительные методы создания защищенного кода 446 Часть III Шаблон SiteLock Во время мероприятий по укреплению безопасности Windows и Office в начале 2002 г. был разработан SiteLock, ATL-шаблон на C++. призванный облегчить при вязку к Web-сайтам и ограничение области действия элементов управления. При менение шаблона SiteLock позволяет разработчикам ограничить доступ к ActiveX элементам таким образом, чтобы элемент считался безопасным только в заранее определенном списке доменов, что не позволит взломщику задействовать ActiveX элемент для выполнения вредоносных действий. Программистам шаблон SiteLock позволяет создавать ActiveX-элементы, ведущие себя по-разному при вызове из различных доменов. В шаблоне средства проверки правомочности домена выде лены в отдельную общую библиотеку, что намного укрепляет защиту ActiveX-эле ментов и облегчает устранение обнаруженных ошибок, Примечание Исходный код шаблона SiteLock открыт для общего доступа на странице http://msdn.microsofi,com/doit^loa(^/samples/mternet/components/ sitelock/defaultasp.

Установка Kill Bit Предположим, что все ваши старания пошли насмарку и вы умудрились выпус тить ActiveX-элемент с брешью в защите. Новая версия не решит проблему, осо бенно если пользователи решили полностью доверять подписанным вами ActiveX элементам. Со своего Web-сайта взломщик предоставит пользователю и запустит на исполнение старый ActiveX-элемент — и все: пользователь беззащитен перед лицом злоумышленника. Подскажу, как решить проблему. В разделе реестра HKLM\Soft ware\Microsqft\Internet Explorer есть подраздел ActiveX Compatibility, в котором со держится ряд ActiveX-элементов в виде списка идентификаторов классов (CLSID).

Чтобы прекратить действие старого ActiveX-элемента, создайте новый подраздел (если он еще не существует), указав в качестве названия строковое представле ние CLSID элемента, и добавьте в подраздел параметр типа REG_DWORD с именем Compatibility Flags и значением 0x00000400. Все. этого достаточно. Рекомендую при появлении новых версий ActiveX-эле ментов задавать этот параметр для всех устаревших версий, чтобы защитить пользователя, впервые устанавливающего ваш ActiveX-элемент, от ошибок в предыдущих версиях. За подробной информацией обращайтесь к статье Q240797 из Microsoft Knowledge Base.

Резюме Технологии DCOM и ActiveX обе базируются на RFC;

часто навыки, приобретен ные при изучении RPC, полезны при работе с другими технологиями. Итак, «су хой» остаток о проверенных методах обеспечения безопасности:

• RPC — компилируйте все с параметром /robust MIDL-компилятора и ничего не запускайте в контексте учетной записи SYSTEM;

• DCOM — также ничего не выполняйте в контексте SYSTEM;

• ActiveX — никогда не объявляйте элементы управления безопасными в плане исполнения сценариев, не убедившись, что это действительно так, и исполь зуйте шаблон SiteLock.

ГЛАВА Противостояние атакам типа «отказ в обслуживании» vyT атак типа «отказ в обслуживании* (Denial of Service, DoS) защититься слож нее всего. Чтобы выяснить, каким образом злоумышленники атакуют приложение, и выработать стратегию зашиты, необходим самый тщательный анализ. В этой главе я расскажу о некоторых видах DoS-атак из числа наиболее распространенных и проиллюстрирую рассказ фрагментами кода и примерами «из жизни». Часто про граммисты не заботятся о защите от DoS-атак, объясняя это тем, что они не ведут напрямую к повышению привилегий, но учтите: организовав подставной сер пер и выведя из строя настоящий, атакующий получает такие же права, как и у атаку емого сервера. DoS-атаки случаются все чаще, поэтому следует быть готовым к ним.

В этой главе рассказывается о наиболее часто реализуемых DoS-атаках, которые приводят к:

«краху» приложения и/или операционной системы;

недостатку процессорных ресурсов:

нехватке памяти:

общей нехватке ресурсов;

перегрузке сети.

Атаки, вызывающие «крах» приложений DoS-атаки, вызывающие аварийное завершение приложений, почти всегда исполь зуют недостатки не очень качественно написанных программ. Наиболее извест ны атаки такого типа на стеки сетевых протоколов. Одна из первых — «UDP-бом ба» (UDP bomb) — «'Обваливает» системы под управлением SunOS 4.x. Создается UDP пакет, в заголовке которого указывается длина, превышающая реальный размер пакета. Получение такой «бомбы» приводит к ошибке доступа к памяти, а само ядро 448 Часть III Дополнительные методы создания защищенного кода ОС «падает* (точнее, UNIX-системы впадают в состояние «паники», а Windows системы отображают «синий экран смерти» или сообщение об ошибке), далее происходит перезагрузка системы.

В атаке типа «ping смерти* (Ping of Death), используются некоторые недостат ки внутреннего строения заголовков IP-пакетов. Это более свежий пример DoS атаки. Вот как выглядит структура 1Ру4-заголовка:

struct ip_hdr.

unsigned char ip_version:4, ip_header_len;

4;

unsigned char ip_type_of_service;

unsigned short ip_len;

unsigned short ip_ld;

unsigned short ip_offset;

unsigned char ip_tiine_to_live;

unsigned char ip_protocol;

unsigned short ip_checksum;

struct in_addr ip_source, ip_destination;

>;

В поле ipjen указан полный размер пакета в байтах. Число типа беззнакового целого (unsigned short) принимает значения не более 65 535, поэтому максималь ный размер всего пакета — 65 535 байт. Структура поля ip_offset выглядит стран но — три бита в нем отведены для управления процессом фрагментации. Один из битов определяет, может ли пакет быть фрагментирован, еще один показыва ет, следуют ли за этим пакетом другие фрагментированные пакеты. Если оба бита равны 0, возможны два варианта: или этот пакет — последний в серии фрагмен тированных пакетов, или никакой фрагментации не было. Остальные 13 бит оп ределяют смещение фрагмента относительно начала оригинального пакета. Так как квант смещения равен 8 байтам, максимальное смещение составляет 65 535 байт.

Так в чем же проблема? Она в том, что последний фрагмент можно «приклеить» к последнему возможному байту пакета (то есть к байту под номером 65 535). Если вы добавите несколько байт к этой позиции, суммарный объем собранного паке та превысит 2 1 Й.

Примечание Тем, кто хочет точно знать, как работает атака «ping смерти* (Ping of Death), советую обратиться к одному из первых описаний этой ата ки, например, на странице bttp://imvw.insecure.org/sploits/pmg-o-deathbtml.

Имеющаяся информация о том, какие системы уязвимы по отношению к этой атаке, противоречива. Известно только то, как обнаружили про блему: кто-то заметил, что вызов команды ping -1 65510 <1Р-адрес атакуемого уэла> в системе под управлением Microsoft Windows 95/NT приводит к «кра ху* многих UNIX и Linux-систем, а также некоторых сетевых устройств.

Как защититься от подобных ошибок в сетевом ПО? Во-первых, ни при каких обстоятельствах не доверяйте тому, что приходит из сети. Единственный способ ГЛАВА 17 Противостояние атакам типа «отказ в обслуживании» избежать «падения» приложений — писать надежные программы и тщательно их тестировать. Не забывайте также, что многие DoS-атаки. вызывающие «крах* при ложений, создают ситуацию, в которой возможно выполнение произвольного кода, для этого взломщику придется приложить совсем немного усилий. Следующий фрагмент кода иллюстрирует описанную проблему.

/* Пример кода сборки пакетов, выявляющего слишком длинные пакеты ftinclude tfinclude using namespace std;

// Многие сборщики пакетов работают со связным списком.

// Фрагменты не всегда поступают в правильном порядке.

// Реальный код для сборки пакетов намного сложнее.

struct ip_hdr.

unsigned char ip_version:4, ip_header_len:4;

unsigned char ip_type_of_service;

unsigned short ip_len;

unsigned short ip_id;

unsigned short ip_offset;

unsigned char ip_time_to_live;

unsigned char ip_protocol;

unsigned short ip_checksum;

struct in_addr ip_source, ip_destination;

typedef list FragList;

bool ReassemblePacket{FragList& frags, char** outbuf) • II Предполагаем, что сборщику пакетов передали список, // упорядоченный по смещению.

// Сперва определяем, сколько памяти выделять // для всего пакета.

unsigned long packetlen = 0;

// Проверяем пакет на "вредность" // и определяем его максимальный размер, unsigned short last_offset;

unsigned short datalen;

ip_hdr Packet;

// Проблемы с очередностью байтов не рассматриваются Часть I I I Дополнительные методы создания защищенного кода // ведь это всего лишь пример.

//Получаем последний пакет, Packet = frags.back();

// Как вы помните, смещение кратно 8 байтам.

// Проверяем и маскируем флаги.

last_offset = (Packet.ip_offset 4 OxIFFF) - 8;

// Проверка необходима, чтобы убедиться, // что размер пакета не больше указанного в заголовке!

datalen = Packet.ip_len - Packet.ip_header_len * 4;

// Приведение всех и вся к типу unsigned long // позволяет избежать переполнения, packetlen = (unsigned long)last_offset + (unsigned long)datalen;

// Если packetlen - переменная типа unsigned short, // то вычисления будут выглядеть вот так:

// offset = OxfffO;

// datalen = 0x0020;

// total = 0x // что сократится до общей суммы 0x0010, // и следующая проверка всегда будут возвращать true, так как значение // переменной типа unsigned short не может превышать Oxffff.

if(packetlen > Oxffff) { // 0-па! Плохой пакет!

return false;

// Выделяем память и начинаем сборку пакета из фрагментов, return true;

:• Далее — иллюстрация другой проблемы: реально поступившие данные не со ответствуют описанию в структуре данных. Я знаю множество случаев, когда именно такая ошибка порождала массу проблем в различных приложениях, начиная с Microsoft Office и заканчивая ядром операционной системы.

/* Второй пример */ struct UNICODE_STRING !

WCHAR* buf;

unsigned short len;

unsigned short max_len;

ГЛАВА 17 Противостояние атакам типа -отказ в обслуживании" :

void CopyString(UNICODE_STRING* pStr) WCHAR buf[20];

// В чем проблема ТАКОГО представления?

if(pStr->len < 20) memcpy(buf, pStr->buf, pStr- >ler\ * sizeof(WCHAR)};

f/ Выполним вдобавок кое-какие операции.

I Наиболее очевидный недостаток в том, что функция не проверяет, встречаю" ся или нет указатели на NULL. Вторая ошибка — функция «верит» на слово сведени ям, предоставляемым структурой. Запомните;

при создании защищенного кода обязательно проверяйте все, что только можно. Например, при получении подобг ой строки механизм маршалиига в RPC должен выяснить, согласуется ли заявленный размер строки с размером буфера. Функции необходимо как минимум выяснить, не указывает яи pStr->bufna NULL. Никогда не считайте заранее, что клиенты доб ропорядочны, Атаки, вызывающие перегрузку процессора Цель подобных атак — заставить приложение «зависнуть» в цикле (лучше всего в бесконечном) ресурсоемких вычислений. Понятно, что от подвергшейся такой атаке системы толку мало. Вот пример того, как взломщик обнаруживает уяз ш мость приложения к таким атакам: он запрашивает файл c:\\foo.lxt и получает со общение об ошибке с информацией, что файл c:\foo.txt не найден. Ага, приложе ние убирает лишние символы обратного слэша — посмотрим-ка, как оно спра вится с множеством повторяющихся символов? Вот пример приложения (см. папку Secureco2\Cbapterll\CPU-DoS)\ I* CPU_DoS_Example.cpp Приложение демонстрирует результаты работы двух различных методов удаления повторяющихся символов обратного слеша.

Существует множество способов выполнения этой задачи.

Применяемые здесь способы приводятся лишь в качестве примера.

ttinclude ((include Kinclude /* 452 Часть III Дополнительные методы создания защищенного кода В этом методе повторно используется буфер, но такой подход не слишком эффективный.

Затраты растут пропорционально квадрату размера входной строки.

Возвращается значение true, если обратный слеш убран.

// Предполагается, что buf - строка с завершающим нулем, bool StripBackslash1(char* buf) I char* tmp = buf;

bool ret = false;

for(tmp = buf;

«tmp != '\0';

tmp++).

if(tmp[0] == AV && tmp[1] == -\V) // Сдвигать все символы вниз на одну позицию // с помощью функции strcpy, когда источник и адресат // совпадают очень ПЛОХО!

// Это пример того, как НЕЛЬЗЯ делать.

// Это приложение чревато опасностями // никогда не делайте этого.

strcpy(tmp, tmp+1);

ret = true;

} :

return ret;

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

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

*/ bool StripBackslash2(char* buf} unsigned long len, written;

char* tmpbuf = NULL;

char* tmp;

bool foundone = false;

len = strlen(buf) + 1;

ifflen == 1) return false;

ГЛАВА 17 Противостояние атакам типа «отказ в обслуживании" tmpbuf = (char*)malloc(len);

// Это не то, чего хотелось бы - стоит вернуть ошибку.

IfCtmpbuf == NULL) assert(false);

return false;

written = 0;

for(tmp = buf;

«tmp != '\0';

tmp++) if(tmp[0] == AV &A tmp[1] == ' \ ) \' { //Просто не копируем его в другой буфер.

foundone = true;

} else •' tmpbuf[written] = *tmp;

written++;

if(foundone) ;

// Повторное копирование временного буфера поверх входных данных // вызовом strncpy позволяет работать с буфером // без завершающего null.

// Переменная tmp была увеличена на единицу // при последнем выходе из цикла, strncpy(buf, tmpbuf, written);

buf[written] = АО';

if (tmpbuf != HULL) free(tmpbuf );

return foundone;

int main(int argc, char* argv[]) I char* input;

char* end = "foo";

DWORD tickcount;

int i, j;

// Теперь собираем строку.

for(i =10;

i < 10000001;

i *= 10) 454 Часть III Дополнительные методы создания защищенного кода input = (char*)malloc(l);

iffinput == NULL) I assert(false);

break;

!

// Теперь заполняем строку.

// Учтите, что строку замыкает "foo".

// Мы запишем 2 байта после элемента input[j], // затем добавим "foo\0", for(] = 0;

j < i - 5;

J += 3) :

input[j] = -\\';

input[j+i] = AV;

input[J+2] = 'Z';

// Помните, что переменная j увеличивается до // проверки логического выражения в условном операторе, strncpy(input + j, end, 4);

tickcount = GetTickCountO;

StripBackslasnl(input);

printf("StripBackslash1: на входе = Xd символов, время = *d мс\п' i, GetTickCountO - tickcount);

// Возвращаем строку в исходное состояние // это разрушительный тест, for(J =0;

j < i - 5;

j += 3) I input[j] = input[j+i] input[j+2] !

// Как вы помните, переменная ] увеличивается до // проверки условного оператора.

strncpy(input + j, end, 4);

tickcount = GetTickCountO;

StripBackslash2(input);

printf("StripBackslash2: на входе = Xd символов, время = Kd мс\п' i, GetTickCountO - tickcount);

free(input);

;

return 0;

ГЛАВА 17 Противостояние атакам типа «отказ в обслуживании» Программа CPU_DoS_Example.cpp отлично проверяет устойчивость функции к зловредным входным параметрам. Функция тат создает тестовую строку и выводит информацию о производительности. Функция StripBackslasb 1 избавляет от необходимости выделения дополнительного буфера, но ценой дополнитель ной нагрузки: число исполняемых команд пропорционально квадрат)- числа по вторяющихся символов обратного слеша. В функции StripBackslash2 использует ся дополнительный буфер, но дополнительное выделение памяти компенсирует ся тем, что количество исполняемых команд становится пропорциональным длине строки. В табл. 17-1 сравниваются результаты работы двух функций.

Таблица 17-1. Результаты выполнения CPU.DoS Example.cpp Время выполнения Время выполнения Размер строки StripBacksfashl, мс StripBackslash2, мс 10 II • 1000 0 10000 !i!

100 000 1 1 306 1 000 000 2 170 160 Из таблицы видно, что поведение двух функций не различается, пока длина строки не достигает величины около 10 000 байт. При длине в 1 млн. байт, разни ца составляет 36 минут на компьютере с процессором Pentium III (частота 800 МП1;

).

Направив всего несколько таких запросов, взломщик «вырубит» сервер на доводы :о длительное время, Несколько читателей первого издания этой книги указали мне на то, что фун кция StripBackslo$h2 также не очень эффективна, так как можно избежать выделе ния дополнительной памяти. Я составил третью версию программы, которая рас ставляет все по местам. Производительность этой версии не удается измерить с помощью GetTickCount — возвращается время 0 мс для строк с размером вплоть до 1 Мб. Я не использовал такой подход ранее, чтобы проиллюстрировать ситуа цию, когда с самого начала отказываются от одного решения в пользу другого из за худшей производительности, демонстрируемой в «тепличных•> условиях. Фун кция StripBackslashl превосходит по скорости функцию StripBackslasb2 при работе с очень короткими строками, но эту разницу в производительности можно не принимать в расчет в масштабах всего приложения. Применение функции Strip Backslasb2 связано с дополнительными расходами, что компенсируется линейной масштабируемостью при росте нагрузки. Мне известны случаи, когда рабочие характеристики приложения проверялись только в обычных условиях, и такой ошибочный подход оставлял возможность реализации DoS-атак. Наверняка вы предпочтете небольшое снижение производительности в обычных условиях уя i вимости к атакам типа «отказ в обслуживании». К сожалению, приведенный при мер не без изъянов, потому что существует и третий вариант, превосходящий г о скорости оба первоначальных, к тому же не подверженный DoS-атакам. Вот текст функции StripBackslash3.

16- Дополнительные методы создания защищенного кода 456 Часть III bool StripBackslash3(char« str) char* read;

char* write;

// Всегда проверяйте все возможности.

assert(str != NULL);

if(strlen(str) < 2) // Никаких дубликатов, return false;

// Инициализируем оба указателя.

for(read = write = str + 1;

*read != ' \ 0 ' l read++) // Если и этот, и последний символ - обратные // слеши, то на единицу увеличивается // не write, а только read.

if(*read == '\V && -{read - 1) == Л\') continue;

else *write = *read;

write++;

Pages:     | 1 |   ...   | 6 | 7 || 9 | 10 |   ...   | 12 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.