Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

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

pmnh & Dark9TPosted 2 Years Ago
  • Spring Boot has a pretty famous issue: when data is reflected within an error message, the Exception message uses Spring Expression Language (SpEL). For instance, $(7*7) will render 49. The authors of this post found an outdated version of Spring Boot being used which had a parameter reflected in an error message. Simple, right?
  • The SpEL language is a templating language that can be used within Spring. When having an injection via server-side rendering, this can be used to execute Java methods, construct objects and other data to eventually get code execution. For information on SSTI in SpEL, spel-injection is a good link.
  • Once they discover the SSTI, they try to try the simple payload ${T(java.lang.Runtime)}. This is a SpEL shorthand for referencing a Java class by name. However, the Akamai WAF blocked this request, even though they knew it was vulnerable. Now, we must learn why this was blocked by the WAF and how to work around it, which took over 500 requests to do.
  • With Java-based code injection vulnerabilities, the first issue is finding out how to load arbitrary classes. 1${2.class} will output java.lang.Integer. To create an arbitrary class, the author tried 1${2.class.forName("java.lang.String")} which was rejected based upon the function forName.
  • They needed to be able to create a string. The most straight forward route to RCE is .exec() is why. Reading the Java documentation (which is usually amazing), gave them the function toString() to get a non-static reference to a character. With this, they could use a toString() on an integer to return the proper character in a string they needed. Progress!
  • With the ability to create arbitrary strings, we're getting closer. With a string at hand, we need to find a way to call java.lang.Runtime.exec. They used a known technique as follows:
    • Use reflection (introspection feature) to get access to the Class.forName function to get an arbitrary package.
    • Build a String with the value java.lang.Runtime to pass to the function.
    • Use reflection to get access to the getRuntime function.
    • Build a string for exec using the crazy method above.
    • Call exec with our string.
  • A GET request has a limitation of 2kb. Since each character was 45 bytes long, this limited us to 45 characters in our payload. They tried to find an optimization, but couldn't find one after hours of trying. Eventually, they settled for a small payload in the exec function.
  • After hours of grinding, a simple uname -a response could be seen! The bug had been exploited and the WAF bypassed. To me, the interesting part of the article isn't the bypass itself but the thoughts around bypassing the WAF and problem solving.