Дэвид Лебланк - 19 смертных грехов, угрожающих безопасности программ
Следующий фрагмент кода на языке С# показывает, как легко производятся Ьа5е64–кодирование и декодирование:
...string s = «<some string>»;
string s1 = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(s));
string s2 = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(s1));
Короче говоря, хранить секретные данные в URL либо в теле HTTP–запроса или ответа – грех, если только полезная нагрузка не защищена криптографическими средствами.
Следует принять во внимание характер конкретного Web–сайта. Если данные, передаваемые в составе URL, используются для аутентификации, то, скорее всего, безопасность под угрозой. Впрочем, если сайт использует эти данные для определения членства в сообществе, то, может быть, ничего страшного и не случится. Все зависит от того, что именно вы пытаетесь защитить.
Представьте себе следующий сценарий. Вы создали и хотите продать сайт для организации фотогалерей, позволяющий пользователям загружать снимки, сделанные во время отпуска. Такая система может считаться основанной на членстве, поскольку фотографии, скорее всего, не секретны. Но допустим, что некий злоумышленник (Маллет) перехватил верительные грамоты другого пользователя (Дэйва) (имя, пароль или опознавательную строку), передаваемые в составе URL или полезной нагрузки. Тогда Маллет сможет отправить серверу от имени Дэйва запрос, в котором на сайт загружается порнографическое изображение. С точки зрения любого пользователя системы, картинка пришла от Дэйва, а не от Маллета.
Скрытые поля формы
Другая ошибка заключается в передаче важной информации от приложения клиенту в скрытом поле формы в надежде, что клиент (1) не сможет ее увидеть и (2) не сможет ей манипулировать. Но злоумышленнику ничего не стоит прочитать все, в том числе и скрытое, содержимое формы с помощью операции просмотра исходного HTML–кода, имеющейся в любом браузере, а затем отправить серверу поддельную форму с измененными значениями скрытых полей. Сервер понятия не имеет, кто выступает в роли клиента: браузер или Perl–сценарий! В представленных ниже примерах такая угроза безопасности разъясняется подробнее.
Родственные грехи
Иногда Web–разработчики совершают и другие грехи, в частности описанный под заголовком «Загадочные URL». Суть этого греха состоит в использовании негодных методов «шифрования».
Где искать ошибку
Искать нужно места в программе, где:
□ Web–приложение получает секретную информацию из формы или из URL;
□ для принятия решения о безопасности, доверии или авторизации используются данные;
□ данные передаются по незащищенному или не заслуживающему доверия каналу.
Выявление ошибки на этапе анализа кода
Чтобы обнаружить загадочные URL, просмотрите весь серверный код Web–приложения и выпишите точки входа, через которые данные поступают из сети. Ищите следующие конструкции:
Для скрытых полей форм задача несколько проще. Ищите в коде места, где клиенту посылается HTML–код, содержащий строку вида:
...type=HIDDEN
Напомним, что слово hidden может быть заключено в одинарные или двойные кавычки. Такой текст можно найти с помощью следующего регулярного выражения, которое написано на С#, но легко переносится на другие языки:
...Regex r = new Regex(«type\s*=\s*['»]?hidden['"]?",
Regex.Options.IgnoreCase);
bool isHidden = r.IsMatch(stringToTest);
На Perl это выглядит так:
...my $hidden = /types*=s*['"]?hidden['"]?/i;
Для каждого найденного скрытого поля задайтесь вопросом, почему оно скрыто и что случится, если злоумышленник изменит его значение.
Тестирование
Самый лучший способ найти подобные ошибки – подвергнуть код анализу, но на случай, если это невозможно или вы что–нибудь пропустили, можно выполнить некоторые тесты. Например, такие инструменты, как TamperlE (www.bayden.com/ Other), Web Developer (www.chrispederick.com/work/firefox/ webdeveloper) или Paessler Site Inspector (www.paessler.com), показывают исходный текст форм прямо в окне браузера. Они же позволяют модифицировать поля формы и отправлять их обратно серверу. На рис. 9.1 показано, как выглядит окно Paessler Site Inspector.
Рис. 9.1 . Программа Paessler Site Inspector показывает текст форм на Web–странице
Примеры из реальной жизни
Следующие примеры взяты из базы данных CVE (http://cve.mitre.org).
CAN–2000–1001
Web–страница add_2_basket.asp в программе Element InstantShop позволяет противнику модифицировать информацию о цене, которая находится в скрытом поле «price».
Исходный текст формы выглядит следующим образом:
<INPOT TYPE = HIDDEN NAME = «id» VALUE = «AOTO0034»>
<INPOT TYPE = HIDDEN NAME = «product» VALUE = «BMW545»>
<INPUT TYPE = HIDDEN NAME = «name» VALUE = «Дорогая машина»>
<INPUT TYPE = HIDDEN NAME = «price» VALUE = «100»>
Вы можете записать в поле price (цена) любое значение, отправить форму на сайт, где установлена программа Element InstantShop, и получить очень дорогую машину всего за 100 долл. Правда, придется оплатить расходы по доставке.
Модификация скрытого поля формы в программе MaxWebPortal
Этой ошибке в CVE не присвоен номер, но она фигурирует также в базе OSVDB (www.osvdb.org) под номером 4933.
MaxWebPortal – это Web–портал и система организации онлайновых сообществ. Для решения большинства административных задач используются скрытые поля. Это позволяет злоумышленнику проанализировать код HTML–страниц, изменить значения в скрытых полях и потенциально получить доступ к функциям, предназначенным только для администраторов.
Например, можно записать в скрытое поле news значение 1. Тогда сообщение будет помещено на первую страницу в качестве новости!
Или можно параметру allmem (все члены) присвоить значение true. Тогда все члены сообщества получат почтовое сообщение. Таким образом можно завалить пользователей системы спамом.
Искупление греха
Анализируя угрозы, исходящие от загадочных URL и скрытых полей формы, а также возможные контрмеры, рассматривайте следующие варианты:
□ противник просматривает данные;
□ противник воспроизводит данные;
□ противник предсказывает данные;
□ противник изменяет данные.
Противник просматривает данные
Это представляет собой угрозу, только если данные конфиденциальны, например речь идет о пароле или идентификаторе, позволяющем войти в систему. Любая персональная информация также должна приниматься во внимание. Исправить ситуацию поможет использование протоколов Secure Socket Layers (SSL),
Transport Layer Security (TLS), Internet Protocol Security (IPSec) и других технологий шифрования секретных данных. Например, данные можно зашифровать на сервере, а потом отправить клиенту в скрытом поле или в виде кука, тогда клиент автоматически вернет те же данные серверу при следующем запросе. Поскольку ключ хранится на сервере, то эта строка не может быть интерпретирована клиентом, так что с точки зрения криптографии метод вполне приемлем.
Противник воспроизводит данные
Вы можете поддаться искушению зашифровать или свернуть секретные данные на сервере, воспользовавшись своим собственным алгоритмом, который вам представляется безопасным. Но подумайте, что произойдет, если противник сумеет воспроизвести зашифрованные или свернутые данные. Например, следующий код на С# вычисляет свертку имени и пароля пользователя и пересылает результат в скрытом поле, чтобы потом использовать для идентификации пользователя:
...SHA1Managed s = new SHA1Managed();
byte [] h = s.ComputeHash(UTF8Encoding.UTF8.GetBytes(uid + ":" + pwd));
h = s.ComputeHash(h);
string b64 = Convert.ToBase64String(h); // в кодировке base64
А вот аналогичный код на языке JavaScript (вызываемый из HTML или ASP–страницы) с применением СОМ–объекта CAPICOM в Windows:
...// Результат хэширования в 16-ричном виде
var oHash = new ActiveXObject("CAPICOM.HashedData");
oHash.Algorithm = 0;
oHash.Hash("mikey" + ":" + "ABCDE");
oHash.Hash(oHash.Value);
var b64 = oHash.Value; // результат в 16-ричном виде
Тот же код для вычисления свертки имени и пароля пользователя на Perl:
...use Digest::SHA1 qw(sha1 sha1_base64);
my $s = $uid . ":" . $pwd;
my $b64 = sha1_base64(sha1($s)); # в кодировке base64
Отметим, что во всех этих примерах результат хэширования конкатенированной строки снова хэшируется, чтобы обойти уязвимость, которая называется атакой с увеличением длины (length extension attack). Объяснение этой уязвимости выходит за рамки данной книги [2] , но если говорить о практической стороне дела, то не ограничивайтесь просто хэшированием конкатенированных данных, а сделайте одно из двух:
...Result = H(data1, H(data2))
или
...Result = H(H(data1 CONCAT data2))
Ниже приводится криптографически стойкая версия:
...static string IterateHashAppendSalt(string uid, string pwd, UInt32 iter)
{
// границы числа итераций
const UInt32 MIN_ITERATIONS = 1024;
const UInt32 MAX_ITERATIONS = 32768;
// ограничить переданное значение параметра для безопасности
if (iter < MIN_ITERATIONS) iter = MIN_ITERATIONS;
if (iter > MAX_ITERATIONS) iter = MAX_ITERATIONS;
// получить 24-байтовое начальное значение (затравку)