WWW.DISSERS.RU

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

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

Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 10 |

«Michael Howard David LeBlank WRITING SECURE CODE Second Edition Press Майкл Ховард Дэвид Лебланк ЗАЩИЩЕННЫЙ код 2-е издание, исправленное Москва 2004 УДК 004.45 ББК 32.973.26-018.2 Х68 ...»

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

фикатор (salt) — это случайное число, которое добавляется к ным и позволяет предотвратить «взлом по словарю», сильно усложняя задачу шифровки сообщения. Взломом по словарю (dictionary attack) атака, когда пытаются расшифровать текст, подставляя известные слова и символов из предварительно созданного массива, или словаря. Незашиф рованный модификатор присоединяется к хешируемым данным, при этом он должен быть достаточно случайным и создаваться с применением качественных криптографических алгоритмов генерации случайных (Подробнее об алгоритмах генерации случайных значений — в главе 8).

Создавать хеш с модификатором или простой верификатор с помощью ций CryptoAPI очень просто. Вот пример на C/C++:

260 Часть II Методы кодирования // Создаем хеш сообщения с присоединенным if 0, 0, throw if (LPBYTE)bSecret, cbSecret, throw if 0» throw GetLastErrorO;

// Получаем готового хеша с DWORD cbSaltedHash = 0;

DWORD cbSaltedHashLen = sizeof (DWORD);

if throw GetLastErrorO;

// Получаем хеш с BYTE = new if (NULL *pbSaltedHash) throw;

throw GetLastErrorO;

А вот та же задача, решенная средствами С#:

using using using using static byte[] byte[] SHA1 = = new cs = new cs.

return Файлы с текстами этих примеров вы найдете в папке Выяснить, известен ли пользователю секрет, очень просто: берется сек рет, к нему добавляется модификатор, полученный текст хешируется и сравнива ется с полученным с сообщением значением. Функция интер фейса Windows API добавляет данные к хешу и выполняет повторное хеширова эта операция так же как и только что описанная. Совпадение полученных значений свидетельствует, что пользователь знает секрет. Преимуще ство в том, что секрет нигде не хранится, а передается только проверочное зна чение (верификатор). Даже получив доступ к системе, взломщик узнает только верификатор, но не сами данные. Ему придется атаковать шифр «в лоб» или по ГЛАВА Защита секретных данных словарю. При удачном выборе паролей подобная атака нецелесообразна из за слишком большой трудоемкости.

Применение PKCS #5 для усложнения задачи взломщика Как уже говорилось, во многих приложениях до хеширования к паролю ется модификатор — только после этого используется в качестве ключа шифрования или Однако есть способ получить ключ из удобочитаемого пароля — это метод PKCS (Public-Key Это один из примерно дюжины стандартов, одобренных RSA Data Security и ли дерами отрасли, в том числе Microsoft, Apple и Sun Microsystems. Стандарт PKCS описан в RFC В пароль с модификатором несколько сотен или тысяч раз подряд. Или, если быть совсем точным, так работает алгоритм Based Key Derivation Function #1) — наиболее популярный вариант PKCS #5. Дру гой PBKDF2, немного отличается: в нем применяется генератор случайных чисел. В этой книге всюду, где мы упоминаем PKCS PBKDF1.

Основное назначение PKCS #5 — защита от атак по словарю, так как при реборе паролей на обсчет каждого уходит много процессорного Во многих программах хранят только хеш пароля, а при хеш вЕ;

е пользователем пароля просто сравнивается с имеющимся в системе. Бы значительно затрудните задачу хакеру, храня не хеш, а значение, вычисленное но методу PKCS #5.

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

Создать или достать где-то файл со словарем.

2. Сгенерировать возможный 3. Выбрать модификатор, 4. Выбрать число итераций.

5. Вычислить выбранное число раз хеш-функцию PKCS 5.

Если длина модификатора велика, по крайней мере 64 бита, то, чтобы вычисли гь пароль, взломщику придется перепробовать на комбинаций (или если предположить, что подобрать пароль удастся, перепробовав примерно возможных комбинаций). Таким образом, атака значительно усложняется.

Бы можете хранить информацию о числе итераций, модификаторе и тате работы алгоритма PKCS #5. Когда пользователь вводит применить PKCS tf 5 указанное число раз и с заданным модификатором. Если лученное значение совпадает с хранимым, можно с большой степенью уверенности утверждать, что пользователь ввел правильный пароль.

В этом примере на С# демонстрируется генерация ключа на основе фразы:

byte[] byte[] salt, int iter) { p = new return 262 Часть Методы безопасного кодирования стандартные криптопровайдеры в Windows не поддержи вают напрямую PKCS #5, однако есть которая обеспечи вает примерно такой же уровень защиты.

Как видите, хранить пароль и напрашиваться на неприятности совершенно не обязательно.

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

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

Защита секретов в Windows и следующих ОС семейства Для сохранения секретных данных в Windows 2000 применяют фун кции и API-интерфейса (Data Protection API). DPAPI поддерживает два способа хранения секретов: когда доступ к данным предоставляется только их владельцу и когда данные доступны любому пользова телю компьютера. В последнему случае нужно установить флаг в поле однако при этом вам придется назначить надежный ACL папке, разделу реестра или файлу, где хранятся данные, созданные функция ми DPAPI. Например, чтобы предоставить доступ к защищенным данным на ком пьютере всем членам группы Accounting (бухгалтерия), следует создать список ACI.

с записями для следующих групп:

ГЛАВА Защита секретных данных • Administrators: Full Control (Администраторы: Полный доступ);

• Accounting: Read (Чтение).

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

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

Внимание! Любые доступные вам данные, защищенные DPAPI (да, в принци пе, любым другим механизмом защиты), из любых работающих в контексте вашей учетной записи. Мораль проста: не за пускайте на исполнение код, которому не доверяете.

Самый популярный о DPAPI Вопрос. Могу ли я средствами DPAPI данные под одной ной записью, а — под другой?

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

Ответ. В современных условиях нет удовлетворительного способа реше ния этой проблемы, так как все приложения, работающие в контексте од пользователя, имеют равный доступ к данным. Однако если при вы зове в поле передать дополнительный роль или случайное то данные будут зашифрованы ключом, со стоящим из объединения этого значения и пароля Чтобы расшифровать данные, придется передать в такое же то есть его придется Некоторые программы передают 264 Часть II Методы безопасного кодирования фиксированное случайное 1б байт), другие — комби из значения и имени пользователя и/или некото рыми другими пользователем Внимание! Защищая данные путем установки флага CHINE, делайте резервную копию шифрованного текста. В противном случае при критическом сбое компьютера, сопровождающим ся разрушением операционной системы, ключ потеряется и доступ к данным станет невозможным, Хотя это и не рекомендуется, но, если выполняется с высокими при вилегиями или как SYSTEM, в Windows можно воспользоваться и — API-функциями для хранения секретов дис петчера локальной безопасности LSA. Нежелательность применения этих секре тов обусловлена тем, что LSA способен хранить не более 4096 секретов, причем половина (2048) уже зарезервирована для ОС. Как видите, секреты — очень огра ниченный ресурс. Лучше пользуйтесь DPAPI. Детальнее о секретах я расскажу в разделе "Защита секретов в Windows NT В следующем примере демонстрируется, как сохранять и восстанавливать дан ные с помощью функций DPAPI текст есть в папке данные.

= reinterpret_cast

blobEntropy;

pbData = = lstrlen( // Шифруем данные.

DWORD Secure Code Example", NULL, dwFlags, { успешно else { -> ГЛАВА 9 Защита секретных данных • // Расшифровка данных.

if NULL, 0, данные: blobVerify.pbData);

} else { вызова - > :

Примечание Подробнее о внутренних механизмах работы DPAPI — на Частный случай: реквизиты пользователя в Windows XP В Windows XP есть особый компонент Stored Names and Passwords (Coxp нение имен пользователей и который унифицирует храпение и т работу с паролями и другими реквизитами пользователей (например ключами) проще и безопаснее. Настоятельно рекомендуем при разработке ложения, в котором приходится запрашивать и хранить реквизиты задействовать этот механизм. Его преимущества очевидны:

• поддержка различных типов реквизитов, в том числе паролей и ключей на смарт-картах;

• поддержка безопасного хранения реквизитов средствами DPAPI;

• отсутствие необходимости создавать пользовательский интер фейс — ОС предоставляет свое диалоговое окно, в которое вы вправе ь изображение по своему выбору.

Stored User Names and Passwords поддерживает реквизиты двух типов: доменов Windows и общего типа. Первые нужны различным компонентам ОС и доступны только через компонент например Kerberos. Доступ к доменным реквизитам также возможен через специально созданный пользовательский терфейс (Security Support Provider Interface). Состав реквизитов общего типа определяется приложения, и они применяются я программах, 266 Часть Методы безопасного кодирования собственные механизмы аутентификации и авторизации, например программе бухгалтерского учета с собственной таблицей данных по безопаснос ти в СУБД на базе SQL.

В следующем примере (см. папку показано, как за прашивать реквизиты общего типа.

Л */ ffinclude cui;

cui.cbSize = = NULL;

= введите пароль вашей учетной в Traders = Traders Accounts") ;

= NULL;

= DWORD = 0;

Char DWORD dwName = DWORD = BOOL fSave = FALSE;

DWORD dwFlags = | // Обнуляем параметры имени пользователя и пароля, dwName);

DWORD = pszTargetName, NULL, dwFlags);

if (err) -> ГЛАВА 9 Защита секретных данных else { // доступ к приложению Traders Accounting // с pszName и по защищенному каналу.

Эта программа открывает диалоговое окно, показанное на рис. 9-1.

поля имени пользователя и пароля уже заполнены, если реквизиты для (в нашем случае это уже в кэше DPAPI.

Please enter ] my password Рис. 9-1. Диалоговое окно диспетчера Credential Manager с полями имени и пароля Можно также вызвать функцию командной строки но она не выводит диалоговое окно.

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

Защита секретов в Windows NT В Windows NT 4 нет DPAPI. но она поддерживает и но данные в этой ОС защищают в следующей последовательности.

1. Вызовом создается случайный ключ.

2. Ключ сохраняется в Разделу реестра назначается с полным доступом для Creator/Owner и Admi nistrators.

4. Страдающим паранойей предлагается дополнительно защитить ресурс, разме стив АСЕ-запись для аудита — это позволит отслеживать доступ к нему.

Операции шифрования и расшифровки данных доступны только ключа и локальному администратору. Это не обеспечивает идеальной защиты, ЕЮ по крайней мере позволяет планку безопасности на существенно более 268 Часть II Методы безопасного кодирования высокий уровень. Конечно же, если вы «пригласите» на свой компьютер «троянца», утаить от него ключ и предотвратить расшифровку данных не удастся.

Как я уже говорил, вы также вправе воспользоваться секретами LSA (то есть функциями и Секреты LSA бывают че тырех типов: локальные, глобальные, машинные и частные данные. Первые до ступны только для чтения и только с машины (не через сеть), на которой хранят ся. При попытке удаленного чтения возвращается ошибка доступа. У локальных секретов имена начинаются с префикса LS. Глобальные секреты LSA, создан ные на одном из контроллеров автоматически копируются на все остальные контроллеры данного домена. Имена глобальных секретов LSA начинаются с G$, К машинным секретам LSA доступ предоставляется только операционной систе ме, а их имена начинаются MS. Частные (private) секреты в отличие от всех остальных специализированных типов, не с префикса. Такие данные не копируются на все контроллеры и доступны для чтения как с так и удаленного компьютера. Следует иметь в виду, что пароли учетных записей служб не доступны через сеть и начинаются с префикса SC_. Есть и другие о вы можете узнать, обратившись к описанию функции LsaStorePrivateData в библиотеке MSDN.

Отличия тов LSA и интерфейса DPAPI Вы должны чем отличаются две технологии • секретов ограничено 4096 объектами, а за щищаемых данных ничем не • программа с поддержкой сложна, а приложение на базе создается DPAPI просто;

• DPAPI добавляет к данным информацию целостности, a нет;

• хранит данные «от и по DPAPI же программе большой бинарный объект шифрованной инфор а та уже сама где его хранить;

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

Чтобы сохранить или секретные данные LSA, приложение должно получить дескриптор объекта политики LSA. Вот пример функции C++, которая открывает объект политики:

// : Определяет точку приложения, "ntsecapl.h" const DWORD = 0;

ГЛАВА 9 Защита секретных данных return false;

= > // Если длина меньше 32k, то FALSE;

} pUs->Buffer = = * = + 1) * return true;

I LSA_HANDLE { NULL;

LSA_HANDLE = NULL;

NTSTATUS = DWORD = if (dwStatus != { returned return NULL;

return hLSAPolicy;

} Следующий пример (см. папку как секреты LSA применяются дая шифрования и расшифровки;

DWORD hLSA, *wszSecret, WCHAR if(! InitUnicodeString(&lucName, return lucSecret;

return NTSTATUS = DWORD dwStatus = if (dwStatus != ERROR_SUCCESS) удалось сохранить частный объект return dwStatus;

DWORD WCHAR WCHAR 270 Часть И Методы безопасного кодирования return ERROR_INVALID_PARAMETER;

plucSecret NULL;

NTSTATUS ntsResult = LsaRetrievePrivateData(hLSA, DWORD dwStatus = if (dwStatus He удалось сохранить частный объект else if (plucSecret) return dwStatus;

int main(int argc, char* { hLSA = WCHAR = WCHAR *wszSecret = Secret if wszSecret, wszName) == ERROR_SUCCESS) { WCHAR if wszSecretRead / sizeof WCHAR, LSA это if (hLSA) LsaClose(hLSA);

return 0;

Удаляют секрет LSA, присваивая параметру NULL.

Примечание Секреты, защищенные LSA, доступны локальным администрато рам, для этого достаточно задействовать утилиту создан ную компанией Понятно, что администратор может делать с секретом что угодно!

Защита секретов в Windows 95/98/Ме и Windows СЕ Windows 95/98/Ме и Windows СЕ (последняя устанавливается на карманных ком пьютерах) — но не поддерживают списки Размес тить секретные данные в таком как реестр или файл просто, но где спрятать ключ, которым они зашифрованы? Также в системном реестре? И кто его защи ГЛАВА 9 Защита секретных данных в условиях полного Трудная задачка. Поэтому системы нельзя применять в средах. Можно попытаться скрыть сек реты, но найти их намного проще, чем в Windows NT 4 или Windows Короче конфиденциальные данные (например, в Windows 95/98/Ме или Windows СЕ хранить только при ч го ключ поступает от пользователя или из внешнего источника.

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

В разделе реестра ОС Windows 95/98/Ме масса аппаратно-зависимых которые вполне годятся для генерации ключа, не безукоризненное но все-таки лучше, чем ничего. Итак, познако мимся с некоторыми способами получения информации для генера ции ключа.

Получение информации об устройстве средствами Поддержка механизма Plug and Play в Windows 98, Windows 2000 и ОС этих семейств позволяет разработчику получать информацию о системном оборудовании. Это достаточно замысловатая информация, и она вполне может стать для создания ключа защиты данных, которые не должны поки дать компьютер. В примере как это сделать: программа перечисляет устройства на компьютере, получает описание устройств и на основании этих данных создает хеш SHA-1, который служит материалом ключа для временных данных. Больше о функциях управления устройствами рассказывается на ffinclude ffinclude // Эти константы определены в для разработки драйверов (DDK), // но не у каждого он есть!

\ Ox4d36e965L, Охе325, 0x08, 0x00, Ох2Ь, Охе1, 0x03, 0x18 );

\ Охе325, Ох11се, 272 Часть II Методы безопасного кодирования 0x08, 0x00, 0x03, 0x \ Охе325, Ох11се, Oxbf, 0x00, Ох2Ь, Охе1, 0x18 );

Охе325, Ох11се, Oxbf, Oxc1, 0x08, 0x00, Ох2Ь, Охе1, 0x03, 0x18 );

\ Ox4d36e96fL, Охе325, Ох11се, Oxbf, Oxc1, 0x08, 0x00, Ох2Ь, Охе1, 0x03, 0x18 };

DEFINE_GUID{ \ Ox4d36e97cL, Охе325, Ох11се, Oxbf, Oxc1, 0x08, 0x00, Охе1, 0x03, 0x18 );

\ Ox36fc9e60L, Oxc465, Ox11cf, 0x80, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 );

\ Oxe325, Ox11ce, Oxbf, 0x08, 0x00, Ox2b, Oxe1, 0x18 );

DEFINE_GUID( \ Oxe325, Ox11ce, Oxbf, Oxc1, 0x08, 0x00, Ox2b, Oxe1, 0x03, 0x18 );

\ OxOf36, Ox415e, Охсс, Ox4c, Oxb3, Oxbe, 0x91, 0x65 );

DWORD pGuid, LPTSTR szData, DWORD cData) { HDEVINFO = NULL, NULL, | if == hDevInfo) return // Перечисляем все устройства.

SP_DEVINFO_DATA did;

= i = 0;

// Отбираем нужное устройство.

(*pGuid != continue;

const DWORD cBuff = 256;

= 0, cNeeded = 0;

ГЛАВА 9 Защита секретных данных if cBuff, // Возможна потерн данных, но это не страшно.

if (cData > { return 0;

DWORD hHash) struct { _TCHAR device [] = const cData = 4096;

TCHAR = new if return DWORD = 0;

for (int i < if == 0) { ttifdef if { dwErr = 274 Часть II Методы безопасного кодирования break;

} } delete [] pData;

return dwErr;

int argc, { HCRYPTPROV = NULL;

HCRYPTHASH hHash = NULL;

if { if 0, 0, { if == 0) { // хеш.

BYTE DWORD = 20;

if { (DWORD i < cbHash;

i++) } if (hHash) if (hProv) 0);

if (hHash) if (hProv) 0);

ГЛАВА 9 Защита секретных данных Не следует применять подобный метод для создания долгосрочных шифрования. При смене оборудования поменяется и ключ, поэтому используй ге только те устройства, которые меняться не будут. И не забудьте протестирова в стыковочной станции и вне ее!

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

Слабость единого универсального решения Без сомнения вы в курсе, что различные версии Windows поддерживают ные технологии защиты данных. Последние версии ОС справляются с этой кцией лучше за счет использования списков криптографических служб и возможностей данных. Но что делать, если приложение должно работать под управлением Windows NT 4 и последующих ОС, вая максимально возможную защиту на новых операционных системах? Вы использовать все средства, доступные в Windows NT но в Windows 2000 те же API-функции гарантируют более высокий уровень безопасности. Лучший способ — активно задействовать преимущества новой ОС, вызывая функции косвенно, за счет динамической привязки в период а не на этапе загрузки, а так же инкапсулировать эти вызовы в функции-обертки, чтобы изолировать код от операционной системы. В частности, следующий пример работает как в Win NT, так и в Windows 2000 и последующих ОС, и поэтому логично в Win dows 2000 применить а в NT 4 — секреты // BOOL (WINAPI CALLBACK* CPD) Сигнатура typedef BOOL (WINAPI CALLBACK* CUD) EncryptData(LPCTSTR szPlaintext) { hr = S_OK;

= if return CPD cpd = 276 Часть И Методы безопасного кодирования if // с cpd и аргументов;

// в разделе реестра, // защищенном списком else { // Вызов API-функции для работы с секретами Управление секретами в памяти Последовательность работы с секретными данными в памяти такова:

• получение секретных данных;

• обработка и использование секретных данных;

• отбрасывание секретных данных;

• очистка памяти.

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

Закончив работу с секретами, перезапишите буфер посторонней ей (или просто обнулите его) вызовам или последнее — это простой макрос на основе memset:

ffdefine ZeroMemory Есть трюк, позволяющий очистить динамический буфер, если вы забыли или не сохранили в программе размер буфера. (Многие считают, что отказ от наблю дения за буферным размером — плохой стиль программирования.

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

void *p = size_t cb = _msize{p);

ГЛАВА 9 Защита секретных данных Оптимизирующий компилятор... с подвохом компиляторы С и C++ массу возможностей по опти мизации кода. Они самостоятельно определяют, как лучше использовать машин ные регистры, как вывести код с неизменяемыми данными из циклов и т.п.

из наиболее интересных оптимизации — удаление кода (dead code). Анализируя, нужен ли тот или иной отрезок текста программы, ком пилятор выясняет, вызывается ли код из другого места программы, или использу ются ли данные, с которыми он работает. Попытайтесь найти брешь в таком коде:

char if { // Прекрасно, мы успешно подключились к базе данных.

// Далее следует код работы с базой данных.

} I } Ответ: никаких ошибок здесь нет! А дыра — в компи лятором. В результирующем ассемблерном коде нет вызова Его вы бросил компилятор, обнаружив, что переменная szPwd больше не функцией Зачем тратить драгоценное процессорное время на очистку памяти, которая больше никогда не понадобится? Ниже приводится с небольшими купюрами ассемблерный код, созданный для данного примера сре дой Microsoft Visual C++ Параллельно показаны исходный текст на С и ко манды процессора Intel Строки исходного текста С выделяются точкой с пятой (;

) и номером строки (начиная с 30).

;

30 : void *szDB) { sub esp, 68;

00000044H eax, DWORD PTR eax, PTR ;

31 :

;

32 : if { push 64;

PTR eax lea eax, DWORD PTR push eax add test Je SHORT ;

33 : if { 278 Часть II Методы безопасного PTR lea ecx, DWORD PTR push ecx push edx call add б // Прекрасно, мы подключились к базе данных.

35 // Далее следует код работы с базой данных.

3S mov ecx, DWORD PTR $ArrayPad$[esp+68] xor ecx, DWORD PTR add esp, 68;

Код ассемблера после строки 30 компилятор добавил из-за наличия парамет ра поддерживающего так называемые (stack-based cookie). (Подробнее этот параметр обсуждается в главе 5.) Код в строках проверяет, остается ли действительным после строки 30 ранее созданный cookie файл. Но куда подевался код обнуления буфера? В обычных здесь раз мещается вызов (Как вы помните, — это макрос, вызыва ющий Базовые сведения о оптимизации Оптимизация в компиляторе принимает много форм, но наиболее ная — удаление кода. удаление никогда не исполня емого блока кода в операторе условного перехода из-за того, что логи ческое выражение в операторе ложно. Точно так же оптимизатор удаляет манипулирующий локальными переменными без видимого Например, операцией с локальной пе ременной является запись в нее, то такой оператор бессмысленный (ведь при выходе из блока переменная выходит их области и можно безболезненно удалить. Чтобы убрать подобные компиля тор структуру данных, называемую потоков ния flow graph), которая представляет все пути исполнения в обратном порядке, оптимизатор легко на ходит и удаляет операции записи Это называется удалением тупиковых записей (dead store Оптимизированная ведет себя точно так же, как — это наглядное правила «как бы» («AS IF» во языках.

ГЛАВА Защита секретных данных заметить, что, переменная не локальна, не удается до конца ее «жизнь». потока управле ния не позволяет определить, будет ли позже нелокальная переменная, данных принятия решения не хватает и удаление возмож ной тупиковой записи не выполняется. Подобную информацию сложно поэтому оптимизация возможна лишь в некоторых Сей в подобной ситуации Visual вообще не выполняет оптимизации, но не исключено, что будет делать это будущем.

Проблема заключается в том, что компилятор не должен удалять этот так как всегда требуется очищать память от секретных данных, но, что в функции больше он решил по-другому. Подобным образом ведет себя Microsoft Visual C++ версий б и 7, а также компилятор GNU С (GCC) версии 3.x.

Наверняка это далеко не полный список, и подобным грешат и другие компиля торы. Во время кампании Windows Security Push (см. главу 2) мы создали встраи ваемую (inline) версию ZeroMemory по имени которую лятор не удалял. Вот код этой встраиваемой функции (она доступна я ном файле flifndef >= 1200) FORCEINLINE Seise FORCEINLINE „inline ttendif FORCEINLINE void size_t cnt) { volatile = char while (cnt) { = 0;

} return Смело используйте этот код в своем если у вас нет еще ленных заголовочных файлов Но имейте в виду он выполняется нительно дольше по сравнению с ZeroMemory или memset и годится только для небольших блоков особо секретных данных. Если не хотите вызвать на себя ответственных за повышение производительности приложения, не его как стандартную функцию очистки памяти!

Есть и другие методы предотвращения удаления вызовов memset оптимизато ром. Например, добавить после функции очистки строку чтения важных в память, но снова будьте осторожны с оптимизатором. Обмануть его 280 Часть II Методы безопасного кодирования приводя указатель к типу — такие указатели доступны из вне области ви димости приложения и не оптимизируются компилятором. Чтобы оптимизатор, достаточно после вызова добавить следующую = Недостаток описанных двух методов в том, что в них предполагается отсут ствие оптимизации компиляторами C/C++ отмеченных спецификато ром но подобное предположение верно только сегодня. Разработчики оптимизатора постоянно работают над тем, чтобы удалить из кода лишний и добиться максимальной производительности, и кто знает — может, года через три они научатся безопасно Другой способ решить проблему, прибегая к трюкам с — отключить оптимизацию кода очистки данных. Для этого функцию надо обернуть инструкциями // Здесь располагаются функции очистки памяти.

Это оптимизацию целой функции. Параметры глобальной оптими зации (под которым здесь подразумеваются флаги периода компиляции и -О2) в Visual C++ применяются для удаления тупиковой записи. Но не забы вайте о преимуществах глобальной оптимизации и постарайтесь сократить до минимума неоптимизируемый кода, выделенный инструкциями Шифрование секретных данных в памяти Если программе приходится подолгу работать с долгосрочными секретными дан ными, размещенными в памяти, рекомендуется их шифровать на время, пока они не используются. Так предотвращают просмотр данных из-за их выгрузки в стра ничный файл. Для выполнения этой задачи годится любой из приведенных ра нее примеров применения Одновременно следует грамотно решить вопрос управления ключами.

В Windows Server 2003 появились две новых API-функции, и они выдержаны в идеологии DPAPI, но защища ют данные оперативной памяти. Основной ключ шифрования данных обновля ется при каждой загрузке компьютера, материал для ключей определяется флаж ками, передаваемыми в функции. Когда применяются эти функции, приложению незачем «видеть» ключ шифрования. Вот пример.

ffinclude 15 // содержит null.

HRESULT = S_OK;

pSensitiveText NULL;

DWORD = 0;

DWORD = SECRET_LEN * DWORD = 0;

ГЛАВА 9 Защита секретных данных // шифруемой памяти должен быть // if = cbPlainText X cbSensitiveText = cbPlainText + - dwMod);

else cbSensitiveText = cbPlainText;

pSensitiveText = cbSensitiveText);

if (NULL == pSensitiveText) return // Строка секретной 8 pSensitiveText, // а затем шифруется на месте, if cbSensitiveText, { // При сбое очистить память, pSensitiveText = NULL;

return GetLastError();

// расшифровки и использования памяти.

// Очистка.

pSensitiveText NULL;

return hr;

Намного подробнее об этих новых функциях рассказано в комплекте Platform SDK.

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

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

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

282 Часть II Методы безопасного кодирования Подробнее о VirtualLock в Windows NT 4 и последующих ОС семей ства позволяет блокировать отдельные в своем рабочем По возвращении из функции адреса никогда не выгружаются в страничный файл. Побочный эффект блокирования — пре выгрузки всего процесса того, потоки которого, выпол няясь в пользовательском режиме, находятся в ожидания), по тому что процесс разрешается полностью выгрузить только после освобож дения всех без исключения страниц его рабочего набора, Правда, есть ряд условий.

Запрещение кода в страничный файл программисты обычно применяют для повышения скорости работы программы. Однако следует тщательно оттестировать производительность до и после внесения блоки потому что эта операция влияет на работу всей физической памяти машины. Иногда нельзя выполнить другие нуждающиеся в памяти опера может даже это же приложение! Вообще говоря, страницы, к которым часто обращаются, редко выгружаются, так как ОС в первую оче редь освобождает физическую от неактивных страниц. Активные страницы выгружаются только при явном дефиците ресурсов, когда у ОС нет Другая причина вызова этой функции — предотвращение выгрузки на диск страниц с секретными данными, Процедура отличается от создания резидентной памяти есть страница может выгружаться на диск, остава ясь резидентной). Есть ряд особенностей, усложняющих процесс:

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

• если блокируемые процессом виртуальные адресации входят в совмес тно используемый раздел памяти и второй процесс изменяет страницы, не блокируя их, то они могут выгружаться несмотря то, что первый процесс корректно заблокировал страницы. Суть если вы не хотите, чтобы страницы процесса попадали на все процессы, име ющие доступ к должны их Защита секретных данных в управляемом коде Пока в общеязыковой среде (common runtime, CLR) и каркасе Framework нет специальной службы для безопасного размещения секретной ин формации, а хранение пароля открытым текстом в XML-файле вряд ли назовешь безопасным! Отчасти причина подобной функции — в принятой в идеологии XCOPY, согласно которой любое приложение должно развертываться простым копированием. Никакой регистрации DLL-библиотек и элементов управ ления, никаких конфигурационных параметров в реестре — для нормальной ра боты приложения достаточно скопировать файлы! Ясно, что это несовмести мо с надежным сохранением секретов, когда специальные ГЛАВА 9 Защита секретных данных ментальные средства, поддерживающие шифрование и управление ключами, Од нако ничто не мешает вам прикладную программу после рирования секретных данных специальными инструментами. Или приложение может работать с но не хранить их. Другими словами, по методу XCOPY — вполне жизнеспособный но позаботьтесь, чтобы приложение не хранило секреты, а только использовало их.

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

public static char[] data) { // Только никому не говорите, string key = char[] text = for (int i = 0;

i < i++) text[i] "= key[i return text;

Пока единственный способ защитить секретные данные из управляемого кода вызвать неуправляемый то есть функции или DPAPI.

В следующем примере демонстрируется, как на С# создать класс а с DPAPI. Обратите внимание на дополнительный файл NativeMethods.cs, прилагае мый к данному и содержащий определения, структуры данных и константы вы зова платформы необходимые для обращения к DPAPI. Текст файла вы найдете в папке Пространство имен предоставляет набор классов для доступа к и [ функциям из программ // namespace { using System;

using using public class { строку и // закодированные по методу public static string string name, int flags) { byte[] = byte[] = name, flags);

return (null != dataOut) ?

: null;

// Снимаем защиту с закодированных по методу Base-64 данных 284 Часть It Методы безопасного кодирования // и строку, public static string data) byte[] = = | return (null != dataOut) ?

: null;

// Внутренние функции // internal static byte[] data, string name, int { byte[] = null;

// данные в неуправляемую память.

din = new = = 0, din.pbData, din.cbData);

dout = new ps = new // структуру ps);

{ = din, name, dwFlags, dout);

if { cipherText = new cipherText, 0, ГЛАВА 9 Защита секретных данных } else { + Marshal.

} finally { if { != ) !

return cipherText;

internal static byte[] data, int { clearText = null;

// Копируем данные в неуправляемую din = new = din.pbData = 0, din.pbData, din.cbData);

ps = new ps);

= new try { ret = ref din, null, ps, dwFlags, ref dout);

if (ret) { clearText = new byte[ ] ;

clearText, 0, dout.cbData);

286 Часть II Методы безопасного кодирования else { tif (DEBUG) + ffendif finally if ( != ) i return clearText;

static internal void ps) ps.cbSize = = 0;

= = null;

Следующий код драйвера на С# показывает, как использовать класс using using System;

using class TestStub { public static void { string data = берегись Барлога в string строка: + data);

string s = if (null == s) { не return;

данные: + s);

s = текст: + s);

ГЛАВА Защита секретных данных Бы можете также использовать строки конструирования позволяют определить строки сохраняемые в метаданных Таким образом устраняется потребность жестко прошивать в коде класса конфи гурационную информацию. Для работы со строками конструирования начено пространство имен Этот вариант стоит только для защиты данных в серверных приложениях. Следующий пример иллю стрирует создание компонента на для управления строками конструк тора. Задача у него одна — он служит средством передачи строки Имейте в виду: вам придется создать собственную пару «закрытый + открытый клю утилитой SN.exe, а также заменить на файл с ключами указав свои данные. За подробным описанием сборок со строгими именами (strong named assemblies) отсылаю вас к главе 18.

using System;

using using using [assembly:

[assembly:

[assembly:

[assembly:

namespace { = // Включаем поддержку строк конструирования new public class : { private string override protected void s) { = s;

public string { return > А в этом примере кода Microsoft ASP.NET показано, как обращаться к данным строке конструктора:

Function As String ' Создаем новый класса ServicedComponent ' и вызываем наш метод, строку конструктора.

As DemoComp = New DemoComp = End Sub 288 Часть II Методы безопасного кодирования Администрирование строки конструктора выполняется в оснастке Component Services (Службы компонентов) (рис. 9-2). Подробнее о пространстве имен Sys рассказывается на Web-странице http://msdn.microsoft.com/ Рис. 9-2. Настройка новой строки для компонента Управление секретами в памяти в управляемом коде Управление секретными данными в управляемом коде ничем не отличается от этой же процедуры в обычной неуправляемой программе: получить, использовать и уничтожить конфиденциальную информацию. Однако одна оговорка все же есть:

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

class ErasableData ;

IDisposable { private byte[] private GCHandle public ErasableData(int size) { = new byte public void { 0, Free{);

// public byte[] Data ГЛАВА 9 Защита секретных данных set { // Выделяем для данных фиксированный двоичный объект, = GCHandle.Alloc(_rbSecret, // Копируем данные в массив.

Data = value;

(Data, _rbSecret, I { return _rbSecret;

class DriverClass { static void Main(string[] args) if == 0) { // ошибка!

return;

Получаем байты аргумента.

[] plaintext = new // Шифруем данные в using (ErasableData key = new { = Rijndael aes = = cipherTextStream = new Cryptostream = new CryptoStream( CipherTextStream, С 0, с ryptoSt // Получаем зашифрованный текст и вектор инициализации (IV), byte [] ciphertext = byte [] IV = // Уничтожаем данные, поддерживаемые классом шифрования.

с 290 Часть II Методы безопасного кодирования Заметьте: для автоматического удаления ставшего ненужным приме няется интерфейс Оператор using в получает один или несколько ресурсов, операторы, а затем ресурсы методом Dispose.

Обратите также внимание на явный вызов и Clear очищает все секретные данные классов потоков и шифрования, В папке с примерами вы найдете более полный класс написанный планку безопасности В этом разделе я расскажу о нескольких способах секретных данных и усилиях, которые придется затратить взломщику для просмотра (опасность раскрытия информации) или изменения (опасность подмены информации) дан ных. Во всех примерах секретные данные хранятся в файле Secret.txt. Я покажу, как в каждом случае укрепить защиту и усложнить задачу хакеру.

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

Применение встроенного ключа и операции XOR Ситуация почти идентична предыдущей за тем исключением, что встроенный в приложение ключ налагается на данные по методу XOR. Прочитав файл, атакую щий в считанные минуты взломает подобный шифр, особенно если известно, что файл содержит текст. Часто дело осложняется тем, что взломщику известна часть файла, например заголовок файла Microsoft Word или GIF-файла. Чтобы ключ или достаточный объем для вычисления ключа, достаточно применить XOR к известному и закодированному тексту.

Применение встроенного ключа и алгоритма 3DES То же, что и в предыдущем случае, но шифрование встроенным ключом выпол няется по алгоритму 3DES И здесь взлом не представляет особой сложности: хакеру достаточно понаблюдать за приложением и определить момент, когда оно обратится за ключом, Использование 3DES для шифрования данных и хранение пароля в реестре Напоминает предыдущий сценарий, но ключ шифрования размещается в реест ре, а не «зашивается» в код программы. Если атакующий в состоянии читать ре естр, то получить ключ и расшифровать данные ему будет несложно. Следует за метить, что, если файл зашифрован слабым паролем, велика вероятность, что.

прочитав взломщик без труда угадает пароль.

ГЛАВА 9 Защита секретных данных Использование 3DES для шифрования данных и хранение пароля в защищенном разделе реестра Ситуация идентична предыдущей с той разницей, что атакующему придется при ложить дополнительные чтобы считать ключ из реестра. Потребуется на что обычно требуется много времени. взломав реестр, хакер без проблем расшифрует файл.

Использование 3DES для шифрования данных, хранение пароля в надежном разделе реестра, а также защита самого файла и раздела реестра списками ACL Если списки ACL надежны разрешающие чтение и запись только ад министраторам), без административных полномочий взломщику не удастся про читать ни ни файл. Однако при наличии позволяющей получить администраторские привилегии, ничто не помешает ему прочитать ные. Некоторые из вас скажут, что в такой ситуации только и остается, что опус тить руки, ведь администратор (читай — взломщик) — хозяин системы. Это правда, но побороться еще можно! Как же защититься от лотного Читайте дальше.

Шифрование данных по алгоритму 3DES, хранение пароля в надежном разделе реестра, требование ввести пароль, а также защита списками ACL файла и раздела реестра Это напоминает предыдущий пример. Однако здесь даже администратор не по лучит доступ к данным, потому что ключ генерируется на ключа реестра и известного только владельцу данных. Вы можете заметить, ч го необходимость ключа в реестре сомнительна. Однако он полезен, когда одни и те же данные шифруют два используя общий ключ из реестра. До бавление пароля пользователя в процедуру генерации ключа шифрования созда ет неудобства, но зато у каждого пользователя свой шифр, По-хорошему следует применять другие методы хранения ключей, предпоч тительно не на компьютере. Существует масса методов этой задачи, из них — использование специальных аппаратных средств, создавае мых компанией nCipher (http://www.ncipfoer.corn).

Компромиссы при защите секретных данных Как и все остальное в мире разработки ПО, обеспечение безопасности системы один огромный клубок компромиссов. Самые значительные из них:

• относительная защита;

• усилия, необходимые для разработки надежной прикладной программы;

• простота развертывания.

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

292 Часть II Методы безопасного кодирования Самый большой компромисс — между защитой и простотой раз вертывания приложения. Ситуация очевидна: особо надежно защищенные данные не очень-то удобны для развертывания! В табл. 9-1 показаны относительные за траты на различные методы защиты данных;

используйте ее как руководство.

Таблица 9-1. Компромиссы, которые следует учитывать при реализации защиты секретных данных Относительная Сложность Простота Вариант защита реализации развертывания файлы Отсутствует Низкая Высокая (шифрование отсутствует, только для сравнения) Секреты, в код: Отсутствует Низкая Средняя никогда не этого!

Строки конструктора Средняя Средняя Средняя LSA Высокая Высокая Низкая DPAPI (локальный компьютер) Высокая Средняя Низкая DPAPI (пользовательские данные) Высокая Средняя Средняя Резюме Падежное хранение секретной информации в ПО — трудная задача. В сущности, в современных компьютерах в принципе невозможно обеспечить полностью безопасное хранение секретной информации. Чтобы сократить риск компроме тации секретных данных, следует максимально задействовать имеющиеся в ОС возможности защиты операционной системы, а также не хранить ин формацию на компьютере, если этого можно избежать. Если в системе не будет то и скомпрометировать хакеру их не удастся. «Достаточность» уровня защиты определяется исключительно важностью данных и серьезностью опасности, Все входные данные от лукавого!

постучит в дверь вашего дома и предложит вам пищу, станете ли вы ее есть? Уверен, что нет. Так почему же так много приложений принимает дан ные от незнакомцев без какой бы то ни было предварительной проверки? Могу с уверенностью утверждать, что большинство защиты происходят из-за тог >, что приложение неправильно проверяет или вообще не проверяет вводимые дан ные. Сразу сформулирую свою позицию: ни в коем случае нельзя доверять дан ным, пока вы их не проверили. В противном случае приложение становится очень уязвимым. Можно этот принцип сформулировать и по-другому;

все входные дан ные зловредны, пока не доказано Это первейшее и важнейшее прави ло. Забудете о нем — и ваше приложение падет жертвой взломщика.

Правило номер два: проверку корректности данных следует при каждом пересечении ими границы между ненадежной и доверенной средами. По определению, доверенные данные — это информация, которую вы или ваши до веренные субъекты активно используют, все остальные данные считаются дежными. Короче говоря, это любые предоставляемые пользователем сведения. К чему я затеял этот разговор? Да просто слишком многие разработчики проверкой входных данных, надеясь на функцию, вызывающую создаваемое приложение, и не желая вводить дополнительные проверки, которые отрицательно влияют на производительность программы. Но что, если данные поступают из ненадежного источника или функция, предоставляющая данные, сама их не веряет, а полагается на другую программу проверки корректности? Или еще прос: что случится, если добросовестный пользователь просто ошибется при e и нечаянно приложение? Помните об этом, читая о брешах и возможностях эксплуатации приложений.

294 Часть Методы безопасного кодирования Как-то мне пришлось анализировать программу защиты с небольшим недостат ком;

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

Внимание! Нет системы более недоступной, чем взломанная!

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

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

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

и многие разработчики полагаются на прогнозируемое и вполне определенное поведение клиентской части. Однако после развертывания клиентское ПО выхо дит из-под контроля разработчика и администраторов сервера, поэтому нельзя гарантировать, что клиентские запросы всегда будут поступать от «легального» пользователя. Очень часто их фальсифицируют, поэтому сервер не должен ни в коем случае доверять пользовательским запросам. Главная проблема — доверие, точнее, излишнее доверие данным, поступающим из ненадежного источника. То же применимо и к клиенту. Должен ли он доверять серверу? Что если сервер — подложный? Хороший пример атаки на клиента — сценарии (cross site scripting) — об этом подробно рассказано в главе 13.

ГЛАВА Все входные данные — от лукавого! Излишнее доверие Зачастую при анализе проекта и кода места обнаружить очень для этого достаточно задаться двумя простыми вопросами: ли я данным в этой точке и «Что мне известно о корректности данных?» хотя бы переполнение буфера. Оно происходит по нескольким причинам:

• данные поступили из ненадежного источника (от • слишком большая надежда на корректность формата данных — в данном слу чае на правильную длину буфера;

• аварийное происшествие — в данном случае сбой программы и запись буфе ра в память.

Посмотрите на этот код. Что здесь не так?

void { char // I Удивительно, но, в общем-то, ничего здесь нет! Все зависит от того, как вызывается Data и откуда поступило значение szData: из доверен источника или нет. такой код безопасен:

= потому что имена жестко «прописаны» и поэтому длина каждой из строк не превышает 32 символа. Следовательно, вызов — всегда безопасный. Одна ко, если значение единственного параметра, поступает из ненадежного источника, например или файла со «слабым» списком тогда strcpv продолжать копировать данные, пока не встретит null. И если их объем 32 символа, буфер cDest переполнится, затирая всю информацию, расположенную в памяти буфера (рис. 10-1).

Данные из ненадежного I szData);

strcpy копирует Ошибочное предположение, данные что длина не превышает Рис. 10-1. Условия, при которых вызов strcpy становится опасным Тщательно исследовав этот пример, вы заметите, что, если удалить любое и t условий, шанс переполнения буфера снизится до нуля. Удалите способность strcpy копировать в память, и переполнение буфера станет невозможным, но это ально, потому что не копирующая версия бесполезна! Если данные всегда пают из надежного источника, например от проверенного пользователя или ! 1- 296 Часть II Методы безопасного кодирования щищенного строгим списком ACL файла, вы доверять данным. Наконец, если программа с самого начала не полагается на корректность данных и прове ряет их до копирования, то переполнение буфера попросту невозможно. Если контролировать правомочность данных до копирования, совершенно неважно, из какого источника они поступили — доверенного или нет. Таким образом, прихо дим к единственно возможному решению: прежде чем делать с инфор мацией, следует проверять ее корректность, и до этого ни в коем случае не дове рять данным.

Следующий код менее «доверчив» и поэтому более безопасен:

void *szData, DWORD cbData) { const DWORD cbDest = 32;

if (szData != NULL cbDest > CbData) // Используем \ Код все так же копирует данные но параметры szData и cbData с са мого начала считаются ненадежными, и программа ограничивает объем данных, копируемых в cDest. Возможно, вы что здесь слишком много кода для проверки корректности данных, но это не так — это дополнение в тексте программы защищает приложение от серьезных атак. Кроме того, после первой же успешной атаки небезопасной версии программы вам придется в спешном поряд ке выпускать «заплаты». Так почему же не сделать все «по с самого начала, Я уже говорил, что слабые списки ACL — это ненадежные данные. Представьте себе, что у раздела реестра, где указывается файл журнала, список ACL разрешает полный доступ группе Everyone (Все). Стоит ли в полной мере доверять инфор мации в этом разделе? Ни в коем случае! Изменить имя файла может кто угодно на что угодно, да хоть на c:\boot.ini. Доверия станет больше, если ACL будет содер жать разрешение на полный доступ для администраторов и только на чтение для Everyone;

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

Методы защиты от атак, основанных на изменении входных данных Самый простой и наиболее эффективный способ защитить прикладную программу от подобных атак — проверить правильность данных до передачи их в дальней шую обработку. Для этого разработан ряд методов:

• создание границы вокруг приложения и преобразование его в доверенную зону:

• создание пунктов (chokepoint) для входных данных.

ГЛАВА 10 Все входные данные — от лукавого! е проектах всех приложений есть точки, где данные считаются корректными и потому что прошли После того как дан ные попали внутрь границы доверенной зоны, нет причин повторно так как предполагается, что код справился со своей задачей. С другой сторо ны, принцип защиты на всех уровнях диктует проверку на всех этапах, и это тоже не лишено смысла. Определять тонкий баланс между защитой и производитель ностью вашего приложения придется вам самому на основании важности и среды, в которой работает приложение.

Во-вторых, требуется проверка на доверенного кода. Необходимо определить эту точку в проекте;

она должна быть единственной, и никаким вхо дящим не следует открывать путь в доверенный код проверки на этом пункте (КПП). В общем, можно пре дусмотреть несколько таких КПП, по одному для каждого источника данных реестр, файловая файлы конфигурации и т.п.), причем данные должны попадать в доверенную зону не через любой КПП, а только через что опреде лен для конкретного источника, Внимание! Повторно используемые компоненты, такие как DLL, элементы управления ActiveX и библиотеки классов, следует разрабатывать и пи сать очень аккуратно. Эти компоненты не должны доверять вызываю щей так как она вполне может оказаться злонамеренной. Для всех доступных методов или свойств сле дует предусмотреть проверку корректности входных данных, Как видите, понятие доверенной границы и КПП тесно связаны (рис. 10-2).

Переменная окружения пункт Контрольно Граница доверенной зоны пропускной ! НИ Данные т ta-lf Сервис доверенной зоны Контрольно-пропускной пункт Конфигурационные данные Рис. 10-2. Граница и пункты 298 Часть II Методы безопасного кодирования Между сервисом и его хранилищем данных нет так как они находятся внутри доверенной зоны, куда данные попадают только после тщательной про верки на одном из КПП. Поэтому сервис и хранилище обмениваются гарантиро ванно доверенными и корректными данными.

Сегодня очень часто подвергаются атакам, основанным на сценариях (cross-site scripting): вредные данные (код HTML и сце нарии) загружаются в пользовательский браузер с хакерского Web-сайта. Я не буду здесь приводить детали — все подробности вы найдете в главе Многие Web сайты уязвимы для подобных атак, и их администраторы об этом ничего не зна ют. В начале 2001 г. я проводил занятия по безопасности с разработчиками очень большого Web-сайта, где описанная проблема отсутствовала. А все благодаря на личию в приложении Web-сервера двух КПП: одного — для данных, поступающих от пользователя (или хакера), и второго — для данных, возвращаемых пользова телю. Все входящие и исходящие данные проходили только через эти два КПП.

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

Как проверять корректность данных Организуя проверку входных данных, следует руководствоваться простым прави лом: отбирать только корректные данные, а все остальное отбрасывать. Описан ный в главе 3 принцип сохранения конфиденциальности при любых сбоях пред писывает отказывать всем запросам, если они не проходят строгую проверку на корректность. Выбирать из общего потока следует только правомочные а не соответствующие стандартам — отбрасывать. На то есть две причины;

• существует несколько корректных способов представления информации:

• всегда существует риск пропустить данные в некорректном формате.

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

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

{ = false;

if { size_t = if (cFilename >= 3) { *szBadExt[] = ГЛАВА 10 Все входные данные — от лукавого! char = i < / if == == szBadExt[i][2] == szLCase[cFilename-4] == } return { if Что «не так» с этим кодом? Функция выполняет массу проверок и достаточно эффективна. Проблема — в списке недействительных» расширений файлов: он далеко не полный, точнее, ему недостает очень и очень многого. > грамма разрешает загружать такие исполняемые файлы, как Perl-сценарии или файлы, обрабатываемые сервером сценариев Windows Scripting Host и поэтому автор приложения решил обновить код и занести в список и эти типы.

Однако неделей позже он вспомнил, что документы Microsoft Office также содержат исполняемые макросы и т.д.), и ему снова пришлось обновлять программу. И этот может продолжаться бесконечно. Единственный вильный способ решения задачи — отбирать только файлы с корректными и безопасными расширениями и отклонять все остальные. Если программа назначается для загрузки на сервер информационного наполнения пользователям следует разрешить загрузку текстовых и графических файлов вполне определенных типов. Безопасная версия программы выглядит так:

IsOKExtension(char { bool = false;

if size_t = (cFilename >= 3) { *szOKExt[] = char *szLCase = szFilename»;

for (int 300 Часть II Методы безопасного кодирования / if szOKExt[i][3] && szLCase[cFilename-2] == szOKExt[i][2] == == = return flsOK;

Как видите, программа разрешает загрузку только безопасных данных: текстовых (.cxt), некоторых графических файлов и файлов в формате Rich Text Format Так-то лучше! Худшее, что может случиться, — недовольство пары-тройки пользо вателей, считающих, что нужна поддержка других файлов, но это в любом случае лучше, чем компрометация сервера.

«Осторожные» переменные в Perl В Perl есть полезная возможность: трактовка всех входных данных как (tainted), или ненадежных, пока они не прошли об работку. При попытке выполнить потенциально опасную задачу с такими данны ми (например, запрос операционной системы) ядро инициирует исключение.

Вот пример:

use strict;

my = ;

. or die close FILENAME;

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

Insecure dependency open while running with -T switch at testtaint.pl line 3, line Вызов open с ненадежным именем опасен. Есть простой способ устранить не достаток: проверить корректность данных с помощью регулярного выражения, use strict;

my $filename = ;

open. $1) or die print FILENAME Close FILENAME;

ГЛАВА 10 Все входные данные лукавого! Перед открытием имя файла проверяется. Регулярное выражение отбирает только файлы с именами длиной 8 символов и расширением ние «обернуто» в операции перехвата данных (открывающая и закрывающая скоб ки), поэтому имя файла в переменной $1, а используется в операторе open. Обработчик не насколько регулярное вы ражение (например, любые входные данные), поэто му это не панацея. Но даже в таких проверка позволяет избавиться от множества ошибок, связанных с излишним доверием входным данным.

Регулярные выражения как средство проверки входящих данных Для простой проверки данных вполне годится показанный ранее код простого сравнения строк. Однако для обработки сложных данных служат конструкции более высокого уровня, такие как регулярные выражения. В примере на показано, как регулярные выражения заменяют проверку расширений в C++. Мы задействуем пространство имен RegularExpressions каркаса Framework.

using static { г = new return В этот же код выглядит так;

sub { $_ = shift;

return ? -1 : 0;

} Об особенностях языка я расскажу чуть позже, а пока объясню, как все это работает. Основа выражения — строка Ее компоненты описаны в табл. 10-1.

Таблица 10-1. Некоторые элементы простых регулярных выражений Элемент Примечание Разрешается только строка ххх или Конец строки Если строка поиска одному из расширений файла, после кото рого конец выражение возвращает true. Заметьте также, что в мере па устанавливается флаг так как Microsoft нечувствительна к регистру в именах файлов.

В табл. 10-2 приводится более подробный элементов регулярных вы Обратите внимание, что часть из этих элементов реализована в неко торых языках программирования, 302 Часть II Методы безопасного кодирования Таблица Стандартные элементы регулярных выражений Начало строки Конец строки Повторение предшествующего шаблона нуль или более раз.

То же, что и Повторение предшествующего шаблона один или более раз, То же, что и Повторение предшествующего шаблона нуль или один раз.

То же, что и Повторение предшествующего шаблона точно п раз Повторение предшествующего шаблона точно п или более раз Повторение предшествующего шаблона не более m раз Повторение предшествующего шаблона более п, но менее m Соответствует любому одиночному символу кроме \л Служит для сравнения и сохранения (захвата) данных в переменной.

Вид переменной для хранения данных отличается в разных языках программирования. Может применяться для объединения символов в например означает повторение шаблона, заданного в скобках, один или более раз. Если требуется создавать группы, мож но применять синтаксис без перехвата данных чтобы указать обработчику регулярных выражений не перехватывать данные или Один из перечисленных символов: а. Ь или с Любой символ кроме перечисленных в списке Диапазон символов или значений. Соответствует любой от а до z Управляющий символ. Одни управляющие символы обозначают спе циальные символы и другие — предопределенные последова тельности символов Также применяется как ссылка ранее при нятые (перехваченные) данные Обозначает позицию между словом и пробелом Обозначает границу отличной от слова подстроки Любая То что и [0-9] \D Любой отличный от цифры символ. То же, что и Специальные символы форматирования: новая строки.

перевод табуляция и табуляция по вертикали категория категорию подробнее об этом выражении расска зывается далее в этой главе то же, что и Символ, отличный от разделителя;

то же, что и Текстовый символ;

то что и [a-zA-ZO-9_] He текстовый (не буквенно-цифровой) символ;

то же, что и или Символ, двухразрядным числом, кода Unicode, четырьмя или ми цифрами, Я говорю «единица кода», а не «символ» из-за нали чия суррогатных — в них для представления символа нужны две единицы кода (подробнее о суррогатах — в главе 14) ГЛАВА 10 Все входные данные — от лукавого! А теперь — некоторые примеры регулярных выражений (табл. 10-3).

Таблица 10-3. Примеры регулярных выражений Одно или более чисел HTML-тэг. Заметьте: первый тэг перехватывается (.*) и ется для проверки закрывающего тэга Так, если (.') — это FORM, тогда — также FORM Почтовый индекс в США Действительное, но ограниченное имя файла. 1 символа •имени файла, за которыми следуют необязательные точка и 0 символа расширения. Открывающие и закрывающие круглые скобки группируют точку и но расшире ние не фиксируется, так как указана последовательность Заметьте: символы и $ начало и конец входных данных. Почему — объясняется далее Будьте внимательны с поиском (или проверкой) данных Применяют регулярные выражения в двух случаях. Первый — нахождение а второй (именно он нас интересует) — проверка корректности данных. Когда кто то вводит имя задача заключается не в том, чтобы найти в запросе имя а.

а в том, чтобы проверить, правильный ли файл запрашивается. Сейчас объясню поподробнее. Вот псевдокод, который определяет действительность имени RegExp г = if { // Предоставляем доступ к файлу // это корректное имя, else { Ай-я-яй! Такое имя не :• Этот код пропускает только запросы файлов с именами, состоящими из 1- символов в нижнем за которым следует точка и символа нижнего регистра (расширение файла). Так или нет? Вы заметили в регулярном вы ражении? Что, если пользователь запросит Проверка пройдет без суч ка и так как обработчик найдет в строке которая соответствует заданному формату. Однако запрос явно некорректен.

Решение в том, чтобы выражение анализировало полное имя файла:

Символ начало, а — конец входной строки. То есть на это звучит так: «весь запрос (с начала и до конца) должен состоять только из 1 — 8 символов нижнего регистра, за которым следуют точка и 1 — 3 символа го регистра, не больше и не меньше*. Явно, что строка будет отброше так как символы и запрещены и не соответствуют требованиям > ного выражения.

304 Часть II Методы безопасного кодирования Регулярные выражения и Unicode Исторически сложилось так, что выражения работали только с 8-бит ными символами, которые хорошо подходят только для однобайтных алфавитов, и ни для каких других! А как же тогда обрабатывать символы Unicode? Как конт ролировать входные данные, предоставляемые, скажем, японскими или немецки ми пользователями? Универсального метода нет, а решение зависит от выбран ных обработки строк.

Примечание Превосходно применение регулярных выражений в Unicode описа но в документе «Unicode Regular Expression странице Начните знакомство с особенностями ре гулярных выражений в Unicode именно с этой статьи.

Три особенности Unicode создание качественных регулярных вы ражений:

• немногие строк поддерживают Unicode (я уже говорил об этом);

• Unicode — очень большой набор символов. В Windows применяется представ UTF-16 с прямым порядком байт (little endian). По сути, вместе с сурро гатами Windows поддерживает более миллиона символов;

проверка такого объема — задача не из простых;

• Unicode поддерживает массу систем письма помимо англоязычной.

Есть определенные подвижки: растет число поддерживающих Unicode-выражения, так как их создатели что без этого не обойтись, Например, выпущена версия Perl 5.8.0 с поддержкой Unicode. Еще один пример — каркас Framework Microsoft, где предусмотрена превосходная поддержка регулярных выражений и локализации. Кроме того, все строки в управляемом коде существуют только в формате Unicode.

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

• очень трудно (если не невозможно) определить действительные диапазоны для конкретного языка, даже английского. Вы что в английском нет диа критических знаков? А как насчет слова cafe?

Следующее регулярное выражение выбирает все символы японской слоговой азбуки — от «а» до но за исключением значков и других специальных меток с кодами больше Regex r = new Секрет создания регулярных выражений в Unicode — конструкция которая позволяет найти любой символ в категории поименованных символов Unicode. Каркас Framework и Perl 5.8.0 поддерживают категории и это значительно упрощает работу с интернациональными символами.

ГЛАВА 10 Все входные данные — от лукавого! К категориям Unicode относятся буквы метки (М). числа знаки пунктуации (Р), символы (S), разделители (Z) и другие (О и С). Вот как они классифицируются:

• (все буквы):

D (заглавные буквы);

L1 (строчные буквы);

Lt буквы с первой заглавной). Некоторые символы, они ются состоят из двух букв. Например, некоторые хорватские которые соответствуют кириллическим символам из набора Latin — это версия «с первой заглавной*. Другие версии выглядят так: заглавная — LJ (U+01C7), строчная — lj П (модификаторы, символы);

П (другие не имеющие в иврите, арабском и тибетском);

• М (все знаки):

(надстрочные, несамостоятельные знаки, в том числе ударения и П Мс (самостоятельные в тамильском языке это обычные гласные):

П Me включающие символы, например круги вокруг символа);

• N (все цифры):

D Nd (десятичные цифры от 0 до 9. Категория не некоторые ази атские языки, в том числе китайский, японский и корейский. Например, числительные в стиле обрабатываются по аналогии с цифрами и классифицируются как N1 (номер, символ), а не как Nd);

D (числовой символ, римские цифры от U+2160 до U+2182);

D No (другие числа, представленные как дроби, а также верхние и нижние индексы);

• Р (все знаки пунктуации):

П Рс (соединители, символы, такие как подчеркивание, которые соединяют другие буквы);

П Pd (все тире и дефисы);

D Ps (открывающие такие как ( и П Ре (закрывающие символы, такие как },) и D Pi (открывающие кавычки, такие как и П Pf кавычки, такие как кавычки, ', и П Ро (другие символы, в том числе и т.п.);

• S (все символы):

П Sm (математические);

П Sc (денежные знаки);

П Sk (модификаторы, такие как циркумфлекс и гравис);

П So (другие символы, в том числе символ градуса Цельсия и значок кого права);

306 Часть II Методы безопасного кодирования Z (все разделители):

D 2s (пробелы, в том числе обычный пробел);

D Z1 (строка — вертикальная линия с разрывом (U+OOA6) также считается символом);

D Zp (абзац О (другие):

Сс (управление, в том числе все управляющие такие как перевод ка ретки, перевод строки и звонок);

D Cf (символы форматирования, невидимые символы, например в арабском языке);

Со (частные в том числе логотипы и символы компаний);

Сп определено);

П Cs (суррогатные символы высокого и низкого порядка), Примечание Замечательный справочник по символам просмотра Unicode опу бликован на странице А теперь поэкспериментируем с этими категориями. Пусть -приложение должно принимать только обозначение денежной единицы, например доллара или евро. Для этого применим такой код:

Regex г = new if { // } else { // Попытайтесь еще } Замечательно то, что поддерживаются всех денежных определенных в Unicode, в том числе доллара ($), фунта стерлингов иены (Г), франка евро нового шекеля (и) и других.

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

Regex г = Причина наличия строки в том, что во многих языках используются диакритические знаки.

Каркас Framework также поддерживает различных языков, на (иврит). (арабский) и (японская слоговая азбука катакана).

Эксперименты с другими языками я рекомендую выполнять в Windows 2000, XP или Microsoft Windows Server 2003 с Unicode-шрифтом (напри мер Unicode и использовать утилиту Character Map (рис. 10-3). Однако Этот шрифт (файл есть в установочном пакете Word При необходимости можно установить вручную, просто скопировав этот файл в системную папку со шрифтами. — Прим.

ГЛАВА Все входные данные — от лукавого! имейте в виду, что в Unicode-шрифте не обязательно есть глифы для всех кодов Unicode. Таблицы кодов Unicode опубликованы на сайте charts.

• Рис. утилиты Character Map для просмотра шрифтов, отличных от набора Примечание Я уже что в Perl 5.8.0 добавлена расширенная поддерж ка Unicode и синтаксиса Подробнее об этом читайте на сайте Внимание! Соблюдайте особую осторожность при преобразовании: программа должна сначала исполнять декодирования и лишь затем об работку на основе регулярного выражения. В противном случае может оказаться, что данные прошли проверку регулярными выражениями, но до декодирования!

Розеттский* камень регулярных выражений Регулярные выражения — невероятно мощный и универсальный инструмент, об ласть применения которого выходит далеко за рамки контроля входных данных.

освоить эту позволяющую решать массу самых сложных и задач обработки данных. Я часто создаю камень содержит благодарственную запись египетских жрецов царю Птолемею V сделанную на нескольких языках: древнеегипетском, иерогли фами и демотическим письмом и древнегреческом языке. Это позволило Франсуа расшифровать древнеегипетскую письменность. Ка мень был найден предместье города, расположенного недалеко от Алек сандрии, в 1799 году французскими солдатами, — 308 Часть N Методы безопасного кодирования — главным образом на Per] и — где применяются регулярные выра жения для анализа журналов на обнаружения атак и исходных текстов — на предмет брешей в системе безопасности. Между разными языками программирования и средами исполнения существуют тонкие различия в синтак сисе регулярных выражений, о которых сейчас и пойдет речь. (Заметьте: речь пойдет далеко не обо всех особенностях регулярных выражений, а лишь о неко торых из них.) Регулярные выражения в Perl Perl — признанный лидер в поддержке регулярных выражений, отчасти это так из-за превосходной поддержки обработки строк и Вот регулярное выра жение на Perl, извлекающее время из строки:

$_ = отправляемся на гору в 12: if { print $1;

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

Регулярные выражения в управляемом коде В большинстве, если не всех приложениях на С#, управляемом C++, Microsoft Visual Basic ASP.NET и других, имеется доступ к каркасу Framework и простран ству имен Я уже говорил о синтаксисе в этом про странстве. Однако для полноты привожу аналоги приведенной выше программы извлечения даты из строки на С#, Visual Basic и управляемом C++.

Пример на С# // Пример на String s = на Роковую гору в 12: Regex г = new if Пример на Visual Basic ' Пример на Visual Basic Imports Dim s As String Dim г As Regex s = "Мы отправляемся на гору в 12:15 пополудни."

г = New ГЛАВА 10 Все входные данные — от лукавого! If Then End If Пример на управляемом C++ // Пример на управляемом C++.

ffusing using namespace using namespace using namespace String *s = на Роковую гору в 12: = new if В код выглядит точно также, так как эта технология нейтральна по к языку.

Регулярные выражения в сценариях В базовой версии языка JavaScript 1.2 синтаксис регулярных выражений чески такой, как и в РегЗ. Начиная с версии 4, браузеры Netscape Navigator и Microsoft Internet Explorer поддерживают регулярные выражения.

var г = s = "Мы на Роковую гору в 12: if alert(RegExp.$1);

Регулярные выражения также доступны программирующим на VBScript версии через объект RegExp:

Set г = new RegExp = = Set m = отправляемся на Роковую гору в 12: Использовать регулярные выражения в клиентском коде следует только для экономии и предотвращения лишних обращений к серверу, но ни в коем не как метод защиты, Примечание Поскольку ASP поддерживает JScript и VBScript, к регулярным выражениям на этих языках можно обращаться с Web-страниц.

31 0 Часть II Методы безопасного кодирования Регулярные выражения в C++ А теперь пора поговорить о трудном языке! Дело не в том, что на C++ сложно писать код. — просто этом языке весьма ограничен набор классов, поддерживающих регулярные выражения. Если вы пользуетесь библиотекой шаблонов STL (Standard Template советую взять поддерживающий класс на сайте (На странице вы найдете хорошее описание этого класса — его Microsoft Visual C++ с составе Microsoft Visual Studio содержит облегчен ный шаблонный анализатора регулярных Об ратите внимание, что синтаксисы регулярных выражений в Regex++ и отличаются от классического;

некоторые из редко используемых операторов от сутствуют, а иные выглядят по-другому. Синтаксис регулярных выражений в классе CAtlRegExp описан на странице Вот пример CAtlRegExp:

re.

if на Роковую гору в 12:15 { const szStart = 0;

const = 0;

= szEnd - szStart;

Хороший подход, но без использования регулярных выражений Один из способов проверки входных данных до их использования — задействовать классы в языках, которые их поддерживают, например классы C++.

и Visual Basic Вот пример класса в C++:

using namespace std;

class Userlnput { public:

str) { // При желании можете добавить здесь проверку.

if(!Validate(str)){ return false;

ГЛАВА 10 Все входные данные — от лукавого!

} else input = return true;

:

const char* DWORD private:

str);

string input;

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

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

все подобные данные в обязательном порядке должны проходить только через один из этих КПП. Ищите в запросе не а только корректные данные и отклоняйте запрос, если данные не критериям. Помните: вы создали программу для доступа и управления вашими ресурсами и поэтому прекрасно каким должен быть «правильный» запрос.

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

Список правильных запросов ограничен, а перечень неверных в принципе бес ну или, по крайней очень-очень велик.

Недостатки канонического представления бы я решился вместо главы написать лишь одну фразу, то выбрал бы такую: «Ни в коем случае не принимайте никаких решений, имеющих отношение к безопасности, на основании только имени ресурса, частности, имени файла», Почему? Если не ответа, перечитать предыдущую главу. Как однажды сказала Гертруда Штейн (Gertrude Stein), «Роза — это Или что? А что если роза предоставил пользователь, которому мы не доверяем? Обозначают ли одно и то же понятие ROSE* следующие слова: или И да, и нет. Да, все они имеют отношение к розе, но синтаксически различаются, что может привести к проблемам с безопасностью в приложении. Например, это ше представление для буквы «о».

Как же эти разные «розы» способны подорвать защиту приложения? Если ко ротко: приложение принимает касающиеся безопасности, на имени ресурса, например введенного пользователем имени файла, однако вели ка вероятность принятия неверного поскольку существует несколько способов (и все они правильные) представления имени объекта. Все ошибки при в вид приводят к опасности подмены сетевых объектов, что в свою очередь часто позволяет хакеру завладеть инфор мацией и более высокие полномочия, В этой главе я поясню, что значит и, не упуская случая позна комить со свежими примерами ошибок в отрасли, расскажу о некоторых ошиб ках приведения в вид имен файлов и характерных для Web про блемах. Ну и наконец, научу бороться с подобными Rose в языке означает «роза». — Прим.

ГЛАВА 11 Недостатки канонического представления Что означает «канонический» и как это понятие создает проблемы Я понятия не имел, что слово канонический, когда впервые его знакомый мне канон был знаменитый «Канон ре-мажор* Пахельбеля В словаре Random House Dictionary (Random House, 2000) это слово объясняется так: «Каноничес кий — находящийся в простейшей или стандартной форме». Следовательно, ка ноническое представление это стандартный, прямой и наиболее способ представления. Приведение в канонический вид — это пре образование различных эквивалентных форм имени к единому, каноническому. Например, на компьютере имена и обычно обозначают один и тот же файл. Приведения в вид может предусматривать каноническое представление всех этих имен в виде Ошибки, связанные с безопасностью, возникают, когда приложение делает неверное заключение на основе неканонического представления Проблемы канонического представления имен файлов Безусловно, вы сами все знаете, но все же позвольте мне что мы гово рим на одном языке. Многие приложения принимают решения, связанные с бе зопасностью, основываясь на имени файла. Проблема в том, что у файла го несколько имен. Сейчас я покажу несколько «свежих» ошибок такого рода, чтобы пояснить, что я имел в виду.

Обход фильтров имен файлов в сервисе Napster Это моя любимая ошибка приведения в канонический вид из-за ее «нетехничес происхождения. Если вы не вели отшельнический образ жизни в дремучем лесу до начала то что был такой сервис обмена музыкальными файлами, которому предъявила иск Американская ассоциация звукозапи сывающей индустрии (Recording Industry Association of сочтя e 'o пиратским. Судья предписал компании Napster заблокировать доступ к определе что и было сделано. Однако это решение реализовали на ос новании названия композиции, и очень скоро пользователи нашли е:

давать композициям название, очень похожее на исходное, но не мое фильтрами Napster. Вот несколько примеров переименования песен группы and the Banshees»: можно переименовать в аналогии с детской игрой слова-перевертыши), «92 degrees» — в «92 a «Deepest — в Это брешь типа «раскрытие информации», поскольку дает доступ к файлам пользователям, которым он, по идее, не предоставляться. Вот как отсутствие эффективного алгоритма приведения файлов в канонический вид на практике позволило обойти предписание суда.

Подробности истории читайте на Web-странице 314 Часть II Методы безопасного кодирования Брешь в Mac OS X и Apache Версия Web-сервера Apache, поставляемая с первой редакцией ОС Mac OS X ком пании Apple, становилась уязвимой в случае использования файловой системы Hierarchical System Plus (HFS+). HFS+ не различает регистр символов, и эта ее сводила на нет эффективность механизма защиты каталогов Apache.

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

/scripts> order deny, allow deny all Обычный пользователь, попытавшийся обратиться к ders.com/scripts/index.btml, получил бы отказ в Однако если ввести то доступ к файлу разре шается, Эта брешь существовала из-за того, что в отличие от HFS+, которая нечувстви тельна к символов, версия Apache, поставляемая с Mac OS X, различала регистр. Таким образом, для Apache имя SCRIPTS — совсем не одно и то же, что и конфигурационный сценарий на него не действует. Но для и scripts — одно и то же, так что хакер преспокойненько получал «защищенный» файл Подробности об этой бреши читайте на странице Брешь в именах устройств DOS Вы знаете, что некоторые имена файлов в MS-DOS операционные си стемы семейства Windows унаследовали для обратной совместимости. На самом деле это не файлы, а устройства, такие как последовательный порт и прин тер и Используя эту хакеры получили возможность заставить 95 и Windows 98 обращаться к этим устройствам. Когда Windows пыта лась проинтерпретировать имя устройства как файловый ресурс, происходило недопустимое обращение к ресурсу, что обычно кончалось крахом. Подробности на странице Брешь в символической ссылке на каталог /tmp в пакете StarOffice компании Sun Я упомянул эту брешь, поскольку дыры из-за символических ссылок очень часто встречаются в UNIX и Linux. ссылка (symbolic link, — это файл, указывающий на другой файл;

таким образом, его можно считать еще од ним именем файла. В UNIX также есть файлы, представляющие собой жесткие ссылки (hard link). У такого файла права доступа совпадают с исходным а у — нет.

ГЛАВА Недостатки канонического Примечание Жесткую ссылку в 2000 создают вызовом функции Например, символическая ссылка во временном каталоге может указывать на файл с паролями UNIX или на какой-нибудь другой важный файл.

При запуске создает объект с который тически кто угодно может для почти любых целей. На языке это означает, что он имеет маску доступа что так же плохо, как (полный доступ). Хакер может создать символическую ссылку на пользовательский файл. Когда пользователь запустит StarOffice, пакет слепо п меняет права доступа на этот файл (поскольку установка доступа на симв ссылку автоматически устанавливает права и на целевой файл, процесс обладает достаточными для этого полномочиями). После этого хакер получает доступ на чтение файла, Если хакер сделал так, что /tmp/soffice.tmp ссылается на /etc/passwd и запустит StarOffice с правами администратора, права на /etc/passwd я, Подробности смотрите на сайте Практически описанные здесь ошибки приведения в канонический вид возникают при пересылке введенных пользователем данных между компонент ми системы. Если первый компонент, принимающий вводимые данные, не пол ностью выполняет приведение перед отправкой следующему компоненту в цепочье.

система подвергается опасности, Внимание! Все проблемы с приведением в канонический вид существуют из за того, что приложение, выяснив, что запрос ресурса не удовлетворяет «вываливается» в незащищенный режим.

Внимание! Если касающиеся безопасности решения принимаются на основа нии имени файла, ошибки неизбежны!

Стандартные ошибки в канонических именах Windows В Windows существует много способов имен файлов, причина — в расширения и поддержке обратной совместимости. Если вы нимаете имя файла и используете для принятия решений, касающихся рекомендуем прочесть этот раздел, Представление длинных имен файлов в формате «8.3» Как вы, конечно же, знаете, устаревшая файловая система FAT, впервые шаяся в MS-DOS, требует особого формата имени файлов: его длина не более 8 и расширение не более 3 знакомест (или, что то же самое, символов). Файловые системы FAT32 и NTFS поддерживают длинные имена файлов, например, в NTFS длина имен файлов ограничена 255 Unicode-символами. В целях обратной стимости NTFS и FAT32 по умолчанию генерируют имена файлов в формате 316 Часть II Методы безопасного кодирования что даст возможность приложениям для MS-DOS и 16-разрядной Windows рабо тать с такими файлами.

Примечание Имена файлов в формате «8.3» сле дующим образом: имя усекается до первых 6 символов, потом тильда (~) и далее цифра (для файлов с похожими именами она изме няется от 1 до 9), затем после точки следуют 3 символа расширения.

Например, файл My Secret превращается в до усечения имени и расширения все недопустимые символы и пробелы удаляются.

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

String SensitiveFiles[] = RestrictedIP[] = BOOL IPAddress) { If In IPAddress In Return FALSE;

Else Return TRUE;

= FALSE;

// Доступ запрещен, fAllow = // Доступ разрешен. Приехали!

fAllow = "172.30.43. Примечание Здравый смысл подсказывает, что, создавая безопасные системы, следует избавиться от приложений для MS-DOS и 16-разрядной Windows и, следовательно, отключить поддержку формата «8.3». В свое время мы поговорим и об этом.

Интересен побочный эффект имени файла в формате «8.3»: неко торые процессы удается атаковать тогда и только тогда, когда запрашиваемый файл не содержит пробелов в имени. Догадались в чем дело? У имен файлов Б формате не может быть пробелов! Предлагаю вам самим сформулировать тактику та кой атаки.

ГЛАВА 11 Недостатки канонического представления Альтернативные потоки данных NTFS Я расскажу об ошибке приведения в канонический вид чуть позже, пока же будьте исключительно осторожны, если ваш код принимает решения на файла. Например, получив запрос файла с рас ширением сервер IIS перенаправляет его библиотеке Если хакер просит файл с расширением IIS не обратит на то, что основной поток данных NTFS, и хакер получит исходный Примечание Для просмотра потоков файлов существуют специальные утили ты, например компании Crucial ADS фирмы Crucial Security или Security Expressions фирмы Pedestal Software ware.com), В дополнение ко всему, если в приложении используются альтернативные потоки данных, следует обеспечить корректный разбор имени файла, чтобы или запись выполнялись только для нужного потока. К слову сказать, у пото ков нет отдельных списков управления доступом (access control list, — наследуют ACL своего файла.

Завершающие символы Я видел много случаев, когда завершающая точка (.) или обратный слеш (\), до бавленные к имени файла, заставляли приложение некорректно разбирать ег.

Проблема с точкой — Б основном «заслуга» Win32, поскольку файловая считает, что в этом месте не должно быть точки, и удаляет ее перед разбором файла. обратный слеш — проблема, имеющая отношение скорее к Web, и детально я о расскажу в главе Следующий код демонстрирует, что я в виду, говоря о завершающей точке (см. папку char h = 0, NULL, if (h != { DWORD = 0;

b, NULL);

;

h = // Завершающая точка.

GENERIC_READ, 0, NULL, 318 Часть Методы безопасного кодирования NULL);

if != { DWORD =0;

b, sizeof b, NULL);

Обратили внимание на разницу в именах файлов? время второго вызова функции для доступа к somefile.txt в качестве аргумента передается имя файла с точкой на конце, тем не менее открывается и читается кор ректно. Это все потому, что файловая система заботливо неправильный символ! Как видите, somefile.txt. и somefile.txt для ОС одно и то же, и плевать ей на завершающую точку.

Формат Обычно длина имени файла (число ANSI-символов) ограничена значением (260). Unicode-версии многих функций для работы с файлами позво ляют увеличить это число до 32 000 Unicode-символов, если в начале имени фай ла поставить Этот префикс заставляет функцию отключить проверку пути.

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

Выход из текущего каталога Допустим, приложение хранит файлы данных в каталоге В принципе, пользователи вообще не должны иметь доступа ко всем остальным файлам в си стеме. Веселье когда хакер попытается получить доступ к файлу хранящему информацию о параметрах загрузки (он лежит в корневом каталоге загрузочного диска) или, еще к где хранится файл базы данных диспетчера учетных (Security Account Manager, SAM) с именами пользователей и хешами паролей всех локальных учетных записей.

(К в Windows 2000 и более поздних ОС доменные учетные записи хра нятся в Active Directory, а не в SAM.) После этого хакеру достаточно запустить ути литу подбора паролей, например (доступна на сайте чтобы сравнительно быстро «вычислить» пароли. Вот почему так важны надежные пароли!

ГЛАВА 11 Недостатки канонического представления Примечание В Windows 2000 и более поздних ОС файл SAM шифруется [по умолчанию применяется системный ключ что несколько услож няет атаку. Подробнее о SysKey — в статье «Windows NT System Key Permits Strong Encryption of the (http://supporlmicrosoft.com/support/kb/ в базе данных Microsoft Knowledge Base.

Которое из имен В структуре каталогов все следующие строки ссылаются на один и тот же файл • • • Вот так!

Абсолютные и относительные имена файлов Если пользователь передал имя файла, не указав каталог, то где его искать? В те кущем каталоге? В каталоге, указанном в переменной окружения PATH? У прило жения масса возможностей ошибиться и открыть «не тот» файл. Например, при запросе на открытие файла File.exe откуда загрузит приложение файл из текущего каталога или из каталога, указанного в переменной PATH?

Имена файлов, нечувствительные к регистру символов Мне не известны дыры в Windows, связанные с регистром символов в имени файла, Файловая система NTFS сохраняет, но не учитывает информацию о регистре сим волов. То есть для файловой системы и — один и тот же файл.

Это не так лишь в одном случае: если ваше приложение работает в подсистеме (Portable Operating System Interface for UNIX). Однако если оно выполняет сравнение имен файлов с учетом регистра, то его можно взломать по тому же методу, что и описанная ранее Apple Mac OS X с Web-сервером Apache.

Общие ресурсы UNC Файлы доступны по именам в формате об нии общих ресурсов (Universal Naming Convention, UNC). Общие применяются для доступа к файлам и принтерам в Windows и трактуются опера ционной системой как обычные элементы файловой системы. Средствами можно назначить букву диска локальному или удаленному серверу. Пусть на ком пьютере BlakeLaptop есть общий ресурс Files, которому соответствует физичес кий каталог Чтобы назначить букву для этого общего ре сурса, надо выполнить команду net use z: \\BlakeLaptop\Files. После этого file.txt и станут ссылаться на один и тот же файл Также позволяет получить доступ к файлу напрямую, в обход буквы. Например, аналогично Так же комбинируется с разновидностью формата например соответствует 320 Часть II Методы безопасного кодирования Имейте в что Windows ХР содержит buted Authoring and который позволяет пользователям спроецировать виртуальный каталог Web на локальный диск, воспользовавшись мастером Add Network Place Wizard (Мастер добавления в сетевое окружение). Это означает, что диски могут располагаться на Web-сервере, а не только на файло вом сервере.

Когда файл не является файлом: почтовые ящики и именованные каналы Некоторые API-функции (например позволяют открывать только но и каналы (named pipe) и ящики (mailslot).

Именованный канал — это одно- или двунаправленный комму никационный канал между сервером и одним или несколькими клиентами. По чтовый ящик — это протокол межпроцессного взаимодействия без проверки сообщения адресатом (fire-and-forget). Как только кли ент подключился к серверу канала или почтового ящика (при условии успешной проверки прав доступа), возвращенный операционной системой, терпретируется, как обычный описатель файла. Синтаксис для канала:

а для почтового ящика:

slot\ < Когда файл не является файлом: имена устройств и зарезервированные имена Многие операционные системы — Windows в их числе — имено вание устройства и доступ к устройствам с консоли. Например, — первый последовательный порт, — последовательный порт по умолчанию, LPT2 — второй порт принтера и т.д. Следующие зарезервированные имена запрещено использовать в качестве имен файлов: CON, PRN, CLOCKS, COM 1 - COM9, и — Однако имена с добавлением расширения, например NUL.txt, допустимо задавать в качестве имен устройств. Есть еще одна особенность: каждое из этих «устройств» доступно из любого каталога. Например, — это первый последовательный так же как и Ситуация, в которой пользователь сам передает имя а программа сле по его открывает, чревата проблемами, если в действительности указывается не на файл, а на устройство. Пусть в приложении имеется один рабочий поток, ко торый принимает пользовательские запросы с именами файлов. Если хакер за просит приложение откроет для чтения. Поток заблоки пока последовательный порт снова не откроется по тайм-ауту! К существует метод определения типа файла, и я немного позже расскажу о нем, Как видите, существует много способов именования файлов, и если ваш код принимает касающиеся безопасности решения на основании имени файла, ма ловероятно, что оно окажется адекватным. А теперь перенесемся в еще одну сфе ру имен — в Web.

ГЛАВА Недостатки канонического представления Проблемы с устройств в других операционных системах Проблемы в канонический вид, конечно присущи не толь Windows. в можно заблокировать определенные при ложения, попытавшись вместо например и многие Тест, в котором в кролика» Mandrake Linux 7.1 с Netscape 4.73, показал, что после попытки открыть файл file:/// придется перезагрузить компьютер — это единственный способ разблокировать мышь в такой ситуации. Кроме того, команда ftle;

///deif/zero браузер. Это довольно серьезные бреши — чтобы надежно хакеру достаточно создать Web-страничку с тэгом Вам следует научиться разбираться в именах устройств, если вы - приложения для разных систем.

Проблемы приведения в канонический вид в Web К сожалению, многие приложения принимают касающиеся безопасно сти на основании URL-адреса или его компонентов. Точно так же, как и с ми, такой подход чреват Посмотрим, какими именно.

Обход родительского контроля AOL В браузере America Online (AOL) 5.0 предусмотрены функции, которые позволя ют родителям запретить доступ к определенным Web-сайтам их малолетним ча дам. При загрузке браузер проверяет, не значится ли соответствующий Web-сайт в списке и при положительном результате блокирует доступ к нему. А брешь такова: если в конец адреса добавить точку, браузер без предоставит доступ к сайту. причина в том, что программа при сравнении адреса со списком запрещенных сайтов учитывала завершающую точку, а при загрузке (то есть уже после недопустимые символы удалялись из Сейчас эта ошибка исправлена Обход механизмов обеспечения безопасности еЕуе Хоть плачь, хоть смейся — эта дыра обнаружена в продукте ченном для защиты от атак на сервер Internet Information Services (IIS). Вот вы держка из маркетинговых материалов еЕуе (http://www.eeye.com) по SecurellS:

SecurellS защищает Web-сервер Microsoft Internet Services от известных и атак. SecurellS JIS в обо лочку и постоянно проверяет и входящие и исходя щие данные Web-сервера на предмет нарушении защиты.

324 Часть II Методы безопасного кодирования Эти команды отображают содержимое потока в консоли. данные файла хранятся в потоке без встроенный в NTFS тип которого носит на Таким образом, чтобы получить доступ к стандартному потоку данных достаточно такого синтаксиса:

тоге < На рис. показано, что он означает.

Имя потока (в нашем случае отсутствует) Имя файла Тип потока Рис. 11-1. Синтаксис файловых потоков в NTFS Поток NTFS следует правилам именования, принятым в NTFS, то есть разре шаются все буквенно-цифровые символы и ограниченный набор знаков пункту ации. Например, два файла, потоки и now и readme соответственно и Разрешены любые комбинации допустимых символов.

Но вернемся «к нашим При получении запроса от пользователя дей ствия определяются расширением. Например, запрос файла с расши рением то есть (Active Server Pages), сервер перенаправляет для обработки в библиотеку Если IIS расширение неизвестно, запрос посыла ется для обработки напрямую Windows, и значит, пользователь получает доступ к содержимому файла. Эта обеспечивается статическим обработ чиком файлов, то есть его можно трактовать как один большой раздел default в операторе switch. Таким образом, если пользователь запросит файл а сервер не сможет найти нужный обработчик, сопоставленный расширению.txt, то пользо ватель получит исходный текст файла, Неприятности возможны, если запросить файл в форме Анализируя IIS не распознает и передаст файл на обра ботку системе. NTFS, увидев в свою очередь, что пользователь зап росил поток данных по умолчанию, вместо результата обработки вернет хакеру сам файл Подробнее об этой ошибке рассказано на странице asp, Две строки вместо одной Относительно брешь связана с обработкой строк с символами «возврат каретки» и Пусть приложение регистрирует запросы пользователей в журнал, а пользователь запросил файл file.txt. Сервер записывает IP-адрес и имя клиента, дату и а также запрошенный ресурс в следующем формате:

172,23.11.19 Mike 2002-09-03 13:02:43 file.txt Если «скормить» серверу в журнале появится запись:

ГЛАВА 11 Недостатки канонического представления 172.23.11.19 Hike 2002-09-03 13:02: 127.0.0.1 Cheryl 2002-09-03 13:03: Таким образом, Cheryl получила доступ к секретному локально подключившись к серверу? Конечно, нет. Мы заставили приложение сделать эту запись, используя символы возврата каретки и перевода строки в запра шиваемого ресурса!

об этой бреши вы узнаете на странице http://online.securityfocus.com/ Еще одна напасть в Web — управляющие символы Причина частого возникновения и трудности предотвращения проблем приве дения в канонический вид в Web — огромное число способов представления си волов. Например, любой символ URL-адреса или Web-страницы одним или несколькими из механизмов:

• «стандартное» 7- или 8-битные ASCII-символы;

• управляющие коды;

• кодировка с переменной символов;

• кодировка Unicode UCS-2;

• двойная кодировка:

• управляющие коды HTML (только на Web-страницах, но не в URL-адресах).

7- и 8-битные ASCII-символы Полагаю, вы знакомы с этим форматом. Он в компьютерных систе мах уже долгие так что я не стану тратить время на объяснение.

Шестнадцатеричные управляющие коды Шестнадцатеричные управляющие коды — это способ символов, в основном непечатаемых, при помощи их Например, представляется как а знак фунта стерлингов ) — как Подобное представление разрешается в например вызов http://www.northwindtraders.com/ откроет файл ту расположенный на Traders.

Я уже рассказывал об ошибке приведения в канонический вид в фирмы Эта утилита отклоняет клиентские запросы, заданные слова. Однако достаточно представить любой символ запроса в надцатеричном и пропустит запрос, а это вопиющее безопасности, Кодировка UTF- В RFC 2279 (http://www.ietf.org/rfc/rfc2279.txt) описан метод представления Unicode символов (Eight-bit Unicode Transformation Format, UTF-8).

длина символов позволяет кодировать многие наборы с разной длиьой символов, такие, как и (UCS-4) Unicode-символы и 326 Часть II Методы безопасного кодирования реже — ASCII-символы. Однако то, что один и тот же символ в разре шается представлять в многобайтном создает проблемы, Как кодируются данные в UTF- В символы кодируются в различные последовательности байт, в зависимости от значения исходных символов. Например, символы из 7-битного диапазона ASCII (0x00 — Ox7F) как где первый О — стар ший бит, установленный в 0, а представляют 7 бит, из которых и состо ит ASCII-символ. В частности, буква Н, чей код в представле нии — 0x48 или 1001000 — в двоичном, преобразуется в как 01001000 или 0x48. Как видите, 7-битные символы ASCII в UTF-8 не меняются.

Все немного усложняется, когда преобразуются символы, выходящие из 7-бит ного диапазона ASCII, до верхней границы диапазона Ox7FFFFFFF. Напри мер, символ из диапазона 0x80 — Ox7FF преобразуется в 1 где и 10 — предопределенные а каждый х представляет собой один бит кодируемого символа. Например, код символа фунта стерлинга — ОхАЗ в шестнад цатеричном или — в двоичном представлении. представление 10100011 в шестнадцатеричном виде выглядит так: ОхС2 Одна ко это еще не все. В поддерживается кодировка символов с большим чис лом байт (табл.

Таблица Соответствие символов UTF- Диапазон кодов байты Юхххххх Юхххххх Юхххххх llllOxxx Юхххххх Юхххххх Юхххххх Юхххххх Юхххххх Юхххххх llllllOx Юхххххх Юхххххх Юхххххх Юхххххх, Юхххххх Вот и начинается самое интересное: любой символ можно в любом из перечисленных форматов, хотя в спецификации этого делать и не рекомендуется. Все символы должны представляться в самом корот ком из возможных форматов. Например, единственно правильное представление символа «знак вопроса» (?) — в шестнадцатеричном или 00111111 — в дво виде. С другой стороны, взломщику никто не запретит указать не самый короткий «нестандартный» например один из этих:

• • ОхЕО 0x80 OxBF • OxFO 0x80 0x80 OxBF • OxF8 0x80 0x80 0x80 OxBF • OxFC 0x80 0x80 0x80 0x80 OxBF Некорректный анализатор текстов на UTF-8 может посчитать, что все эти форматы равнозначны, в то время как правильный только ГЛАВА 11 Недостатки канонического представления Наверное, самые известные атаки, эксплуатирующие недостатки были направлены на серверы IIS 4 и IIS 5 без установленных пакетов исправлений. Сервер оказывался обработать последовательность примерно так:

Как вы думаете, что означает В двоичном виде это соответствии с форматом UTF-8, указанным в табл. 8 1, получается 10101 Таким образом, код символа или а это не что иное, как (/)! Последовательности символов такого рода часто sequence).

Так что, запросив подобный URL-адрес, хакер получал доступ к Иначе говоря, ему удавалось из виртуального каталога scripts, в котором разрешено и доступ к корневому а оттуда — к каталогу откуда можно отправлять команды в оболочку Примечание Очень подробно об ошибках приведения в вид разрешений файлов рассказывается на Кодировка Unicode UCS- Проблемы в — это комбинация из недостатков коди ровки и отчасти — Двухбайтные символы (Universal Set) разрешается виде так как и м но в формате где — значение символа Unicode. Например, — это и обратного слеша а — тот же символ, но в кодировке Unicode, Чтобы уж сбить вас с толку, скажу, что также можно предста вить в Unicode, называемом версией. Полноширинная кодировка в Unicode нужна для поддержки некоторых унаследованных двухбайтовых кодировок символов азиатских языков. Символы в диапазоне — зарезервированы для эквивален тов символов с кодами — Например, символ можно представить как и Двойная кодировка Как только вам показалось, что вы наконец разобрались с различными схемами кодировки, а мы рассмотрели лишь самые общие, как на сцене появляется двой ная кодировка, то есть повторная кодировка закодированных данных.

мер, в представление обратного слеша состоит из трех символов:

5 и с, и всех их можно перекодировать, повторно применив и В табл. показаны некоторые варианты двойного кодирования «обратный (\).

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

12- 328 Часть II Методы Таблица Примеры представлений символа в двойной кодировке Представление Комментарий Нормальное представление — представление символа а за ним символы 5 и с Символ 5 и — представление с Представления символов 5, и с в формате Управляющие коды HTML HTML-страницы также содержат символы, закодированные другими специальными символами. Например, угловые скобки (< и >) обозначаются как и а сим вол фунта стерлинга — И это далеко не все! Управляющие последователь ности также разрешается представлять с использованием десятичных и шестнад кодов, а не только легко запоминающимися мнемоническими после Например, — это то же самое, что и (шестнадца теричное значение символа <) или (десятичное значение символа <). Пол ный список подобных последовательностей вы найдете на странице fottp:// Как видите, в Web масса способов кодировки данных, а это значит, что при нимать решения, касающиеся безопасности на основании имени ресурса — не разумно и очень опасно. А теперь пора познакомиться с от опи санных напастей.

Атаки на основании визуального совпадения и атаки В начале 2002 года два Евгений Габрилович и Алекс Гонтмахер Gontmakher), опубликовали интересную статью под названием «The Homograph Attack* (Гомографические атаки) Ос новная что некоторые символы внешне как две капли воды похожи друг на хотя по сути совершенно различны (рис.

file Edit looks but not! a remote computer, the V localhost is not an 'o' a that like an V.

Look at the zone at bottom of the page, not My local Рис. Адрес выглядит как верно? Однако все не так просто.

В слове localhost содержится символ из кириллицы, а не ASCII-символ ГЛАВА 11 Недостатки канонического представления Проблема в том, что последний символ в слове не является латин ским «о», а на самом деле это символ кириллицы «о» и визуально они эквивалентны, семантически Пользователь полагает, что обра щается к локальному а на самом деле удаленный сер вер. Есть и другие символы, которые выглядят одинаково как в латинице, так и в кириллице: х, Другой пример — знак дроби и слеш выглядят одинаково. В репертуаре Unicode еще много подобных «номеров», я рас скажу о некоторых в главе 14.

Один из самых старых подобных — цифра ноль (0) и буква «О».

Проблема визуального совпадения в том, что, видя один и на предпринимая действия, пользователь на самом деле выполняет совершенно другую операцию. Кому придет в голову, что ссылка, лядящая как localhost, приведет на удаленный компьютер с именем localhost?

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

Никогда не принимайте решений на основании имен Простейший и в большинстве самый эффективный способ избавиться от ошибок, связанных с приведением имен, — это отказ от принятия решений на основании имен файлов. Заставьте ОС и файловую систему работать на — применяйте списки управления и другие системные авторизации. Конечно, все не так просто, как может показаться! Файловая ма не поддерживает некоторые семантики. Например. IIS поддерживает сценарии, то есть файл сценария, такой как ASP-страница с внедренным кодом на или Microsoft JScript, читается и а результат отсылается поль Это не то же самое, что права на чтение и исполнение, а нечто ее.

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

Как уже говорилось, вы вправе ограничить доступ к на основании IP адреса пользователя. Однако такую семантику в настоящее время нельзя предста вить в виде списка управления доступом, поэтому приложения, поддерживающие ограничения на основании IP-адреса, DNS-имени или маски подсети, должны сами заботиться о безопасности.

Внимание! Воздержитесь от принятия решений, касающихся безопасности, на основании имени файла. Ошибка может иметь катастрофические послед ствия.

330 Часть И Методы безопасного кодирования Используйте регулярные выражения как метод контроля имени Я подробно рассказывал об этом в главе 10, но нелишне повторить. Если вам приходится принимать касающееся безопасности решение на основании имени, определите, как должно выглядеть имя и запретите все ос тальные форматы. Например, только полные имена файлов, состоящие из жестко ограниченного набора символов. Вот еще один пример: имя файла считается правильным при выполнении следующих условий:

• файл должен храниться на диске С или • путь должен состоять из последовательности обратных слешей и буквенно цифровых СИМВОЛОВ;

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

Наиболее простой способ реализации контроля — регулярные выражения.

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

Перечисленные требования к имени файла реализуются таким выражением (по дробности — в главе 10):

Этому довольно жесткому выражению удовлетворяют следующие имена;

• • А эти — нет:

• (не тот диск);

• (перед именем файла должно быть имя каталога);

• (в имени каталога не разрешается ничего, кроме и символа подчеркивания);

• (неправильное расширение файла);

• (тильда • (двоеточие разрешается только после буквы диска, символ тоже недопустим);

• (завершающая точка недопустима);

• (нет буквы диска);

• (нет буквы диска).

Как видите, простое регулярное выражение способно радикально сократить возможность использования неканонических имен. Одно в этом случае нельзя проверить, не является ли имя файла устройством, но всему свое время.

ГЛАВА Недостатки канонического представления Внимание! Регулярные выражения преподносят хороший урок, так как учат отделять «зерна» от Проверка на корректность — единственно правильный способ разбора любых входных данных. Ни в коем случае не реализуйте проверку так: поиск и блокировка только неверных дан ных, а всему остальному открыт Скорее всего вы проглядите какой-нибудь редко встречающийся случай. Это крайне важно, Повторяю: ищите только то, что гарантированно правильно, а все ос тальное безжалостно «убивайте».

Отключайте генерацию имен файлов в формате «8.3» Неплохо запретить файловой системе генерировать короткие имена файлов. Это делается не из программы, поскольку это административная функция. Чтобы от ключить генерацию имен файлов в формате надо добавить в раздел реест ра параметр:

: : Имейте в виду: ранее сгенерированные короткие имена файлов останутся.

Не полагайтесь на переменную PATH — указывайте полные имена файлов Никогда не полагайтесь на переменную окружения PATH для поиска файлов. С.

дует всегда абсолютно точно указывать, где они лежат. Имейте в виду, хакер мо жет подменить переменную чтобы получить возможность читать каталог или какой-нибудь другой! Когда вы последний раз проверяли в своей системе? Мораль проста: указывайте полные имена с з тем к файлам данных и исполняемым файлам, не полагайтесь на не очень-то на дежную переменную.

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

но добавьте этот параметр: тип — значение по умолчанию — 0.

При значении «1» текущий каталог будет просматриваться после Выбор только допустимых имен файлов и запрещение всего остального до статочно надежно, но только если используются корректные регулярные выра жения. Дополнительной гибкости удается добиться за счет самостоятельного ведения в канонический вид, чему посвящен следующий раздел.

332 Часть II Методы безопасного кодирования Самостоятельно приводим имена в канонический вид Приводить имена файлов в канонический вид не так сложно, как кажется, надо лишь знать о нескольких полезных функциях Win32. Задача — представить имя файла в программе так, чтобы оно как можно точнее совпадало с его представле нием в файловой а затем на основании результата принимать решения, Я считаю, что следует максимально близко подойти к каноническому представ лению и немедленно «отбраковывать» имя, если оно не соответствует требовани ям. Например, приложение CleanCanon надежно справляется с приведением имен.

выполняя следующие операции, 1. Принимает непроверенное имя файла от пользователя, например 2. Выясняет корректность имени. Например, — правильное имя, а и (завершающая точка) — не правильные.

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

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

5. Вызовом функции корректно определяет структуру каталогов с учетом двух точек (..).

Вызовом функции определяет длинное имя файла в случае, если пользователь передал короткое. Например, mysecr~l.txt становится mysec retfile.txt. Это спорный с технической точки зрения этап, поскольку проверка на шаге 2. Однако это мера защиты действует на каждом уровне!

7. Определяет, не является ли имя файла устройством. Регулярными ми подобной проверки не добиться. Если функция определит, что файл относится к типу то это действительно файл, а не уст ройство.

Примечание Я уже говорил, что проблемы с именами устройств есть в Linux и UNIX. Чтобы определить, является ли файл файлом или устройством, в программах на С и C++ надо вызвать функцию — если значение переменной равно то это действительно файл, а не устройство или ссылка.

Вот текст приложения CleanCanon, оно написано в среде Visual C++ с применением функций Win32:

Л ffinclude ГЛАВА 11 Недостатки канонического представления ttinclude = errCanon LPCTSTR szDir, LPTSTR *pszNewFilename) { // ШАГ // Должен передаваться путь, // общая длина не должна превышать НАХ_РАТН if (szDir == NULL) return size_t cchDirLen = 0;

if cchDirLen > return = NULL;

LPTSTR = NULL;

HANDLE = NULL;

errCanon = { // ШАГ Проверить имя файла (буквенно-цифровые символы, // точка и 1-4 буквенно-цифровых // Проверить корректность пути (только и // Регистр игнорируется.

if throw if throw size_t = size_t cDir = 334 Часть II Методы безопасного кодирования // Новый буфера достаточен для размещения // символов "обратный слеш" (\).

size_t = cFilename + cDir + 1;

// ШАГ // Проверяем, короче ли переменной длина полного if (cNewFilename > throw // Выделяем память для нового полного имени файла.

// Не забываем о префиксе и завершающей комбинации LPCTSTR = size_t = size_t cchTempFullDir = cNewFilename + 1 + cchPrefix;

= new if == NULL) throw // ШАГ // Конкатенация пути и имени файла.

// Подставить \\?\, чтобы ОС обрабатывала символы как есть, // не предпринимая дополнительных шагов // в канонический вид.

if szPrefix, szDir, != throw // ШАГ // Получаем полный путь, // где учтены парные точки (..), завершающая точка и пробелы.

TCHAR + LPTSTR = NULL;

DWORD = szFullPathName, if (dwFullPathLen > throw // ШАГ // Получаем длинное имя файла if szFullPathName, HAX_PATH) == 0) < = switch { case :

ГЛАВА 11 Недостатки канонического представления errName = break;

case :

case :

errName = break;

default : break;

throw errName;

!

ШАГ // Файл или = | NULL);

if == throw if (GetFileType(hFile) != throw Вроде, все хорошо!

// Вызывающая сторона должна удалить квадратные скобки, // полное имя файла const size_t cNewFllenane = = new if != NULL) else = } e) = e;

} catch a) = I delete [] szTempFullDir;

if return err;

Полный листинг есть в папке У функции есть побочный определяя, является ли файл дисковым, она терпит если файла не существует, не выполнить проверку.

336 Часть Методы безопасного кодирования Безопасно вызывайте CreateFile Вы, обратили внимание, что в программе флаги не пусты. На то есть свои причины. Наш код контролирует лишь корректность имени файла и проверяет, что это не устройство или механизм межпроцессного такой как канал или почтовый ящик. И все. Если это именованный канал, то процесс, им должен олицетворять вызывающий процесс. Однако в интересах безопасности я не хочу, чтобы код, которому я не доверяю, олицетворял мою учетную и этот флаг не устанавливаю.

Есть небольшая опасность с этим хотя она и не относится к показан ному коду, поскольку тот не пытается манипулировать файлом. Проблема в что константа \ — это то же самое, что которая показывает, что файл не извле кается из удаленного хранилища, не существует. Этот флаг предназначен для использования в иерархических системах хранения (Hierarchical Storage Manage ment) или в удаленных хранилищах.

А теперь займемся проблемами канонического представления в Web, Лекарства от болезни приведения в канонический вид в Web Как и раньше, первейшее превентивное средство — никогда не принимать реше ния, основываясь на имени ресурса, его можно представить более чем од ним способом, Контроль правильности входных данных Следующее по эффективности средство — ограничить принимаемые данные только корректными. Вы создали защищенный ресурс и должны определить корректные способы доступа к данным, остальные запросы должны попросту игнорировать ся. Еще раз: правильность проверяется помощи регулярных выражений. По вторю еще раз: раз и навсегда определите, что такое корректные входные данные, и принимайте только их, а все остальное без сожаления отбрасывайте. Пускай лучше клиент жалуется, что что-то не работает из-за того, что вы слегка переборщили с регулярными чем это что-то перестанет работать из-за взлома!

Исключительная осторожность с UTF- Если приходится обрабатывать символы приводите данные к каноничес кому виду вызовом Windows-функции Следующий пример демонстрирует, как вызывать эту с различными правильными и непра вильными символами Полный исходный текст примера есть в папке Secure Заметьте также, что создавать символы можно, вызы вая ту же но определив кодовую страницу void DWORD ( DWORD = ГЛАВА Недостатки канонического представления iRes = О, if (iRes == 0) { DWORD = с -> dwErr);

else вернула" широких iRes);

void { // Определить для Ох5с;

// должен быть обратный слеш (\).

BYTE DWORD = // Определить Unicode-символ для OxAF.

// Должно с ошибкой, // поскольку это удлиненное представление обратного слеша (/), BYTE = DWORD = sizeof pUTF8_2;

cbUTF8_2);

// Определить для ОхС // получиться символ авторского права (®), BYTE = DWORD = sizeof ;

— между молотом и наковальней и фильтры, пожалуй, наиболее уязвимые технологии, посколь ку в основном создаются путем сравнительно низкоуровневого программирова ния на С и C++, применяются для обработки Web-запросов и операций с ми. При создании приложений для IIS 6 используйте переменную сервера так как она возвращает корректно приведенное имя файла на основании URL-адрес, избавляя вас от этой работы (и, кстати, от массы ошибок), 338 Часть N Методы безопасного кодирования На закуску: проблемы приведения в канонический вид, не связанные с файлами Практически вся глава представлению и это логично, так как подавляющее большинство проблем с безопасностью при при ведении в канонический вид связаны с файлами. Однако существуют и другие опасности, когда ресурс представим более чем одним именем. На ум приходят две основные угрозы, связанные с именами серверов и пользователей.

Имена серверов У будь то Web-серверы, файловые, почтовые серверы или серверы пе чати, обычно несколько имен для доступа. Наиболее часто используемый способ — DNS-имя, например Другое имя — IP-адрес, например 100. Оба имена обозначают один и тот же сервер. Локальный компь ютер доступен по имени а его может располагаться подсети А сервер в сети Windows обычно доступен по NetBIOS-имени, например И что будет, если приложение принимает касающиеся безопасности решения.

основываясь на имени сервера? Ответственность за определение подходящего канонического сравнения с ним и отклонения всех неподходящих имен ложится на вас. Следующий код (полный исходный текст есть в папке Secure позволяет собрать различные имена локального ком пьютера, /* for i = i <= TCHAR DWORD = sizeof / TCHAR;

TCHAR { case 0 cnf = break;

case 1 cnf = break ;

case 2 cnf = break;

case 3 cnf = break;

case 4 cnf = break;

case 5 cnf = ";

break;

case 6 cnf = break;

case 7 cnf = break;

default ;

cnf "Unknown";

break;

BOOL = ГЛАВА 11 Недостатки канонического представления if { в cnf);

} else { Для получения IP-адрес (или адреса) компьютера вызывают функцию из библиотеки Windows Sockets или средства Perl. Напри таю my $length, = { my = $_);

print "IP:

Имена пользователей Исторически сложилось так, что поддерживает одну форму имени поль вателя: Эта форму называют Напри мер, — запись Blake в домене DEVE LOPMENT. Однако с появлением Windows 2000 было введено основное имя (user principal name, которое имеет ставший классическим формат адреса электронной почты: например Посмотрите на этот код:

for (i = 0;

i < / if == 0) return false;

return !

Функция вернет false для всех членов доменов MARKETING или SALES. Напри мер, вернет false, поскольку Brian состоит в домене MARKETING.

Однако, если у него есть brian@marketing.northwindtraders.com, функция возвратит поскольку формат имени отличается, а при несовпадении ция сравнения строк без учета регистра всегда возвращает отличное от нуля зна чение.

340 Часть II Методы безопасного кодирования В Windows 2000 и более поздних ОС семейства в качестве канонического при меняется имя SAM. У каждой учетной записи должно быть уникальное имя SAM.

Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 10 |



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

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