Шрифт:
Есть три основные стратегии обработки ошибок. Они не являются взаимоисключающими, и, по крайней мере, первые два способа лучше применять совместно.
□ Известить об ошибке (и, разумеется, не запускать команду, несмотря ни на что). Но думайте о том, как выглядит сообщение об ошибке. Если вы просто включите в него неверные данные, то можете нарваться на атаку с кросс–сайтовым сценарием. Не стоит также сообщать противнику слишком много информации (особенно если в ходе проверки используются данные из конфигурационного файла). Иногда лучше всего просто сказать «недопустимый символ» или что–нибудь, столь же туманное.
□ Протоколировать ошибку и все связанные с ней данные. Но следите, чтобы сам процесс протоколирования не оказался мишенью атаки; некоторые системы протоколирования принимают символы форматирования, а попытка бесхитростно записать в протокол некоторые данные (например, символ возврата каретки или перевода строки) может привести к порче протокола.
□ Модифицировать поступившие данные, заменив их значением по умолчанию или как–то преобразовав.
В общем случае мы не рекомендуем третий вариант. Вы можете ошибиться, но даже в том случае, когда правы вы, а ошибся пользователь, результат может оказаться неожиданным. Лучше совсем отказаться от операции, но сделать это безопасно.
Дополнительные защитные меры
В языке Perl есть средства, которые позволяют обнаружить такого рода ошибки во время выполнения. Это так называемый «осторожный режим» (taint mode). Идея в том, что Perl не позволит передать непроверенные данные любой из перечисленных выше функций. Однако проверка выполняется только в осторожном режиме, поэтому, не включив его, вы не получите никаких преимуществ. Кроме того, вы можете случайно отключить этот режим, предварительно ничего не проверив. Есть и другие мелкие ограничения, поэтому лучше не полагаться только на этот механизм. Тем не менее это прекрасный инструмент для тестирования, и обычно стоит задействовать его в качестве одного из средств защиты.
Для стандартных вызовов API, с помощью которых происходит обращение к командным процессорам, имеет смысл написать собственные обертки, которые фильтруют входные данных по списку разрешенных символов и возбуждают исключение, если что–то не так. Это не должно быть единственным средством контроля, поскольку часто необходима более тщательная проверка. Однако в качестве первой линии обороны сойдет, к тому же и реализовать совсем нетрудно. Можно либо заменить «плохие» функции обертками прямо в библиотеке, либо пропустить исходный текст через простейшую программу поиска, найти все места, где они встречаются, и быстро провести контекстную замену.
Другие ресурсы
□ «How То Remove Metacharacters From User–Supplied Data in CGI Scripts»: www.cert.org/tech_tips/cgi_metacharacters.html
Резюме
Рекомендуется
□ Проверяйте все входные данные до передачи их командному процессору.
□ Если проверка не проходит, обрабатывайте ошибку безопасно.
Не рекомендуется
□ Не передавайте непроверенные входные данные командному процессору, даже если полагаете, что пользователь будет вводить обычные данные.
□ Не применяйте подход «все кроме», если не уверены на сто процентов, что учли все возможности.
Стоит подумать
□ О том, чтобы не использовать регулярные выражения для проверки входных данных; лучше написать простую и понятную процедуру проверки вручную.
Грех 6. Пренебрежение обработкой ошибок
В чем состоит грех
Безопасность подвергается серьезной угрозе, когда программист не уделяет должное внимание обработке ошибок. Иногда программа может оказаться в некорректном состоянии, но чаще все заканчивается отказом от обслуживания, так как приложение просто «падает». Эта проблема не утрачивает значимости даже в таких современных языках, как С# и Java, только в них аварийное завершение программы происходит из–за необработанного исключения, а не из–за того, что автор забыл проверить код возврата.
Суровая реальность такова: любая ненадежность программы, приводящая к краху или перезапуску, – это отказ от обслуживания, а следовательно, может угрожать безопасности, особенно если речь идет о сервере.
Очень часто причиной подобной ошибки становится некритическое копирование кода из какого–нибудь примера. Ведь обычно в примерах для простоты опускают проверку ошибок.
Подверженные греху языки
Уязвим любой язык, в котором функция извещает об ошибке с помощью кода возврата, например ASP, PHP, С и С++, а также языки, полагающиеся на исключения: С#, VB.NET и Java.