Agent: Fix twisted import parallelization bug

This commit is contained in:
Mike Salvatore 2022-03-03 14:49:39 -05:00
parent 93415cf2c8
commit df495f98c7
1 changed files with 31 additions and 14 deletions

View File

@ -6,14 +6,16 @@ import threading
import time import time
from pathlib import Path from pathlib import Path
from ldaptor.interfaces import IConnectedLDAPEntry
from ldaptor.ldiftree import LDIFTreeEntry
from ldaptor.protocols.ldap.ldapserver import LDAPServer from ldaptor.protocols.ldap.ldapserver import LDAPServer
from twisted.application import service
from twisted.internet import reactor
from twisted.internet.protocol import ServerFactory from twisted.internet.protocol import ServerFactory
from twisted.python import log
from twisted.python.components import registerAdapter # WARNING: It was observed that this LDAP server would raise an exception and fail to start if
# multiple Python threads attempt to start multiple LDAP servers simultaneously. It was
# thought that since each LDAP server is started in its own process, there would be no
# issue, however this is not the case. It seems that there may be something that is not
# thread- or multiprocess-safe about some of the twisted imports. Moving the twisted
# imports down into the functions where they are required and removing them from the top of
# this file appears to resolve the issue.
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -32,6 +34,8 @@ class Tree:
""" """
def __init__(self, http_server_ip: str, http_server_port: int, storage_dir: Path): def __init__(self, http_server_ip: str, http_server_port: int, storage_dir: Path):
from ldaptor.ldiftree import LDIFTreeEntry
self.path = tempfile.mkdtemp(prefix="log4shell", suffix=".ldap", dir=storage_dir) self.path = tempfile.mkdtemp(prefix="log4shell", suffix=".ldap", dir=storage_dir)
self.db = LDIFTreeEntry(self.path) self.db = LDIFTreeEntry(self.path)
@ -91,14 +95,7 @@ class LDAPExploitServer:
self._http_server_ip = http_server_ip self._http_server_ip = http_server_ip
self._http_server_port = http_server_port self._http_server_port = http_server_port
self._storage_dir = storage_dir self._storage_dir = storage_dir
self._server_process = None
# A Twisted reactor can only be started and stopped once. It cannot be restarted after it
# has been stopped. To work around this, the reactor is configured and run in a separate
# process. This allows us to run multiple LDAP servers sequentially or simultaneously and
# stop each one when we're done with it.
self._server_process = multiprocessing.Process(
target=self._run_twisted_reactor, daemon=True
)
def run(self): def run(self):
""" """
@ -108,6 +105,15 @@ class LDAPExploitServer:
:raises LDAPServerStartError: Indicates there was a problem starting the LDAP server. :raises LDAPServerStartError: Indicates there was a problem starting the LDAP server.
""" """
logger.info("Starting LDAP exploit server") logger.info("Starting LDAP exploit server")
# A Twisted reactor can only be started and stopped once. It cannot be restarted after it
# has been stopped. To work around this, the reactor is configured and run in a separate
# process. This allows us to run multiple LDAP servers sequentially or simultaneously and
# stop each one when we're done with it.
self._server_process = multiprocessing.Process(
target=self._run_twisted_reactor, daemon=True
)
self._server_process.start() self._server_process.start()
reactor_running = self._reactor_startup_completed.wait(REACTOR_START_TIMEOUT_SEC) reactor_running = self._reactor_startup_completed.wait(REACTOR_START_TIMEOUT_SEC)
@ -117,6 +123,8 @@ class LDAPExploitServer:
logger.debug("The LDAP exploit server has successfully started") logger.debug("The LDAP exploit server has successfully started")
def _run_twisted_reactor(self): def _run_twisted_reactor(self):
from twisted.internet import reactor
logger.debug(f"Starting log4shell LDAP server on port {self._ldap_server_port}") logger.debug(f"Starting log4shell LDAP server on port {self._ldap_server_port}")
self._configure_twisted_reactor() self._configure_twisted_reactor()
@ -128,6 +136,8 @@ class LDAPExploitServer:
reactor.run() reactor.run()
def _check_if_reactor_startup_completed(self): def _check_if_reactor_startup_completed(self):
from twisted.internet import reactor
check_interval_sec = 0.25 check_interval_sec = 0.25
num_checks = math.ceil(REACTOR_START_TIMEOUT_SEC / check_interval_sec) num_checks = math.ceil(REACTOR_START_TIMEOUT_SEC / check_interval_sec)
@ -141,6 +151,11 @@ class LDAPExploitServer:
time.sleep(check_interval_sec) time.sleep(check_interval_sec)
def _configure_twisted_reactor(self): def _configure_twisted_reactor(self):
from ldaptor.interfaces import IConnectedLDAPEntry
from twisted.application import service
from twisted.internet import reactor
from twisted.python.components import registerAdapter
LDAPExploitServer._output_twisted_logs_to_python_logger() LDAPExploitServer._output_twisted_logs_to_python_logger()
registerAdapter(lambda x: x.root, LDAPServerFactory, IConnectedLDAPEntry) registerAdapter(lambda x: x.root, LDAPServerFactory, IConnectedLDAPEntry)
@ -155,6 +170,8 @@ class LDAPExploitServer:
@staticmethod @staticmethod
def _output_twisted_logs_to_python_logger(): def _output_twisted_logs_to_python_logger():
from twisted.python import log
# Configures Twisted to output its logs using the standard python logging module instead of # Configures Twisted to output its logs using the standard python logging module instead of
# the Twisted logging module. # the Twisted logging module.
# https://twistedmatrix.com/documents/current/api/twisted.python.log.PythonLoggingObserver.html # https://twistedmatrix.com/documents/current/api/twisted.python.log.PythonLoggingObserver.html