Титульная картинка

Одна из самых популярных тенденций в области защиты приложений нынешнего десятилетия — технология виртуального патчинга (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>");</source>

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

{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 выглядит так:

Пример CompFG

Одним из свойств CompFG является его конкретизируемость — возможность вычислить множества конкретных значений всех аргументов в любой точке выполнения приложения, задав значения для всех входных параметров.

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

Рабочий процесс RVP

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

Перед развертыванием очередной версии приложения осуществляется его анализ с помощью 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), и на его основе генерируется бинарный модуль, позволяющий вычислять все присутствующие в нем формулы. Декомпилированный код расчета условия достижимости уязвимой точки рассмотренного примера выглядит так:

Пример вычислителя

Для того, чтобы вычисление формул было возможным, на стороне 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-фрагменты):

Пример инрементального вычисления

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

Пример токенизации

По окончании вычисления формул всех уязвимостей, относящихся к текущей точке входа, обработка запроса передается в основной модуль 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).

Advanced RASP

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

Процесс ARASP представляет собой рассмотренный выше процесс RVP со следующими дополнениями:

  • В выгруженном из PT AI отчете каждое выражение в формуле имеет дополнительный атрибут — его координаты в коде:
+ ("Parameter value is `")  
  (Default.aspx.cs:36:7:FromBase64Str   
    (Default.aspx.cs:35:13:UrlDecodeStr 
      (Default.aspx.cs:32:11:FromBase64Str   
        (Default.aspx.cs:31:10:GetParameterData
          ("param")))))
  • При помощи данного отчета генерируется не только модуль вычисления формул, но и модуль инструментирования, который выполняется на стороне приложения. Этот модуль встраивает сенсоры детектирования во все точки выполнения приложения, которые соответствуют неопределенным выражениям в отчете, а также устанавливает точки останова, которые перед переходом к уязвимой точке выполнения передают управление RVP:

Инструментированный код

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

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

  • При обнаружении атаки обработка запроса прекращается (и, следовательно, приложение не доходит до уязвимой точки выполнения).

  • При отсутствии атаки задача по обработке запроса возвращается приложению до момента достижения следующей точки останова или до тех пор, пока не завершится обработка запроса.

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

Рабочий процесс ARASP

Преимущества ARASP: больше чем просто виртуальный патчинг

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

  • незначительное снижение производительности (обработка запроса фрагментами приложения происходит параллельно с обработкой этого же запроса модулем WAF с работающим ARASP);

  • минимальный урон стабильности приложения (инструментирование применяется только для тех точек исполнения, которые действительно необходимы для вычисления формул);

  • точное (практически 100%) обнаружение атак благодаря использованию методов модели CompFG и формальных методов для работы на этих элементах.

Таким образом, RVP и ARASP являются многообещающим перспективным решением в обеспечении защиты приложений, и мы продолжим разрабатывать их в качестве основного вектора улучшения интеграции между PT Application Inspector и PT Application Firewall.


Комментарии

comments powered by Disqus