Ваш отладочный код может быть уязвимостью в безопасности: подгрузка опциональной отладочной 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 ищут библиотеки в таком порядке:
- Каталог приложения;
- Каталог system32;
- Каталог system;
- Каталог Windows;
- Текущий каталог;
- Пути из 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:PROPOSALABC.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:PROPOSALABC.LIT
, то увидите то же (неверное) поведение программы. Иными словами, это поведение системы — не баг, а дизайн».
Ответ на «Это поведение по проекту» обычно звучит как «Я скажу так, что дизайн, который не даёт мне выполнять мою работу — отстойный дизайн» или кратко: «Нет, это не дизайн, это баг» (вы не верите мне, что такое происходит? Просто почитайте Slashdot).
Итак, чтобы все такие программы продолжили бы работу в Windows, система устанавливает текущий каталог для программы в каталог, содержащий открываемый документ. Это вовсе не чрезмерное и не безрассудное решение — ведь благодаря ему начала работать программа. Не то чтобы запускаемой программе было важно конкретное значение текущего каталога — ведь у неё нет контроля над ним!
Но сказанное также означает, что если вы запустили программу двойным щелчком по ассоциированному документу, то программа будет удерживать каталог с документом — потому что этот каталог будет текущим. Если только, конечно, сама программа потом не изменит свой текущий каталог.
Бонус-замечание: изначально я написал эту серию заметок более двух лет назад. Но даже тогда я не считал их чем-то удивительным, новаторским или интересным. Но, похоже, некоторые люди вновь открыли это несколько месяцев назад, и вылезли из кожи вон, чтобы присвоить себе лавры первооткрывателей. Это как новое поколение подростков, которые думают, что они изобрели секс. Чисто для протокола: вот некоторые официальные инструкции (и, чтобы расставить все точки над i: это официальные инструкции по атаке на текущий каталог, а не официальные инструкции по сексу).
Историческое замечание: почему вообще есть текущий каталог? Ответ: это идёт со времён CP/M. В CP/M не было PATH
. Всё запускалось из текущего каталога. Сегодняшнее состояние вещей — это цепочка из обратных совместимостей.