19 смертных грехов, угрожающих безопасности программ
Шрифт:
Иногда противнику удается манипулировать путями, чтобы стереть важный файл или изменить параметры его защиты в промежуток времени между проверкой и действиями, основанными на результатах проверки. Целый ряд проблем безопасности возникает при удаленном доступе к файлам, например по протоколу SMB (Server Message Block) или NFS (Network File System). Чаще такого рода ошибки возникают при работе с временными файлами, поскольку каталоги, в которые создаются временные файлы, обычно открыты для всех. Поэтому, воспользовавшись гонкой, противник может обманом заставить вас открыть файл, который он будет контролировать, даже если вы предварительно убедились,
Вторая распространенная ошибка получила название «а это вовсе не файл». Суть ее в том, что программа открывает нечто, считая, что это файл на диске, тогда как фактически это нечто является ссылкой на другой файл, именем устройства или канала.
И третья проблема состоит в том, что противник получает контроль над файлом, к которому не должен иметь доступа. В результате он может прочитать, а быть может, даже изменить конфиденциальные данные.
Подверженные греху языки
Любой язык, позволяющий обращаться к файлам, подвержен этому греху. А это все без исключения современные языки программирования!
Как происходит грехопадение
Как мы сказали, есть три возможные ошибки. Глубинная причина первой – «гонки» – заключается в том, что в большинстве современных операционных систем (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.