forked from p15670423/monkey
Merge pull request #2263 from guardicore/2216-remove-unused-code
2216 remove unused code
This commit is contained in:
commit
20649b5e3c
|
@ -8,4 +8,3 @@ class TelemCategoryEnum:
|
|||
SCAN = "scan"
|
||||
STATE = "state"
|
||||
TRACE = "trace"
|
||||
TUNNEL = "tunnel"
|
||||
|
|
|
@ -2,20 +2,16 @@ import json
|
|||
import logging
|
||||
import platform
|
||||
from socket import gethostname
|
||||
from typing import Mapping, Optional
|
||||
|
||||
import requests
|
||||
from urllib3 import disable_warnings
|
||||
|
||||
import infection_monkey.tunnel as tunnel
|
||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
||||
from infection_monkey.config import GUID
|
||||
from infection_monkey.network.info import get_host_subnets, local_ips
|
||||
from infection_monkey.transport.http import HTTPConnectProxy
|
||||
from infection_monkey.transport.tcp import TcpProxy
|
||||
from infection_monkey.utils import agent_process
|
||||
from infection_monkey.utils.environment import is_windows_os
|
||||
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
disable_warnings() # noqa DUO131
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -28,8 +24,7 @@ class ControlClient:
|
|||
# https://github.com/guardicore/monkey/blob/133f7f5da131b481561141171827d1f9943f6aec/monkey/infection_monkey/telemetry/base_telem.py
|
||||
control_client_object = None
|
||||
|
||||
def __init__(self, server_address: str, proxies: Optional[Mapping[str, str]] = None):
|
||||
self.proxies = {} if not proxies else proxies
|
||||
def __init__(self, server_address: str):
|
||||
self.server_address = server_address
|
||||
|
||||
def wakeup(self, parent=None):
|
||||
|
@ -50,37 +45,14 @@ class ControlClient:
|
|||
"launch_time": agent_process.get_start_time(),
|
||||
}
|
||||
|
||||
if self.proxies:
|
||||
monkey["tunnel"] = self.proxies.get("https")
|
||||
|
||||
requests.post( # noqa: DUO123
|
||||
f"https://{self.server_address}/api/agent",
|
||||
data=json.dumps(monkey),
|
||||
headers={"content-type": "application/json"},
|
||||
verify=False,
|
||||
proxies=self.proxies,
|
||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
def set_proxies(self, proxy_find):
|
||||
"""
|
||||
Note: The proxy schema changes between different versions of requests and urllib3,
|
||||
which causes the machine to not open a tunnel back.
|
||||
If we get "ValueError: check_hostname requires server_hostname" or
|
||||
"Proxy URL had not schema, should start with http:// or https://" errors,
|
||||
the proxy schema needs to be changed.
|
||||
Keep this in mind when upgrading to newer python version or when urllib3 and
|
||||
requests are updated there is possibility that the proxy schema is changed.
|
||||
https://github.com/psf/requests/issues/5297
|
||||
https://github.com/psf/requests/issues/5855
|
||||
"""
|
||||
proxy_address, proxy_port = proxy_find
|
||||
logger.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
|
||||
if is_windows_os():
|
||||
self.proxies["https"] = f"http://{proxy_address}:{proxy_port}"
|
||||
else:
|
||||
self.proxies["https"] = f"{proxy_address}:{proxy_port}"
|
||||
|
||||
def send_telemetry(self, telem_category, json_data: str):
|
||||
if not self.server_address:
|
||||
logger.error(
|
||||
|
@ -95,7 +67,6 @@ class ControlClient:
|
|||
data=json.dumps(telemetry),
|
||||
headers={"content-type": "application/json"},
|
||||
verify=False,
|
||||
proxies=self.proxies,
|
||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
except Exception as exc:
|
||||
|
@ -111,41 +82,16 @@ class ControlClient:
|
|||
data=json.dumps(telemetry),
|
||||
headers={"content-type": "application/json"},
|
||||
verify=False,
|
||||
proxies=self.proxies,
|
||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(f"Error connecting to control server {self.server_address}: {exc}")
|
||||
|
||||
def create_control_tunnel(self, keep_tunnel_open_time: int):
|
||||
if not self.server_address:
|
||||
return None
|
||||
|
||||
my_proxy = self.proxies.get("https", "").replace("https://", "")
|
||||
if my_proxy:
|
||||
proxy_class = TcpProxy
|
||||
try:
|
||||
target_addr, target_port = my_proxy.split(":", 1)
|
||||
target_port = int(target_port)
|
||||
except ValueError:
|
||||
return None
|
||||
else:
|
||||
proxy_class = HTTPConnectProxy
|
||||
target_addr, target_port = None, None
|
||||
|
||||
return tunnel.MonkeyTunnel(
|
||||
proxy_class,
|
||||
keep_tunnel_open_time=keep_tunnel_open_time,
|
||||
target_addr=target_addr,
|
||||
target_port=target_port,
|
||||
)
|
||||
|
||||
def get_pba_file(self, filename):
|
||||
try:
|
||||
return requests.get( # noqa: DUO123
|
||||
PBA_FILE_DOWNLOAD % (self.server_address, filename),
|
||||
verify=False,
|
||||
proxies=self.proxies,
|
||||
timeout=LONG_REQUEST_TIMEOUT,
|
||||
)
|
||||
except requests.exceptions.RequestException:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import io
|
||||
import threading
|
||||
from functools import lru_cache
|
||||
from typing import Mapping
|
||||
|
||||
import requests
|
||||
|
||||
|
@ -18,9 +17,8 @@ class CachingAgentBinaryRepository(IAgentBinaryRepository):
|
|||
request is actually sent to the island for each requested binary.
|
||||
"""
|
||||
|
||||
def __init__(self, island_url: str, proxies: Mapping[str, str]):
|
||||
def __init__(self, island_url: str):
|
||||
self._island_url = island_url
|
||||
self._proxies = proxies
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def get_agent_binary(
|
||||
|
@ -40,7 +38,6 @@ class CachingAgentBinaryRepository(IAgentBinaryRepository):
|
|||
response = requests.get( # noqa: DUO123
|
||||
f"{self._island_url}/api/agent-binaries/{os_name}",
|
||||
verify=False,
|
||||
proxies=self._proxies,
|
||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import json
|
||||
import logging
|
||||
from pprint import pformat
|
||||
from typing import MutableMapping, Optional, Sequence
|
||||
from typing import Optional, Sequence
|
||||
from uuid import UUID
|
||||
|
||||
import requests
|
||||
|
@ -22,10 +22,9 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class ControlChannel(IControlChannel):
|
||||
def __init__(self, server: str, agent_id: str, proxies: MutableMapping[str, str]):
|
||||
def __init__(self, server: str, agent_id: str):
|
||||
self._agent_id = agent_id
|
||||
self._control_channel_server = server
|
||||
self._proxies = proxies
|
||||
|
||||
def register_agent(self, parent: Optional[UUID] = None):
|
||||
agent_registration_data = AgentRegistrationData(
|
||||
|
@ -44,7 +43,6 @@ class ControlChannel(IControlChannel):
|
|||
url,
|
||||
json=agent_registration_data.dict(simplify=True),
|
||||
verify=False,
|
||||
proxies=self._proxies,
|
||||
timeout=SHORT_REQUEST_TIMEOUT,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
@ -68,7 +66,6 @@ class ControlChannel(IControlChannel):
|
|||
response = requests.get( # noqa: DUO123
|
||||
url,
|
||||
verify=False,
|
||||
proxies=self._proxies,
|
||||
timeout=SHORT_REQUEST_TIMEOUT,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
@ -89,7 +86,6 @@ class ControlChannel(IControlChannel):
|
|||
response = requests.get( # noqa: DUO123
|
||||
f"https://{self._control_channel_server}/api/agent-configuration",
|
||||
verify=False,
|
||||
proxies=self._proxies,
|
||||
timeout=SHORT_REQUEST_TIMEOUT,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
@ -116,7 +112,6 @@ class ControlChannel(IControlChannel):
|
|||
response = requests.get( # noqa: DUO123
|
||||
propagation_credentials_url,
|
||||
verify=False,
|
||||
proxies=self._proxies,
|
||||
timeout=SHORT_REQUEST_TIMEOUT,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Optional
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from common import OperatingSystem
|
||||
|
||||
|
@ -7,10 +7,9 @@ class VictimHost(object):
|
|||
def __init__(self, ip_addr: str, domain_name: str = ""):
|
||||
self.ip_addr = ip_addr
|
||||
self.domain_name = str(domain_name)
|
||||
self.os = {}
|
||||
self.services = {}
|
||||
self.os: Dict[str, Any] = {}
|
||||
self.services: Dict[str, Any] = {}
|
||||
self.icmp = False
|
||||
self.default_tunnel = None
|
||||
self.default_server = None
|
||||
|
||||
def as_dict(self):
|
||||
|
|
|
@ -4,7 +4,6 @@ from typing import Optional, Tuple
|
|||
from infection_monkey.model import VictimHost
|
||||
from infection_monkey.network import NetworkAddress
|
||||
from infection_monkey.network.tools import get_interface_to_target
|
||||
from infection_monkey.tunnel import MonkeyTunnel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -12,12 +11,10 @@ logger = logging.getLogger(__name__)
|
|||
class VictimHostFactory:
|
||||
def __init__(
|
||||
self,
|
||||
tunnel: Optional[MonkeyTunnel],
|
||||
island_ip: Optional[str],
|
||||
island_port: Optional[str],
|
||||
on_island: bool,
|
||||
):
|
||||
self.tunnel = tunnel
|
||||
self.island_ip = island_ip
|
||||
self.island_port = island_port
|
||||
self.on_island = on_island
|
||||
|
@ -26,19 +23,15 @@ class VictimHostFactory:
|
|||
domain = network_address.domain or ""
|
||||
victim_host = VictimHost(network_address.ip, domain)
|
||||
|
||||
if self.tunnel:
|
||||
victim_host.default_tunnel = self.tunnel.get_tunnel_for_ip(victim_host.ip_addr)
|
||||
|
||||
if self.island_ip:
|
||||
ip, port = self._choose_island_address(victim_host.ip_addr)
|
||||
victim_host.set_island_address(ip, port)
|
||||
|
||||
logger.debug(f"Default tunnel for {victim_host} set to {victim_host.default_tunnel}")
|
||||
logger.debug(f"Default server for {victim_host} set to {victim_host.default_server}")
|
||||
|
||||
return victim_host
|
||||
|
||||
def _choose_island_address(self, victim_ip: str) -> Tuple[str, Optional[str]]:
|
||||
def _choose_island_address(self, victim_ip: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
# Victims need to connect back to the interface they can reach
|
||||
# On island, choose the right interface to pass to children monkeys
|
||||
if self.on_island:
|
||||
|
|
|
@ -78,7 +78,6 @@ from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter im
|
|||
LegacyTelemetryMessengerAdapter,
|
||||
)
|
||||
from infection_monkey.telemetry.state_telem import StateTelem
|
||||
from infection_monkey.telemetry.tunnel_telem import TunnelTelem
|
||||
from infection_monkey.utils.aws_environment_check import run_aws_environment_check
|
||||
from infection_monkey.utils.environment import is_windows_os
|
||||
from infection_monkey.utils.file_utils import mark_file_for_deletion_on_windows
|
||||
|
@ -162,9 +161,7 @@ class InfectionMonkey:
|
|||
|
||||
run_aws_environment_check(self._telemetry_messenger)
|
||||
|
||||
should_stop = ControlChannel(
|
||||
self._control_client.server_address, GUID, self._control_client.proxies
|
||||
).should_agent_stop()
|
||||
should_stop = ControlChannel(self._control_client.server_address, GUID).should_agent_stop()
|
||||
if should_stop:
|
||||
logger.info("The Monkey Island has instructed this agent to stop")
|
||||
return
|
||||
|
@ -180,9 +177,7 @@ class InfectionMonkey:
|
|||
if firewall.is_enabled():
|
||||
firewall.add_firewall_rule()
|
||||
|
||||
control_channel = ControlChannel(
|
||||
self._control_client.server_address, GUID, self._control_client.proxies
|
||||
)
|
||||
control_channel = ControlChannel(self._control_client.server_address, GUID)
|
||||
control_channel.register_agent(self._opts.parent)
|
||||
|
||||
config = control_channel.get_config()
|
||||
|
@ -199,7 +194,6 @@ class InfectionMonkey:
|
|||
self._relay.start()
|
||||
|
||||
StateTelem(is_done=False, version=get_version()).send()
|
||||
TunnelTelem(self._control_client.proxies).send()
|
||||
|
||||
self._build_master()
|
||||
|
||||
|
@ -209,9 +203,7 @@ class InfectionMonkey:
|
|||
local_network_interfaces = InfectionMonkey._get_local_network_interfaces()
|
||||
|
||||
# TODO control_channel and control_client have same responsibilities, merge them
|
||||
control_channel = ControlChannel(
|
||||
self._control_client.server_address, GUID, self._control_client.proxies
|
||||
)
|
||||
control_channel = ControlChannel(self._control_client.server_address, GUID)
|
||||
propagation_credentials_repository = AggregatingPropagationCredentialsRepository(
|
||||
control_channel
|
||||
)
|
||||
|
@ -283,7 +275,7 @@ class InfectionMonkey:
|
|||
puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER)
|
||||
|
||||
agent_binary_repository = CachingAgentBinaryRepository(
|
||||
f"https://{self._control_client.server_address}", self._control_client.proxies
|
||||
f"https://{self._control_client.server_address}"
|
||||
)
|
||||
exploit_wrapper = ExploiterWrapper(
|
||||
self._telemetry_messenger, event_queue, agent_binary_repository
|
||||
|
@ -383,7 +375,7 @@ class InfectionMonkey:
|
|||
on_island = self._running_on_island(local_network_interfaces)
|
||||
logger.debug(f"This agent is running on the island: {on_island}")
|
||||
|
||||
return VictimHostFactory(None, self._cmd_island_ip, self._cmd_island_port, on_island)
|
||||
return VictimHostFactory(self._cmd_island_ip, self._cmd_island_port, on_island)
|
||||
|
||||
def _running_on_island(self, local_network_interfaces: List[IPv4Interface]) -> bool:
|
||||
server_ip, _ = address_to_ip_port(self._control_client.server_address)
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
from typing import Mapping
|
||||
|
||||
from common.common_consts.telem_categories import TelemCategoryEnum
|
||||
from infection_monkey.telemetry.base_telem import BaseTelem
|
||||
|
||||
|
||||
class TunnelTelem(BaseTelem):
|
||||
def __init__(self, proxy: Mapping[str, str]):
|
||||
"""
|
||||
Default tunnel telemetry constructor
|
||||
"""
|
||||
super(TunnelTelem, self).__init__()
|
||||
self.proxy = proxy.get("https")
|
||||
|
||||
telem_category = TelemCategoryEnum.TUNNEL
|
||||
|
||||
def get_data(self):
|
||||
return {"proxy": self.proxy}
|
|
@ -1,31 +0,0 @@
|
|||
import time
|
||||
from threading import Thread
|
||||
|
||||
g_last_served = None
|
||||
PROXY_TIMEOUT = 2.5
|
||||
|
||||
|
||||
class TransportProxyBase(Thread):
|
||||
def __init__(self, local_port, dest_host=None, dest_port=None, local_host=""):
|
||||
global g_last_served
|
||||
|
||||
self.local_host = local_host
|
||||
self.local_port = local_port
|
||||
self.dest_host = dest_host
|
||||
self.dest_port = dest_port
|
||||
self._stopped = False
|
||||
super(TransportProxyBase, self).__init__()
|
||||
self.daemon = True
|
||||
|
||||
def stop(self):
|
||||
self._stopped = True
|
||||
|
||||
|
||||
def update_last_serve_time():
|
||||
global g_last_served
|
||||
g_last_served = time.time()
|
||||
|
||||
|
||||
def get_last_serve_time():
|
||||
global g_last_served
|
||||
return g_last_served
|
|
@ -1,17 +1,9 @@
|
|||
import http.server
|
||||
import select
|
||||
import socket
|
||||
import threading
|
||||
import urllib
|
||||
from logging import getLogger
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from infection_monkey.network.tools import get_interface_to_target
|
||||
from infection_monkey.transport.base import (
|
||||
PROXY_TIMEOUT,
|
||||
TransportProxyBase,
|
||||
update_last_serve_time,
|
||||
)
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
@ -110,56 +102,6 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
|||
)
|
||||
|
||||
|
||||
class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
|
||||
timeout = 30 # timeout with clients, set to None not to make persistent connection
|
||||
|
||||
def version_string(self):
|
||||
return ""
|
||||
|
||||
def do_CONNECT(self):
|
||||
logger.info("Received a connect request!")
|
||||
# just provide a tunnel, transfer the data with no modification
|
||||
req = self
|
||||
req.path = "https://%s/" % req.path.replace(":443", "")
|
||||
|
||||
u = urlsplit(req.path)
|
||||
address = (u.hostname, u.port or 443)
|
||||
try:
|
||||
conn = socket.create_connection(address)
|
||||
except socket.error as e:
|
||||
logger.debug(
|
||||
"HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s"
|
||||
% (repr(address), e)
|
||||
)
|
||||
self.send_error(504) # 504 Gateway Timeout
|
||||
return
|
||||
self.send_response(200, "Connection Established")
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
|
||||
conns = [self.connection, conn]
|
||||
keep_connection = True
|
||||
while keep_connection:
|
||||
keep_connection = False
|
||||
rlist, wlist, xlist = select.select(conns, [], conns, self.timeout)
|
||||
if xlist:
|
||||
break
|
||||
for r in rlist:
|
||||
other = conns[1] if r is conns[0] else conns[0]
|
||||
data = r.recv(8192)
|
||||
if data:
|
||||
other.sendall(data)
|
||||
keep_connection = True
|
||||
update_last_serve_time()
|
||||
conn.close()
|
||||
|
||||
def log_message(self, format_string, *args):
|
||||
logger.debug(
|
||||
"HTTPConnectProxyHandler: %s - [%s] %s"
|
||||
% (self.address_string(), self.log_date_time_string(), format_string % args)
|
||||
)
|
||||
|
||||
|
||||
class LockedHTTPServer(threading.Thread):
|
||||
"""
|
||||
Same as HTTPServer used for file downloads just with locks to avoid racing conditions.
|
||||
|
@ -226,11 +168,3 @@ class LockedHTTPServer(threading.Thread):
|
|||
def stop(self, timeout=STOP_TIMEOUT):
|
||||
self._stopped = True
|
||||
self.join(timeout)
|
||||
|
||||
|
||||
class HTTPConnectProxy(TransportProxyBase):
|
||||
def run(self):
|
||||
httpd = http.server.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
|
||||
httpd.timeout = PROXY_TIMEOUT
|
||||
while not self._stopped:
|
||||
httpd.handle_request()
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import select
|
||||
import socket
|
||||
from logging import getLogger
|
||||
from threading import Thread
|
||||
|
||||
from infection_monkey.transport.base import (
|
||||
PROXY_TIMEOUT,
|
||||
TransportProxyBase,
|
||||
update_last_serve_time,
|
||||
)
|
||||
|
||||
READ_BUFFER_SIZE = 8192
|
||||
SOCKET_READ_TIMEOUT = 10
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class SocketsPipe(Thread):
|
||||
def __init__(self, source, dest, timeout=SOCKET_READ_TIMEOUT):
|
||||
Thread.__init__(self)
|
||||
self.source = source
|
||||
self.dest = dest
|
||||
self.timeout = timeout
|
||||
self._keep_connection = True
|
||||
super(SocketsPipe, self).__init__()
|
||||
self.daemon = True
|
||||
|
||||
def run(self):
|
||||
sockets = [self.source, self.dest]
|
||||
while self._keep_connection:
|
||||
self._keep_connection = False
|
||||
rlist, wlist, xlist = select.select(sockets, [], sockets, self.timeout)
|
||||
if xlist:
|
||||
break
|
||||
for r in rlist:
|
||||
other = self.dest if r is self.source else self.source
|
||||
try:
|
||||
data = r.recv(READ_BUFFER_SIZE)
|
||||
except Exception:
|
||||
break
|
||||
if data:
|
||||
try:
|
||||
other.sendall(data)
|
||||
update_last_serve_time()
|
||||
except Exception:
|
||||
break
|
||||
self._keep_connection = True
|
||||
|
||||
self.source.close()
|
||||
self.dest.close()
|
||||
|
||||
|
||||
class TcpProxy(TransportProxyBase):
|
||||
def run(self):
|
||||
pipes = []
|
||||
l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
l_socket.bind((self.local_host, self.local_port))
|
||||
l_socket.settimeout(PROXY_TIMEOUT)
|
||||
l_socket.listen(5)
|
||||
|
||||
while not self._stopped:
|
||||
try:
|
||||
source, address = l_socket.accept()
|
||||
except socket.timeout:
|
||||
continue
|
||||
|
||||
dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
dest.connect((self.dest_host, self.dest_port))
|
||||
except socket.error:
|
||||
source.close()
|
||||
dest.close()
|
||||
continue
|
||||
|
||||
pipe = SocketsPipe(source, dest)
|
||||
pipes.append(pipe)
|
||||
logger.debug(
|
||||
"piping sockets %s:%s->%s:%s",
|
||||
address[0],
|
||||
address[1],
|
||||
self.dest_host,
|
||||
self.dest_port,
|
||||
)
|
||||
pipe.start()
|
||||
|
||||
l_socket.close()
|
||||
for pipe in pipes:
|
||||
pipe.join()
|
|
@ -1,230 +0,0 @@
|
|||
import logging
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
from threading import Event, Thread
|
||||
|
||||
from common.utils import Timer
|
||||
from infection_monkey.network.firewall import app as firewall
|
||||
from infection_monkey.network.info import get_free_tcp_port, local_ips
|
||||
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target
|
||||
from infection_monkey.transport.base import get_last_serve_time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MCAST_GROUP = "224.1.1.1"
|
||||
MCAST_PORT = 5007
|
||||
BUFFER_READ = 1024
|
||||
DEFAULT_TIMEOUT = 10
|
||||
QUIT_TIMEOUT = 60 * 10 # 10 minutes
|
||||
|
||||
|
||||
def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=""):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
sock.settimeout(timeout)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind((adapter, MCAST_PORT))
|
||||
sock.setsockopt(
|
||||
socket.IPPROTO_IP,
|
||||
socket.IP_ADD_MEMBERSHIP,
|
||||
struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY),
|
||||
)
|
||||
return sock
|
||||
|
||||
|
||||
def _check_tunnel(address, port, existing_sock=None):
|
||||
if not existing_sock:
|
||||
sock = _set_multicast_socket()
|
||||
else:
|
||||
sock = existing_sock
|
||||
|
||||
logger.debug("Checking tunnel %s:%s", address, port)
|
||||
is_open, _ = check_tcp_port(address, int(port))
|
||||
if not is_open:
|
||||
logger.debug("Could not connect to %s:%s", address, port)
|
||||
if not existing_sock:
|
||||
sock.close()
|
||||
return False
|
||||
|
||||
try:
|
||||
sock.sendto(b"+", (address, MCAST_PORT))
|
||||
except Exception as exc:
|
||||
logger.debug("Caught exception in tunnel registration: %s", exc)
|
||||
|
||||
if not existing_sock:
|
||||
sock.close()
|
||||
return True
|
||||
|
||||
|
||||
def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT):
|
||||
l_ips = local_ips()
|
||||
|
||||
if default:
|
||||
if default.find(":") != -1:
|
||||
address, port = default.split(":", 1)
|
||||
if _check_tunnel(address, port):
|
||||
return address, port
|
||||
|
||||
for adapter in l_ips:
|
||||
for attempt in range(0, attempts):
|
||||
try:
|
||||
logger.info("Trying to find using adapter %s", adapter)
|
||||
sock = _set_multicast_socket(timeout, adapter)
|
||||
sock.sendto(b"?", (MCAST_GROUP, MCAST_PORT))
|
||||
tunnels = []
|
||||
|
||||
while True:
|
||||
try:
|
||||
answer, address = sock.recvfrom(BUFFER_READ)
|
||||
if answer not in [b"?", b"+", b"-"]:
|
||||
tunnels.append(answer)
|
||||
except socket.timeout:
|
||||
break
|
||||
|
||||
for tunnel in tunnels:
|
||||
if tunnel.find(":") != -1:
|
||||
address, port = tunnel.split(":", 1)
|
||||
if address in l_ips:
|
||||
continue
|
||||
|
||||
if _check_tunnel(address, port, sock):
|
||||
sock.close()
|
||||
return address, port
|
||||
|
||||
except Exception as exc:
|
||||
logger.debug("Caught exception in tunnel lookup: %s", exc)
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def quit_tunnel(address, timeout=DEFAULT_TIMEOUT):
|
||||
try:
|
||||
sock = _set_multicast_socket(timeout)
|
||||
sock.sendto(b"-", (address, MCAST_PORT))
|
||||
sock.close()
|
||||
logger.debug("Success quitting tunnel")
|
||||
except Exception as exc:
|
||||
logger.debug("Exception quitting tunnel: %s", exc)
|
||||
return
|
||||
|
||||
|
||||
class MonkeyTunnel(Thread):
|
||||
def __init__(
|
||||
self,
|
||||
proxy_class,
|
||||
keep_tunnel_open_time,
|
||||
target_addr=None,
|
||||
target_port=None,
|
||||
timeout=DEFAULT_TIMEOUT,
|
||||
):
|
||||
self._target_addr = target_addr
|
||||
self._target_port = target_port
|
||||
self._proxy_class = proxy_class
|
||||
self._keep_tunnel_open_time = keep_tunnel_open_time
|
||||
self._broad_sock = None
|
||||
self._timeout = timeout
|
||||
self._stopped = Event()
|
||||
self._clients = []
|
||||
self.local_port = None
|
||||
super(MonkeyTunnel, self).__init__(name="MonkeyTunnelThread")
|
||||
self.daemon = True
|
||||
self.l_ips = None
|
||||
self._wait_for_exploited_machines = Event()
|
||||
|
||||
def run(self):
|
||||
self._broad_sock = _set_multicast_socket(self._timeout)
|
||||
self.l_ips = local_ips()
|
||||
self.local_port = get_free_tcp_port()
|
||||
|
||||
if not self.local_port:
|
||||
return
|
||||
|
||||
if not firewall.listen_allowed(localport=self.local_port):
|
||||
logger.info("Machine firewalled, listen not allowed, not running tunnel.")
|
||||
return
|
||||
|
||||
proxy = self._proxy_class(
|
||||
local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port
|
||||
)
|
||||
logger.info(
|
||||
"Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s",
|
||||
proxy.__class__.__name__,
|
||||
self.local_port,
|
||||
self._target_addr,
|
||||
self._target_port,
|
||||
)
|
||||
proxy.start()
|
||||
|
||||
while not self._stopped.is_set():
|
||||
try:
|
||||
search, address = self._broad_sock.recvfrom(BUFFER_READ)
|
||||
if b"?" == search:
|
||||
ip_match = get_interface_to_target(address[0])
|
||||
if ip_match:
|
||||
answer = "%s:%d" % (ip_match, self.local_port)
|
||||
logger.debug(
|
||||
"Got tunnel request from %s, answering with %s", address[0], answer
|
||||
)
|
||||
self._broad_sock.sendto(answer.encode(), (address[0], MCAST_PORT))
|
||||
elif b"+" == search:
|
||||
if not address[0] in self._clients:
|
||||
logger.debug("Tunnel control: Added %s to watchlist", address[0])
|
||||
self._clients.append(address[0])
|
||||
elif b"-" == search:
|
||||
logger.debug("Tunnel control: Removed %s from watchlist", address[0])
|
||||
self._clients = [client for client in self._clients if client != address[0]]
|
||||
|
||||
except socket.timeout:
|
||||
continue
|
||||
|
||||
logger.info("Stopping tunnel, waiting for clients: %s" % repr(self._clients))
|
||||
|
||||
# wait till all of the tunnel clients has been disconnected, or no one used the tunnel in
|
||||
# QUIT_TIMEOUT seconds
|
||||
timer = Timer()
|
||||
timer.set(self._calculate_timeout())
|
||||
while self._clients and not timer.is_expired():
|
||||
try:
|
||||
search, address = self._broad_sock.recvfrom(BUFFER_READ)
|
||||
if b"-" == search:
|
||||
logger.debug("Tunnel control: Removed %s from watchlist", address[0])
|
||||
self._clients = [client for client in self._clients if client != address[0]]
|
||||
except socket.timeout:
|
||||
continue
|
||||
|
||||
timer.set(self._calculate_timeout())
|
||||
|
||||
logger.info("Closing tunnel")
|
||||
self._broad_sock.close()
|
||||
proxy.stop()
|
||||
proxy.join()
|
||||
|
||||
def _calculate_timeout(self) -> float:
|
||||
try:
|
||||
return QUIT_TIMEOUT - (time.time() - get_last_serve_time())
|
||||
except TypeError: # get_last_serve_time() may return None
|
||||
return 0.0
|
||||
|
||||
def get_tunnel_for_ip(self, ip: str):
|
||||
|
||||
if not self.local_port:
|
||||
return
|
||||
|
||||
ip_match = get_interface_to_target(ip)
|
||||
return "%s:%d" % (ip_match, self.local_port)
|
||||
|
||||
def set_wait_for_exploited_machines(self):
|
||||
self._wait_for_exploited_machines.set()
|
||||
|
||||
def stop(self):
|
||||
self._wait_for_exploited_machine_connection()
|
||||
self._stopped.set()
|
||||
|
||||
def _wait_for_exploited_machine_connection(self):
|
||||
if self._wait_for_exploited_machines.is_set():
|
||||
logger.info(
|
||||
f"Waiting {self._keep_tunnel_open_time} seconds for exploited machines to connect "
|
||||
"to the tunnel."
|
||||
)
|
||||
time.sleep(self._keep_tunnel_open_time)
|
|
@ -64,16 +64,6 @@ class TelemetryFeed(AbstractResource):
|
|||
def get_telem_brief_parser_by_category(telem_category):
|
||||
return TELEM_PROCESS_DICT[telem_category]
|
||||
|
||||
@staticmethod
|
||||
def get_tunnel_telem_brief(telem):
|
||||
tunnel = telem["data"]["proxy"]
|
||||
if tunnel is None:
|
||||
return "No tunnel is used."
|
||||
else:
|
||||
tunnel_host_ip = tunnel.split(":")[-2].replace("//", "")
|
||||
tunnel_host = NodeService.get_monkey_by_ip(tunnel_host_ip)["hostname"]
|
||||
return "Tunnel set up to machine: %s." % tunnel_host
|
||||
|
||||
@staticmethod
|
||||
def get_state_telem_brief(telem):
|
||||
if telem["data"]["done"]:
|
||||
|
@ -132,7 +122,6 @@ TELEM_PROCESS_DICT = {
|
|||
TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief,
|
||||
TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief,
|
||||
TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief,
|
||||
TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ from monkey_island.cc.services.telemetry.processing.exploit import process_explo
|
|||
from monkey_island.cc.services.telemetry.processing.post_breach import process_post_breach_telemetry
|
||||
from monkey_island.cc.services.telemetry.processing.scan import process_scan_telemetry
|
||||
from monkey_island.cc.services.telemetry.processing.state import process_state_telemetry
|
||||
from monkey_island.cc.services.telemetry.processing.tunnel import process_tunnel_telemetry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -22,7 +21,6 @@ TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = {
|
|||
TelemCategoryEnum.SCAN: process_scan_telemetry,
|
||||
TelemCategoryEnum.STATE: process_state_telemetry,
|
||||
TelemCategoryEnum.TRACE: lambda *args, **kwargs: None,
|
||||
TelemCategoryEnum.TUNNEL: process_tunnel_telemetry,
|
||||
}
|
||||
|
||||
# Don't save credential telemetries in telemetries collection.
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
from monkey_island.cc.services.node import NodeService
|
||||
from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field
|
||||
from monkey_island.cc.services.telemetry.zero_trust_checks.tunneling import (
|
||||
check_tunneling_violation,
|
||||
)
|
||||
|
||||
|
||||
def process_tunnel_telemetry(telemetry_json, _):
|
||||
check_tunneling_violation(telemetry_json)
|
||||
monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])["_id"]
|
||||
if telemetry_json["data"]["proxy"] is not None:
|
||||
tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(telemetry_json)
|
||||
NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip)
|
||||
else:
|
||||
NodeService.unset_all_monkey_tunnels(monkey_id)
|
|
@ -14,8 +14,3 @@ def get_edge_by_scan_or_exploit_telemetry(telemetry_json):
|
|||
dst_label = NodeService.get_label_for_endpoint(dst_node["_id"])
|
||||
|
||||
return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"], src_label, dst_label)
|
||||
|
||||
|
||||
def get_tunnel_host_ip_from_proxy_field(telemetry_json):
|
||||
tunnel_host_ip = telemetry_json["data"]["proxy"].split(":")[-2].replace("//", "")
|
||||
return tunnel_host_ip
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import common.common_consts.zero_trust_consts as zero_trust_consts
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.models.zero_trust.event import Event
|
||||
from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field
|
||||
from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import (
|
||||
MonkeyZTFindingService,
|
||||
)
|
||||
|
||||
|
||||
def check_tunneling_violation(tunnel_telemetry_json):
|
||||
if tunnel_telemetry_json["data"]["proxy"] is not None:
|
||||
# Monkey is tunneling, create findings
|
||||
tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(tunnel_telemetry_json)
|
||||
current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json["monkey_guid"])
|
||||
tunneling_events = [
|
||||
Event.create_event(
|
||||
title="Tunneling event",
|
||||
message="Monkey on {hostname} tunneled traffic through {proxy}.".format(
|
||||
hostname=current_monkey.hostname, proxy=tunnel_host_ip
|
||||
),
|
||||
event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK,
|
||||
timestamp=tunnel_telemetry_json["timestamp"],
|
||||
)
|
||||
]
|
||||
|
||||
MonkeyZTFindingService.create_or_add_to_existing(
|
||||
test=zero_trust_consts.TEST_TUNNELING,
|
||||
status=zero_trust_consts.STATUS_FAILED,
|
||||
events=tunneling_events,
|
||||
)
|
||||
|
||||
MonkeyZTFindingService.add_malicious_activity_to_timeline(tunneling_events)
|
|
@ -1,18 +1,9 @@
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from infection_monkey.model import VictimHostFactory
|
||||
from infection_monkey.network import NetworkAddress
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_tunnel():
|
||||
tunnel = MagicMock()
|
||||
tunnel.get_tunnel_for_ip = lambda _: "1.2.3.4:1234"
|
||||
return tunnel
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_get_interface_to_target(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
|
@ -21,37 +12,18 @@ def mock_get_interface_to_target(monkeypatch):
|
|||
|
||||
|
||||
def test_factory_no_tunnel():
|
||||
factory = VictimHostFactory(
|
||||
tunnel=None, island_ip="192.168.56.1", island_port="5000", on_island=False
|
||||
)
|
||||
factory = VictimHostFactory(island_ip="192.168.56.1", island_port="5000", on_island=False)
|
||||
network_address = NetworkAddress("192.168.56.2", None)
|
||||
|
||||
victim = factory.build_victim_host(network_address)
|
||||
|
||||
assert victim.default_server == "192.168.56.1:5000"
|
||||
assert victim.ip_addr == "192.168.56.2"
|
||||
assert victim.default_tunnel is None
|
||||
assert victim.domain_name == ""
|
||||
|
||||
|
||||
def test_factory_with_tunnel(mock_tunnel):
|
||||
factory = VictimHostFactory(
|
||||
tunnel=mock_tunnel, island_ip="192.168.56.1", island_port="5000", on_island=False
|
||||
)
|
||||
network_address = NetworkAddress("192.168.56.2", None)
|
||||
|
||||
victim = factory.build_victim_host(network_address)
|
||||
|
||||
assert victim.default_server == "192.168.56.1:5000"
|
||||
assert victim.ip_addr == "192.168.56.2"
|
||||
assert victim.default_tunnel == "1.2.3.4:1234"
|
||||
assert victim.domain_name == ""
|
||||
|
||||
|
||||
def test_factory_on_island(mock_tunnel):
|
||||
factory = VictimHostFactory(
|
||||
tunnel=mock_tunnel, island_ip="192.168.56.1", island_port="99", on_island=True
|
||||
)
|
||||
def test_factory_on_island():
|
||||
factory = VictimHostFactory(island_ip="192.168.56.1", island_port="99", on_island=True)
|
||||
network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey")
|
||||
|
||||
victim = factory.build_victim_host(network_address)
|
||||
|
@ -59,14 +31,11 @@ def test_factory_on_island(mock_tunnel):
|
|||
assert victim.default_server == "1.1.1.1:99"
|
||||
assert victim.domain_name == "www.bogus.monkey"
|
||||
assert victim.ip_addr == "192.168.56.2"
|
||||
assert victim.default_tunnel == "1.2.3.4:1234"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("default_port", ["", None])
|
||||
def test_factory_no_port(mock_tunnel, default_port):
|
||||
factory = VictimHostFactory(
|
||||
tunnel=mock_tunnel, island_ip="192.168.56.1", island_port=default_port, on_island=True
|
||||
)
|
||||
def test_factory_no_port(default_port):
|
||||
factory = VictimHostFactory(island_ip="192.168.56.1", island_port=default_port, on_island=True)
|
||||
network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey")
|
||||
|
||||
victim = factory.build_victim_host(network_address)
|
||||
|
@ -74,8 +43,8 @@ def test_factory_no_port(mock_tunnel, default_port):
|
|||
assert victim.default_server == "1.1.1.1"
|
||||
|
||||
|
||||
def test_factory_no_default_server(mock_tunnel):
|
||||
factory = VictimHostFactory(tunnel=mock_tunnel, island_ip=None, island_port="", on_island=True)
|
||||
def test_factory_no_default_server():
|
||||
factory = VictimHostFactory(island_ip=None, island_port="", on_island=True)
|
||||
network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey")
|
||||
|
||||
victim = factory.build_victim_host(network_address)
|
||||
|
|
|
@ -16,7 +16,6 @@ HOST_AS_DICT = {
|
|||
"os": {},
|
||||
"services": {},
|
||||
"icmp": False,
|
||||
"default_tunnel": None,
|
||||
"default_server": None,
|
||||
}
|
||||
EXPLOITER_NAME = "SSHExploiter"
|
||||
|
|
|
@ -14,7 +14,6 @@ HOST_AS_DICT = {
|
|||
"os": {},
|
||||
"services": {},
|
||||
"icmp": False,
|
||||
"default_tunnel": None,
|
||||
"default_server": None,
|
||||
}
|
||||
HOST_SERVICES = {}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from infection_monkey.telemetry.tunnel_telem import TunnelTelem
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tunnel_telem_test_instance():
|
||||
return TunnelTelem({})
|
||||
|
||||
|
||||
def test_tunnel_telem_send(tunnel_telem_test_instance, spy_send_telemetry):
|
||||
tunnel_telem_test_instance.send()
|
||||
expected_data = {"proxy": None}
|
||||
expected_data = json.dumps(expected_data, cls=tunnel_telem_test_instance.json_encoder)
|
||||
|
||||
assert spy_send_telemetry.data == expected_data
|
||||
assert spy_send_telemetry.telem_category == "tunnel"
|
|
@ -1,16 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from monkey.infection_monkey.control import ControlClient
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"is_windows_os,expected_proxy_string",
|
||||
[(True, "http://8.8.8.8:45455"), (False, "8.8.8.8:45455")],
|
||||
)
|
||||
def test_control_set_proxies(monkeypatch, is_windows_os, expected_proxy_string):
|
||||
monkeypatch.setattr("monkey.infection_monkey.control.is_windows_os", lambda: is_windows_os)
|
||||
control_client = ControlClient("8.8.8.8:5000")
|
||||
|
||||
control_client.set_proxies(("8.8.8.8", "45455"))
|
||||
|
||||
assert control_client.proxies["https"] == expected_proxy_string
|
|
@ -18,7 +18,6 @@ SCAN_DATA_MOCK = [
|
|||
},
|
||||
},
|
||||
"monkey_exe": None,
|
||||
"default_tunnel": None,
|
||||
"default_server": None,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -299,9 +299,6 @@ event
|
|||
deserialize
|
||||
serialized_event
|
||||
|
||||
# TODO: Remove when removing Tunnel code
|
||||
create_control_tunnel
|
||||
set_wait_for_exploited_machines
|
||||
|
||||
# pydantic base models
|
||||
underscore_attrs_are_private
|
||||
|
|
Loading…
Reference in New Issue