CupidCards
Advanced Track
Scenario
My Dearest Hacker,
Spread the love this Valentine's Day with CupidCards - the web app that lets you create personalised Valentine cards! Upload a photo, add a heartfelt message, and generate a custom card for that special someone.
Summary
Summary
In CupidCards we begin by enumerating a web service running on port 1337 that allows users to generate personalised Valentine cards. Initial testing reveals no obvious SSTI or template injection, but deeper inspection of the card generation process uncovers a command injection vulnerability in the filename parameter. Although outbound connections are blocked, timing-based payloads confirm command execution. We pivot to file-write primitives, successfully writing arbitrary files into /opt/cupidcards/cards, which allows us to copy sensitive files and ultimately inject our SSH public key into /home/cupid/.ssh/authorized_keys. This grants us SSH access as cupid, where we retrieve the first flag.
Further enumeration reveals matchmaking engine in /opt/heartbreak that processes .love files using MessagePack and unsafely unpickles the notes field. By crafting a malicious pickle payload wrapped in a valid MessagePack structure and placing it into /var/spool/heartbreak/inbox, we trigger deserialization-based RCE as aphrodite. We first leverage this to create a SUID bash binary, then establish stable SSH access as aphrodite and obtain the second flag.
Finally, as aphrodite (member of the hearts group), we discover a root-owned SUID binary /usr/local/bin/heartstring that dynamically loads plugins defined in a group-writable manifest.json. Although the plugin directory itself is not writable, the binary’s undocumented --dev flag allows loading plugins from the current directory. By crafting a malicious shared object that spawns a privileged shell, adding its hash to the manifest, and invoking the binary in development mode, we achieve root execution.
Recon
We use rustscan -b 500 -a 10.81.131.2026 -- -sC -sV -Pn to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts -sC, service and version detection -sV, and treats the host as online without ICMP echo -Pn.
A batch size of 500 trades speed for stability, the default 1500 balances both, while much larger sizes increase throughput but risk missed responses and instability.
We identify port 1337 to be open and serving a website.

We visit the site manually and observe that we can generate greeting cards containing a message, sender, and receiver.

We create a sample card to observe how the application behaves.

During testing, we include payloads to check for SSTI, command injection, and possible ImageMagick exploits. None of these appear to work at first glance.

Inspecting the source code reveals that generated cards are stored under /cards with randomly generated names.

Command Injection
We test for command injection via the filename parameter. The filename appears to be validated by structure and file extension, which suggests it might be passed to a system command e.g., via system().
Initial attempts using reverse shells and outbound requests to our listener fail; we receive no callback. However, when testing simpler commands such as sleep 10, we observe a noticeable delay, confirming command execution.

Since outbound connections are blocked, we pivot to writing files directly to the system. If we cannot get a shell, but still be able to write files we can leverage that to enumerate the system or gain further access.
We attempt writing to various directories such as:
/var/www/html/cards/var/www/html/cupid/cards/var/www/html/cupidcards/cards/opt/cards/opt/cupid/cards/opt/cupidcards/cards
We succeed with the following path:


Shell as cupid
Now that we can write files, we proceed with enumeration. We copy /etc/passwd to a readable location, and identify the following users on the system:

... also the current user running.

Since we know user cupid is running the web application we try to write an SSH public key to the authorized_keys file in the .ssh folder of the user to connect to the system via SSH as cupid.
We generate the key pair.

Now we store the public key to the /home/cupid/.ssh/authorized_keys file.

Next, we use the private key to connect as cupid and are successful. We find the first flag in the users home directory.

Shell as aphrodite
Initial enumeration shows that cupid is a member of the lovers group.

We discover a directory /opt/heartbreak containing multiple Python scripts. The file match_engine.py uses hbproto.py to decode supplied notes:

Inspecting hbproto.py, we notice that it loads a pickle object. This opens the door for a deserialization attack to become aphrodite if the engine is called by aphrodite in a cron job.
Engine behavior summary:
Reads
.lovefiles from:/var/spool/heartbreak/inboxRequires valid MessagePack
Required fields:
from,to,desire,compatdesiremust be ≥ 50 charactersOptional field:
notes→ if bytes → unpickledDeletes the file after processing
Exploitation strategy:
Create a malicious pickle payload
Insert it into the
notesfield as bytesWrap the structure in MessagePack
Save it as a
.lovefileDrop it into the spool directory
When the engine processes it we could get remote code execution as aphrodite.
We prepare the following exploit which does the entire process depicted before.
We use it to generate us a SUID /bin/bash binary owned by the user running the engine.
We run the script...

... it places a pwn.love file inside /var/spool/heartbreak/inbox. After a short duration we see it gets deleted and we see our SUID bash binary in /tmp.

The bash binary is owned by aphordite. Running it with -p tag we receive a shell as aphrodite. We find the second flag in the users home directory.

Shell as root
First enumeration reveals that the user is memeber of the hearts group: groups aphrodite
But the group is not applied to our session gained with the bash binary. We might need that later.

Upon further enumeration we identify the following SUID binary which can be executed by the hearts group and is owned by root. We need a more stable session.

We adapt our exploit.py script to write the public key of our previously generated key pair to the .ssh/authorized_keys file to connect as aphrodite using SSH.
We run the script and wait a moment.

After a short duration we can connect as aphrodite using our private key via SSH.

Now we are able to run the SUID heartstring binary.
It seems like an executable to encrypt and decrypt files. It uses different plugins, shared object .so files, that can be loaded. The path the plugins are loaded from seem to be fixed at /opt/heartbreak/plugins. Furthmore it uses a manifest file that has the plugin name and hash of the binary to check the integrity of the plugin. We can write to the manifest file, but cannot add any other plugins to /opt/heartbreak/plugins missing the write permissions.
In summary:
We are in group
heartsmanifest.jsonis group-writablePlugin directory is readable/executable by group
.sofiles are owned byroot


If it:
Trusts the manifest
Loads plugins by name
Runs as root
Then we can:
Add a malicious plugin entry
Compile a malicious
.soExecute it as
root
The only problem is, that we can't add any plugins to the required folder.
We analyze the binary using strings to see if anything special pops up.

We identify another command parameter --dev. It looks like the path is not absolute with that parameter.

We craft a shared object that sets the process user and group IDs to root and then spawn a privileged /bin/bash -p shell.
We compile the object and calculate the hash.

We add our plugin to the manifest.json.
Next, we run the heartstring binary from the location of our shared object binary with the --dev tag. The shared object is loaded and we receive a root shell. We find the final flag at /root/flag3.txt.

Last updated
Was this helpful?