Дэвид Лебланк - 19 смертных грехов, угрожающих безопасности программ
Стоит подумать
□ О применении менее распространенных защитных механизмов, встроенных в операционную систему, в частности о шифровании на уровне файлов.
□ Об использовании таких реализаций криптографических алгоритмов, которые препятствуют атакам с хронометражем.
□ Об использовании модели Белла–ЛаПадулы, предпочтительно в виде готового механизма.
Грех 14. Некорректный доступ к файлам
В чем состоит грех
Найти в программе грех некорректного доступа к файлам довольно трудно, он легко может ускользнуть от внимания. В этом направлении можно выделить три типичные проблемы безопасности. Первая – это «гонки»: между моментом проверки условий защиты для файла и моментом использования этого файла часто есть некоторое окно, когда файл уязвим. Гонка обычно проистекает из–за ошибок синхронизации, вследствие чего один процесс может вмешаться в работу другого, открывая брешь для атаки.
Иногда противнику удается манипулировать путями, чтобы стереть важный файл или изменить параметры его защиты в промежуток времени между проверкой и действиями, основанными на результатах проверки. Целый ряд проблем безопасности возникает при удаленном доступе к файлам, например по протоколу SMB (Server Message Block) или NFS (Network File System). Чаще такого рода ошибки возникают при работе с временными файлами, поскольку каталоги, в которые создаются временные файлы, обычно открыты для всех. Поэтому, воспользовавшись гонкой, противник может обманом заставить вас открыть файл, который он будет контролировать, даже если вы предварительно убедились, что такого файла нет. Если вы вообще ничего не проверяете, а просто полагаетесь на уникальность имени файла, то будете неприятно удивлены, обнаружив, что противник контролирует файл с таким же именем. Раньше это была серьезная проблема в некоторых библиотечных функциях в UNIX, поскольку они генерировали детерминированные имена временных файлов, которые противник мог предсказать.
Вторая распространенная ошибка получила название «а это вовсе не файл». Суть ее в том, что программа открывает нечто, считая, что это файл на диске, тогда как фактически это нечто является ссылкой на другой файл, именем устройства или канала.
И третья проблема состоит в том, что противник получает контроль над файлом, к которому не должен иметь доступа. В результате он может прочитать, а быть может, даже изменить конфиденциальные данные.
Подверженные греху языки
Любой язык, позволяющий обращаться к файлам, подвержен этому греху. А это все без исключения современные языки программирования!
Как происходит грехопадение
Как мы сказали, есть три возможные ошибки. Глубинная причина первой – «гонки» – заключается в том, что в большинстве современных операционных систем (Windows, Linux, Unix, Max OS X и прочих) приложение не изолировано, как может показаться. В любой момент его работу может прервать другой процесс, а приложение к этому может оказаться не готовым. Другими словами, некоторые файловые операции не являются атомарными. Гонка может привести к эскалации привилегий или отказу от обслуживания из–за краха или взаимной блокировки.
Классический сценарий выглядит так: программа проверяет, существует ли файл, затем обращается к нему так, как диктует результат проверки. Примеры будут приведены ниже.
Другой вариант греха – открыть файл с переданным именем, не проверив, к чему это имя относится. В операционных системах типа Linux, Unix и Max OS X такая уязвимость проявляется обычно при неправильной работе с символическими ссылками. Программа думает, что открывает файл, тогда как на самом деле противник подсунул ей символическую ссылку. Это может привести к печальным последствиям, если процесс работает от имени пользователя root, так как root может удалить любой файл.
И наконец, противник может получить контроль над файлами, к которым обращается программа. Если приложению доступна некоторая конфиденциальная информация (например, имена других пользователей или системная база данных паролей), то вряд ли вы захотите показывать ее противнику. Наткнуться на такую ловушку можно, в частности, если приписать в начало имени файла, полученного из не заслуживающего доверия источника, некий «зашитый» в программу путь, например в случае Unix–машины – «/var/myapp/». Если библиотечные функции умеют разрешать относительные пути, то противник может подсунуть, например, такое имя: «../../etc/passwd». Это плохо, если приложению разрешено читать системные файлы, и уж совсем плохо, если оно может в них писать. Описанная техника называется «атакой с проходом по каталогам».
Греховность C/C++ в Windows
В следующем фрагменте разработчик рассчитывал на нормального пользователя, полагая, что тот укажет обычное имя файла, но забыл, что бывают и другие представители рода человеческого. Если такой код является частью серверной программы, то дело может закончиться плохо. Ведь если противник задаст имя устройства (например, порта принтера: lptl), то сервер перестанет отвечать до тех пор, пока устройство не вернет управление по тайм–ауту.
...void AccessFile(char * szFileNameFromUser) {
HANDLE hFile =
CreateFile(szFileNameFromUsers,
0, 0,
NULL,
OPEN_EXISTING,
0,
NULL);
Греховность C/C++
Следующий код дает классический пример гонки за доступ к файлу. Между обращениями к access(2) и ореп(2) операционная система может переключиться на другой процесс. Если в течение этого промежутка времени файл /tmp/splat будет удален, то приложение завершится аварийно.
...#include «sys/types.h»
#include "sys/stat.h"
#include "unistd.h"
#include "fcntl.h"
const char *filename = "/tmp/splat";
if (access(filename, R_OK) == 0) {
int fd=open(filename, O_RDONLY);
handle_file_contents(fd);
close(fd);
} else {
// обработать ошибку
}
Греховность Perl
И снова программа обращается к файлу по имени. Она определяет, разрешено ли читать файл пользователю, запустившему сценарий, и если это так, то читает его содержимое. Греховность, как и в предыдущем примере на C/C++, заключается в том, что между проверкой и чтением файл мог исчезнуть.
...#!/usr/bin/perl
my $file = "$ENV{HOME}/.config";
read_config($file) if -r $file;
Греховность Python
А здесь ошибка не так очевидна:
...import os
def safe_open_file(fname, base="/ver/myapp"):
# Убрать '..' и '.'
fname = fname.replace('../', '');
fname = fname.replace('./', '');
return open(os.path.join(base, fname))
Программа пытается воспрепятствовать атаке с проходом по каталогам. Но есть две проблемы. Во–первых, удаление недопустимых символов в данном случае представляется неверной стратегией. Если обнаружено две точки, то почему сразу не завершить программу – ведь этого не должно быть?
Во–вторых, метод replace не достигает той цели, которую поставил перед собой автор кода. Что произойдет, если противник подсунет такую строку: «…/….///»? А вот что:
□ При первом обращении к replace () будут произведены две замены и останется «…///».
□ При втором обращении к replace () будет произведена одна замена и останется «../».
Сюрприз!
Родственные грехи
Если говорить о гонках, то этот грех очень близок к греху 16, но проблематика доступа к файлам не исчерпывается одними лишь гонками. Прочитав эту главу, сразу же познакомьтесь с грехом 16.
Где искать ошибку
Вашу программу можно заподозрить в грехе, если:
□ она обращается к файлам, имена которых задаются извне;
□ она обращается к файлам исключительно по именам, а не по описателям или дескрипторам;
□ она открывает временные файлы в общедоступных каталогах, причем имя временного файла можно предсказать.
Выявление ошибки на этапе анализа кода
Простейший способ обнаружить этот грех во время анализа кода – найти все функции ввода/вывода, в особенности те, где используются имена файлов. Выявив такую функцию, задайте себе следующие вопросы:
□ Откуда поступает имя файла? Можно ли ему доверять?
□ Используется ли это имя файла более одного раза для проверки существования и манипулирования файлом?
□ Находится ли файл в той части файловой системы, к которой потенциально может иметь доступ противник?
□ Может ли противник задать имя так, чтобы оно указывало на файл, к которому он не должен иметь доступа?
Вот перечень типичных функций и операторов ввода/вывода, которые следует искать в программе.
Использование этих функций не обязательно приводит к каким–либо проблемам. Например, в современных системах Unix самые популярные функции создания временных файлов атакам не подвержены. Но если ваша программа работает в слегка устаревшей системе, то неприятности возможны.
Тестирование
Самый простой способ найти ошибки типа «это не файл» и «проход по каталогам» – подать на вход приложения случайные имена файлов и посмотреть, как оно будет реагировать. В частности, попробуйте такие имена: