среда, 14 января 2015 г.

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

Это перевод Your debugging code can be a security vulnerability: Loading optional debugging DLLs without a full path. Автор: Реймонд Чен.

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

Посмотрите на такой код:
type
  TDocLoadingProc = procedure(Stream: TStream);

var
  OnDocLoading: TDocLoadingProc;

procedure LoadDebuggingHooks;
var
  hmodDebug: HMODULE;
begin
  hmodDebug := LoadLibrary('DebugHooks.dll');
  if hmodDebug = 0 then
    Exit;
  OnDocLoading := GetProcAddress(hmodDebug, 'OnDocLoading');
end;

function LoadDocument({...}): HRESULT;
begin
  // ...
  if Assigned(OnDocLoading) 
    // Дадим отладочной ловушке изменить документ перед загрузкой
    OnDocLoading(DocStream);
  // ...
end;
Если вы хотите отлаживать вашу программу, вы копируете DebugHooks.dll в папку приложения. Код выше ищет эту DLL и загружает её, если она есть. Для примера я включил код одной из возможных ловушек из этой отладочной DLL. Идея примера (и это просто пример, так что давайте не будем обсуждать, хороший ли это пример) состоит в том, что непосредственно перед загрузкой документа мы вызываем функцию OnDocLoading. Эта функция может обернуть переданный ей поток в другой поток, так что содержимое потока можно логгировать при его чтении - в попытке локализовать на чём стопорится загрузка документа. Или её можно использовать для инъекции ошибок ввода-вывода с целью тестирования. В общем, используйте своё воображение.

Но этот отладочный код также содержит уязвимость.

Вспомните, что пути для поиска DLL ищут библиотеки в таком порядке:
  1. Каталог приложения;
  2. Каталог system32;
  3. Каталог system;
  4. Каталог Windows;
  5. Текущий каталог;
  6. Пути из PATH.
Когда вы отлаживаете программу, вы копируете DebugHooks.dll в папку приложения, откуда она и загружается (шаг 1 в списке выше). Но если вы не отлаживаете свою программу, то шаг 1 пропускается, и поиск продолжается дальше. DLL не будет найдена на шаге 2, 3 и 4, и в итоге мы достигаем шага 5: текущего каталога.

И теперь вас хакнули.

Как правило, ваше приложение не имеет прямого контроля над текущим каталогом. Пользователь может запустить вашу программу из любого каталога - и это каталог станет вашим текущим каталогом. И когда вызов LoadLibrary будет искать DLL в текущем каталоге, плохие парни могут положить в него их собственную DLL, ваша программа станет жертвой инъекции кода.

Это особенно опасно, если ваше приложение ассоциировано с каким-то типом файла, поскольку пользователь может запустить вашу программу просто дважды-щёлкнув по файлу, с которым вы ассоциированы.

Когда вы дважды-щёлкаете по файлу, Проводник устанавливает текущий каталог для приложения-обработчика этого файла равным каталогу, в котором расположен этот файл. Это необходимо для правильной работы приложений, которые ищут в текущем каталоге вспомогательные файлы. Например, представьте гипотетическое приложение LitWare Writer, ассоциированное с файлами *.LIT. Документ приложения LitWare Writer, представленный файлом ABC.LIT - это просто представитель семейства файлов: ABC.LIT (главный документ), ABC.LTC (индекс документа и содержание), ABC.LDC (словарь грамматики для документа), ABC.LLT (пользовательский шаблон для документа) и так далее. Когда вы открываете документ C:\PROPOSAL\ABC.LIT, LitWare Writer ищет остальные части документа в текущей папке, вместо C:\PROPOSAL. Чтобы помочь таким приложениям найти все необходимые файлы, Проводник указывает функции CreateProcess, что текущий каталог для приложения LitWare Writer должен быть равен C:\PROPOSAL.

Так, вы можете сказать, что программы вроде этого LitWare Writer (которые ищут файлы в текущей папке вместо папки с главным документом) плохо написаны, и я в этом с вами соглашусь. Однако Windows нужно работать даже с плохо написанными программами (упреждающее ехидное замечание: Windows сама плохо написана). В реальном мире есть слишком много плохо написанных программ, некоторые из которых являются лидерами в своих сегментах (см. выше упреждающий остроумный комментарий), и если Windows перестанет их запускать, люди скажут, что это вина Windows, а не этих программ.

Мне даже не надо закрывать глаза, чтобы представить, как нам присылают баг-отчёт с этим поведением:
"Эта программа отлично работает в MS-DOS, но в Windows она не работает. Тупая Windows".
Клиент не будет счастлив услышать следующий ответ: "Вообще-то, эта программа никогда и не работала верно, ей просто везло все эти X лет. Авторы этой программы никогда не рассматривали случай, если открываемый документ расположен вне текущего каталога. Им это сошло с рук, потому что обычно вы открываете файлы в MS-DOS следующим образом: вы переходите в каталог с документом и потом набираете LWW ABC.LIT. Но если вы выполните в MS-DOS LWW C:\PROPOSAL\ABC.LIT, то увидите то же (неверное) поведение программы. Иными словами, это поведение системы - не баг, а дизайн".

Ответ на "Это поведение по проекту" обычно звучит как "Я скажу так, что дизайн, который не даёт мне выполнять мою работу - отстойный дизайн" или кратко: "Нет, это не дизайн, это баг" (вы не верите мне, что такое происходит? Просто почитайте Slashdot).

Итак, чтобы все такие программы продолжили бы работу в Windows, система устанавливает текущий каталог для программы в каталог, содержащий открываемый документ. Это вовсе не чрезмерное и не безрассудное решение - ведь благодаря ему начала работать программа. Не то чтобы запускаемой программе было важно конкретное значение текущего каталога - ведь у неё нет контроля над ним!

Но сказанное также означает, что если вы запустили программу двойным щелчком по ассоциированному документу, то программа будет удерживать каталог с документом - потому что этот каталог будет текущим. Если только, конечно, сама программа потом не изменит свой текущий каталог.

Бонус-замечание: изначально я написал эту серию заметок более двух лет назад. Но даже тогда я не считал их чем-то удивительным, новаторским или интересным. Но, похоже, некоторые люди вновь открыли это несколько месяцев назад, и вылезли из кожи вон, чтобы присвоить себе лавры первооткрывателей. Это как новое поколение подростков, которые думают, что они изобрели секс. Чисто для протокола: вот некоторые официальные инструкции (и, чтобы расставить все точки над i: это официальные инструкции по атаке на текущий каталог, а не официальные инструкции по сексу).

Историческое замечание: почему вообще есть текущий каталог? Ответ: это идёт со времён CP/M. В CP/M не было PATH. Всё запускалось из текущего каталога. Сегодняшнее состояние вещей - это цепочка из обратных совместимостей.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.