forked from p15670423/monkey
Merge pull request #527 from VakarisZ/old_machine_bootloader
Old machine bootloader
This commit is contained in:
commit
4da6a6a491
|
@ -546,6 +546,38 @@ fullTest.conf is a good config to start, because it covers all machines.
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th><p><span id="_Toc536021463" class="anchor"></span>Nr. <strong>12</strong> Tunneling M4</p>
|
||||||
|
<p>(10.2.0.12)</p></th>
|
||||||
|
<th>(Exploitable)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>OS:</td>
|
||||||
|
<td><strong>Windows server 2019 x64</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Default service’s port:</td>
|
||||||
|
<td>445</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Root password:</td>
|
||||||
|
<td>t67TC5ZDmz</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Server’s config:</td>
|
||||||
|
<td>Default</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Notes:</td>
|
||||||
|
<td>Accessible only trough Nr.10</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="header">
|
<tr class="header">
|
||||||
|
|
|
@ -5,7 +5,7 @@ requests
|
||||||
odict
|
odict
|
||||||
paramiko
|
paramiko
|
||||||
psutil
|
psutil
|
||||||
PyInstaller
|
git+https://github.com/guardicore/pyinstaller
|
||||||
ecdsa
|
ecdsa
|
||||||
netifaces
|
netifaces
|
||||||
ipaddress
|
ipaddress
|
||||||
|
|
|
@ -7,9 +7,12 @@ import urllib
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
import infection_monkey.monkeyfs as monkeyfs
|
import infection_monkey.monkeyfs as monkeyfs
|
||||||
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
|
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
|
||||||
from infection_monkey.network.tools import get_interface_to_target
|
from infection_monkey.network.tools import get_interface_to_target
|
||||||
|
import infection_monkey.control
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -108,12 +111,35 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
|
class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
|
||||||
timeout = 30 # timeout with clients, set to None not to make persistent connection
|
timeout = 30 # timeout with clients, set to None not to make persistent connection
|
||||||
proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
|
proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
|
||||||
protocol_version = "HTTP/1.1"
|
|
||||||
|
def do_POST(self):
|
||||||
|
try:
|
||||||
|
content_length = int(self.headers['Content-Length'])
|
||||||
|
post_data = self.rfile.read(content_length).decode()
|
||||||
|
LOG.info("Received bootloader's request: {}".format(post_data))
|
||||||
|
try:
|
||||||
|
dest_path = self.path
|
||||||
|
r = requests.post(url=dest_path,
|
||||||
|
data=post_data,
|
||||||
|
verify=False,
|
||||||
|
proxies=infection_monkey.control.ControlClient.proxies)
|
||||||
|
self.send_response(r.status_code)
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
LOG.error("Couldn't forward request to the island: {}".format(e))
|
||||||
|
self.send_response(404)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Failed to forward bootloader request: {}".format(e))
|
||||||
|
finally:
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(r.content)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Failed receiving bootloader telemetry: {}".format(e))
|
||||||
|
|
||||||
def version_string(self):
|
def version_string(self):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def do_CONNECT(self):
|
def do_CONNECT(self):
|
||||||
|
LOG.info("Received a connect request!")
|
||||||
# just provide a tunnel, transfer the data with no modification
|
# just provide a tunnel, transfer the data with no modification
|
||||||
req = self
|
req = self
|
||||||
req.path = "https://%s/" % req.path.replace(':443', '')
|
req.path = "https://%s/" % req.path.replace(':443', '')
|
||||||
|
|
|
@ -19,6 +19,7 @@ from monkey_island.cc.resources.island_configuration import IslandConfiguration
|
||||||
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
||||||
from monkey_island.cc.resources.netmap import NetMap
|
from monkey_island.cc.resources.netmap import NetMap
|
||||||
from monkey_island.cc.resources.node import Node
|
from monkey_island.cc.resources.node import Node
|
||||||
|
from monkey_island.cc.resources.node_states import NodeStates
|
||||||
from monkey_island.cc.resources.remote_run import RemoteRun
|
from monkey_island.cc.resources.remote_run import RemoteRun
|
||||||
from monkey_island.cc.resources.reporting.report import Report
|
from monkey_island.cc.resources.reporting.report import Report
|
||||||
from monkey_island.cc.resources.root import Root
|
from monkey_island.cc.resources.root import Root
|
||||||
|
@ -30,6 +31,7 @@ from monkey_island.cc.resources.version_update import VersionUpdate
|
||||||
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
||||||
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
||||||
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
||||||
|
from monkey_island.cc.resources.bootloader import Bootloader
|
||||||
from monkey_island.cc.services.database import Database
|
from monkey_island.cc.services.database import Database
|
||||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||||
from monkey_island.cc.services.representations import output_json
|
from monkey_island.cc.services.representations import output_json
|
||||||
|
@ -87,6 +89,7 @@ def init_app_url_rules(app):
|
||||||
def init_api_resources(api):
|
def init_api_resources(api):
|
||||||
api.add_resource(Root, '/api')
|
api.add_resource(Root, '/api')
|
||||||
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
|
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
|
||||||
|
api.add_resource(Bootloader, '/api/bootloader/<string:os>')
|
||||||
api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/')
|
api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/')
|
||||||
api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/')
|
api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/')
|
||||||
api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/<string:monkey_guid>')
|
api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/<string:monkey_guid>')
|
||||||
|
@ -97,6 +100,7 @@ def init_api_resources(api):
|
||||||
api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
|
api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
|
||||||
api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
|
api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
|
||||||
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
|
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
|
||||||
|
api.add_resource(NodeStates, '/api/netmap/nodeStates')
|
||||||
|
|
||||||
# report_type: zero_trust or security
|
# report_type: zero_trust or security
|
||||||
api.add_resource(
|
api.add_resource(
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
from socketserver import ThreadingMixIn
|
||||||
|
from urllib import parse
|
||||||
|
import urllib3
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
from monkey_island.cc.environment import Environment
|
||||||
|
|
||||||
|
# Disable "unverified certificate" warnings when sending requests to island
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BootloaderHttpServer(ThreadingMixIn, HTTPServer):
|
||||||
|
|
||||||
|
def __init__(self, mongo_url):
|
||||||
|
self.mongo_client = pymongo.MongoClient(mongo_url)
|
||||||
|
server_address = ('', 5001)
|
||||||
|
super().__init__(server_address, BootloaderHTTPRequestHandler)
|
||||||
|
|
||||||
|
|
||||||
|
class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
content_length = int(self.headers['Content-Length'])
|
||||||
|
post_data = self.rfile.read(content_length).decode()
|
||||||
|
island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(self.request.getsockname()[0])
|
||||||
|
island_server_path = parse.urljoin(island_server_path, self.path[1:])
|
||||||
|
r = requests.post(url=island_server_path, data=post_data, verify=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if r.status_code != 200:
|
||||||
|
self.send_response(404)
|
||||||
|
else:
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(r.content)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to respond to bootloader: {}".format(e))
|
||||||
|
finally:
|
||||||
|
self.connection.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_bootloader_resource_url(server_ip):
|
||||||
|
return "https://" + server_ip + ":" + str(Environment._ISLAND_PORT) + "/api/bootloader/"
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os.path
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
MINIMUM_MONGO_DB_VERSION_REQUIRED = "3.6.0"
|
MINIMUM_MONGO_DB_VERSION_REQUIRED = "3.6.0"
|
||||||
|
|
||||||
|
@ -26,9 +27,20 @@ from monkey_island.cc.environment.environment import env
|
||||||
from monkey_island.cc.database import is_db_server_up, get_db_version
|
from monkey_island.cc.database import is_db_server_up, get_db_version
|
||||||
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
||||||
from common.version import get_version
|
from common.version import get_version
|
||||||
|
from monkey_island.cc.bootloader_server import BootloaderHttpServer
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
logger.info("Starting bootloader server")
|
||||||
|
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
|
||||||
|
bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True)
|
||||||
|
|
||||||
|
bootloader_server_thread.start()
|
||||||
|
start_island_server()
|
||||||
|
bootloader_server_thread.join()
|
||||||
|
|
||||||
|
|
||||||
|
def start_island_server():
|
||||||
from tornado.wsgi import WSGIContainer
|
from tornado.wsgi import WSGIContainer
|
||||||
from tornado.httpserver import HTTPServer
|
from tornado.httpserver import HTTPServer
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import json
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import flask_restful
|
||||||
|
from flask import request, make_response
|
||||||
|
|
||||||
|
from monkey_island.cc.services.bootloader import BootloaderService
|
||||||
|
|
||||||
|
|
||||||
|
class Bootloader(flask_restful.Resource):
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
|
def post(self, os):
|
||||||
|
if os == 'linux':
|
||||||
|
data = Bootloader._get_request_contents_linux(request.data)
|
||||||
|
elif os == 'windows':
|
||||||
|
data = Bootloader._get_request_contents_windows(request.data)
|
||||||
|
else:
|
||||||
|
return make_response({"status": "OS_NOT_FOUND"}, 404)
|
||||||
|
|
||||||
|
result = BootloaderService.parse_bootloader_telem(data)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return make_response({"status": "RUN"}, 200)
|
||||||
|
else:
|
||||||
|
return make_response({"status": "ABORT"}, 200)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_request_contents_linux(request_data: bytes) -> Dict[str, str]:
|
||||||
|
parsed_data = json.loads(request_data.decode().replace("\"\n", "")
|
||||||
|
.replace("\n", "")
|
||||||
|
.replace("NAME=\"", "")
|
||||||
|
.replace("\":\",", "\":\"\","))
|
||||||
|
return parsed_data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_request_contents_windows(request_data: bytes) -> Dict[str, str]:
|
||||||
|
return json.loads(request_data.decode("utf-16", "ignore"))
|
|
@ -0,0 +1,56 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from monkey_island.cc.resources.bootloader import Bootloader
|
||||||
|
from monkey_island.cc.services.utils.bootloader_config import SUPPORTED_WINDOWS_VERSIONS
|
||||||
|
|
||||||
|
|
||||||
|
class TestBootloader(TestCase):
|
||||||
|
|
||||||
|
def test_get_request_contents_linux(self):
|
||||||
|
data_without_tunnel = b'{"system":"linux", ' \
|
||||||
|
b'"os_version":"NAME="Ubuntu"\n", ' \
|
||||||
|
b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' \
|
||||||
|
b'"hostname":"test-TEST", ' \
|
||||||
|
b'"tunnel":false, ' \
|
||||||
|
b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}'
|
||||||
|
data_with_tunnel = b'{"system":"linux", ' \
|
||||||
|
b'"os_version":"NAME="Ubuntu"\n", ' \
|
||||||
|
b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' \
|
||||||
|
b'"hostname":"test-TEST", ' \
|
||||||
|
b'"tunnel":"192.168.56.1:5002", ' \
|
||||||
|
b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}'
|
||||||
|
|
||||||
|
result1 = Bootloader._get_request_contents_linux(data_without_tunnel)
|
||||||
|
self.assertTrue(result1['system'] == "linux")
|
||||||
|
self.assertTrue(result1['os_version'] == "Ubuntu")
|
||||||
|
self.assertTrue(result1['glibc_version'] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23")
|
||||||
|
self.assertTrue(result1['hostname'] == "test-TEST")
|
||||||
|
self.assertFalse(result1['tunnel'])
|
||||||
|
self.assertTrue(result1['ips'] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"])
|
||||||
|
|
||||||
|
result2 = Bootloader._get_request_contents_linux(data_with_tunnel)
|
||||||
|
self.assertTrue(result2['system'] == "linux")
|
||||||
|
self.assertTrue(result2['os_version'] == "Ubuntu")
|
||||||
|
self.assertTrue(result2['glibc_version'] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23")
|
||||||
|
self.assertTrue(result2['hostname'] == "test-TEST")
|
||||||
|
self.assertTrue(result2['tunnel'] == "192.168.56.1:5002")
|
||||||
|
self.assertTrue(result2['ips'] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"])
|
||||||
|
|
||||||
|
def test_get_request_contents_windows(self):
|
||||||
|
windows_data = b'{\x00"\x00s\x00y\x00s\x00t\x00e\x00m\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o' \
|
||||||
|
b'\x00w\x00s\x00"\x00,\x00 \x00"\x00o\x00s\x00_\x00v\x00e\x00r\x00s\x00i\x00o\x00n' \
|
||||||
|
b'\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x008\x00_\x00o\x00r\x00_\x00g\x00r' \
|
||||||
|
b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 \x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"' \
|
||||||
|
b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006\x00B\x00"' \
|
||||||
|
b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e\x00,\x00 ' \
|
||||||
|
b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00[\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005' \
|
||||||
|
b'\x006\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009' \
|
||||||
|
b'\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.' \
|
||||||
|
b'\x001\x00"\x00]\x00}\x00'
|
||||||
|
|
||||||
|
result = Bootloader._get_request_contents_windows(windows_data)
|
||||||
|
self.assertTrue(result['system'] == "windows")
|
||||||
|
self.assertTrue(result['os_version'] == "windows8_or_greater")
|
||||||
|
self.assertTrue(result['hostname'] == "DESKTOP-PJHU36B")
|
||||||
|
self.assertFalse(result['tunnel'])
|
||||||
|
self.assertTrue(result['ips'] == ["192.168.56.1", "192.168.249.1", "192.168.217.1"])
|
|
@ -0,0 +1,11 @@
|
||||||
|
from flask import request
|
||||||
|
import flask_restful
|
||||||
|
|
||||||
|
from monkey_island.cc.auth import jwt_required
|
||||||
|
from monkey_island.cc.services.utils.node_states import NodeStates as NodeStateList
|
||||||
|
|
||||||
|
|
||||||
|
class NodeStates(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
|
def get(self):
|
||||||
|
return {'node_states': [state.value for state in NodeStateList]}
|
|
@ -0,0 +1,69 @@
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
|
from monkey_island.cc.database import mongo
|
||||||
|
from monkey_island.cc.services.node import NodeService, NodeCreationException
|
||||||
|
from monkey_island.cc.services.utils.node_states import NodeStates
|
||||||
|
from monkey_island.cc.services.utils.bootloader_config import SUPPORTED_WINDOWS_VERSIONS, MIN_GLIBC_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
class BootloaderService:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_bootloader_telem(telem: Dict) -> bool:
|
||||||
|
telem['ips'] = BootloaderService.remove_local_ips(telem['ips'])
|
||||||
|
if telem['os_version'] == "":
|
||||||
|
telem['os_version'] = "Unknown OS"
|
||||||
|
|
||||||
|
telem_id = BootloaderService.get_mongo_id_for_bootloader_telem(telem)
|
||||||
|
mongo.db.bootloader_telems.update({'_id': telem_id}, telem, upsert=True)
|
||||||
|
|
||||||
|
will_monkey_run = BootloaderService.is_os_compatible(telem)
|
||||||
|
try:
|
||||||
|
node = NodeService.get_or_create_node_from_bootloader_telem(telem, will_monkey_run)
|
||||||
|
except NodeCreationException:
|
||||||
|
# Didn't find the node, but allow monkey to run anyways
|
||||||
|
return True
|
||||||
|
|
||||||
|
node_group = BootloaderService.get_next_node_state(node, telem['system'], will_monkey_run)
|
||||||
|
if 'group' not in node or node['group'] != node_group.value:
|
||||||
|
NodeService.set_node_group(node['_id'], node_group)
|
||||||
|
return will_monkey_run
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_next_node_state(node: Dict, system: str, will_monkey_run: bool) -> NodeStates:
|
||||||
|
group_keywords = [system, 'monkey']
|
||||||
|
if 'group' in node and node['group'] == 'island':
|
||||||
|
group_keywords.extend(['island', 'starting'])
|
||||||
|
else:
|
||||||
|
group_keywords.append('starting') if will_monkey_run else group_keywords.append('old')
|
||||||
|
node_group = NodeStates.get_by_keywords(group_keywords)
|
||||||
|
return node_group
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_mongo_id_for_bootloader_telem(bootloader_telem) -> ObjectId:
|
||||||
|
ip_hash = hex(hash(str(bootloader_telem['ips'])))[3:15]
|
||||||
|
hostname_hash = hex(hash(bootloader_telem['hostname']))[3:15]
|
||||||
|
return ObjectId(ip_hash + hostname_hash)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_os_compatible(bootloader_data) -> bool:
|
||||||
|
if bootloader_data['system'] == 'windows':
|
||||||
|
return BootloaderService.is_windows_version_supported(bootloader_data['os_version'])
|
||||||
|
elif bootloader_data['system'] == 'linux':
|
||||||
|
return BootloaderService.is_glibc_supported(bootloader_data['glibc_version'])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_windows_version_supported(windows_version) -> bool:
|
||||||
|
return SUPPORTED_WINDOWS_VERSIONS.get(windows_version, True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_glibc_supported(glibc_version_string) -> bool:
|
||||||
|
glibc_version_string = glibc_version_string.lower()
|
||||||
|
glibc_version = glibc_version_string.split(' ')[-1]
|
||||||
|
return glibc_version >= str(MIN_GLIBC_VERSION) and 'eglibc' not in glibc_version_string
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_local_ips(ip_list) -> List[str]:
|
||||||
|
return [i for i in ip_list if not i.startswith("127")]
|
|
@ -0,0 +1,35 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from monkey_island.cc.services.bootloader import BootloaderService
|
||||||
|
|
||||||
|
WINDOWS_VERSIONS = {
|
||||||
|
"5.0": "Windows 2000",
|
||||||
|
"5.1": "Windows XP",
|
||||||
|
"5.2": "Windows XP/server 2003",
|
||||||
|
"6.0": "Windows Vista/server 2008",
|
||||||
|
"6.1": "Windows 7/server 2008R2",
|
||||||
|
"6.2": "Windows 8/server 2012",
|
||||||
|
"6.3": "Windows 8.1/server 2012R2",
|
||||||
|
"10.0": "Windows 10/server 2016-2019"
|
||||||
|
}
|
||||||
|
|
||||||
|
MIN_GLIBC_VERSION = 2.14
|
||||||
|
|
||||||
|
|
||||||
|
class TestBootloaderService(TestCase):
|
||||||
|
|
||||||
|
def test_is_glibc_supported(self):
|
||||||
|
str1 = "ldd (Ubuntu EGLIBC 2.15-0ubuntu10) 2.15"
|
||||||
|
str2 = "ldd (GNU libc) 2.12"
|
||||||
|
str3 = "ldd (GNU libc) 2.28"
|
||||||
|
str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23"
|
||||||
|
self.assertTrue(not BootloaderService.is_glibc_supported(str1) and
|
||||||
|
not BootloaderService.is_glibc_supported(str2) and
|
||||||
|
BootloaderService.is_glibc_supported(str3) and
|
||||||
|
BootloaderService.is_glibc_supported(str4))
|
||||||
|
|
||||||
|
def test_remove_local_ips(self):
|
||||||
|
ips = ["127.1.1.1", "127.0.0.1", "192.168.56.1"]
|
||||||
|
ips = BootloaderService.remove_local_ips(ips)
|
||||||
|
self.assertEqual(["192.168.56.1"], ips)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from bson import ObjectId
|
||||||
|
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
import monkey_island.cc.services.node
|
import monkey_island.cc.services.node
|
||||||
from monkey_island.cc.models import Monkey
|
from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError
|
||||||
|
|
||||||
__author__ = "itay.mizeretz"
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
@ -145,7 +145,10 @@ class EdgeService:
|
||||||
from_id = edge["from"]
|
from_id = edge["from"]
|
||||||
to_id = edge["to"]
|
to_id = edge["to"]
|
||||||
|
|
||||||
from_label = Monkey.get_label_by_id(from_id)
|
try:
|
||||||
|
from_label = Monkey.get_label_by_id(from_id)
|
||||||
|
except MonkeyNotFoundError:
|
||||||
|
from_label = node_service.get_node_by_id(from_id)['domain_name']
|
||||||
|
|
||||||
if to_id == ObjectId("000000000000000000000000"):
|
if to_id == ObjectId("000000000000000000000000"):
|
||||||
to_label = 'MonkeyIsland'
|
to_label = 'MonkeyIsland'
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict
|
||||||
|
import socket
|
||||||
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
|
|
||||||
|
@ -6,9 +8,9 @@ import monkey_island.cc.services.log
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.models import Monkey
|
from monkey_island.cc.models import Monkey
|
||||||
from monkey_island.cc.services.edge import EdgeService
|
from monkey_island.cc.services.edge import EdgeService
|
||||||
from monkey_island.cc.utils import local_ip_addresses
|
from monkey_island.cc.utils import local_ip_addresses, is_local_ips
|
||||||
import socket
|
|
||||||
from monkey_island.cc import models
|
from monkey_island.cc import models
|
||||||
|
from monkey_island.cc.services.utils.node_states import NodeStates
|
||||||
|
|
||||||
__author__ = "itay.mizeretz"
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
@ -122,20 +124,25 @@ class NodeService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_monkey_group(monkey):
|
def get_monkey_group(monkey):
|
||||||
|
keywords = []
|
||||||
if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0:
|
if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0:
|
||||||
monkey_type = "island_monkey"
|
keywords.extend(["island", "monkey"])
|
||||||
else:
|
else:
|
||||||
monkey_type = "manual" if NodeService.get_monkey_manual_run(monkey) else "monkey"
|
monkey_type = "manual" if NodeService.get_monkey_manual_run(monkey) else "monkey"
|
||||||
|
keywords.append(monkey_type)
|
||||||
|
|
||||||
monkey_os = NodeService.get_monkey_os(monkey)
|
keywords.append(NodeService.get_monkey_os(monkey))
|
||||||
monkey_running = "" if Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead() else "_running"
|
if not Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead():
|
||||||
return "%s_%s%s" % (monkey_type, monkey_os, monkey_running)
|
keywords.append("running")
|
||||||
|
return NodeStates.get_by_keywords(keywords).value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_node_group(node):
|
def get_node_group(node) -> str:
|
||||||
|
if 'group' in node and node['group']:
|
||||||
|
return node['group']
|
||||||
node_type = "exploited" if node.get("exploited") else "clean"
|
node_type = "exploited" if node.get("exploited") else "clean"
|
||||||
node_os = NodeService.get_node_os(node)
|
node_os = NodeService.get_node_os(node)
|
||||||
return "%s_%s" % (node_type, node_os)
|
return NodeStates.get_by_keywords([node_type, node_os]).value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def monkey_to_net_node(monkey, for_report=False):
|
def monkey_to_net_node(monkey, for_report=False):
|
||||||
|
@ -166,6 +173,12 @@ class NodeService:
|
||||||
"os": NodeService.get_node_os(node)
|
"os": NodeService.get_node_os(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_node_group(node_id: str, node_group: NodeStates):
|
||||||
|
mongo.db.node.update({"_id": node_id},
|
||||||
|
{'$set': {'group': node_group.value}},
|
||||||
|
upsert=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unset_all_monkey_tunnels(monkey_id):
|
def unset_all_monkey_tunnels(monkey_id):
|
||||||
mongo.db.monkey.update(
|
mongo.db.monkey.update(
|
||||||
|
@ -207,6 +220,43 @@ class NodeService:
|
||||||
})
|
})
|
||||||
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
|
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool):
|
||||||
|
new_node_insert_result = mongo.db.node.insert_one(
|
||||||
|
{
|
||||||
|
"ip_addresses": bootloader_telem['ips'],
|
||||||
|
"domain_name": bootloader_telem['hostname'],
|
||||||
|
"will_monkey_run": will_monkey_run,
|
||||||
|
"exploited": False,
|
||||||
|
"creds": [],
|
||||||
|
"os":
|
||||||
|
{
|
||||||
|
"type": bootloader_telem['system'],
|
||||||
|
"version": bootloader_telem['os_version']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_or_create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool) -> Dict:
|
||||||
|
if is_local_ips(bootloader_telem['ips']):
|
||||||
|
raise NodeCreationException("Bootloader ran on island, no need to create new node.")
|
||||||
|
|
||||||
|
new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem['ips']}})
|
||||||
|
if new_node is None:
|
||||||
|
new_node = NodeService.create_node_from_bootloader_telem(bootloader_telem, will_monkey_run)
|
||||||
|
if bootloader_telem['tunnel']:
|
||||||
|
dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem['tunnel'])
|
||||||
|
else:
|
||||||
|
dst_node = NodeService.get_monkey_island_node()
|
||||||
|
edge = EdgeService.get_or_create_edge(new_node['_id'], dst_node['id'])
|
||||||
|
mongo.db.edge.update({"_id": edge["_id"]},
|
||||||
|
{'$set': {'tunnel': bool(bootloader_telem['tunnel']),
|
||||||
|
'ip_address': bootloader_telem['ips'][0],
|
||||||
|
'group': NodeStates.get_by_keywords(['island']).value}},
|
||||||
|
upsert=False)
|
||||||
|
return new_node
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_or_create_node(ip_address, domain_name=''):
|
def get_or_create_node(ip_address, domain_name=''):
|
||||||
new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
|
new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
|
||||||
|
@ -354,3 +404,6 @@ class NodeService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_hostname_by_id(node_id):
|
def get_hostname_by_id(node_id):
|
||||||
return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))
|
return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))
|
||||||
|
|
||||||
|
class NodeCreationException(Exception):
|
||||||
|
pass
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
MIN_GLIBC_VERSION = 2.14
|
||||||
|
|
||||||
|
SUPPORTED_WINDOWS_VERSIONS = {
|
||||||
|
"xp_or_lower": False,
|
||||||
|
"vista": False,
|
||||||
|
"vista_sp1": False,
|
||||||
|
"vista_sp2": True,
|
||||||
|
"windows7": True,
|
||||||
|
"windows7_sp1": True,
|
||||||
|
"windows8_or_greater": True,
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from typing import List
|
||||||
|
import collections
|
||||||
|
|
||||||
|
|
||||||
|
class NodeStates(Enum):
|
||||||
|
CLEAN_UNKNOWN = 'clean_unknown'
|
||||||
|
CLEAN_LINUX = 'clean_linux'
|
||||||
|
CLEAN_WINDOWS = 'clean_windows'
|
||||||
|
EXPLOITED_LINUX = 'exploited_linux'
|
||||||
|
EXPLOITED_WINDOWS = 'exploited_windows'
|
||||||
|
ISLAND = 'island'
|
||||||
|
ISLAND_MONKEY_LINUX = 'island_monkey_linux'
|
||||||
|
ISLAND_MONKEY_LINUX_RUNNING = 'island_monkey_linux_running'
|
||||||
|
ISLAND_MONKEY_LINUX_STARTING = 'island_monkey_linux_starting'
|
||||||
|
ISLAND_MONKEY_WINDOWS = 'island_monkey_windows'
|
||||||
|
ISLAND_MONKEY_WINDOWS_RUNNING = 'island_monkey_windows_running'
|
||||||
|
ISLAND_MONKEY_WINDOWS_STARTING = 'island_monkey_windows_starting'
|
||||||
|
MANUAL_LINUX = 'manual_linux'
|
||||||
|
MANUAL_LINUX_RUNNING = 'manual_linux_running'
|
||||||
|
MANUAL_WINDOWS = 'manual_windows'
|
||||||
|
MANUAL_WINDOWS_RUNNING = 'manual_windows_running'
|
||||||
|
MONKEY_LINUX = 'monkey_linux'
|
||||||
|
MONKEY_LINUX_RUNNING = 'monkey_linux_running'
|
||||||
|
MONKEY_WINDOWS = 'monkey_windows'
|
||||||
|
MONKEY_WINDOWS_RUNNING = 'monkey_windows_running'
|
||||||
|
MONKEY_WINDOWS_STARTING = 'monkey_windows_starting'
|
||||||
|
MONKEY_LINUX_STARTING = 'monkey_linux_starting'
|
||||||
|
MONKEY_WINDOWS_OLD = 'monkey_windows_old'
|
||||||
|
MONKEY_LINUX_OLD = 'monkey_linux_old'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_by_keywords(keywords: List) -> NodeStates:
|
||||||
|
potential_groups = [i for i in NodeStates if NodeStates._is_state_from_keywords(i, keywords)]
|
||||||
|
if len(potential_groups) > 1:
|
||||||
|
raise MultipleGroupsFoundException("Multiple groups contain provided keywords. "
|
||||||
|
"Manually build group string to ensure keyword order.")
|
||||||
|
elif len(potential_groups) == 0:
|
||||||
|
raise NoGroupsFoundException("No groups found with provided keywords. "
|
||||||
|
"Check for typos and make sure group codes want to find exists.")
|
||||||
|
return potential_groups[0]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_state_from_keywords(group, keywords) -> bool:
|
||||||
|
group_keywords = group.value.split("_")
|
||||||
|
return collections.Counter(group_keywords) == collections.Counter(keywords)
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleGroupsFoundException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoGroupsFoundException(Exception):
|
||||||
|
pass
|
|
@ -0,0 +1,15 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from monkey_island.cc.services.utils.node_states import NodeStates, NoGroupsFoundException
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeGroups(TestCase):
|
||||||
|
|
||||||
|
def test_get_group_by_keywords(self):
|
||||||
|
self.assertEqual(NodeStates.get_by_keywords(['island']), NodeStates.ISLAND)
|
||||||
|
self.assertEqual(NodeStates.get_by_keywords(['running', 'linux', 'monkey']), NodeStates.MONKEY_LINUX_RUNNING)
|
||||||
|
self.assertEqual(NodeStates.get_by_keywords(['monkey', 'linux', 'running']), NodeStates.MONKEY_LINUX_RUNNING)
|
||||||
|
with self.assertRaises(NoGroupsFoundException):
|
||||||
|
NodeStates.get_by_keywords(['bogus', 'values', 'from', 'long', 'list', 'should', 'fail'])
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
|
let getGroupsOptions = (stateList) => {
|
||||||
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
|
|
||||||
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
|
|
||||||
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
|
|
||||||
|
|
||||||
let getGroupsOptions = () => {
|
|
||||||
let groupOptions = {};
|
let groupOptions = {};
|
||||||
for (let groupName of groupNames) {
|
for (let stateName of stateList) {
|
||||||
groupOptions[groupName] =
|
groupOptions[stateName] =
|
||||||
{
|
{
|
||||||
shape: 'image',
|
shape: 'image',
|
||||||
size: 50,
|
size: 50,
|
||||||
image: require('../../images/nodes/' + groupName + '.png')
|
image: require('../../images/nodes/' + stateName + '.png')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,11 +47,11 @@ export const basic_options = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const options = (() => {
|
export function getOptions(stateList) {
|
||||||
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||||
opts.groups = getGroupsOptions();
|
opts.groups = getGroupsOptions(stateList);
|
||||||
return opts;
|
return opts;
|
||||||
})();
|
}
|
||||||
|
|
||||||
export const optionsPth = (() => {
|
export const optionsPth = (() => {
|
||||||
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||||
|
|
|
@ -1,246 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import Toggle from 'react-toggle';
|
|
||||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
|
||||||
import download from 'downloadjs'
|
|
||||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
|
||||||
|
|
||||||
class InfMapPreviewPaneComponent extends PreviewPaneComponent {
|
|
||||||
|
|
||||||
osRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>Operating System</th>
|
|
||||||
<td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ipsRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>IP Addresses</th>
|
|
||||||
<td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
servicesRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>Services</th>
|
|
||||||
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
accessibleRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Accessible From
|
|
||||||
{this.generateToolTip('List of machine which can access this one using a network protocol')}
|
|
||||||
</th>
|
|
||||||
<td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
statusRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>Status</th>
|
|
||||||
<td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
forceKill(event, asset) {
|
|
||||||
let newConfig = asset.config;
|
|
||||||
newConfig['alive'] = !event.target.checked;
|
|
||||||
this.authFetch('/api/monkey/' + asset.guid,
|
|
||||||
{
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({config: newConfig})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
forceKillRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Force Kill
|
|
||||||
{this.generateToolTip('If this is on, monkey will die next time it communicates')}
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
|
|
||||||
onChange={(e) => this.forceKill(e, asset)}/>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unescapeLog(st) {
|
|
||||||
return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
|
|
||||||
.replace(/\\n/g, '\n')
|
|
||||||
.replace(/\\r/g, '\r')
|
|
||||||
.replace(/\\t/g, '\t')
|
|
||||||
.replace(/\\b/g, '\b')
|
|
||||||
.replace(/\\f/g, '\f')
|
|
||||||
.replace(/\\"/g, '\"')
|
|
||||||
.replace(/\\'/g, '\'')
|
|
||||||
.replace(/\\&/g, '\&');
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadLog(asset) {
|
|
||||||
this.authFetch('/api/log?id=' + asset.id)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
let timestamp = res['timestamp'];
|
|
||||||
timestamp = timestamp.substr(0, timestamp.indexOf('.'));
|
|
||||||
let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
|
|
||||||
let logContent = this.unescapeLog(res['log']);
|
|
||||||
download(logContent, filename, 'text/plain');
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadLogRow(asset) {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Download Log
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<a type="button" className="btn btn-primary"
|
|
||||||
disabled={!asset.has_log}
|
|
||||||
onClick={() => this.downloadLog(asset)}>Download</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
exploitsTimeline(asset) {
|
|
||||||
if (asset.exploits.length === 0) {
|
|
||||||
return (<div/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h4 style={{'marginTop': '2em'}}>
|
|
||||||
Exploit Timeline
|
|
||||||
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
|
||||||
</h4>
|
|
||||||
<ul className="timeline">
|
|
||||||
{asset.exploits.map(exploit =>
|
|
||||||
<li key={exploit.timestamp}>
|
|
||||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
|
||||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
|
||||||
<div>{exploit.origin}</div>
|
|
||||||
<div>{exploit.exploiter}</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
assetInfo(asset) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-condensed">
|
|
||||||
<tbody>
|
|
||||||
{this.osRow(asset)}
|
|
||||||
{this.ipsRow(asset)}
|
|
||||||
{this.servicesRow(asset)}
|
|
||||||
{this.accessibleRow(asset)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{this.exploitsTimeline(asset)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
infectedAssetInfo(asset) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-condensed">
|
|
||||||
<tbody>
|
|
||||||
{this.osRow(asset)}
|
|
||||||
{this.statusRow(asset)}
|
|
||||||
{this.ipsRow(asset)}
|
|
||||||
{this.servicesRow(asset)}
|
|
||||||
{this.accessibleRow(asset)}
|
|
||||||
{this.forceKillRow(asset)}
|
|
||||||
{this.downloadLogRow(asset)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{this.exploitsTimeline(asset)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
scanInfo(edge) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-condensed">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Operating System</th>
|
|
||||||
<td>{edge.os.type}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>IP Address</th>
|
|
||||||
<td>{edge.ip_address}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Services</th>
|
|
||||||
<td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{
|
|
||||||
(edge.exploits.length === 0) ?
|
|
||||||
'' :
|
|
||||||
<div>
|
|
||||||
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
|
||||||
<ul className="timeline">
|
|
||||||
{edge.exploits.map(exploit =>
|
|
||||||
<li key={exploit.timestamp}>
|
|
||||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
|
||||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
|
||||||
<div>{exploit.origin}</div>
|
|
||||||
<div>{exploit.exploiter}</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
islandEdgeInfo() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfoByProps() {
|
|
||||||
switch (this.props.type) {
|
|
||||||
case 'edge':
|
|
||||||
return this.scanInfo(this.props.item);
|
|
||||||
case 'node':
|
|
||||||
return this.props.item.group.includes('monkey', 'manual') ?
|
|
||||||
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
|
|
||||||
case 'island_edge':
|
|
||||||
return this.islandEdgeInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InfMapPreviewPaneComponent;
|
|
|
@ -253,8 +253,15 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
info = this.scanInfo(this.props.item);
|
info = this.scanInfo(this.props.item);
|
||||||
break;
|
break;
|
||||||
case 'node':
|
case 'node':
|
||||||
info = this.props.item.group.includes('monkey', 'manual') ? this.infectedAssetInfo(this.props.item) :
|
if (this.props.item.group.includes('monkey') && this.props.item.group.includes('starting')) {
|
||||||
this.props.item.group !== 'island' ? this.assetInfo(this.props.item) : this.islandAssetInfo();
|
info = this.assetInfo(this.props.item);
|
||||||
|
} else if (this.props.item.group.includes('monkey', 'manual')) {
|
||||||
|
info = this.infectedAssetInfo(this.props.item)
|
||||||
|
} else if (this.props.item.group !== 'island') {
|
||||||
|
info = this.assetInfo(this.props.item)
|
||||||
|
} else {
|
||||||
|
info = this.islandAssetInfo();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'island_edge':
|
case 'island_edge':
|
||||||
info = this.islandEdgeInfo();
|
info = this.islandEdgeInfo();
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
|
||||||
|
|
||||||
class PthPreviewPaneComponent extends PreviewPaneComponent {
|
|
||||||
nodeInfo(asset) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-condensed">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Hostname</th>
|
|
||||||
<td>{asset.hostname}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>IP Addresses</th>
|
|
||||||
<td>{asset.ips.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Services</th>
|
|
||||||
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Compromised Users</th>
|
|
||||||
<td>{asset.users.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeInfo(edge) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-condensed">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Compromised Users</th>
|
|
||||||
<td>{edge.users.map(val => <div key={val}>{val}</div>)}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfoByProps() {
|
|
||||||
switch (this.props.type) {
|
|
||||||
case 'edge':
|
|
||||||
return this.edgeInfo(this.props.item);
|
|
||||||
case 'node':
|
|
||||||
return this.nodeInfo(this.props.item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PthPreviewPaneComponent;
|
|
|
@ -3,9 +3,9 @@ import {Col, Modal} from 'react-bootstrap';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faStopCircle, faMinus } from '@fortawesome/free-solid-svg-icons'
|
import { faStopCircle, faMinus } from '@fortawesome/free-solid-svg-icons'
|
||||||
import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
|
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
import {getOptions, edgeGroupToColor} from 'components/map/MapOptions';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
|
|
||||||
class MapPageComponent extends AuthComponent {
|
class MapPageComponent extends AuthComponent {
|
||||||
|
@ -13,6 +13,7 @@ class MapPageComponent extends AuthComponent {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
graph: {nodes: [], edges: []},
|
graph: {nodes: [], edges: []},
|
||||||
|
nodeStateList:[],
|
||||||
selected: null,
|
selected: null,
|
||||||
selectedType: null,
|
selectedType: null,
|
||||||
killPressed: false,
|
killPressed: false,
|
||||||
|
@ -33,6 +34,7 @@ class MapPageComponent extends AuthComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.getNodeStateListFromServer();
|
||||||
this.updateMapFromServer();
|
this.updateMapFromServer();
|
||||||
this.interval = setInterval(this.timedEvents, 5000);
|
this.interval = setInterval(this.timedEvents, 5000);
|
||||||
}
|
}
|
||||||
|
@ -41,6 +43,14 @@ class MapPageComponent extends AuthComponent {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNodeStateListFromServer = () => {
|
||||||
|
this.authFetch('/api/netmap/nodeStates')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
this.setState({nodeStateList: res.node_states});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
timedEvents = () => {
|
timedEvents = () => {
|
||||||
this.updateMapFromServer();
|
this.updateMapFromServer();
|
||||||
this.updateTelemetryFromServer();
|
this.updateTelemetryFromServer();
|
||||||
|
@ -201,7 +211,7 @@ class MapPageComponent extends AuthComponent {
|
||||||
</div>
|
</div>
|
||||||
{this.renderTelemetryConsole()}
|
{this.renderTelemetryConsole()}
|
||||||
<div style={{height: '80vh'}}>
|
<div style={{height: '80vh'}}>
|
||||||
<ReactiveGraph graph={this.state.graph} options={options} events={this.events}/>
|
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)} events={this.events}/>
|
||||||
</div>
|
</div>
|
||||||
{this.renderTelemetryLineCount()}
|
{this.renderTelemetryLineCount()}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -226,7 +236,7 @@ class MapPageComponent extends AuthComponent {
|
||||||
</div>
|
</div>
|
||||||
: ''}
|
: ''}
|
||||||
|
|
||||||
<InfMapPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
<PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
||||||
</Col>
|
</Col>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,8 +3,6 @@ import '../../styles/report/ReportPage.scss';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Route} from 'react-router-dom';
|
import {Route} from 'react-router-dom';
|
||||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
import {Col, Nav, NavItem} from 'react-bootstrap';
|
||||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
|
||||||
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
|
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
|
||||||
import AttackReport from '../report-components/AttackReport'
|
import AttackReport from '../report-components/AttackReport'
|
||||||
|
|
|
@ -3,10 +3,18 @@ import Graph from 'react-graph-vis';
|
||||||
import Dimensions from 'react-dimensions'
|
import Dimensions from 'react-dimensions'
|
||||||
|
|
||||||
class GraphWrapper extends React.Component {
|
class GraphWrapper extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let newOptions = this.props.options;
|
let newOptions = null;
|
||||||
newOptions.height = this.props.containerHeight.toString() + 'px';
|
if(this.props.options !== undefined){
|
||||||
newOptions.width = this.props.containerWidth.toString() + 'px';
|
newOptions = this.props.options;
|
||||||
|
newOptions.height = this.props.containerHeight.toString() + 'px';
|
||||||
|
newOptions.width = this.props.containerWidth.toString() + 'px';
|
||||||
|
}
|
||||||
return (<Graph graph={this.props.graph} options={newOptions} events={this.props.events}/>)
|
return (<Graph graph={this.props.graph} options={newOptions} events={this.props.events}/>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import BreachedServers from 'components/report-components/security/BreachedServe
|
||||||
import ScannedServers from 'components/report-components/security/ScannedServers';
|
import ScannedServers from 'components/report-components/security/ScannedServers';
|
||||||
import PostBreach from 'components/report-components/security/PostBreach';
|
import PostBreach from 'components/report-components/security/PostBreach';
|
||||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
import {edgeGroupToColor, getOptions} from 'components/map/MapOptions';
|
||||||
import StolenPasswords from 'components/report-components/security/StolenPasswords';
|
import StolenPasswords from 'components/report-components/security/StolenPasswords';
|
||||||
import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell';
|
import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell';
|
||||||
import {Line} from 'rc-progress';
|
import {Line} from 'rc-progress';
|
||||||
|
@ -54,14 +54,24 @@ class ReportPageComponent extends AuthComponent {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
report: props.report,
|
report: props.report,
|
||||||
graph: {nodes: [], edges: []}
|
graph: {nodes: [], edges: []},
|
||||||
|
nodeStateList: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.getNodeStateListFromServer();
|
||||||
this.updateMapFromServer();
|
this.updateMapFromServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNodeStateListFromServer = () => {
|
||||||
|
this.authFetch('/api/netmap/nodeStates')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
this.setState({nodeStateList: res.node_states});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
|
@ -396,7 +406,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
|
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{position: 'relative', height: '80vh'}}>
|
<div style={{position: 'relative', height: '80vh'}}>
|
||||||
<ReactiveGraph graph={this.state.graph} options={options}/>
|
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)}/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{marginBottom: '20px'}}>
|
<div style={{marginBottom: '20px'}}>
|
||||||
<BreachedServers data={this.state.report.glance.exploited}/>
|
<BreachedServers data={this.state.report.glance.exploited}/>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 153 KiB |
Binary file not shown.
After Width: | Height: | Size: 135 KiB |
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
|
@ -1,5 +1,7 @@
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
from typing import List
|
||||||
|
import collections
|
||||||
|
|
||||||
import array
|
import array
|
||||||
|
|
||||||
|
@ -49,6 +51,11 @@ else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def is_local_ips(ips: List) -> bool:
|
||||||
|
filtered_local_ips = [ip for ip in local_ip_addresses() if not ip.startswith('169.254')]
|
||||||
|
return collections.Counter(ips) == collections.Counter(filtered_local_ips)
|
||||||
|
|
||||||
|
|
||||||
# The local IP addresses list should not change often. Therefore, we can cache the result and never call this function
|
# The local IP addresses list should not change often. Therefore, we can cache the result and never call this function
|
||||||
# more than once. This stopgap measure is here since this function is called a lot of times during the report
|
# more than once. This stopgap measure is here since this function is called a lot of times during the report
|
||||||
# generation.
|
# generation.
|
||||||
|
|
Loading…
Reference in New Issue