RCE via SSTI on Spring Boot Error Page with Akamai WAF Bypass

We discussed this vulnerability during Episode 175 on 12 December 2022

Great documentation of the process finding a WAF process, building up the final payload bit by bit.

The actual bug being exploited is this Spring bug. Its an SSTI so you can inject SpEL (Spring Expression Language) expressions which give you access to sort of “navigate” Java objects using standard dot notation and call functions, chain responses. The classic way to exploit this might be:

${T(java.lang.Runtime).getRuntime().exec("<my command here>")}

Unfortunately there is a WAF that must be bypassed and it is blocking the most direct route. Most of the post follows his path towards finding a bypass.

The first step, was “try the obvious,” or as I tend to say “test your assumptions”. Kinda the same idea, do the obvious things, test everything, and establish some ground truth/things that definitely work to work from.

Then we just get into the problem solving flow:

First, trying to gain access to an arbitrary class. This ended up being easy ${2.class} worked. From there he discovered that the common technique of using .forName was easily detected by the WAF and did not work because the ' and " were being transformed in some way. So they couldn’t use strings directly.

So the hunt for a way to instantiate a string begun…and died as the means to directly instantiate a new class were all blocked by the WAF. So they could not simply instantiate a new String, instead they found a round-about way to craft a String object containing a single character.


Basically what they get here is access to the Character class in Java which contains a .toString method that takes in character value (avoiding the need for quotes) and returns a String.

  • 2.toString() giving an original instance of a string object
  • .charAt(0) returning a Character object for the first character
  • .class giving access to the Character class
  • .toString(99) - the desired toString class that will give a controllable output.

This string crafting method then became the basis for accessing arbitrary Classes, as now the forName method could be called without tripping the WAF. With arbitrary Classes the java.lang.Runtime could be accessed, then .getRuntime() which has the .exec method to execute commands.