Модели уязвимостей, используемые при статическом и динамическом анализе веб-приложений
Цель и статического и динамического анализа – выявление интересующих нас свойств программы. Актуальным свойством для выявления являются уязвимости. Однако для того, чтобы что-то найти, надо сначала это что-то формально определить. В данном посте рассматриваются формальные модели уязвимостей в ПО.
Итак, мы решили найти уязвимости в программе. Возникает вопрос, как переформулировать классическое определение уязвимости (свойство, позволяющее нарушить конфиденциальность, целостность или доступность информационных ресурсов ) в терминах представлений программы (см. пост по представлениям)? Давайте попробуем разобраться.
Фундаментальное определение уязвимости в ПО было дано Деннинг аж в 1975 году и потом было развито в моделях невыводимости и невмешательства, а также их расширениях (хороший обзор есть вот тут). Больше всего нас интересует модель невмешательства. Вкратце, модель определяет уязвимость так:
Пусть программа получает данные из high-level (доверенных) и low-level (недоверенных) каналов и генерирует вывод в high-level (критические) и low-level (обычные) каналы. Вывод low-level (недоверенных) данных в high-level (критический) канал считается нарушением принципа невмешательства. 2
С этим определением уже можно работать - надо только определить, что в программе мы будем считать каналами ввода и каналами вывода.
Самый простой вариант, известный как taint analysis (или taint checking), решает задачу в лоб: недоверенные каналы ввода - это методы, возвращающие данные HTTP-запросов, а критические каналы вывода - это вызов критических функций (нам надо явно указать, каких). Модель уязвимости выглядит так:
- данные, полученные из HTTP-запросов, считаются ненадежными; это low-level каналы ввода;
- данные, полученные из локального хранилища данных (файловая система, СУБД), считаются надежными; это high-level каналы ввода;
- ненадежные данные могут стать надежными в следствие специальной обработки (фильтрации);
- ненадежные данные не должны попадать в критические операции (запросы к СУБД, вывод HTML, системные вызовы, eval'ы и т.д.); это high-level каналы вывода.
Это значит, для применения модели для конкретного языка и конкретного веб-фреймворка надо сделать небольшое исследование. Во-первых, найдо перечислить все конструкции, возвращающие данные HTTP-запросов (например, для PHP к этим конструкциям будут относится обращения к массивам $_GET, $_POST, $_COOKIE и т.д.) и указать их анализатору. Соответственно, анализатор будет помечать переменные, получившие свое значение из таких конструкций, как "tainted". В графе зависимостей по данным вершины, соответствующие такой инициализации, будут раскрашиваться, например, в красный цвет.
Во-вторых, надо перечислить все критические операции, аргументы для которых мы бы не хотели отдавать под контроль злоумышленника. Для PHP в этот список войдут, например, mysql_query, echo, system, eval и т.д. В графе зависимостей по данным вершины, соответствующие вызовам этих функций, будут раскрашиваться, например, в синий цвет.
В-третьих, надо перечислить все фильтрующие конструкции, которые нормализуют, а потом удаляют, экранируют или кодируют специальные символы в пользовательском вводе. Для PHP в этот список войдут, например, htmlspecialchars, addslashes, escapeshellcmd.3
Как видно, определение уязвимости в рассмотренной модели идеально ложится на представление программы в виде графа зависимостей. Фактически, задача наличия уязвимости сводится к нахождению пути в графе от вершины, помеченной как "инициализация от пользовательского ввода" (красная), до вершины (синей) вызова критической операции, между которыми нет вершины (желтой) вызова фильтрующей функции.
Второй вариант претворения базового определения невмешательства в жизнь основан на следующем наблюдении. Все веб-приложения в процессе своей работы взаимодействуют с окружением: СУБД, LDAP, браузером клиента, файловой системой, стандартным интерпретатором ОС и т.д. Многие из этих элементов окружения предоставляют специальный язык (в общем смысле этого понятия) для взаимодействия с ними: СУБД - язык SQL, LDAP - язык запросов LDAP, браузер - HTML/CSS/JavaScript/расширения, файловая система - язык именования объектов (пути), стандартный интерпретатор ОС - *nix shell/CMD shell/whatever. Большинство обращений веб-приложений к окружению параметризованы, а параметры этих обращений зависят от пользовательского ввода.
Заметим теперь, что все уязвимости к внедрению кода (HTML, SQL, OS, и т.д.) позволяют злоумышленнику изменять структуру соответствующего обращения к окружению. А это значит, что дерево синтаксического разбора соответствующего обращения при внедренном коде и без внедренного кода будет различным не только в листьях (потому что запрос параметризован), но и на более верхних ярусах. Это тезис очень хорошо проиллюстрирован на рисунке ниже.4 На нем показаны два дерева разбора WHERE-клаузы в SQL-запросах с внедрением (а) и без внедрения (b):
SELECT cardnum FROM accounts WHERE uname='John' AND cardtype=2
SELECT cardnum FROM accounts WHERE uname='John' AND cardtype=2 OR 1=1
Таким образом, мы приходим к формулировке принципа невмешательства в следующем виде. Пусть у нас есть язык взаимодейтсвия с окружением. Если злоумышленник может изменить структуру дерева разбора для некоторого обращения к окружению, то в приложении содержится уязвимость. 5
В следующем посте я расскажу об ограничениях этих моделей уязвимостей, а потом о реализациях этих моделей в рамках статического и динамического анализа.
Note 1. На самом деле, здесь есть небольшое лукавство. Статические анализаторы реально ищут не уязвимости, а ошибки, которые могут стать (а могут и не стать) уязвимостями. Пример ошибок в программе, которые не являются уязвимостями:
1. Допустим, у нас есть веб-приложение, со следующими характеристиками:- не имеет авторизации, все интерфейсы доступны в public;
- приложение предоставляет функции по поиску и фильтрации результатов футбольных матчей;
- приложение обращается в СУБД только к одной таблице, с теми самыми результатами матчей;
- для этого в СУБД заведен специальный пользователь, которому даны права только на выполнение SELECT только в этой таблице;
- возможные ошибки СУБД корректно обрабатываются - пользователь получает обычную HTML-страницу с некими общими словами "все плохо, попробуйте еще раз".
Пусть мы в исходном коде нашли, что у пользователей веб-приложения имеется возможность осуществить внедрение операторов SQL. И что? А ничего: ни доступность, ни целостность, ни конфиденциальность ресурсов веб-приложения нарушить не получится. При этом информация об окружении, в котором работает данное приложение статическому анализатору, вообще говоря, не доступна, так что у него нет шансов определить, является ли ошибка уязвимостью, или нет.
2. Еще один пример - это возможность переполнения буфера в программе ls. Ошибка программистов? Безусловно. Уязвимость? Ниразу...
Note 2. Изначально принцип невмешательства формулировался по-другому: он запрещал попадание конфиденциальных данных в недоверенный канал. Потом, однако, умные люди воспользовались идеей Биба о дуализме конфиденциальности и целостности данных, и принцип невмешательства был расширен.
Note 3. На самом деле, есть ситуации, в которых даже при использовании этих функций программа остается уязвимой к соответствующим атакам. Более подробно об этом можно почитать тут, тут и тут.
Note 4. Рисунок взят из статьи "The Essence of Command Injection Attacks in Web Applications".
Note 5. Получается, что low-level канал вывода - это нетерминалы языка, из которых выводятся только строковые и числовые константы, а high-level канал вывода - это нетерминалы языка, из которых выводятся терминалы (например, ключевые слова). В low-level канал пользователи веб-приложений могут писать сколько угодно, а вот в high-level - никак. В него может записывать только программист, задающий структуру запросов.

