Защита систем. Чему «Звездные войны» учат инженера ПО — страница 49 из 68

Повторяемый вход

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

From: Darth Sideous

From: Darth Sideous

From: Senator Palpatane

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

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

Тем не менее другие синтаксические анализаторы могут выделить или токен ‘^From’, или токен ‘From:’ (Да, с конечным двоеточием или без него. Один из них является конвертом SMTP, который использовали старые почтовые серверы; другой – заголовок SMTP.)

Двусмысленные типы

Программы принимают ввод разными способами, включая стандартный или ввод из файлов, которые они явно открывают. Многие из них либо случайно, либо злонамеренно двусмысленны или переопределены. Например, когда имя файла начинается с тире, как предоставить его в качестве аргумента командной строки? («Как удалить файл с именем «-f»?») Неоднозначная семантика означает, что у программ должен быть способ устранять эту неоднозначность. Когда мы хотим помочь программе, мы можем использовать путь типа «./-f», а когда злоумышленник хочет ее запутать, он может использовать переопределение смысла.

Аналогичная проблема может возникнуть с файлами с точкой с запятой в именах или другими символами, которые оболочка обрабатывает как специальные. (Чтобы посмеяться, создайте файл с косой чертой в имени в Windows и поделитесь этой файловой системой с Unix-клиентом, если вам будет не лень перезагружать систему.) Проблема определения типа по входным данным является проблемой для Excel, поскольку гены официально переименовываются, чтобы Excel не рассматривал их как даты. Membrane Associated Ring-CH-Type 1 – это уже не March1, а MarchF1 [Whitwam, 2020].

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

Длина и счетчики

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

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

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

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

Семейство атак «контрабанда HTTP-запросов» использует именно такого рода несоответствия между длинами контента и передает кодирующие заголовки для отправки дополнительных заголовков сквозь HTTP-прокси.

Размазанный парсер

Размазанный парсер – это тот, который разбросан повсюду, в насмешливом контрасте с тем, который сконцентрирован. Когда парсер разбросан повсюду, Ворфу приходится бегать туда-сюда. Подождите, что здесь делает Ворф? Разве это не книга по «Звездным войнам»? Это показывает, что, когда парсер разбросан повсюду, сложнее понять, что он делает.

Размазанный парсинг также имеет тенденцию смешивать логику преобразования и бизнес-логику. Многие массивы кода со временем развивают стиль разбора, который сравнивают с пальбой из дробовика. Как вы увидите в разделе «Защита», рефакторинг (и изоляция) такого кода является чрезвычайно мощным ответом на повторяющиеся атаки в одну и ту же точку.

Вложенные форматы

Хорошо, когда брак – это «сон во сне», но сон во сне – это кошмар для разбора [Reiner, 1987]. Дело не только в том, что сны эфемерны и неструктурированны, но и во вложенности. Например, XML не имеет ограничений на то, насколько глубоко могут вкладываться элементы.

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

Внешние зависимости

Когда Дарт Вейдер говорит: «Будь со мной, и мы станем править галактикой как отец и сын», – он представляет, как отреагирует Люк. Это либо гибель Вейдера, либо его спасение, но, когда вы соединяете внешние документы со своими собственными, у вас появляется целая галактика новых проблем при разборе.

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

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

Путаница «код/данные»

Даже если парсер не выполняет код намеренно, часто случаются ошибки, когда код и данные путаются. Как это происходит? Это может произойти из-за того, что что-то перезаписывает стек, или потому, что входные данные не разобраны на токены так, как ожидает каждый синтаксический анализатор. В командах оболочки Unix точка с запятой (;) обычно разделяет команды и позволяет вводить более одной команды в командной строке. На самом деле, все эти проблемы являются проблемами синтаксического анализа. Поток битов помещается туда, куда хочет злоумышленник, и интерпретируется так, как он надеется.

Чрезмерно мощный ввод

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

Существует несколько переменных среды, таких как LD_LIBRARY_PATH и IFS, которые кардинально изменяют выполнение. LOCALE изменяет способ синтаксического анализа чисел. (Значение 1,33 – это «один и одна треть» или «один, запятая, тридцать три»? Первое – это общеевропейское представление; второе распространено в Северной Америке.) Заманчиво вычистить такие известные опасные переменные; см. раздел «Белые списки и черные списки» этой главы, чтобы понять, почему лучше выбирать входные данные, которые вы умеете анализировать.

Некоторые средства синтаксического анализа предназначены для исполнения кода по мере их работы. Есть что-то умиротворяющее в формате, который явно запускает код. Приятно, когда никто не может утверждать, что удивлен, если вредоносная программа делает вредоносные вещи – в конце концов, так было задумано. Форматы, в которых намеренно выполняется код, включают документы Microsoft Office (макросы), PDF-файлы (весь формат, по сути, является выполняемой программой), HTML с JavaScript и даже шрифты TrueType. По крайней мере, можно надеяться, что никто не удивится. Но люди все же часто удивляются. Возникает важный вопрос о том, кто спланировал это выполнение кода.

Есть и другие форматы, которые являются полными по Тьюрингу, то есть мы даже не можем определить, остановятся ли они, не говоря уже о том, какие эффекты породят. Есть вещи, которые на удивление полны по Тьюрингу, в том числе карточная игра Magic: The Gathering и PowerPoint – даже с отключенными макросами [Wildenhain, 2020]!

Иногда, конечно, приложение выигрывает от наличия программируемости или даже требует его. Очевидно, что это не относится к Magic: The Gathering, и можно спорить о том, справедливо ли это в отношении PowerPoint. Такие дебаты могут быть интересны с интеллектуальной точки зрения, но, что более важно, мы можем спросить, возможно ли достигнуть этих целей с меньшими полномочиями?

Интересным крайним случаем этого являются установщики пакетов, где некоторые пакеты запускают произвольный код во время установки, а другие приостанавливаются, чтобы запросить разрешение. PyPi неизбежно запускает функцию setup.py при установке кода, в то время как установщик пакетов Mac спрашивает: «Этот пакет запустит программу, чтобы определить, можно ли установить программное обеспечение». Здесь обычно ожидается, что будет запущен новый код, и менеджер пакета знает, что в итоге он получит все права вашей учетной записи. Диалог направлен на то, чтобы человек знал об этом, поскольку он продолжает: «Чтобы обеспечить безопасность вашего компьютера, вы должны запускать программы или устанавливать программное обеспечение только из надежного источника. Если вы не уверены в источнике этого программного обеспечения, нажмите „Отмена“, чтобы остановить программу и установку» [Lakshmanan, 2022].