Парсеры также подвержены атакам типа «отказ в обслуживании». Когда первым шагом в обработке формата является его распаковка, это может привести к отказу в обслуживании либо памяти, либо процессора еще до того, как мы перейдем к другим угрозам, рассмотренным ранее. Общую постановку вопроса можно найти в главе 5 «Отказ в обслуживании и доступность».
Последняя угроза для отдельных парсеров – это плохие советы. Существует множество плохих советов, наверняка и я дал какое-то количество таких. Я не хочу бросать камни, живя в стеклянном доме, но хочу подготовить вас к этому, чтобы, столкнувшись с угрозами темной стороны, готовы были вы, юные джедаи.
К плохим советам относятся «тщательный синтаксический анализ», «канонизация» и «использование типобезопасного языка». На самом деле это разумные отправные точки, но их недостаточно. Не совсем понятно, что такое тщательный разбор. Канонизация – это хорошо, но нам нужно убедиться, что использование данных соответствует правилам, по которым они были канонизированы: данные, нормализованные к виду URL-адреса, все равно могут быть не ожидаемым путем к файлу. Типобезопасные языки— это здорово, но не все угрозы приводят к путанице типов. Каждая из этих банальностей плоха, потому что они склоняют нас к самоуспокоенности. Они хороши, но недостаточно хороши.
По-настоящему плохие советы – это «очищать» и «это просто десериализация». Очистка описана в разделе «Валидация входа». Десериализация – это парсинг, который несет в себе все сложности любого другого парсинга.
Системы часто строятся из меньших систем, и каждая из этих систем может иметь синтаксический анализатор. На самом деле, внедрение SQL-кода служит хорошим примером этого. Веб-сервер имеет синтаксический анализатор HTTP, а база данных – синтаксический анализатор SQL. Они связаны в цепочку, и цепочка может иметь формально определенную грамматику или контракт. (Если это не так, то кто-то почти наверняка будет удивлен переданными данными; это одна из причин, почему фаззинг, обсуждаемый далее в этой главе, так эффективен.)
Каждый последующий синтаксический анализатор должен четко указывать, что он распознает и передает. Например, если у нас есть номер телефона в сообщении HTTP, то, возможно, парсер HTTP проверяет, что он не превышает 20 символов, и передает его, но не пытается проверить, что номер телефона и адрес находятся в одной стране.
Недостаточно сказать: «Синтаксический анализатор HTTP пропустил это…» – и, конечно, опасно продолжать фразу: «…следовательно, это безопасно». Безопасность исходит из точного понимания того, для чего будет использоваться переменная, поле или элемент объекта, а не из необоснованных предположений о том, что это будет. Например, синтаксический анализатор XML может пропустить файл с еще не полученными удаленными файлами, размещенными в sith.org. Возможно, они уже были извлечены и интегрированы, и больше удаленные включения невозможны. Объект может содержать набор эксплойтов в виде CDATA, которые не были дополнительно проанализированы. При использовании объекта, создаваемого синтаксическим анализатором XML, должен существовать четкий контракт о том, что выдается и что может быть использовано.
Использование этих битов может включать вызов другого API. Когда вы это делаете, консервативность в отправке данных является базовым уровнем надежности. Включение элементов ваших входных данных может быть неизбежным, и тогда вам не хватит контекста для проверки или валидации. В той мере, в какой вы можете рассказать о том, что вы сделали, склонность к слепому доверию будет ограничена.
Слои
Подобно тому, как все входные данные представляют собой биты, а интерпретация этих битов является многослойной, атаки обычно осуществляются на многих уровнях кодирования. Нижние слои, вероятно, не могут идеально анализировать данные, предназначенные для более высоких слоев. Они не могут работать на сетевом уровне из-за структуры пакетов и других сложностей [Ptacek, 1998] и не должны этого делать из-за скорости и шаблона Single Parser: они, скорее всего, ошибутся. (Шаблон описан в разделе «Защита».)
В качестве конкретного примера рассмотрим фишинговое письмо. Его отправляет тот, кто занимается фишингом, на ваш почтовый сервер, и оно выглядит как SMTP-сообщение в SMTP-конверте, отправленное по протоколу STARTTLS, который реализует SMTP поверх TLS. Это TLS-сообщение находится в наборе кадров TCP, разбитых на IP-пакеты и далее фрагментированных на кадры Ethernet. Должен ли коммутатор Ethernet пытаться проверить IP-пакеты? Должен ли маршрутизатор валидировать TCP-поток? Должен ли коммутатор Ethernet проверять сообщение электронной почты на наличие URL-адресов в каком-либо списке фишинговых сайтов?
Многие устройства безопасности, такие как брандмауэры или системы обнаружения вторжений, пытаются нарушить многослойность из самых лучших побуждений. И в той мере, в какой они ловят наивных злоумышленников, это нормально. Но история этой подобласти – это история обходных атак. Почти все эти атаки в своей основе имеют дело со сложностью идеального понимания и воспроизведения того, как будет работать принимающий синтаксический анализатор.
Конкретные сценарии угроз распознавания
На самом низком уровне устройств интернета вещей может быть загрузчик, который передает управление чему-либо, часто интегрированному компилятором в единый образ. Эти системы с меньшей вероятностью будут иметь защиту памяти (описанную в разделе «Средства защиты»), поэтому синтаксический анализ кода, который считается более безопасным в современных операционных системах, может быть более рискованным для интернета вещей.
Вообще говоря, сетевые протоколы, API и форматы документов – все это соглашения о том, как различные программы могут взаимодействовать. Соглашение о форматах и содержании сообщений позволяет сторонам обмениваться информацией. При определении нового протокола тщательная спецификация обходится дорого и замедляет работу. Таким образом, мы склонны к неформальности и гибкости, потенциально беря на себя огромную ответственность за проектные несовершенства.
Когда у нас есть более одной кодовой базы, обрабатывающей API или формат файла, мы в итоге сталкиваемся с проблемами совместимости. Они множат проектные огрехи. Каждый раз, когда контрагент реализует что-то слегка другое, у нас есть выбор: очевидная многобаговость строгости или совместимость с сопутствующими рисками и сложностью кода.
В качестве примера проблемы с документами можно привести то, как Microsoft и Adobe прошли через процесс улучшения своих средств синтаксического анализа документов для решения многочисленных проблем безопасности (в Office и PDF соответственно). Каждый из них столкнулся с возмущением из-за «антиконкурентного» поведения, когда перестал читать «неправильно оформленные» документы. Каждый процесс был инициирован для обеспечения безопасности и приводил к тому, что конкурентам приходилось тратить деньги на переписывание того, что они считали совершенно прекрасным кодом.
Замена ранних API обработки строк библиотеки C создала аналогичную проблему совместимости. Было недопустимо сломать каждую программу, которая вызывала strcpy, поэтому в библиотеки были добавлены новые функции. Каждый программист должен был переписать свой код, чтобы использовать эти новые функции.
Последний пример компромисса между безопасностью и совместимостью заключается в том, что в интернете за 30 лет накопился плохо написанный HTML. Браузер, который строго анализирует HTML и отображает только то, что соответствует стандартам, будет отображать очень малую часть этого. Многие из авторов кода… что ж, светоч их разума угас во Вселенной. Никто не будет переписывать эту огромную кипу документов, и нам с ней жить.
Если синтаксические анализаторы недостаточно осторожны со своими данными, это может привести к порче памяти.
Мириады способов, которыми злоумышленники используют эту порчу, завораживают. Эта глава не является руководством по эксплуатации, написанию эксплойтов или использованию эксплойтов в качестве оружия, то есть обеспечению их надежной работы в различных обстоятельствах. Несмотря на то что я не хочу перегружать вас подробностями, стоит совершить краткий обзор. Моя цель состоит в том, чтобы закрепить ваше понимание на важном моменте: злоумышленник, получивший контроль даже над одним битом, исключительно опасен.
Для кого-то это может стать воротами в новый мир. Если вы хотите по-настоящему понять, что делает компьютер, эти методы раскрывают анатомию систем. Классические книги, такие как The Shellcoder’s Handbook («Справочник по программированию оболочек») [Anley, 2011] и «Взлом программного обеспечения: анализ и использование кода» (Exploiting Software) [Hoglund, 2004], хорошо объясняют детали.
• Атаки с разбиением стека включают запись в исполняемую память. Первые публичные демонстрации перезаписывали стек выполнения, в частности указатель на следующую функцию. Термин «разбиение стека» используется и как короткое название для перезаписи памяти, и конкретно для обозначения проблем, связанных с перезаписью стека.
• Атаки типа Возврат к libc изменяют поток управления к выбранной злоумышленником, но существующей памяти. Стандартная библиотека C является популярной целью, потому что в ней есть такие вызовы, как system() и exec(), которые позволяют выполнять произвольные команды.
• Возвратно-ориентированное программирование – это набор методов, которые используют код, уже находящийся в пространстве (исполняемой) памяти, связывая его воедино для достижения цели злоумышленника. Код в памяти обрабатывается как «гаджеты», и гаджеты получают неожиданные входные данные. Поскольку гаджеты находятся в исполняемой памяти, это позволяет обойти многие методы защиты памяти,