В процессе работы и исследований различных сервисов мы всё чаще можем встретить Spring Framework. И логичным шагом является знакомство с его структурой и возможными уязвимостями. Самыми интересными для любого пентестера являются уязвимости, которые приводят к исполнению кода.  Одним из способов получить RCE в Spring является инъекция SpEL-выражений. В этой статье мы попробуем разобраться, что такое SpEL, где его можно найти, какие есть особенности использования и как же находить такие инъекции. SpEL — это язык выражений, созданный для Spring Framework, который поддерживает запросы и управление графом объектов во время выполнения.
Также важно отметить, что SpEL создан в виде API-интерфейса, позволяющего интегрировать его в другие приложения и фрэймворки.  Логично, что в Spring Framework SpEL используется сплошь и рядом. Показательным примером можно считать Spring Security, где права назначаются с помощью SpEL выражения:  Apache Camel использует SpEL API; ниже представлены примеры из его документации.
Формирование письма с использованием SpEL-выражений: Или можно использовать правило из внешнего файла, например, для задания Header:  А вот несколько примеров, встреченных на GitHub:
https://github.com/jpatokal/openflights  https://github.com/hbandi/LEP  Чтобы читателю было проще разобраться в том, что собой представляют SpEL-инъекции, необходимо немного познакомиться со Spring и SpEL. Ключевым элементов Spring Framework является Spring Container. Контейнер создаёт объекты, связывает их вместе, настраивает и управляет ими от создания до уничтожения. Для управления компонентами, из которых состоит приложение, Spring Container использует
Внедрение Зависимостей. Это когда объекты настраиваются с помощью внешних сущностей, называемых Spring Beans – в просторечии «бины». Spring Container получает из бина метаданные конфигурации, которые необходимы для получения следующей информации: инструкции, какие объекты инстанциировать и как их конфигурировать через метаданные. Метаданные могут быть получены 3 способами: И ещё одним важным для нас моментом является Application Context. ApplicationContext — это главный интерфейс в Spring-приложении, который предоставляет информацию о конфигурации приложения. Он доступен только для чтения во время выполнения, но может быть перезагружен при необходимости и поддержке приложением. Число классов, реализующих ApplicationContext-интерфейс, доступно для различных параметров конфигурации и типов приложений. По сути, он представляет собой само приложение Spring. Также контекст предоставляет возможности реагирования на различные события, которые происходят внутри приложения, и управлять жизненным циклом бинов.  Теперь остановимся непосредственно на способах задания bean и использовании SpEL-выражений. Bean.XML Примером типичного использования является интеграция SpEL в создание XML или аннотированных определений bean-компонентов: Здесь представлена часть кода в файле Bean.xml, только для одного его бина. Стоит обратить внимание на id бина, по которому к нему можно обратиться, и на свойства. Т.к. в рамках этой статьи мы рассматриваем возможности эксплуатации SpEL, то в примере будут приведены несколько вариантов записи таких выражений. Чтобы указать Spring, что дальше идут SpEL- выражения, используется символ #, а само выражение заключается в фигурные скобки: #{SpEL_expression}. На свойства можно ссылаться, используя символ $ и заключив имя свойства в фигурные скобки: ${someProperty}. Заполнители свойств не могут содержать выражения SpEL, но выражения могут содержать ссылки на свойства: Таким образом можно вызвать любой необходимый нам Java-класс или, к примеру, обратиться к переменным среды, что может быть полезным для определения имени пользователя или версии системы. Удобство такого метода задания бинов – возможность изменять их без перекомпиляции всего приложения, тем самым меняя поведение приложения. Из самого приложения можно обратиться к этому бину, используя интерфейс ApplicationContext, как показано ниже: Т.е. внутри приложения мы просто получаем значения параметров бина, которые содержат SpEL-выражения. Spring, получив такое значение, исполняет выражение и выдает конечный результат. Также не стоит забывать, что этот код не будет работать без соответствующих геттеров, но их описание выходит за рамки статьи. Другим способом задания бинов является способ аннотаций AnnotationBase – значения параметра задаются внутри аннотации для какого-то класса. В этом случае использование переменных невозможно. Чтобы иметь возможность использовать переменные, при создании SpEL выражения нам необходимо задействовать интерфейс ExpressionParser. И тогда в коде приложения появится class, похожий на следующий пример: ExpressionParser преобразует строковое выражение в объект Expression. Таким образом, значение анализируемого выражения можно будет получить в рамках EvaluationContext. Этот EvaluationContext будет единственным объектом, из которого будут доступны все свойства и переменные в строке EL. Стоит отметить ещё один важный факт. При таком способе использования SpEL нам требуется, чтобы строковое выражение содержало # только в случае, если помимо самого выражения там содержатся строковые литералы. Из всего выше описанного стоит запомнить две вещи:
1) Если есть возможность осуществлять поиск по коду приложения, то необходимо искать такие ключевые слова: SpelExpressionParser, EvaluationContext и parseExpression.
2) Важные для Spring указатели #{SpEL}, ${someProperty} и T(javaclass)
Если хотите более подробно почитать про Spring и SpEL, то рекомендуем обратить внимание на документацию docs.spring.io. Согласно документации, SpEL поддерживает следующий функционал: Как мы видим, функционал SpEL очень богат, и это может плохо сказаться на безопасности проекта, если в ExpressionParser попадет пользовательский ввод. Поэтому сам Spring рекомендует использовать вместо полнофункционального StandardEcalutionContext более урезанный по возможностям SimpleEvaluationContext. Если вкратце, то из важного для нас у SimpleEvaluationContext нет возможности обращаться к Java-классам и ссылаться на другие бины. Полное описание возможностей лучше изучить на сайте документации:
StandardEvaluationContext
SimpleEvaluationContext На разнице в функциональности SpEL, исполняющегося в разных контекстах, даже основаны некоторые исправления, но об этом мы поговорим немного позже. Чтобы всё стало действительно понятно, приведём пример. У нас есть явно вредоносная строка, содержащая SpEL-выражение: И есть два контекста: и Expression exp = parser.parseExpression(inj);
java exp.getValue(std_c); — будет запущен калькулятор
java exp.getValue(simple_c); — мы получим сообщение об ошибке Не менее интересным моментом является то, что мы можем запустить обработку выражения без указания вообще какого-либо контекста: exp.getValue();
В этом случае выражение выполнится в рамках стандартного контекста и, как следствие, вредоносный код будет исполнен. Поэтому, если вы программист и используете Spring — никогда не забывайте задавать контекст, в рамках которого должно исполняться выражение. Немного раньше мы говорили, что на отличиях возможностей SpEL в рамках контекстов строятся некоторые исправления. Рассмотрим пример такого исправления. CVE 2018-1273 Spring Data Commons
Данная уязвимость нашлась в методе setPropertyValue и основывалась на двух проблемах:
1) Недостаточная санитизация значений переменной, попадающей в ExpressionParser.
2) Исполнение выражения в рамка стандартного контекста. Вот скриншот уязвимой части кода:  Т.к. имя свойства не требовало сложной обработки в рамках SpEL, логичным решением было заменить контекст, в результате чего получился следующий код:  На скриншотах приведены части кода, задающие контекст, и выражение, которое будет выполненно. Но исполнение выражения происходит в другом месте: Как раз тут и указывается, что мы исполняем SpEL-выражение (expression) для значения value в рамках заданного контекста (context).
Использование SimpleEvaluationContext помогло защититься от внедрения Java Class в parseExpression, и теперь вместо исполнения кода в логе сервера мы увидим ошибку:  Но это не решило проблему с отсутствием достаточной санитизации и сохранило возможность провести атаку redos: Поэтому следующее исправление уже содержало санитизацию имени параметра. Теперь давайте рассмотрим несколько способов поиска SpEL injection методом White Box. Для начала следует найти место обработки SpEL-выражений. Для этого вы можете просто воспользоваться нашей рекомендацией и найти в коде ключевые слова. Напомним эти слова: SpelExpressionParser, EvaluationContext и parseExpression. Другой вариант — воспользоваться различными плагинами для поиска ошибок в коде. Пока что единственным плагином, который указывает на возможные SpEL injection, был findsecbugs-cli.
https://github.com/find-sec-bugs Итак, мы нашли интересующее нас место в коде. Допустим, с помощью findsecbugs-cli:  В коде приложения мы увидим следующее: Следующим шагом будет задача обнаружить, откуда переменная path попадает в парсер выражений. Одним из достаточно удобных и бесплатных способов будет воспользоваться функцией IDE IntelijIdea — Analyze Dataflow:   Раскручивая цепочку, к примеру, для replace и изучая указанные методы и классы, получаем следующее: Метод ReplaceOperation принимает значение переменной path. А чтобы вызвать метод replace, нужно передать в JSON переменную “op” cо значением “replace”. Аналогичным образом мы находим все места, где пользователь может передать нужное ему значение в переменную path. И тогда один вариантов эксплуатации уязвимости будет выглядеть так:
Метод запроса: PATCH
Тело запроса: Использование LGTM QL (в рамках статьи сократим просто до QL) — это ещё один интересный способ поиска уязвимостей.
https://lgtm.com Стоит сразу оговорить его недостаток. Бесплатно вы сможете проанализировать только проекты, которые лежат в открытых репозиториях на GitHub, т.к. чтобы сделать снимок проекта, LGTM выкачивает проект к себе на сервер и уже там компилирует. Но если это вас не беспокоит, то LGTM QL откроет для вас широкие возможности в анализе кода приложения. Итак, что собой представляет анализ приложения с использованием QL?  Для начала, как мы уже говорили, потребуется создать снимок приложения. Когда снимок будет готов, а это может занять несколько часов, вы можете начать писать SQL-подобный запрос в рамках синтаксиса QL. Для этого можно использовать плагин для Eclipse или же действовать прямо в консоли на странице QL проекта. Т.к. сейчас мы рассматриваем Spring, а это framework для Java, то потребуется описать интересующий вас класс и метод из этого класса, вызов которого считается уязвимым. Для нас это любой класс, содержащий метод, который вызывает ExpressionParser. Затем мы делаем выборку всех методов, соответствующих нашим требованиям, допустим, описав попадание переменной в метод, который бы санитизировал и условие непопадания в этот метод.  Итак, что же нужно сделать, чтобы найти уязвимость CVE 2018-1273?
Получив и подключив образ проекта, используем консоль QL, чтобы описать интересующее нас дерево вызовов. Для этого:
Описываем класс Expression parser: И методы, которые могут быть использованы для исполнения в рамках класса ExpressionParser:  Теперь требуется связать между собой эти описания и сделать выборку: Такой запрос выдаст все методы, начинающиеся на parse или с именем doParseExpression, которые будут относиться к классу ExpressionParser. Но это слишком много, скажете вы, и будете правы. Требуется добавить фильтр.  Т.к. в коде встречается комментарий вида: То таким фильтром может быть, к примеру, поиск “path” по Javadoc. Spring очень качественно комментирует свой код, и мы можем найти вызовы методов с нужным комментарием, а заодно убрать все методы, которые входят в тесты. Это всё можно описать следующим образом: Тогда, чтобы объединить класс, методы и фильтр по Javadoc, запрос на выборку примет следующий вид: Этот пример можно считать простым и, в общем-то, избыточным для поиска конкретной уязвимости. Гораздо интереснее выглядит поиск ошибок при написании исправления, т.к. в нём нужно указать сам класс, отвечающий за проверку, методы, которые его всегда вызывают и которые исполняются до того, как попасть под проверку. Обращение к методу, который всегда вызывает verifyPath: Обращение к методу, который исполняется до verifyPath: Рассмотрим ещё одну интересную уязвимость. Её понимание очень важно, т.к. она показывает, что ошибка может быть в сторонней библиотеке, и демонстрирует, как можно использовать XML-аннотированные бины. CVE-2017-17485 основана на использовании FileSystemXmlApplicationContext – это автономный контекст приложения в виде XML, получающий файлы определения контекста из файловой системы или из URL. Согласно документации, это позволяет загрузить бины из файла и перезагрузить контекст приложения.
“… Create a new FileSystemXmlApplicationContext, loading the definitions from the given XML files and automatically refreshing the context” Jackson — это библиотека, которая позволяет сериализовать и десериализовать любые объекты, кроме внесенных в черный список. Этой возможностью часто пользуются злоумышленники. В случае данной уязвимости злоумышленник должен был передать объект org.springframework.context.support.FileSystemXmlApplicationContext со значением, содержащим путь до контролируемого злоумышленником файла. Т.е. в теле запроса можно было передать следующий JSON: Spel.xml же будет содержать параметры бина: Т.к. мы в качестве бина использовали класс java.lang.ProcessBuilder, который имеет метод start, то после перезагрузки контекста Spring считывает из свойства SpEL выражение, запускающее ProcessBuilder, тем самым заставляя сервер подключиться к нам посредствам nc.  Стоит обратить внимание на приведенный в качестве примера spel.xml, т.к. он показывает — каким образом можно передать параметры при запуске команды. А каким ещё способом мы можем загрузить свой бин или перезагрузить контекст?  Даже при беглом просмотре документации по Spring можно найти ещё некоторые классы, которые могут быть нам полезны. ClassPathXmlApplicationContext и AbstractXmlApplicationContext аналогичны FileSystem, но в качестве пути до конфигурации используют ClassPath- и XML-аннотированные бины, соответственно.  Есть ещё один интересный момент, связанный с перезагрузкой контекста — @RefreshScope. Любой Spring Bean, аннотированный с помощью @RefreshScope, будет обновлен во время запуска. И все компоненты, которые его используют, получат новый объект при следующем вызове метода, будут полностью инициализированы и введены в зависимости. RefreshScope — это компонент в контексте, и он имеет публичный метод refreshAll, предназначенный для обновления всех компонентов в области путем очистки целевого кэша. Поэтому в случае использования @RefreshScope пользователь может обратиться к URL, заканчивающемуся на /refresh, и тем самым перезагрузить аннотированные бины. Существует множество других плагинов и программ, позволяющих проанализировать код и найти уязвимость.  Из минусов – платная, но имеет бесплатный период в 10 дней. Считается одной из лучших утилит для анализа поведения приложения, не только с точки зрения безопасности. В общем-то, тут всё стандартно.
Важно определить, что перед нами находится: Spring, использующий в своем коде SpEL, приложение, использующее SpEL API, или такой веб-сервис, который совершенно не имеет отношения к данной теме. Если в качестве способа проверки содержимого сервера используется spring, то стоит обратить внимание на URL, содержащие в себе API. Также стоит проверить ответ сервера на эндпоинты /metrics и /beans — это укажет на наличие Spring Boot Actuator и опубликует список доступных бинов, что может оказаться полезным и само по себе. Далее смотрим, какие параметры можно передавать.  Как мы видели ранее, SpEL можно встретить в разных элементах сервиса, поэтому важно проверять всё то, что может быть обработано.  На самом деле SpEL не первый язык выражений, есть много других, для которых уже находили EL Injection. Вот некоторые из них: OGNL, MVEL, JBoss EL, JSP EL. И в каком-то случае пэйлоады для этих языков даже будут одинаковыми. На ZeroNights был вопрос: “А где, помимо Spring, можно найти SpEL injection?” На первый взгляд, если посмотреть на CVE, то почти нигде. Но на самом деле случаев гораздо больше, и не только среди приложений, представленных на github. К примеру, однажды встретился код, где при работе некоторого менеджерского сервиса данные из базы попадали в SpEL Expression. Т.е. злоумышленнику (возможно, тому самому менеджеру) требовалось всего лишь написать нужный запрос в базу, чтобы получить исполнение кода на сервере. Т.е. от инъекции нас может отделять только возможность записать нужные данные в таблицу. Так что никогда не забывайте посмотреть, какие дополнительные возможности может дать та или иная фича языка, которую вы решили использовать при обработке “непользовательского” ввода.