• Vosman

Cap

This was a nice easy box with a simple initial foothold and an easy escalation to root, but not so simple as to be completely obvious. So with that said let's jump on into it.

Enumeration

NMAP

# Nmap 7.91 scan initiated Sun Jul 25 17:22:34 2021 as: nmap -sCV -vv -n -oA nmap/nmap_CapInitial 10.10.10.245
Nmap scan report for 10.10.10.245
Host is up, received reset ttl 63 (0.017s latency).
Scanned at 2021-07-25 17:22:34 BST for 128s
Not shown: 997 closed ports
Reason: 997 resets
PORT   STATE SERVICE REASON         VERSION
21/tcp open  ftp     syn-ack ttl 63 vsftpd 3.0.3
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 fa:80:a9:b2:ca:3b:88:69:a4:28:9e:39:0d:27:d5:75 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2vrva1a+HtV5SnbxxtZSs+D8/EXPL2wiqOUG2ngq9zaPlF6cuLX3P2QYvGfh5bcAIVjIqNUmmc1eSHVxtbmNEQjyJdjZOP4i2IfX/RZUA18dWTfEWlNaoVDGBsc8zunvFk3nkyaynnXmlH7n3BLb1nRNyxtouW+q7VzhA6YK3ziOD6tXT7MMnDU7CfG1PfMqdU297OVP35BODg1gZawthjxMi5i5R1g3nyODudFoWaHu9GZ3D/dSQbMAxsly98L1Wr6YJ6M6xfqDurgOAl9i6TZ4zx93c/h1MO+mKH7EobPR/ZWrFGLeVFZbB6jYEflCty8W8Dwr7HOdF1gULr+Mj+BcykLlzPoEhD7YqjRBm8SHdicPP1huq+/3tN7Q/IOf68NNJDdeq6QuGKh1CKqloT/+QZzZcJRubxULUg8YLGsYUHd1umySv4cHHEXRl7vcZJst78eBqnYUtN3MweQr4ga1kQP4YZK5qUQCTPPmrKMa9NPh1sjHSdS8IwiH12V0=
|   256 96:d8:f8:e3:e8:f7:71:36:c5:49:d5:9d:b6:a4:c9:0c (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDqG/RCH23t5Pr9sw6dCqvySMHEjxwCfMzBDypoNIMIa8iKYAe84s/X7vDbA9T/vtGDYzS+fw8I5MAGpX8deeKI=
|   256 3f:d0:ff:91:eb:3b:f6:e1:9f:2e:8d:de:b3:de:b2:18 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbLTiQl+6W0EOi8vS+sByUiZdBsuz0v/7zITtSuaTFH
80/tcp open  http    syn-ack ttl 63 gunicorn
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 404 NOT FOUND
|     Server: gunicorn
|     Date: Sun, 25 Jul 2021 16:22:46 GMT
|     Connection: close
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 232
|     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|     <title>404 Not Found</title>
|     <h1>Not Found</h1>
|     <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
|   GetRequest: 
|     HTTP/1.0 200 OK
|     Server: gunicorn
|     Date: Sun, 25 Jul 2021 16:22:40 GMT
|     Connection: close
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 19386
|     <!DOCTYPE html>
|     <html class="no-js" lang="en">
|     <head>
|     <meta charset="utf-8">
|     <meta http-equiv="x-ua-compatible" content="ie=edge">
|     <title>Security Dashboard</title>
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <link rel="shortcut icon" type="image/png" href="/static/images/icon/favicon.ico">
|     <link rel="stylesheet" href="/static/css/bootstrap.min.css">
|     <link rel="stylesheet" href="/static/css/font-awesome.min.css">
|     <link rel="stylesheet" href="/static/css/themify-icons.css">
|     <link rel="stylesheet" href="/static/css/metisMenu.css">
|     <link rel="stylesheet" href="/static/css/owl.carousel.min.css">
|     <link rel="stylesheet" href="/static/css/slicknav.min.css">
|     <!-- amchar
|   HTTPOptions: 
|     HTTP/1.0 200 OK
|     Server: gunicorn
|     Date: Sun, 25 Jul 2021 16:22:41 GMT
|     Connection: close
|     Content-Type: text/html; charset=utf-8
|     Allow: GET, HEAD, OPTIONS
|     Content-Length: 0
|   RTSPRequest: 
|     HTTP/1.1 400 Bad Request
|     Connection: close
|     Content-Type: text/html
|     Content-Length: 196
|     <html>
|     <head>
|     <title>Bad Request</title>
|     </head>
|     <body>
|     <h1><p>Bad Request</p></h1>
|     Invalid HTTP Version &#x27;Invalid HTTP Version: &#x27;RTSP/1.0&#x27;&#x27;
|     </body>
|_    </html>
| http-methods: 
|_  Supported Methods: GET HEAD OPTIONS
|_http-server-header: gunicorn
|_http-title: Security Dashboard
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port80-TCP:V=7.91%I=7%D=7/25%Time=60FD8FD1%P=x86_64-pc-linux-gnu%r(GetR
SF:equest,2FE5,"HTTP/1\.0\x20200\x20OK\r\nServer:\x20gunicorn\r\nDate:\x20
SF:Sun,\x2025\x20Jul\x202021\x2016:22:40\x20GMT\r\nConnection:\x20close\r\
SF:nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x20193
SF:86\r\n\r\n<!DOCTYPE\x20html>\n<html\x20class=\"no-js\"\x20lang=\"en\">\
SF:n\n<head>\n\x20\x20\x20\x20<meta\x20charset=\"utf-8\">\n\x20\x20\x20\x2
SF:0<meta\x20http-equiv=\"x-ua-compatible\"\x20content=\"ie=edge\">\n\x20\
SF:x20\x20\x20<title>Security\x20Dashboard</title>\n\x20\x20\x20\x20<meta\
SF:x20name=\"viewport\"\x20content=\"width=device-width,\x20initial-scale=
SF:1\">\n\x20\x20\x20\x20<link\x20rel=\"shortcut\x20icon\"\x20type=\"image
SF:/png\"\x20href=\"/static/images/icon/favicon\.ico\">\n\x20\x20\x20\x20<
SF:link\x20rel=\"stylesheet\"\x20href=\"/static/css/bootstrap\.min\.css\">
SF:\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=\"/static/css/fon
SF:t-awesome\.min\.css\">\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20
SF:href=\"/static/css/themify-icons\.css\">\n\x20\x20\x20\x20<link\x20rel=
SF:\"stylesheet\"\x20href=\"/static/css/metisMenu\.css\">\n\x20\x20\x20\x2
SF:0<link\x20rel=\"stylesheet\"\x20href=\"/static/css/owl\.carousel\.min\.
SF:css\">\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=\"/static/c
SF:ss/slicknav\.min\.css\">\n\x20\x20\x20\x20<!--\x20amchar")%r(HTTPOption
SF:s,B3,"HTTP/1\.0\x20200\x20OK\r\nServer:\x20gunicorn\r\nDate:\x20Sun,\x2
SF:025\x20Jul\x202021\x2016:22:41\x20GMT\r\nConnection:\x20close\r\nConten
SF:t-Type:\x20text/html;\x20charset=utf-8\r\nAllow:\x20GET,\x20HEAD,\x20OP
SF:TIONS\r\nContent-Length:\x200\r\n\r\n")%r(RTSPRequest,121,"HTTP/1\.1\x2
SF:0400\x20Bad\x20Request\r\nConnection:\x20close\r\nContent-Type:\x20text
SF:/html\r\nContent-Length:\x20196\r\n\r\n<html>\n\x20\x20<head>\n\x20\x20
SF:\x20\x20<title>Bad\x20Request</title>\n\x20\x20</head>\n\x20\x20<body>\
SF:n\x20\x20\x20\x20<h1><p>Bad\x20Request</p></h1>\n\x20\x20\x20\x20Invali
SF:d\x20HTTP\x20Version\x20&#x27;Invalid\x20HTTP\x20Version:\x20&#x27;RTSP
SF:/1\.0&#x27;&#x27;\n\x20\x20</body>\n</html>\n")%r(FourOhFourRequest,189
SF:,"HTTP/1\.0\x20404\x20NOT\x20FOUND\r\nServer:\x20gunicorn\r\nDate:\x20S
SF:un,\x2025\x20Jul\x202021\x2016:22:46\x20GMT\r\nConnection:\x20close\r\n
SF:Content-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x20232\
SF:r\n\r\n<!DOCTYPE\x20HTML\x20PUBLIC\x20\"-//W3C//DTD\x20HTML\x203\.2\x20
SF:Final//EN\">\n<title>404\x20Not\x20Found</title>\n<h1>Not\x20Found</h1>
SF:\n<p>The\x20requested\x20URL\x20was\x20not\x20found\x20on\x20the\x20ser
SF:ver\.\x20If\x20you\x20entered\x20the\x20URL\x20manually\x20please\x20ch
SF:eck\x20your\x20spelling\x20and\x20try\x20again\.</p>\n");
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Jul 25 17:24:42 2021 -- 1 IP address (1 host up) scanned in 128.31 seconds

OK, so three ports open:

21 - FTP

22 - SSH

80 - HTTP


FTP and a web-server are going to be interesting. SSH is there too, but maybe this will come into play a bit later.


Getting User

FTP

Let's take a look at FTP first as that can be an easy win if anonymous logins are enabled:

Connected to 10.10.10.245.
220 (vsFTPd 3.0.3)
Name (10.10.10.245:root): anonymous
331 Please specify the password.
Password:
530 Login incorrect.
Login failed.

OK, no joy there. I'm sure we'll be back later.


Web Application

Right let's take a look at what is on port 80:

The initial look at the web page being presented on the web-server

Looks like some sort of system monitoring tool. These kind of things are common in Security Operation Centers (SOC) that are in larger companies. They help the network and security people keep an eye out for suspicious behavior. However, after looking around this application a bit more it's just a static page and doesn't really help much, apart from noticing the username is "Nathan". Let's have a look into what else we do have.

I ran GoBuster against this web-server but it only brought up the three URL's we find in the menu so nothing more there.

Clicking on the menu bars button we get a couple of options that appear to show information about the host itself such as the IP configuration. There's not much we can do with that but looking at the Security Snapshot option we get sent to the /data/1 URL but there's no captures here so let's try selecting a few more numbers. If there's a number in the URL or argument I always try the number below it first and zero is before one so let's try that:

When looking at this page we do see some packets have been captured. Let's try downloading them and see what we can find out about these network packets.


When the file is downloaded we can see that it is a .pcap file. These can be examined in Wireshark and maybe other network packet inspection tools but Wireshark is definitely my preferred choice when it comes to this sort of thing.


Let's open Wireshark and import the captures, there's usually something interesting in these sorts of things especially when they are from a security monitoring tool.


OK, as we can see in the first couple of lines the capture has FTP packets stored. FTP packets can be a goldmine of information, actually any plain-text protocol packets (such as HTTP) can be too. Right clicking on the first FTP line and selecting Follow > TCP Stream we can see the following:


Awesome! We can see the user Nathan's password! This is exactly why using plain-text protocols on an untrusted network is a bad idea. OK so let's try logging in via FTP:

ftp nathan@10.10.10.245

Enter the password and we're in! Nice, looking at the directory we can see the user.txt file. So let's download it with the command get user.txt.


I always like to see if the FTP server has been configured to restrict a user to the one directory. Trying the cd .. command, we find we can get out of the directory and browse the whole file-system. This isn't best practice as it now means an attacker could potentially gain access to configurations files on the system. However, doing this via FTP is a little time consuming. A good thing to remember is that users often use the same password for different services so let's try logging in with those same user credentials with SSH:

Excellent, it worked! Now we have a lot more flexibility to look around the box and see what's going on.


Getting Root

Right, let's have a look at what processes are running on this box:

Hmm, OK so nothing there. Another command I like to run when jumping on to a box is sudo -l to see if there's any commands we can run as root:

Nope, nothing...



Well we know there is a web server running so lets go take a look around for that. First place to try is the usual /var/www/html directory, and we find app.py:

That could be interesting, let's take a closer look.


Here's the code:

#!/usr/bin/python3

import os
from flask import *
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import tempfile
import dpkt
from werkzeug.utils import append_slash_redirect

app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.secret_key = b'\x81\x02&\x18\\a0ej\x06\xec\x917y*\x04Y\x83e\xebC\xee\xab\xcf\xac;\x8dx\x8bf\xc4\x15'
limiter = Limiter(app, key_func=get_remote_address, default_limits=["99999999999999999 per day", "99999999999999999999 per hour"])
pcapid = 0
lock = False

@app.before_first_request
def get_file_id():
        global pcapid
        path = os.path.join(app.root_path, "upload")
        onlyfiles = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
        ints = []
        for x in onlyfiles:
                try:
                        ints.append(int(x.replace(".pcap", "")))
                except:
                        pass
        try:
                pcapid = max(ints)+1
        except:
                pcapid = 0


def get_appid():
        global pcapid
        return pcapid

def increment_appid():
        global pcapid
        pcapid += 1

def get_lock():
        global lock
        while lock:
                pass
        lock = True

def release_lock():
        global lock
        lock = False

def process_pcap(pcap_path):
        reader = dpkt.pcap.Reader(open(pcap_path, "rb"))
        counter=0
        ipcounter=0
        tcpcounter=0
        udpcounter=0

        for ts, pkt in reader:
                counter+=1
                eth=dpkt.ethernet.Ethernet(pkt)

                try:
                        ip=dpkt.ip.IP(eth.data)
                except:
                        continue

                ipcounter+=1

                if ip.p==0:
                        tcpcounter+=1

                if ip.p==dpkt.ip.IP_PROTO_UDP:
                        udpcounter+=1

        data = {}
        data['Number of Packets'] = counter
        data['Number of IP Packets'] = ipcounter
        data['Number of TCP Packets']  = tcpcounter
        data['Number of UDP Packets']  = udpcounter
        return data


@app.route("/")
def index():
        return render_template("index.html")

PCAP_MAGIC_BYTES = [b"\xa1\xb2\xc3\xd4", b"\xd4\xc3\xb2\xa1", b"\x0a\x0d\x0d\x0a"]

@app.route("/capture")
@limiter.limit("10 per minute")
def capture():

        get_lock()
        pcapid = get_appid()
        increment_appid()
        release_lock()

        path = os.path.join(app.root_path, "upload", str(pcapid) + ".pcap")
        ip = request.remote_addr
        # permissions issues with gunicorn and threads. hacky solution for now.
        #os.setuid(0)
        #command = f"timeout 5 tcpdump -w {path} -i any host {ip}"
        command = f"""python3 -c 'import os; os.setuid(0); os.system("timeout 5 tcpdump -w {path} -i any host {ip}")'"""
        os.system(command)
        #os.setuid(1000)

        return redirect("/data/" + str(pcapid))

@app.route("/ip")
def ifconfig():
	d = os.popen("ifconfig").read().strip()
	print(d)
	return render_template("index.html", rawtext=d)

@app.route("/netstat")
def netstat():
	d = os.popen("netstat -aneop").read().strip()
	print(d)
	return render_template("index.html", rawtext=d)

@app.route("/data")
def data():
        if "data" not in session:
                return redirect("/")
        data = session.pop("data")
        path = session.pop("path")
        return render_template("data.html", data=data, path=path)

@app.route("/data/<id>")
def data_id(id):
        try:
                id = int(id)
        except:
                return redirect("/")
        try:
                data = process_pcap(os.path.join(app.root_path, "upload", str(id) + ".pcap"))
                path = str(id) + ".pcap"
                return render_template("index.html", data=data, path=path)
        except Exception as e:
                print(e)
                return redirect("/")

@app.route("/download/<id>")
def download(id):
        try:
                id = int(id)
        except