Мечтают ли WAF’ы о статанализаторах

в 14:29, , рубрики: Application Firewall, Application Inspector, SAST, waf, Блог компании Positive Technologies, защита веб-приложений, информационная безопасность, статический анализ кода

Мечтают ли WAF’ы о статанализаторах - 1 Одна из самых популярных тенденций в области защиты приложений нынешнего десятилетия — технология виртуального патчинга (virtual patching, VP), позволяющая защитить веб-приложение от эксплуатации имеющихся в нем известных уязвимостей на уровне межсетевого экрана уровня веб-приложений (web application firewall; здесь и далее под WAF подразумевается выделенное решение, функционирующее на отдельном узле, между шлюзом во внешнюю сеть и веб-сервером). Технология VP основана на построении правил фильтрации HTTP-запросов на стороне WAF по результатам работы средств статического анализа защищенности приложения (static application security testing, SAST). Однако из-за того, что средства SAST и WAF опираются на различные модели представления приложения и различные методы принятия решений, на рынке до сих пор нет по-настоящему эффективных решений их интеграции. В рамках SAST работа с приложением осуществляется по модели белого ящика и, как правило, используются формальные подходы к поиску уязвимостей в коде. Для WAF же приложение представляет собой черный ящик, а для детектирования атак применяются эвристики. Это не позволяет эффективно использовать VP для защиты от атак в тех случаях, когда условия эксплуатации уязвимости выходят за рамки тривиальной схемы `http_parameter=plain_text_attack_vector`.

Но что, если «подружить» SAST и WAF таким образом, чтобы информация о внутреннем устройстве приложения, полученная с помощью SAST, стала доступной на стороне WAF и дала ему возможность детектировать атаки на обнаруженные уязвимости — не угадывая, но доказывая факт атаки?

Блеск и нищета традиционного VP

Традиционный подход к автоматизации создания виртуальных патчей для веб-приложений заключается в предоставлении WAF информации о каждой обнаруженной с помощью SAST уязвимости, включающей:

  • класс уязвимости;
  • уязвимую точку входа в веб-приложение (URL или его часть);
  • значения дополнительных параметров HTTP-запроса, при которых атака становится возможной;
  • значения уязвимого параметра — носителя вектора атаки;
  • множество символов или слов (токенов), появление которых в уязвимом параметре приведет к эксплуатации уязвимости.

Для определения множеств значений параметров HTTP-запроса и опасных элементов уязвимого параметра могут использоваться как простое перечисление всех возможных элементов, так и генерирующая функция (как правило, на базе регулярных выражений). Рассмотрим фрагмент кода ASP.NET-страницы, уязвимый для атак XSS:

01  var condition = Request.Params["condition"];
02  var param = Request.Params["param"];
03
04  if (condition == null || param == null)
05  {
06      Response.Write("Wrong parameters!");
07      return;
08  }
09
10  string response;
11  if (condition == "secret")
12  {
13    response = "Parameter value is `" + param + "`";
14  }
15  else
16  {
17    response = "Secret not found!";
18  }
19
20  Response.Write("<b>" + response + "</b>");

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

{condition = «secret» ⇒ param ∈ { XSShtml-text }}, где XSShtml-text — множество возможных векторов XSS-атаки в контексте TEXT, описанном в грамматике HTML.

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

  • для доказательства наличия уязвимости средству SAST достаточно обнаружить один из возможных векторов атак на нее. Для эффективного устранения уязвимости необходимо защититься от всех возможных векторов, которые бывает затруднительно сообщить на сторону WAF, поскольку их множество не только бесконечно, но и зачастую не может быть выражено регулярными выражениями в силу нерегулярности грамматик векторов атак;
  • то же самое касается и значений всех дополнительных параметров запроса, при которых становится возможна эксплуатация уязвимости;
  • информация об опасных элементах уязвимого параметра бесполезна в том случае, если на пути от точки входа до уязвимой точки выполнения вектор атаки подвергается промежуточным преобразованиям, изменяющим контекст его грамматики или даже всю грамматику (например, Base64-, URL- или HTML-кодирование, строковые преобразования).

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

01  var condition = Request.Params["condition"];
02  var param = Request.Params["param"];
03 
04  if (condition == null || param == null)
05  {
06      Response.Write("Wrong parameters!");
07      return;
08  }
09 
10  string response;
11  // CustomDecode реализует цепочку преобразований base64-URL-base64
12  if (CustomDecode(condition).Contains("secret"))
13  {
14      response = "Parameter value is `" + CustomDecode(param) + "`";
15  }
16  else
17  {
18       response = "Secret not found!";
19  }
20 
21  Response.Write(response);

Разница с предыдущим примером лишь в том, что теперь оба параметра запроса подвергаются некоторому преобразованию и условие на параметр `secret` ослаблено до включения подстроки. Формула вектора атаки в результате анализа этого кода примет вид:

(String.Contains (CustomDecode (condition)) («secret»)) ⇒ param ∈ (CustomDecode { XSShtml-text }).

При этом для функции CustomDecode в соответствующей вершине CompFG анализатором будет выведена формула, описывающая цепочку преобразований Base64-URL-Base64:

(Base64Decode (UrlDecode (Base64Decode argument))).

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

  • эксплуатация уязвимости возможна только в том случае, если декодированный параметр запроса `condition` будет содержать подстроку «secret» (строка 12), но множество значений такого параметра весьма велико, а выразить его через регулярные выражения затруднительно из-за нерегулярных функций декодирования;
  • параметр запроса, являющийся вектором атаки, также подвергается декодированию (строка 14), что не позволяет средству SAST сформировать для WAF множество его опасных элементов.

Поскольку все проблемы традиционного VP растут из отсутствия возможности работать с приложением на уровне WAF по модели белого ящика, очевидно, что для их устранения необходимо реализовать такую возможность и доработать подход таким образом, чтобы:

  • средство SAST предоставляло WAF полную информацию обо всех преобразованиях, которым подвергаются уязвимый параметр и переменные условий успешной атаки на пути от точки входа до уязвимой точки, чтобы WAF получил возможность вычислять значения аргументов в ней, исходя из значений параметров обрабатываемого HTTP-запроса;
  • для детектирования атаки использовались не эвристические, а формальные методы, основанные на строгом доказательстве тех или иных утверждений и покрывающие общий случай условий эксплуатации каждой конкретной уязвимости — вместо ограниченного множества частных.

Так и родилась технология виртуального патчинга времени выполнения.

Runtime virtual patching

В основе технологии runtime virtual patching (RVP) лежит используемая в анализаторе исходных кодов PT Application Inspector (PT AI) модель исследуемого приложения под названием «граф потоков вычисления» (computation flow graph, CompFG). Эта модель была подробно описана в рамках мастер-класса «Трущобы AppSec» на PHDays VII. CompFG строится во время анализа приложения в результате абстрактной интерпретации его кода в семантике, схожей с традиционными символическими вычислениями. Вершины данного графа содержат генерирующие формулы на целевом языке, задающие множества допустимых значений всех потоков данных, присутствующих в соответствующих точках выполнения. Эти потоки называются аргументами точки выполнения. Например, вершина уязвимой точки выполнения рассмотренного выше примера в CompFG выглядит так:

Мечтают ли WAF’ы о статанализаторах - 2

Одним из свойств CompFG является его конкретизируемость — возможность вычислить множества конкретных значений всех аргументов в любой точке выполнения приложения, задав значения для всех входных параметров.
Рабочий процесс RVP делится на два этапа, соответствующих этапам жизненного цикла приложения — развертывание (шаги D) и выполнение (шаги R):

Мечтают ли WAF’ы о статанализаторах - 3

Этап развертывания

Перед развертыванием очередной версии приложения осуществляется его анализ с помощью PT AI, в результате которого из каждой вершины CompFG, описывающей уязвимую точку выполнения, выводятся три формулы:

  • условие достижимости самой точки;
  • условие достижимости значений всех ее аргументов;
  • множества значений всех ее аргументов и грамматик, которым они соответствуют.

Все наборы формул группируются по признаку принадлежности уязвимости к потоку управления той или иной точки входа в веб-приложение. Само понятие точки входа специфично для каждого из поддерживаемых PT AI веб-фреймворков и определено в базе знаний анализатора.
После этого отчет c обнаруженными уязвимостями и относящимися к ним формулами выгружается в виде кода на специальном языке предметной области, основанном на синтаксисе S-выражений и позволяющем описывать формулы CompFG в форме, не зависящей от целевого языка. Формула значения аргумента уязвимой точки рассмотренного ранее примера кода выглядит следующим образом:

(+ «Parameter value is `» (FromBase64Str (UrlDecodeStr (FromBase64Str (GetParameterData (param))))) "`"),

а формула условия ее достижимости:
(Contains (FromBase64Str (UrlDecodeStr (FromBase64Str (GetParameterData (condition))))) «secret»).

Полученный отчет загружается в PT Application Firewall (PT AF), и на его основе генерируется бинарный модуль, позволяющий вычислять все присутствующие в нем формулы. Декомпилированный код расчета условия достижимости уязвимой точки рассмотренного примера выглядит так:

Мечтают ли WAF’ы о статанализаторах - 4

Для того, чтобы вычисление формул было возможным, на стороне PT AF необходимо иметь (на выбор):

  • некоторую базу вычислителей всех функций, которые могут появиться в отчете;
  • изолированную песочницу со средой выполнения для языка или платформы, на которой работает защищаемое приложение (CLR, JVM, интерпретатор PHP, Python или Ruby и т. п.), и библиотеками, которые используются в приложении.

Первый вариант дает максимальное быстродействие, но предполагает огромный объем ручной работы со стороны разработчиков WAF по описанию вычислителей (даже если ограничиваться только функциями стандартных библиотек). Второй вариант дает возможность вычислять все функции, которые могут встретиться в отчете, но и увеличивает время обработки каждого HTTP-запроса из-за необходимости вызова среды выполнения для вычисления каждой функции. Оптимальным здесь является вариант, при котором для наиболее часто встречающихся функций используется первый вариант, а все остальные вычисляются с помощью второго.
Вполне возможна ситуация, когда в формуле встретится функция, в которую анализатор не сможет «провалиться» (например, вызов метода, относящегося к отсутствующей зависимости проекта или к native-коду) и (или) вычисление которой также невозможно на стороне PT AF (например, функция чтения данных из внешних источников или окружения сервера). Такие функции отмечаются в формулах флагом unknown и обрабатываются особым образом (см. ниже).

Этап эксплуатации

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

  • каждый флаг unknown распространяется по дереву выражений формулы снизу вверх до тех пор, пока им не будет отмечено какое-либо булево выражение;
  • все такие выражения (unknown-области) заменяются в формуле на булевы переменные и для полученной формулы решается задача булевой выполнимости;
  • из исходной формулы условия конструируются n условий — путем подстановки возможных значений unknown-областей из всех найденных на предыдущем шаге решений;
  • вычисляется значение каждой из полученных формул, и если хотя бы одна из них оказалась выполнима, то исходное условие также считается выполнимым.

Если в результате вычисления было получено ложное значение исходной формулы, то это значит, что данный HTTP-запрос не может привести приложение в уязвимую точку с опасными значениями всех ее аргументов. В этом случае RVP просто возвращает обработку запроса основному модулю WAF.
В случае выполнимости условий атаки на уязвимость наступает очередь вычисления значения аргумента уязвимой точки. Используемый для этого алгоритм зависит от класса уязвимости, к которому относится обрабатываемая точка. Общей для них является только логика обработки формул, содержащих unknown-ноды: в отличие от формул условий, такие формулы аргументов не могут быть вычислены каким-либо образом, о чем сразу сообщается WAF — и затем осуществляется переход к вычислению следующей уязвимой точки. В качестве примера рассмотрим наиболее сложный из алгоритмов, используемый для детектирования атак класса инъекций.

Детектирование инъекций

К классу инъекций относятся любые атаки, целью которых является нарушение целостности текста на каком-либо формальном языке (HTML, XML, JavaScript, SQL, URL, файловые пути и т. п.), формируемого на основе данных, контролируемых атакующим. Атака осуществляется через передачу приложению специально сформированных входных данных, подстановка которых в атакуемый текст приведет к выходу за пределы токена и внедрению в текст синтаксических конструкций, не предусмотренных логикой приложения.
В том случае, если текущая уязвимая точка приложения относится к данному классу атак, значение ее аргумента рассчитывается по алгоритму так называемого инкрементального вычисления с абстрактной интерпретацией в семантике taint-анализа. Суть данного алгоритма заключается в том, что каждое выражение формулы рассчитывается отдельно, снизу вверх, причем результат вычисления, полученный на каждом шаге, дополнительно размечается границами «загрязненности», исходя из семантики каждой вычисленной функции и правил традиционного taint-анализа. Это позволяет выделить в конечном результате вычисления все фрагменты, которые были получены в результате каких-либо преобразований входных данных (tainted-фрагменты).
Например, для приведенного выше кода и HTTP-запроса с параметрами `?condition=YzJWamNtVjA%3d&param=UEhOamNtbHdkRDVoYkdWeWRDZ3hLVHd2YzJOeWFYQjBQZyUzRCUzRA%3d%3d` результат применения этого алгоритма для формулы аргумента уязвимой точки будет выглядеть следующим образом (красным отмечены tainted-фрагменты):

Мечтают ли WAF’ы о статанализаторах - 5

Далее полученное значение разбивается на токены в соответствии с грамматикой аргумента уязвимой точки, и если на любой из tainted-фрагментов пришлось более одного токена, то это и является формальным признаком детектированной атаки (по определению инъекции):

Мечтают ли WAF’ы о статанализаторах - 6
По окончании вычисления формул всех уязвимостей, относящихся к текущей точке входа, обработка запроса передается в основной модуль WAF вместе с результатами детектирования.

Преимущества и особенности RVP

Реализованный таким образом подход к защите приложения на основе результатов анализа защищенности его кода обладает рядом существенных преимуществ по сравнению с традиционным VP:

  • за счет описанного выше формального подхода и возможности учитывать любые промежуточные преобразования выходных данных устранены все указанные недостатки традиционного VP;
  • формальный подход также полностью исключает возможность появления ошибок первого рода (ложных срабатываний, false positive), при условии отсутствия в формулах unknown-нод;
  • отсутствие какого-либо негативного влияния на функции веб-приложения, поскольку защита реализуется не просто в соответствии с ними, а на их же основе.

Для обкатки технологии и подтверждения ее эффективности был разработан прототип модуля интеграции PT Application Inspector и PT Application Firewall в виде HTTP-модуля веб-сервера IIS под платформу .NET. Демонстрацию его работы с рассмотренным примером кода можно посмотреть на YouTube. Тесты производительности на полутора десятках открытых CMS показали более чем приемлемые результаты: время обработки HTTP-запросов с помощью RVP оказалось сравнимо со временем их обработки традиционными (эвристическими) методами WAF. Средний процент замедления реакции веб-приложения на запросы составил:

  • 0% при обработке запросов, не приводящих в уязвимую точку;
  • 6–10% при обработке запросов, приводящих в уязвимую точку, но не являющихся атакой (в зависимости от сложности грамматики уязвимой точки);
  • 4–7% при обработке запросов, приводящих в уязвимую точку и являющихся атакой.

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

  • отсутствует возможность вычислять значения таких формул, в которых присутствуют внешние данные из источников, отсутствующих на стороне WAF (файловых ресурсов, БД, окружение сервера и т. п.);
  • качество формул напрямую зависит от качества аппроксимации некоторых фрагментов кода во время его анализа (циклы, рекурсия, вызовы методов внешних библиотек и т. п.);
  • описание семантики преобразующих функций для базы вычислителей требует некоторого количества инженерной работы, которая слабо автоматизируется и допускает появление ошибок, связанных с человеческим фактором.

Впрочем, и эти недостатки оказалось возможным устранить, перенеся часть функциональности RVP на сторону приложения и применив технологии, лежащие в основе самозащиты приложений времени выполнения (runtime application self-protection, RASP).

Но об этом — уже во второй части статьи :)

Автор: Владимир Кочетков

Источник

Поделиться

* - обязательные к заполнению поля