forked from p34709852/monkey
Merge pull request #107 from guardicore/develop
Finish the current small sprint. New additions * New icons * Encrypted creds in the DB * Option to pull logs from the monkey * Rename C&C to Monkey Island * Get rid of chaos monkey (at last!) * Async scanning of victims
This commit is contained in:
commit
0fb4feb78c
|
@ -6,7 +6,7 @@ Infection Monkey
|
||||||
|
|
||||||
Welcome to the Infection Monkey!
|
Welcome to the Infection Monkey!
|
||||||
|
|
||||||
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Command and Control(C&C) server.
|
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island server.
|
||||||
|
|
||||||
<img src=".github/map-full.png" >
|
<img src=".github/map-full.png" >
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ The Infection Monkey is an open source security tool for testing a data center's
|
||||||
|
|
||||||
The Infection Monkey is comprised of two parts:
|
The Infection Monkey is comprised of two parts:
|
||||||
* Monkey - A tool which infects other machines and propagates to them
|
* Monkey - A tool which infects other machines and propagates to them
|
||||||
* Monkey Island - A C&C server with a dedicated UI to visualize the Chaos Monkey's progress inside the data center
|
* Monkey Island - A dedicated server to control and visualize the Infection Monkey's progress inside the data center
|
||||||
|
|
||||||
To read more about the Monkey, visit http://infectionmonkey.com
|
To read more about the Monkey, visit http://infectionmonkey.com
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ The Infection Monkey uses the following techniques and exploits to propagate to
|
||||||
* Multiple propagation techniques:
|
* Multiple propagation techniques:
|
||||||
* Predefined passwords
|
* Predefined passwords
|
||||||
* Common logical exploits
|
* Common logical exploits
|
||||||
* Password stealing using mimikatz
|
* Password stealing using Mimikatz
|
||||||
* Multiple exploit methods:
|
* Multiple exploit methods:
|
||||||
* SSH
|
* SSH
|
||||||
* SMB
|
* SMB
|
||||||
|
@ -45,7 +45,7 @@ Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in t
|
||||||
Building the Monkey from source
|
Building the Monkey from source
|
||||||
-------------------------------
|
-------------------------------
|
||||||
If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/setup)
|
If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/setup)
|
||||||
and follow the instructions at the readme files under [chaos_monkey](chaos_monkey) and [monkey_island](monkey_island).
|
and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island).
|
||||||
|
|
||||||
|
|
||||||
License
|
License
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 232 KiB |
|
@ -1,45 +0,0 @@
|
||||||
import time
|
|
||||||
from random import shuffle
|
|
||||||
from network import HostScanner, HostFinger
|
|
||||||
from model.host import VictimHost
|
|
||||||
from network.tools import check_port_tcp
|
|
||||||
|
|
||||||
__author__ = 'itamar'
|
|
||||||
|
|
||||||
BANNER_READ = 1024
|
|
||||||
|
|
||||||
|
|
||||||
class TcpScanner(HostScanner, HostFinger):
|
|
||||||
def __init__(self):
|
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
|
|
||||||
def is_host_alive(self, host):
|
|
||||||
return self.get_host_fingerprint(host, True)
|
|
||||||
|
|
||||||
def get_host_fingerprint(self, host, only_one_port=False):
|
|
||||||
assert isinstance(host, VictimHost)
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
# maybe hide under really bad detection systems
|
|
||||||
target_ports = self._config.tcp_target_ports[:]
|
|
||||||
shuffle(target_ports)
|
|
||||||
|
|
||||||
for target_port in target_ports:
|
|
||||||
|
|
||||||
is_open, banner = check_port_tcp(host.ip_addr,
|
|
||||||
target_port,
|
|
||||||
self._config.tcp_scan_timeout / 1000.0,
|
|
||||||
self._config.tcp_scan_get_banner)
|
|
||||||
|
|
||||||
if is_open:
|
|
||||||
count += 1
|
|
||||||
service = 'tcp-' + str(target_port)
|
|
||||||
host.services[service] = {}
|
|
||||||
if banner:
|
|
||||||
host.services[service]['banner'] = banner
|
|
||||||
if only_one_port:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
time.sleep(self._config.tcp_scan_interval / 1000.0)
|
|
||||||
|
|
||||||
return count != 0
|
|
|
@ -1,79 +0,0 @@
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
import logging
|
|
||||||
import struct
|
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 10
|
|
||||||
BANNER_READ = 1024
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def struct_unpack_tracker(data, index, fmt):
|
|
||||||
"""
|
|
||||||
Unpacks a struct from the specified index according to specified format.
|
|
||||||
Returns the data and the next index
|
|
||||||
:param data: Buffer
|
|
||||||
:param index: Position index
|
|
||||||
:param fmt: Struct format
|
|
||||||
:return: (Data, new index)
|
|
||||||
"""
|
|
||||||
unpacked = struct.unpack_from(fmt, data, index)
|
|
||||||
return unpacked, struct.calcsize(fmt)
|
|
||||||
|
|
||||||
|
|
||||||
def struct_unpack_tracker_string(data, index):
|
|
||||||
"""
|
|
||||||
Unpacks a null terminated string from the specified index
|
|
||||||
Returns the data and the next index
|
|
||||||
:param data: Buffer
|
|
||||||
:param index: Position index
|
|
||||||
:return: (Data, new index)
|
|
||||||
"""
|
|
||||||
ascii_len = data[index:].find('\0')
|
|
||||||
fmt = "%ds" % ascii_len
|
|
||||||
return struct_unpack_tracker(data,index,fmt)
|
|
||||||
|
|
||||||
|
|
||||||
def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
sock.settimeout(timeout)
|
|
||||||
|
|
||||||
try:
|
|
||||||
sock.connect((ip, port))
|
|
||||||
except socket.timeout:
|
|
||||||
return False, None
|
|
||||||
except socket.error, exc:
|
|
||||||
LOG.debug("Check port: %s:%s, Exception: %s", ip, port, exc)
|
|
||||||
return False, None
|
|
||||||
|
|
||||||
banner = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
if get_banner:
|
|
||||||
read_ready, _, _ = select.select([sock], [], [], timeout)
|
|
||||||
if len(read_ready) > 0:
|
|
||||||
banner = sock.recv(BANNER_READ)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
sock.close()
|
|
||||||
return True, banner
|
|
||||||
|
|
||||||
|
|
||||||
def check_port_udp(ip, port, timeout=DEFAULT_TIMEOUT):
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
sock.settimeout(timeout)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
is_open = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
sock.sendto("-", (ip, port))
|
|
||||||
data, _ = sock.recvfrom(BANNER_READ)
|
|
||||||
is_open = True
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
return is_open, data
|
|
|
@ -106,6 +106,7 @@ class Configuration(object):
|
||||||
dropper_log_path_linux = '/tmp/user-1562'
|
dropper_log_path_linux = '/tmp/user-1562'
|
||||||
monkey_log_path_windows = '%temp%\\~df1563.tmp'
|
monkey_log_path_windows = '%temp%\\~df1563.tmp'
|
||||||
monkey_log_path_linux = '/tmp/user-1563'
|
monkey_log_path_linux = '/tmp/user-1563'
|
||||||
|
send_log_to_server = True
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# dropper config
|
# dropper config
|
|
@ -25,7 +25,7 @@ class ControlClient(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def wakeup(parent=None, default_tunnel=None, has_internet_access=None):
|
def wakeup(parent=None, default_tunnel=None, has_internet_access=None):
|
||||||
LOG.debug("Trying to wake up with C&C servers list: %r" % WormConfiguration.command_servers)
|
LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers)
|
||||||
if parent or default_tunnel:
|
if parent or default_tunnel:
|
||||||
LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel))
|
LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel))
|
||||||
hostname = gethostname()
|
hostname = gethostname()
|
||||||
|
@ -111,6 +111,21 @@ class ControlClient(object):
|
||||||
LOG.warn("Error connecting to control server %s: %s",
|
LOG.warn("Error connecting to control server %s: %s",
|
||||||
WormConfiguration.current_server, exc)
|
WormConfiguration.current_server, exc)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_log(log):
|
||||||
|
if not WormConfiguration.current_server:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
telemetry = {'monkey_guid': GUID, 'log': json.dumps(log)}
|
||||||
|
reply = requests.post("https://%s/api/log" % (WormConfiguration.current_server,),
|
||||||
|
data=json.dumps(telemetry),
|
||||||
|
headers={'content-type': 'application/json'},
|
||||||
|
verify=False,
|
||||||
|
proxies=ControlClient.proxies)
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.warn("Error connecting to control server %s: %s",
|
||||||
|
WormConfiguration.current_server, exc)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_control_config():
|
def load_control_config():
|
||||||
if not WormConfiguration.current_server:
|
if not WormConfiguration.current_server:
|
|
@ -48,6 +48,7 @@
|
||||||
"max_iterations": 3,
|
"max_iterations": 3,
|
||||||
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
|
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
|
||||||
"monkey_log_path_linux": "/tmp/user-1563",
|
"monkey_log_path_linux": "/tmp/user-1563",
|
||||||
|
"send_log_to_server": true,
|
||||||
"ms08_067_exploit_attempts": 5,
|
"ms08_067_exploit_attempts": 5,
|
||||||
"ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT",
|
"ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT",
|
||||||
"ms08_067_remote_user_pass": "Password1!",
|
"ms08_067_remote_user_pass": "Password1!",
|
|
@ -13,7 +13,7 @@ from exploit import HostExploiter
|
||||||
from exploit.tools import HTTPTools, get_monkey_depth
|
from exploit.tools import HTTPTools, get_monkey_depth
|
||||||
from exploit.tools import get_target_monkey
|
from exploit.tools import get_target_monkey
|
||||||
from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
|
from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
|
||||||
from network.tools import check_port_tcp
|
from network.tools import check_tcp_port
|
||||||
from tools import build_monkey_commandline
|
from tools import build_monkey_commandline
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
@ -245,7 +245,7 @@ class RdpExploiter(HostExploiter):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not self.host.os.get('type'):
|
if not self.host.os.get('type'):
|
||||||
is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT)
|
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
|
||||||
if is_open:
|
if is_open:
|
||||||
self.host.os['type'] = 'windows'
|
self.host.os['type'] = 'windows'
|
||||||
return True
|
return True
|
||||||
|
@ -254,7 +254,7 @@ class RdpExploiter(HostExploiter):
|
||||||
def exploit_host(self):
|
def exploit_host(self):
|
||||||
global g_reactor
|
global g_reactor
|
||||||
|
|
||||||
is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT)
|
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
|
||||||
if not is_open:
|
if not is_open:
|
||||||
LOG.info("RDP port is closed on %r, skipping", self.host)
|
LOG.info("RDP port is closed on %r, skipping", self.host)
|
||||||
return False
|
return False
|
|
@ -7,7 +7,7 @@ from exploit import HostExploiter
|
||||||
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
||||||
from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
|
from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
|
||||||
from network import SMBFinger
|
from network import SMBFinger
|
||||||
from network.tools import check_port_tcp
|
from network.tools import check_tcp_port
|
||||||
from tools import build_monkey_commandline
|
from tools import build_monkey_commandline
|
||||||
|
|
||||||
LOG = getLogger(__name__)
|
LOG = getLogger(__name__)
|
||||||
|
@ -31,12 +31,12 @@ class SmbExploiter(HostExploiter):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not self.host.os.get('type'):
|
if not self.host.os.get('type'):
|
||||||
is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445)
|
is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
|
||||||
if is_smb_open:
|
if is_smb_open:
|
||||||
smb_finger = SMBFinger()
|
smb_finger = SMBFinger()
|
||||||
smb_finger.get_host_fingerprint(self.host)
|
smb_finger.get_host_fingerprint(self.host)
|
||||||
else:
|
else:
|
||||||
is_nb_open, _ = check_port_tcp(self.host.ip_addr, 139)
|
is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139)
|
||||||
if is_nb_open:
|
if is_nb_open:
|
||||||
self.host.os['type'] = 'windows'
|
self.host.os['type'] = 'windows'
|
||||||
return self.host.os.get('type') in self._TARGET_OS_TYPE
|
return self.host.os.get('type') in self._TARGET_OS_TYPE
|
|
@ -7,7 +7,7 @@ import monkeyfs
|
||||||
from exploit import HostExploiter
|
from exploit import HostExploiter
|
||||||
from exploit.tools import get_target_monkey, get_monkey_depth
|
from exploit.tools import get_target_monkey, get_monkey_depth
|
||||||
from model import MONKEY_ARG
|
from model import MONKEY_ARG
|
||||||
from network.tools import check_port_tcp
|
from network.tools import check_tcp_port
|
||||||
from tools import build_monkey_commandline
|
from tools import build_monkey_commandline
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
@ -41,7 +41,7 @@ class SSHExploiter(HostExploiter):
|
||||||
if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
|
if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
|
||||||
port = int(servkey.replace('tcp-', ''))
|
port = int(servkey.replace('tcp-', ''))
|
||||||
|
|
||||||
is_open, _ = check_port_tcp(self.host.ip_addr, port)
|
is_open, _ = check_tcp_port(self.host.ip_addr, port)
|
||||||
if not is_open:
|
if not is_open:
|
||||||
LOG.info("SSH port is closed on %r, skipping", self.host)
|
LOG.info("SSH port is closed on %r, skipping", self.host)
|
||||||
return False
|
return False
|
|
@ -17,7 +17,7 @@ from impacket.dcerpc.v5 import transport
|
||||||
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
||||||
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
||||||
from network import SMBFinger
|
from network import SMBFinger
|
||||||
from network.tools import check_port_tcp
|
from network.tools import check_tcp_port
|
||||||
from tools import build_monkey_commandline
|
from tools import build_monkey_commandline
|
||||||
from . import HostExploiter
|
from . import HostExploiter
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
|
|
||||||
if not self.host.os.get('type') or (
|
if not self.host.os.get('type') or (
|
||||||
self.host.os.get('type') in self._TARGET_OS_TYPE and not self.host.os.get('version')):
|
self.host.os.get('type') in self._TARGET_OS_TYPE and not self.host.os.get('version')):
|
||||||
is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445)
|
is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
|
||||||
if is_smb_open:
|
if is_smb_open:
|
||||||
smb_finger = SMBFinger()
|
smb_finger = SMBFinger()
|
||||||
if smb_finger.get_host_fingerprint(self.host):
|
if smb_finger.get_host_fingerprint(self.host):
|
|
@ -11,7 +11,8 @@ import traceback
|
||||||
from config import WormConfiguration, EXTERNAL_CONFIG_FILE
|
from config import WormConfiguration, EXTERNAL_CONFIG_FILE
|
||||||
from dropper import MonkeyDrops
|
from dropper import MonkeyDrops
|
||||||
from model import MONKEY_ARG, DROPPER_ARG
|
from model import MONKEY_ARG, DROPPER_ARG
|
||||||
from monkey import ChaosMonkey
|
from monkey import InfectionMonkey
|
||||||
|
import utils
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
@ -78,12 +79,10 @@ def main():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if MONKEY_ARG == monkey_mode:
|
if MONKEY_ARG == monkey_mode:
|
||||||
log_path = os.path.expandvars(
|
log_path = utils.get_monkey_log_path()
|
||||||
WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" else WormConfiguration.monkey_log_path_linux
|
monkey_cls = InfectionMonkey
|
||||||
monkey_cls = ChaosMonkey
|
|
||||||
elif DROPPER_ARG == monkey_mode:
|
elif DROPPER_ARG == monkey_mode:
|
||||||
log_path = os.path.expandvars(
|
log_path = utils.get_dropper_log_path()
|
||||||
WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" else WormConfiguration.dropper_log_path_linux
|
|
||||||
monkey_cls = MonkeyDrops
|
monkey_cls = MonkeyDrops
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
@ -91,6 +90,8 @@ def main():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if WormConfiguration.use_file_logging:
|
if WormConfiguration.use_file_logging:
|
||||||
|
if os.path.exists(log_path):
|
||||||
|
os.remove(log_path)
|
||||||
LOG_CONFIG['handlers']['file']['filename'] = log_path
|
LOG_CONFIG['handlers']['file']['filename'] = log_path
|
||||||
LOG_CONFIG['root']['handlers'].append('file')
|
LOG_CONFIG['root']['handlers'].append('file')
|
||||||
else:
|
else:
|
||||||
|
@ -120,6 +121,8 @@ def main():
|
||||||
json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': '))
|
json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': '))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
except Exception:
|
||||||
|
LOG.exception("Exception thrown from monkey's start function")
|
||||||
finally:
|
finally:
|
||||||
monkey.cleanup()
|
monkey.cleanup()
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 108 KiB |
|
@ -6,6 +6,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import tunnel
|
import tunnel
|
||||||
|
import utils
|
||||||
from config import WormConfiguration
|
from config import WormConfiguration
|
||||||
from control import ControlClient
|
from control import ControlClient
|
||||||
from model import DELAY_DELETE_CMD
|
from model import DELAY_DELETE_CMD
|
||||||
|
@ -19,7 +20,7 @@ __author__ = 'itamar'
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ChaosMonkey(object):
|
class InfectionMonkey(object):
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self._keep_running = False
|
self._keep_running = False
|
||||||
self._exploited_machines = set()
|
self._exploited_machines = set()
|
||||||
|
@ -226,6 +227,9 @@ class ChaosMonkey(object):
|
||||||
|
|
||||||
firewall.close()
|
firewall.close()
|
||||||
|
|
||||||
|
if WormConfiguration.send_log_to_server:
|
||||||
|
self.send_log()
|
||||||
|
|
||||||
self._singleton.unlock()
|
self._singleton.unlock()
|
||||||
|
|
||||||
if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'):
|
if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'):
|
||||||
|
@ -244,3 +248,13 @@ class ChaosMonkey(object):
|
||||||
LOG.error("Exception in self delete: %s", exc)
|
LOG.error("Exception in self delete: %s", exc)
|
||||||
|
|
||||||
LOG.info("Monkey is shutting down")
|
LOG.info("Monkey is shutting down")
|
||||||
|
|
||||||
|
def send_log(self):
|
||||||
|
monkey_log_path = utils.get_monkey_log_path()
|
||||||
|
if os.path.exists(monkey_log_path):
|
||||||
|
with open(monkey_log_path, 'r') as f:
|
||||||
|
log = f.read()
|
||||||
|
else:
|
||||||
|
log = ''
|
||||||
|
|
||||||
|
ControlClient.send_log(log)
|
|
@ -1,9 +1,10 @@
|
||||||
import time
|
|
||||||
import logging
|
import logging
|
||||||
from . import HostScanner
|
import time
|
||||||
|
|
||||||
from config import WormConfiguration
|
from config import WormConfiguration
|
||||||
from info import local_ips, get_ips_from_interfaces
|
from info import local_ips, get_ips_from_interfaces
|
||||||
from range import *
|
from range import *
|
||||||
|
from . import HostScanner
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -18,6 +19,12 @@ class NetworkScanner(object):
|
||||||
self._ranges = None
|
self._ranges = None
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
|
"""
|
||||||
|
Set up scanning based on configuration
|
||||||
|
FixedRange -> Reads from range_fixed field in configuration
|
||||||
|
otherwise, takes a range from every IP address the current host has.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
# get local ip addresses
|
# get local ip addresses
|
||||||
self._ip_addresses = local_ips()
|
self._ip_addresses = local_ips()
|
||||||
|
|
||||||
|
@ -27,7 +34,7 @@ class NetworkScanner(object):
|
||||||
LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses)
|
LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses)
|
||||||
# for fixed range, only scan once.
|
# for fixed range, only scan once.
|
||||||
if WormConfiguration.range_class is FixedRange:
|
if WormConfiguration.range_class is FixedRange:
|
||||||
self._ranges = [WormConfiguration.range_class(None)]
|
self._ranges = [WormConfiguration.range_class(fixed_addresses=WormConfiguration.range_fixed)]
|
||||||
else:
|
else:
|
||||||
self._ranges = [WormConfiguration.range_class(ip_address)
|
self._ranges = [WormConfiguration.range_class(ip_address)
|
||||||
for ip_address in self._ip_addresses]
|
for ip_address in self._ip_addresses]
|
|
@ -1,10 +1,11 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
import logging
|
import logging
|
||||||
from . import HostScanner, HostFinger
|
import os
|
||||||
from model.host import VictimHost
|
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from model.host import VictimHost
|
||||||
|
from . import HostScanner, HostFinger
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ class PingScanner(HostScanner, HostFinger):
|
||||||
elif WINDOWS_TTL == ttl:
|
elif WINDOWS_TTL == ttl:
|
||||||
host.os['type'] = 'windows'
|
host.os['type'] = 'windows'
|
||||||
return True
|
return True
|
||||||
except Exception, exc:
|
except Exception as exc:
|
||||||
LOG.debug("Error parsing ping fingerprint: %s", exc)
|
LOG.debug("Error parsing ping fingerprint: %s", exc)
|
||||||
|
|
||||||
return False
|
return False
|
|
@ -1,7 +1,8 @@
|
||||||
import socket
|
|
||||||
import random
|
import random
|
||||||
|
import socket
|
||||||
import struct
|
import struct
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
from model.host import VictimHost
|
from model.host import VictimHost
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
@ -77,5 +78,5 @@ class FixedRange(NetworkRange):
|
||||||
for address in self._fixed_addresses:
|
for address in self._fixed_addresses:
|
||||||
if not address: # Empty string
|
if not address: # Empty string
|
||||||
continue
|
continue
|
||||||
address_range.append(struct.unpack(">L", socket.inet_aton(address))[0])
|
address_range.append(struct.unpack(">L", socket.inet_aton(address.strip()))[0])
|
||||||
return address_range
|
return address_range
|
|
@ -1,7 +1,8 @@
|
||||||
import re
|
import re
|
||||||
from network import HostFinger
|
|
||||||
from network.tools import check_port_tcp
|
|
||||||
from model.host import VictimHost
|
from model.host import VictimHost
|
||||||
|
from network import HostFinger
|
||||||
|
from network.tools import check_tcp_port
|
||||||
|
|
||||||
SSH_PORT = 22
|
SSH_PORT = 22
|
||||||
SSH_SERVICE_DEFAULT = 'tcp-22'
|
SSH_SERVICE_DEFAULT = 'tcp-22'
|
||||||
|
@ -38,7 +39,7 @@ class SSHFinger(HostFinger):
|
||||||
self._banner_match(name, host, banner)
|
self._banner_match(name, host, banner)
|
||||||
return
|
return
|
||||||
|
|
||||||
is_open, banner = check_port_tcp(host.ip_addr, SSH_PORT, TIMEOUT, True)
|
is_open, banner = check_tcp_port(host.ip_addr, SSH_PORT, TIMEOUT, True)
|
||||||
|
|
||||||
if is_open:
|
if is_open:
|
||||||
host.services[SSH_SERVICE_DEFAULT] = {}
|
host.services[SSH_SERVICE_DEFAULT] = {}
|
|
@ -0,0 +1,41 @@
|
||||||
|
from itertools import izip_longest
|
||||||
|
from random import shuffle
|
||||||
|
|
||||||
|
from network import HostScanner, HostFinger
|
||||||
|
from network.tools import check_tcp_ports
|
||||||
|
|
||||||
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
BANNER_READ = 1024
|
||||||
|
|
||||||
|
|
||||||
|
class TcpScanner(HostScanner, HostFinger):
|
||||||
|
def __init__(self):
|
||||||
|
self._config = __import__('config').WormConfiguration
|
||||||
|
|
||||||
|
def is_host_alive(self, host):
|
||||||
|
return self.get_host_fingerprint(host, True)
|
||||||
|
|
||||||
|
def get_host_fingerprint(self, host, only_one_port=False):
|
||||||
|
"""
|
||||||
|
Scans a target host to see if it's alive using the tcp_target_ports specified in the configuration.
|
||||||
|
:param host: VictimHost structure
|
||||||
|
:param only_one_port: Currently unused.
|
||||||
|
:return: T/F if there is at least one open port. In addition, the host object is updated to mark those services as alive.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# maybe hide under really bad detection systems
|
||||||
|
target_ports = self._config.tcp_target_ports[:]
|
||||||
|
shuffle(target_ports)
|
||||||
|
|
||||||
|
ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0,
|
||||||
|
self._config.tcp_scan_get_banner)
|
||||||
|
for target_port, banner in izip_longest(ports, banners, fillvalue=None):
|
||||||
|
service = 'tcp-' + str(target_port)
|
||||||
|
host.services[service] = {}
|
||||||
|
if banner:
|
||||||
|
host.services[service]['banner'] = banner
|
||||||
|
if only_one_port:
|
||||||
|
break
|
||||||
|
|
||||||
|
return len(ports) != 0
|
|
@ -0,0 +1,150 @@
|
||||||
|
import logging
|
||||||
|
import select
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
|
||||||
|
DEFAULT_TIMEOUT = 10
|
||||||
|
BANNER_READ = 1024
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def struct_unpack_tracker(data, index, fmt):
|
||||||
|
"""
|
||||||
|
Unpacks a struct from the specified index according to specified format.
|
||||||
|
Returns the data and the next index
|
||||||
|
:param data: Buffer
|
||||||
|
:param index: Position index
|
||||||
|
:param fmt: Struct format
|
||||||
|
:return: (Data, new index)
|
||||||
|
"""
|
||||||
|
unpacked = struct.unpack_from(fmt, data, index)
|
||||||
|
return unpacked, struct.calcsize(fmt)
|
||||||
|
|
||||||
|
|
||||||
|
def struct_unpack_tracker_string(data, index):
|
||||||
|
"""
|
||||||
|
Unpacks a null terminated string from the specified index
|
||||||
|
Returns the data and the next index
|
||||||
|
:param data: Buffer
|
||||||
|
:param index: Position index
|
||||||
|
:return: (Data, new index)
|
||||||
|
"""
|
||||||
|
ascii_len = data[index:].find('\0')
|
||||||
|
fmt = "%ds" % ascii_len
|
||||||
|
return struct_unpack_tracker(data, index, fmt)
|
||||||
|
|
||||||
|
|
||||||
|
def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
||||||
|
"""
|
||||||
|
Checks if a given TCP port is open
|
||||||
|
:param ip: Target IP
|
||||||
|
:param port: Target Port
|
||||||
|
:param timeout: Timeout for socket connection
|
||||||
|
:param get_banner: if true, pulls first BANNER_READ bytes from the socket.
|
||||||
|
:return: Tuple, T/F + banner if requested.
|
||||||
|
"""
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sock.connect((ip, port))
|
||||||
|
except socket.timeout:
|
||||||
|
return False, None
|
||||||
|
except socket.error as exc:
|
||||||
|
LOG.debug("Check port: %s:%s, Exception: %s", ip, port, exc)
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
banner = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if get_banner:
|
||||||
|
read_ready, _, _ = select.select([sock], [], [], timeout)
|
||||||
|
if len(read_ready) > 0:
|
||||||
|
banner = sock.recv(BANNER_READ)
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
sock.close()
|
||||||
|
return True, banner
|
||||||
|
|
||||||
|
|
||||||
|
def check_udp_port(ip, port, timeout=DEFAULT_TIMEOUT):
|
||||||
|
"""
|
||||||
|
Checks if a given UDP port is open by checking if it replies to an empty message
|
||||||
|
:param ip: Target IP
|
||||||
|
:param port: Target port
|
||||||
|
:param timeout: Timeout to wait
|
||||||
|
:return: Tuple, T/F + banner
|
||||||
|
"""
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
|
||||||
|
data = None
|
||||||
|
is_open = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
sock.sendto("-", (ip, port))
|
||||||
|
data, _ = sock.recvfrom(BANNER_READ)
|
||||||
|
is_open = True
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
return is_open, data
|
||||||
|
|
||||||
|
|
||||||
|
def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
||||||
|
"""
|
||||||
|
Checks whether any of the given ports are open on a target IP.
|
||||||
|
:param ip: IP of host to attack
|
||||||
|
:param ports: List of ports to attack. Must not be empty.
|
||||||
|
:param timeout: Amount of time to wait for connection
|
||||||
|
:param get_banner: T/F if to get first packets from server
|
||||||
|
:return: list of open ports. If get_banner=True, then a matching list of banners.
|
||||||
|
"""
|
||||||
|
sockets = [socket.socket(socket.AF_INET, socket.SOCK_STREAM) for _ in range(len(ports))]
|
||||||
|
[s.setblocking(0) for s in sockets]
|
||||||
|
good_ports = []
|
||||||
|
try:
|
||||||
|
LOG.debug("Connecting to the following ports %s" % ",".join((str(x) for x in ports)))
|
||||||
|
for sock, port in zip(sockets, ports):
|
||||||
|
err = sock.connect_ex((ip, port))
|
||||||
|
if err == 0:
|
||||||
|
good_ports.append((port, sock))
|
||||||
|
continue
|
||||||
|
if err == 10035: # WSAEWOULDBLOCK is valid, see https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
|
||||||
|
good_ports.append((port, sock))
|
||||||
|
continue
|
||||||
|
if err == 115: # EINPROGRESS 115 /* Operation now in progress */
|
||||||
|
good_ports.append((port, sock))
|
||||||
|
continue
|
||||||
|
LOG.warning("Failed to connect to port %s, error code is %d", port, err)
|
||||||
|
|
||||||
|
if len(good_ports) != 0:
|
||||||
|
time.sleep(timeout)
|
||||||
|
# this is possibly connected. meaning after timeout wait, we expect to see a connection up
|
||||||
|
# Possible valid errors codes if we chose to check for actually closed are
|
||||||
|
# ECONNREFUSED (111) or WSAECONNREFUSED (10061) or WSAETIMEDOUT(10060)
|
||||||
|
connected_ports_sockets = [s for s in good_ports if
|
||||||
|
s[1].getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) == 0]
|
||||||
|
LOG.debug(
|
||||||
|
"On host %s discovered the following ports %s" %
|
||||||
|
(str(ip), ",".join([str(x[0]) for x in connected_ports_sockets])))
|
||||||
|
banners = []
|
||||||
|
if get_banner:
|
||||||
|
readable_sockets, _, _ = select.select([s[1] for s in connected_ports_sockets], [], [], 0)
|
||||||
|
# read first BANNER_READ bytes
|
||||||
|
banners = [sock.recv(BANNER_READ) if sock in readable_sockets else ""
|
||||||
|
for port, sock in connected_ports_sockets]
|
||||||
|
pass
|
||||||
|
# try to cleanup
|
||||||
|
[s[1].close() for s in good_ports]
|
||||||
|
return [port for port, sock in connected_ports_sockets], banners
|
||||||
|
else:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
except socket.error as exc:
|
||||||
|
LOG.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc)
|
||||||
|
return [], []
|
|
@ -28,13 +28,13 @@ The monkey is composed of three separate parts.
|
||||||
64bit: http://www.microsoft.com/en-us/download/details.aspx?id=13523
|
64bit: http://www.microsoft.com/en-us/download/details.aspx?id=13523
|
||||||
6. Download the dependent python packages using
|
6. Download the dependent python packages using
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
7. Download and extract UPX binary to [source-path]\monkey\chaos_monkey\bin\upx.exe:
|
7. Download and extract UPX binary to [source-path]\monkey\infection_monkey\bin\upx.exe:
|
||||||
https://github.com/upx/upx/releases/download/v3.94/upx394w.zip
|
https://github.com/upx/upx/releases/download/v3.94/upx394w.zip
|
||||||
8. Build/Download Sambacry and Mimikatz binaries
|
8. Build/Download Sambacry and Mimikatz binaries
|
||||||
a. Build/Download according to sections at the end of this readme.
|
a. Build/Download according to sections at the end of this readme.
|
||||||
b. Place the binaries under [code location]\chaos_monkey\bin
|
b. Place the binaries under [code location]\infection_monkey\bin
|
||||||
9. To build the final exe:
|
9. To build the final exe:
|
||||||
cd [code location]/chaos_monkey
|
cd [code location]/infection_monkey
|
||||||
build_windows.bat
|
build_windows.bat
|
||||||
output is placed under dist\monkey.exe
|
output is placed under dist\monkey.exe
|
||||||
|
|
||||||
|
@ -46,13 +46,13 @@ Tested on Ubuntu 16.04 and 17.04.
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
|
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
|
||||||
Install the python packages listed in requirements.txt using pip
|
Install the python packages listed in requirements.txt using pip
|
||||||
cd [code location]/chaos_monkey
|
cd [code location]/infection_monkey
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
2. Build Sambacry binaries
|
2. Build Sambacry binaries
|
||||||
a. Build/Download according to sections at the end of this readme.
|
a. Build/Download according to sections at the end of this readme.
|
||||||
b. Place the binaries under [code location]\chaos_monkey\bin
|
b. Place the binaries under [code location]\infection_monkey\bin
|
||||||
3. To build, run in terminal:
|
3. To build, run in terminal:
|
||||||
cd [code location]/chaos_monkey
|
cd [code location]/infection_monkey
|
||||||
chmod +x build_linux.sh
|
chmod +x build_linux.sh
|
||||||
./build_linux.sh
|
./build_linux.sh
|
||||||
output is placed under dist/monkey
|
output is placed under dist/monkey
|
||||||
|
@ -63,11 +63,11 @@ Sambacry requires two standalone binaries to execute remotely.
|
||||||
1. Install gcc-multilib if it's not installed
|
1. Install gcc-multilib if it's not installed
|
||||||
sudo apt-get install gcc-multilib
|
sudo apt-get install gcc-multilib
|
||||||
2. Build the binaries
|
2. Build the binaries
|
||||||
cd [code location]/chaos_monkey/monkey_utils/sambacry_monkey_runner
|
cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner
|
||||||
./build.sh
|
./build.sh
|
||||||
|
|
||||||
-- Mimikatz --
|
-- Mimikatz --
|
||||||
|
|
||||||
Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from
|
Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from
|
||||||
https://github.com/guardicore/mimikatz/releases/tag/1.0.0
|
https://github.com/guardicore/mimikatz/releases/tag/1.0.0
|
||||||
Download both 32 and 64 bit DLLs and place them under [code location]\chaos_monkey\bin
|
Download both 32 and 64 bit DLLs and place them under [code location]\infection_monkey\bin
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: UTF-8 -*-
|
# -*- coding: UTF-8 -*-
|
||||||
# NOTE: Launch all tests with `nosetests` command from chaos_monkey dir.
|
# NOTE: Launch all tests with `nosetests` command from infection_monkey dir.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import unittest
|
import unittest
|
|
@ -8,7 +8,7 @@ from threading import Thread
|
||||||
from model import VictimHost
|
from model import VictimHost
|
||||||
from network.firewall import app as firewall
|
from network.firewall import app as firewall
|
||||||
from network.info import local_ips, get_free_tcp_port
|
from network.info import local_ips, get_free_tcp_port
|
||||||
from network.tools import check_port_tcp
|
from network.tools import check_tcp_port
|
||||||
from transport.base import get_last_serve_time
|
from transport.base import get_last_serve_time
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
@ -40,7 +40,7 @@ def _check_tunnel(address, port, existing_sock=None):
|
||||||
sock = existing_sock
|
sock = existing_sock
|
||||||
|
|
||||||
LOG.debug("Checking tunnel %s:%s", address, port)
|
LOG.debug("Checking tunnel %s:%s", address, port)
|
||||||
is_open, _ = check_port_tcp(address, int(port))
|
is_open, _ = check_tcp_port(address, int(port))
|
||||||
if not is_open:
|
if not is_open:
|
||||||
LOG.debug("Could not connect to %s:%s", address, port)
|
LOG.debug("Could not connect to %s:%s", address, port)
|
||||||
if not existing_sock:
|
if not existing_sock:
|
|
@ -0,0 +1,14 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from config import WormConfiguration
|
||||||
|
|
||||||
|
|
||||||
|
def get_monkey_log_path():
|
||||||
|
return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \
|
||||||
|
else WormConfiguration.monkey_log_path_linux
|
||||||
|
|
||||||
|
|
||||||
|
def get_dropper_log_path():
|
||||||
|
return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
|
||||||
|
else WormConfiguration.dropper_log_path_linux
|
|
@ -8,11 +8,12 @@ from flask import Flask, send_from_directory, make_response
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from cc.auth import init_jwt
|
from cc.auth import init_jwt
|
||||||
from cc.database import mongo
|
from cc.database import mongo, database
|
||||||
from cc.environment.environment import env
|
from cc.environment.environment import env
|
||||||
from cc.resources.client_run import ClientRun
|
from cc.resources.client_run import ClientRun
|
||||||
from cc.resources.edge import Edge
|
from cc.resources.edge import Edge
|
||||||
from cc.resources.local_run import LocalRun
|
from cc.resources.local_run import LocalRun
|
||||||
|
from cc.resources.log import Log
|
||||||
from cc.resources.monkey import Monkey
|
from cc.resources.monkey import Monkey
|
||||||
from cc.resources.monkey_configuration import MonkeyConfiguration
|
from cc.resources.monkey_configuration import MonkeyConfiguration
|
||||||
from cc.resources.monkey_download import MonkeyDownload
|
from cc.resources.monkey_download import MonkeyDownload
|
||||||
|
@ -83,6 +84,7 @@ def init_app(mongo_url):
|
||||||
mongo.init_app(app)
|
mongo.init_app(app)
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
database.init()
|
||||||
ConfigService.init_config()
|
ConfigService.init_config()
|
||||||
|
|
||||||
app.add_url_rule('/', 'serve_home', serve_home)
|
app.add_url_rule('/', 'serve_home', serve_home)
|
||||||
|
@ -101,5 +103,6 @@ def init_app(mongo_url):
|
||||||
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
|
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
|
||||||
api.add_resource(Report, '/api/report', '/api/report/')
|
api.add_resource(Report, '/api/report', '/api/report/')
|
||||||
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
||||||
|
api.add_resource(Log, '/api/log', '/api/log/')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from flask_pymongo import PyMongo
|
import gridfs
|
||||||
from flask_pymongo import MongoClient
|
from flask_pymongo import MongoClient, PyMongo
|
||||||
from pymongo.errors import ServerSelectionTimeoutError
|
from pymongo.errors import ServerSelectionTimeoutError
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
@ -7,6 +7,17 @@ __author__ = 'Barak'
|
||||||
mongo = PyMongo()
|
mongo = PyMongo()
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self):
|
||||||
|
self.gridfs = None
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.gridfs = gridfs.GridFS(mongo.db)
|
||||||
|
|
||||||
|
|
||||||
|
database = Database()
|
||||||
|
|
||||||
|
|
||||||
def is_db_server_up(mongo_url):
|
def is_db_server_up(mongo_url):
|
||||||
client = MongoClient(mongo_url, serverSelectionTimeoutMS=100)
|
client = MongoClient(mongo_url, serverSelectionTimeoutMS=100)
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
|
||||||
|
from Crypto import Random
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
|
||||||
|
class Encryptor:
|
||||||
|
_BLOCK_SIZE = 32
|
||||||
|
_DB_PASSWORD_FILENAME = "mongo_key.bin"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._load_key()
|
||||||
|
|
||||||
|
def _init_key(self):
|
||||||
|
self._cipher_key = Random.new().read(self._BLOCK_SIZE)
|
||||||
|
with open(self._DB_PASSWORD_FILENAME, 'wb') as f:
|
||||||
|
f.write(self._cipher_key)
|
||||||
|
|
||||||
|
def _load_existing_key(self):
|
||||||
|
with open(self._DB_PASSWORD_FILENAME, 'rb') as f:
|
||||||
|
self._cipher_key = f.read()
|
||||||
|
|
||||||
|
def _load_key(self):
|
||||||
|
if os.path.exists(self._DB_PASSWORD_FILENAME):
|
||||||
|
self._load_existing_key()
|
||||||
|
else:
|
||||||
|
self._init_key()
|
||||||
|
|
||||||
|
def _pad(self, message):
|
||||||
|
return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr(
|
||||||
|
self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE))
|
||||||
|
|
||||||
|
def _unpad(self, message):
|
||||||
|
return message[0:-ord(message[len(message) - 1])]
|
||||||
|
|
||||||
|
def enc(self, message):
|
||||||
|
cipher_iv = Random.new().read(AES.block_size)
|
||||||
|
cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv)
|
||||||
|
return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message)))
|
||||||
|
|
||||||
|
def dec(self, enc_message):
|
||||||
|
enc_message = base64.b64decode(enc_message)
|
||||||
|
cipher_iv = enc_message[0:AES.block_size]
|
||||||
|
cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv)
|
||||||
|
return self._unpad(cipher.decrypt(enc_message[AES.block_size:]))
|
||||||
|
|
||||||
|
|
||||||
|
encryptor = Encryptor()
|
|
@ -33,6 +33,6 @@ if __name__ == '__main__':
|
||||||
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
|
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
|
||||||
'keyfile': os.environ.get('SERVER_KEY', 'server.key')})
|
'keyfile': os.environ.get('SERVER_KEY', 'server.key')})
|
||||||
http_server.listen(env.get_island_port())
|
http_server.listen(env.get_island_port())
|
||||||
print('Monkey Island C&C Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
|
print('Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
|
||||||
IOLoop.instance().start()
|
IOLoop.instance().start()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
import flask_restful
|
||||||
|
from bson import ObjectId
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
|
from cc.database import mongo
|
||||||
|
from cc.services.log import LogService
|
||||||
|
from cc.services.node import NodeService
|
||||||
|
|
||||||
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
|
||||||
|
class Log(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
|
def get(self):
|
||||||
|
monkey_id = request.args.get('id')
|
||||||
|
exists_monkey_id = request.args.get('exists')
|
||||||
|
if monkey_id:
|
||||||
|
return LogService.get_log_by_monkey_id(ObjectId(monkey_id))
|
||||||
|
else:
|
||||||
|
return LogService.log_exists(ObjectId(exists_monkey_id))
|
||||||
|
|
||||||
|
# Used by monkey. can't secure.
|
||||||
|
def post(self):
|
||||||
|
telemetry_json = json.loads(request.data)
|
||||||
|
|
||||||
|
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])['_id']
|
||||||
|
# This shouldn't contain any unicode characters. this'll take 2 time less space.
|
||||||
|
log_data = str(telemetry_json['log'])
|
||||||
|
log_id = LogService.add_log(monkey_id, log_data)
|
||||||
|
|
||||||
|
return mongo.db.log.find_one_or_404({"_id": log_id})
|
|
@ -65,7 +65,7 @@ class Monkey(flask_restful.Resource):
|
||||||
# if new monkey telem, change config according to "new monkeys" config.
|
# if new monkey telem, change config according to "new monkeys" config.
|
||||||
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
|
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
|
||||||
if not db_monkey:
|
if not db_monkey:
|
||||||
new_config = ConfigService.get_flat_config()
|
new_config = ConfigService.get_flat_config(False, True)
|
||||||
monkey_json['config'] = monkey_json.get('config', {})
|
monkey_json['config'] = monkey_json.get('config', {})
|
||||||
monkey_json['config'].update(new_config)
|
monkey_json['config'].update(new_config)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -12,7 +12,7 @@ __author__ = 'Barak'
|
||||||
class MonkeyConfiguration(flask_restful.Resource):
|
class MonkeyConfiguration(flask_restful.Resource):
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def get(self):
|
def get(self):
|
||||||
return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config())
|
return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config(False, True))
|
||||||
|
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def post(self):
|
def post(self):
|
||||||
|
@ -20,5 +20,5 @@ class MonkeyConfiguration(flask_restful.Resource):
|
||||||
if config_json.has_key('reset'):
|
if config_json.has_key('reset'):
|
||||||
ConfigService.reset_config()
|
ConfigService.reset_config()
|
||||||
else:
|
else:
|
||||||
ConfigService.update_config(config_json)
|
ConfigService.update_config(config_json, should_encrypt=True)
|
||||||
return self.get()
|
return self.get()
|
||||||
|
|
|
@ -36,7 +36,8 @@ class Root(flask_restful.Resource):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_db():
|
def reset_db():
|
||||||
[mongo.db[x].drop() for x in ['config', 'monkey', 'telemetry', 'node', 'edge', 'report']]
|
# We can't drop system collections.
|
||||||
|
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
||||||
ConfigService.init_config()
|
ConfigService.init_config()
|
||||||
return jsonify(status='OK')
|
return jsonify(status='OK')
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from cc.database import mongo
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
from cc.services.edge import EdgeService
|
from cc.services.edge import EdgeService
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
|
from cc.encryptor import encryptor
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
@ -112,6 +113,8 @@ class Telemetry(flask_restful.Resource):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_exploit_telemetry(telemetry_json):
|
def process_exploit_telemetry(telemetry_json):
|
||||||
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
|
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
|
||||||
|
Telemetry.encrypt_exploit_creds(telemetry_json)
|
||||||
|
|
||||||
new_exploit = copy.deepcopy(telemetry_json['data'])
|
new_exploit = copy.deepcopy(telemetry_json['data'])
|
||||||
|
|
||||||
new_exploit.pop('machine')
|
new_exploit.pop('machine')
|
||||||
|
@ -166,25 +169,49 @@ class Telemetry(flask_restful.Resource):
|
||||||
def process_system_info_telemetry(telemetry_json):
|
def process_system_info_telemetry(telemetry_json):
|
||||||
if 'credentials' in telemetry_json['data']:
|
if 'credentials' in telemetry_json['data']:
|
||||||
creds = telemetry_json['data']['credentials']
|
creds = telemetry_json['data']['credentials']
|
||||||
for user in creds:
|
Telemetry.encrypt_system_info_creds(creds)
|
||||||
ConfigService.creds_add_username(user)
|
Telemetry.add_system_info_creds_to_config(creds)
|
||||||
if 'password' in creds[user]:
|
Telemetry.replace_user_dot_with_comma(creds)
|
||||||
ConfigService.creds_add_password(creds[user]['password'])
|
|
||||||
if 'lm_hash' in creds[user]:
|
|
||||||
ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
|
|
||||||
if 'ntlm_hash' in creds[user]:
|
|
||||||
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
|
|
||||||
|
|
||||||
for user in creds:
|
|
||||||
if -1 != user.find('.'):
|
|
||||||
new_user = user.replace('.', ',')
|
|
||||||
creds[new_user] = creds.pop(user)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_trace_telemetry(telemetry_json):
|
def process_trace_telemetry(telemetry_json):
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def replace_user_dot_with_comma(creds):
|
||||||
|
for user in creds:
|
||||||
|
if -1 != user.find('.'):
|
||||||
|
new_user = user.replace('.', ',')
|
||||||
|
creds[new_user] = creds.pop(user)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encrypt_system_info_creds(creds):
|
||||||
|
for user in creds:
|
||||||
|
for field in ['password', 'lm_hash', 'ntlm_hash']:
|
||||||
|
if field in creds[user]:
|
||||||
|
creds[user][field] = encryptor.enc(creds[user][field])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_system_info_creds_to_config(creds):
|
||||||
|
for user in creds:
|
||||||
|
ConfigService.creds_add_username(user)
|
||||||
|
if 'password' in creds[user]:
|
||||||
|
ConfigService.creds_add_password(creds[user]['password'])
|
||||||
|
if 'lm_hash' in creds[user]:
|
||||||
|
ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
|
||||||
|
if 'ntlm_hash' in creds[user]:
|
||||||
|
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encrypt_exploit_creds(telemetry_json):
|
||||||
|
attempts = telemetry_json['data']['attempts']
|
||||||
|
for i in range(len(attempts)):
|
||||||
|
for field in ['password', 'lm_hash', 'ntlm_hash']:
|
||||||
|
credential = attempts[i][field]
|
||||||
|
if len(credential) > 0:
|
||||||
|
attempts[i][field] = encryptor.enc(credential)
|
||||||
|
|
||||||
|
|
||||||
TELEM_PROCESS_DICT = \
|
TELEM_PROCESS_DICT = \
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
from cc.database import mongo
|
import copy
|
||||||
|
import functools
|
||||||
from jsonschema import Draft4Validator, validators
|
from jsonschema import Draft4Validator, validators
|
||||||
|
|
||||||
|
from cc.database import mongo
|
||||||
|
from cc.encryptor import encryptor
|
||||||
from cc.environment.environment import env
|
from cc.environment.environment import env
|
||||||
from cc.utils import local_ip_addresses
|
from cc.utils import local_ip_addresses
|
||||||
|
|
||||||
|
@ -17,60 +20,60 @@ SCHEMA = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"SmbExploiter"
|
"SmbExploiter"
|
||||||
],
|
],
|
||||||
"title": "SMB Exploiter"
|
"title": "SMB Exploiter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"WmiExploiter"
|
"WmiExploiter"
|
||||||
],
|
],
|
||||||
"title": "WMI Exploiter"
|
"title": "WMI Exploiter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"RdpExploiter"
|
"RdpExploiter"
|
||||||
],
|
],
|
||||||
"title": "RDP Exploiter (UNSAFE)"
|
"title": "RDP Exploiter (UNSAFE)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Ms08_067_Exploiter"
|
"Ms08_067_Exploiter"
|
||||||
],
|
],
|
||||||
"title": "MS08-067 Exploiter (UNSAFE)"
|
"title": "MS08-067 Exploiter (UNSAFE)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"SSHExploiter"
|
"SSHExploiter"
|
||||||
],
|
],
|
||||||
"title": "SSH Exploiter"
|
"title": "SSH Exploiter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"ShellShockExploiter"
|
"ShellShockExploiter"
|
||||||
],
|
],
|
||||||
"title": "ShellShock Exploiter"
|
"title": "ShellShock Exploiter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"SambaCryExploiter"
|
"SambaCryExploiter"
|
||||||
],
|
],
|
||||||
"title": "SambaCry Exploiter"
|
"title": "SambaCry Exploiter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"ElasticGroovyExploiter"
|
"ElasticGroovyExploiter"
|
||||||
],
|
],
|
||||||
"title": "ElasticGroovy Exploiter"
|
"title": "ElasticGroovy Exploiter"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -79,46 +82,46 @@ SCHEMA = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"SMBFinger"
|
"SMBFinger"
|
||||||
],
|
],
|
||||||
"title": "SMBFinger"
|
"title": "SMBFinger"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"SSHFinger"
|
"SSHFinger"
|
||||||
],
|
],
|
||||||
"title": "SSHFinger"
|
"title": "SSHFinger"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"PingScanner"
|
"PingScanner"
|
||||||
],
|
],
|
||||||
"title": "PingScanner"
|
"title": "PingScanner"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"HTTPFinger"
|
"HTTPFinger"
|
||||||
],
|
],
|
||||||
"title": "HTTPFinger"
|
"title": "HTTPFinger"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"MySQLFinger"
|
"MySQLFinger"
|
||||||
],
|
],
|
||||||
"title": "MySQLFinger"
|
"title": "MySQLFinger"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"ElasticFinger"
|
"ElasticFinger"
|
||||||
],
|
],
|
||||||
"title": "ElasticFinger"
|
"title": "ElasticFinger"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -483,6 +486,12 @@ SCHEMA = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "%temp%\\~df1563.tmp",
|
"default": "%temp%\\~df1563.tmp",
|
||||||
"description": "The fullpath of the monkey log file on Windows"
|
"description": "The fullpath of the monkey log file on Windows"
|
||||||
|
},
|
||||||
|
"send_log_to_server": {
|
||||||
|
"title": "Send log to server",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Determines whether the monkey sends its log to the Monkey Island server"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -528,7 +537,7 @@ SCHEMA = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cnc": {
|
"cnc": {
|
||||||
"title": "C&C",
|
"title": "Monkey Island",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"servers": {
|
"servers": {
|
||||||
|
@ -794,29 +803,56 @@ SCHEMA = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ENCRYPTED_CONFIG_ARRAYS = \
|
||||||
|
[
|
||||||
|
['basic', 'credentials', 'exploit_password_list'],
|
||||||
|
['internal', 'exploits', 'exploit_lm_hash_list'],
|
||||||
|
['internal', 'exploits', 'exploit_ntlm_hash_list']
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ConfigService:
|
class ConfigService:
|
||||||
|
default_config = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config(is_initial_config=False):
|
def get_config(is_initial_config=False, should_decrypt=True):
|
||||||
|
"""
|
||||||
|
Gets the entire global config.
|
||||||
|
:param is_initial_config: If True, the initial config will be returned instead of the current config.
|
||||||
|
:param should_decrypt: If True, all config values which are set as encrypted will be decrypted.
|
||||||
|
:return: The entire global config.
|
||||||
|
"""
|
||||||
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {}
|
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {}
|
||||||
for field in ('name', '_id'):
|
for field in ('name', '_id'):
|
||||||
config.pop(field, None)
|
config.pop(field, None)
|
||||||
|
if should_decrypt and len(config) > 0:
|
||||||
|
ConfigService.decrypt_config(config)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_value(config_key_as_arr, is_initial_config=False):
|
def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt=True):
|
||||||
config_key = reduce(lambda x, y: x+'.'+y, config_key_as_arr)
|
"""
|
||||||
|
Get a specific config value.
|
||||||
|
:param config_key_as_arr: The config key as an array. e.g. ['basic', 'credentials', 'exploit_password_list'].
|
||||||
|
:param is_initial_config: If True, returns the value of the initial config instead of the current config.
|
||||||
|
:param should_decrypt: If True, the value of the config key will be decrypted
|
||||||
|
(if it's in the list of encrypted config values).
|
||||||
|
:return: The value of the requested config key.
|
||||||
|
"""
|
||||||
|
config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr)
|
||||||
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1})
|
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1})
|
||||||
for config_key_part in config_key_as_arr:
|
for config_key_part in config_key_as_arr:
|
||||||
config = config[config_key_part]
|
config = config[config_key_part]
|
||||||
|
if should_decrypt and (config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS):
|
||||||
|
config = [encryptor.dec(x) for x in config]
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_flat_config(is_initial_config=False):
|
def get_flat_config(is_initial_config=False, should_decrypt=True):
|
||||||
config_json = ConfigService.get_config(is_initial_config)
|
config_json = ConfigService.get_config(is_initial_config, should_decrypt)
|
||||||
flat_config_json = {}
|
flat_config_json = {}
|
||||||
for i in config_json:
|
for i in config_json:
|
||||||
for j in config_json[i]:
|
for j in config_json[i]:
|
||||||
|
@ -860,27 +896,38 @@ class ConfigService:
|
||||||
ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash)
|
ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_config(config_json):
|
def update_config(config_json, should_encrypt):
|
||||||
|
if should_encrypt:
|
||||||
|
ConfigService.encrypt_config(config_json)
|
||||||
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
|
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_config():
|
def init_default_config():
|
||||||
defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator)
|
if ConfigService.default_config is None:
|
||||||
config = {}
|
defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator)
|
||||||
defaultValidatingDraft4Validator(SCHEMA).validate(config)
|
config = {}
|
||||||
|
defaultValidatingDraft4Validator(SCHEMA).validate(config)
|
||||||
|
ConfigService.default_config = config
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_default_config(should_encrypt=False):
|
||||||
|
ConfigService.init_default_config()
|
||||||
|
config = copy.deepcopy(ConfigService.default_config)
|
||||||
|
if should_encrypt:
|
||||||
|
ConfigService.encrypt_config(config)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init_config():
|
def init_config():
|
||||||
if ConfigService.get_config() != {}:
|
if ConfigService.get_config(should_decrypt=False) != {}:
|
||||||
return
|
return
|
||||||
ConfigService.reset_config()
|
ConfigService.reset_config()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_config():
|
def reset_config():
|
||||||
config = ConfigService.get_default_config()
|
config = ConfigService.get_default_config(True)
|
||||||
ConfigService.set_server_ips_in_config(config)
|
ConfigService.set_server_ips_in_config(config)
|
||||||
ConfigService.update_config(config)
|
ConfigService.update_config(config, should_encrypt=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_server_ips_in_config(config):
|
def set_server_ips_in_config(config):
|
||||||
|
@ -922,3 +969,21 @@ class ConfigService:
|
||||||
return validators.extend(
|
return validators.extend(
|
||||||
validator_class, {"properties": set_defaults},
|
validator_class, {"properties": set_defaults},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decrypt_config(config):
|
||||||
|
ConfigService._encrypt_or_decrypt_config(config, True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encrypt_config(config):
|
||||||
|
ConfigService._encrypt_or_decrypt_config(config, False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _encrypt_or_decrypt_config(config, is_decrypt=False):
|
||||||
|
for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS:
|
||||||
|
config_arr = config
|
||||||
|
for config_key_part in config_arr_as_array:
|
||||||
|
config_arr = config_arr[config_key_part]
|
||||||
|
|
||||||
|
for i in range(len(config_arr)):
|
||||||
|
config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i])
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import cc.services.node
|
||||||
|
from cc.database import mongo, database
|
||||||
|
|
||||||
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
|
||||||
|
class LogService:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_log_by_monkey_id(monkey_id):
|
||||||
|
log = mongo.db.log.find_one({'monkey_id': monkey_id})
|
||||||
|
if log:
|
||||||
|
log_file = database.gridfs.get(log['file_id'])
|
||||||
|
monkey_label = cc.services.node.NodeService.get_monkey_label(
|
||||||
|
cc.services.node.NodeService.get_monkey_by_id(log['monkey_id']))
|
||||||
|
return \
|
||||||
|
{
|
||||||
|
'monkey_label': monkey_label,
|
||||||
|
'log': log_file.read(),
|
||||||
|
'timestamp': log['timestamp']
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_logs_by_monkey_id(monkey_id):
|
||||||
|
log = mongo.db.log.find_one({'monkey_id': monkey_id})
|
||||||
|
if log is not None:
|
||||||
|
database.gridfs.delete(log['file_id'])
|
||||||
|
mongo.db.log.delete_one({'monkey_id': monkey_id})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_log(monkey_id, log_data, timestamp=datetime.now()):
|
||||||
|
LogService.remove_logs_by_monkey_id(monkey_id)
|
||||||
|
file_id = database.gridfs.put(log_data)
|
||||||
|
return mongo.db.log.insert(
|
||||||
|
{
|
||||||
|
'monkey_id': monkey_id,
|
||||||
|
'file_id': file_id,
|
||||||
|
'timestamp': timestamp
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def log_exists(monkey_id):
|
||||||
|
return mongo.db.log.find_one({'monkey_id': monkey_id}) is not None
|
|
@ -1,9 +1,12 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
|
|
||||||
|
import cc.services.log
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
from cc.services.edge import EdgeService
|
from cc.services.edge import EdgeService
|
||||||
from cc.utils import local_ip_addresses
|
from cc.utils import local_ip_addresses
|
||||||
|
|
||||||
__author__ = "itay.mizeretz"
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,6 +57,7 @@ class NodeService:
|
||||||
else:
|
else:
|
||||||
new_node["services"] = []
|
new_node["services"] = []
|
||||||
|
|
||||||
|
new_node['has_log'] = cc.services.log.LogService.log_exists(ObjectId(node_id))
|
||||||
return new_node
|
return new_node
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -241,7 +245,7 @@ class NodeService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_monkey_island_pseudo_net_node():
|
def get_monkey_island_pseudo_net_node():
|
||||||
return\
|
return \
|
||||||
{
|
{
|
||||||
"id": NodeService.get_monkey_island_pseudo_id(),
|
"id": NodeService.get_monkey_island_pseudo_id(),
|
||||||
"label": "MonkeyIsland",
|
"label": "MonkeyIsland",
|
||||||
|
|
|
@ -293,19 +293,19 @@ class ReportService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_users():
|
def get_config_users():
|
||||||
return ConfigService.get_config_value(['basic', 'credentials', 'exploit_user_list'], True)
|
return ConfigService.get_config_value(['basic', 'credentials', 'exploit_user_list'], True, True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_passwords():
|
def get_config_passwords():
|
||||||
return ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list'], True)
|
return ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list'], True, True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_exploits():
|
def get_config_exploits():
|
||||||
exploits_config_value = ['exploits', 'general', 'exploiter_classes']
|
exploits_config_value = ['exploits', 'general', 'exploiter_classes']
|
||||||
default_exploits = ConfigService.get_default_config()
|
default_exploits = ConfigService.get_default_config(False)
|
||||||
for namespace in exploits_config_value:
|
for namespace in exploits_config_value:
|
||||||
default_exploits = default_exploits[namespace]
|
default_exploits = default_exploits[namespace]
|
||||||
exploits = ConfigService.get_config_value(exploits_config_value, True)
|
exploits = ConfigService.get_config_value(exploits_config_value, True, True)
|
||||||
|
|
||||||
if exploits == default_exploits:
|
if exploits == default_exploits:
|
||||||
return ['default']
|
return ['default']
|
||||||
|
@ -315,13 +315,13 @@ class ReportService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_ips():
|
def get_config_ips():
|
||||||
if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True) != 'FixedRange':
|
if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True, True) != 'FixedRange':
|
||||||
return []
|
return []
|
||||||
return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True)
|
return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True, True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config_scan():
|
def get_config_scan():
|
||||||
return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True)
|
return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True, True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_issues_overview(issues, config_users, config_passwords):
|
def get_issues_overview(issues, config_users, config_passwords):
|
||||||
|
|
|
@ -12,13 +12,6 @@ module.exports = {
|
||||||
devtool: 'eval',
|
devtool: 'eval',
|
||||||
module: {
|
module: {
|
||||||
preLoaders: [
|
preLoaders: [
|
||||||
{
|
|
||||||
test: /\.(js|jsx)$/,
|
|
||||||
loader: 'isparta-instrumenter-loader',
|
|
||||||
include: [
|
|
||||||
path.join(__dirname, '/../src')
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
loaders: [
|
loaders: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -42,6 +42,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"active-event-stack": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/active-event-stack/-/active-event-stack-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-a1uS661xmvrpgs1R9Jw4xbaADFA=",
|
||||||
|
"requires": {
|
||||||
|
"immutable": "3.8.2",
|
||||||
|
"lodash": "3.10.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||||
|
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"after": {
|
"after": {
|
||||||
"version": "0.8.2",
|
"version": "0.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
|
||||||
|
@ -136,7 +152,7 @@
|
||||||
"arr-flatten": {
|
"arr-flatten": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
|
||||||
"integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
|
"integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"array-find-index": {
|
"array-find-index": {
|
||||||
|
@ -1521,6 +1537,11 @@
|
||||||
"q": "1.5.0"
|
"q": "1.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bowser": {
|
||||||
|
"version": "1.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.2.tgz",
|
||||||
|
"integrity": "sha512-fuiANC1Bqbqa/S4gmvfCt7bGBmNELMsGZj4Wg3PrP6esP66Ttoj1JSlzFlXtHyduMv07kDNmDsX6VsMWT/MLGg=="
|
||||||
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
|
||||||
|
@ -1768,16 +1789,6 @@
|
||||||
"integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=",
|
"integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"clipboard": {
|
|
||||||
"version": "1.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
|
|
||||||
"integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
|
|
||||||
"requires": {
|
|
||||||
"good-listener": "1.2.2",
|
|
||||||
"select": "1.1.2",
|
|
||||||
"tiny-emitter": "2.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cliui": {
|
"cliui": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
|
||||||
|
@ -1976,7 +1987,7 @@
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
||||||
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
|
"integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "1.0.2",
|
"core-util-is": "1.0.2",
|
||||||
|
@ -1991,7 +2002,7 @@
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
"integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "5.1.1"
|
"safe-buffer": "5.1.1"
|
||||||
|
@ -2065,7 +2076,7 @@
|
||||||
"copy-to-clipboard": {
|
"copy-to-clipboard": {
|
||||||
"version": "3.0.8",
|
"version": "3.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz",
|
||||||
"integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==",
|
"integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"toggle-selection": "1.0.6"
|
"toggle-selection": "1.0.6"
|
||||||
}
|
}
|
||||||
|
@ -2132,6 +2143,14 @@
|
||||||
"integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=",
|
"integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"css-in-js-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-yuWmPMD9FLi50Xf3k8W8oO3WM1eVnxEGCldCLyfusQ+CgivFk0s23yst4ooW6tfxMuSa03S6uUEga9UhX6GRrA==",
|
||||||
|
"requires": {
|
||||||
|
"hyphenate-style-name": "1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"css-loader": {
|
"css-loader": {
|
||||||
"version": "0.23.1",
|
"version": "0.23.1",
|
||||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.23.1.tgz",
|
||||||
|
@ -2349,11 +2368,6 @@
|
||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"delegate": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.1.3.tgz",
|
|
||||||
"integrity": "sha1-moJRp3fXAl+qVXN7w7BxdCEnqf0="
|
|
||||||
},
|
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
|
||||||
|
@ -2434,6 +2448,16 @@
|
||||||
"integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=",
|
"integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"downloadjs": {
|
||||||
|
"version": "1.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz",
|
||||||
|
"integrity": "sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw="
|
||||||
|
},
|
||||||
|
"dynamics.js": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/dynamics.js/-/dynamics.js-1.1.5.tgz",
|
||||||
|
"integrity": "sha1-uQvcM2Bc7+ZSuEFucB95v27vzjI="
|
||||||
|
},
|
||||||
"ecc-jsbn": {
|
"ecc-jsbn": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
|
||||||
|
@ -2456,6 +2480,11 @@
|
||||||
"integrity": "sha1-PcyZ2j5rZl9qu8ccKK1Ros1zGpw=",
|
"integrity": "sha1-PcyZ2j5rZl9qu8ccKK1Ros1zGpw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"element-resize-event": {
|
||||||
|
"version": "2.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/element-resize-event/-/element-resize-event-2.0.9.tgz",
|
||||||
|
"integrity": "sha1-L14VgaKW61J1IQwUG8VjQuIY+HY="
|
||||||
|
},
|
||||||
"emitter-component": {
|
"emitter-component": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz",
|
||||||
|
@ -3149,14 +3178,6 @@
|
||||||
"websocket-driver": "0.6.5"
|
"websocket-driver": "0.6.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fbemitter": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-2.1.1.tgz",
|
|
||||||
"integrity": "sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=",
|
|
||||||
"requires": {
|
|
||||||
"fbjs": "0.8.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fbjs": {
|
"fbjs": {
|
||||||
"version": "0.8.14",
|
"version": "0.8.14",
|
||||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz",
|
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz",
|
||||||
|
@ -3298,15 +3319,6 @@
|
||||||
"integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
|
"integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"flux": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/flux/-/flux-3.1.3.tgz",
|
|
||||||
"integrity": "sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=",
|
|
||||||
"requires": {
|
|
||||||
"fbemitter": "2.1.1",
|
|
||||||
"fbjs": "0.8.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"font-awesome": {
|
"font-awesome": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
||||||
|
@ -4148,14 +4160,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"bundled": true,
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
@ -4166,6 +4170,14 @@
|
||||||
"strip-ansi": "3.0.1"
|
"strip-ansi": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"bundled": true,
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"stringstream": {
|
"stringstream": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
|
@ -4325,7 +4337,7 @@
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
"integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fs.realpath": "1.0.0",
|
"fs.realpath": "1.0.0",
|
||||||
|
@ -4376,7 +4388,7 @@
|
||||||
"globals": {
|
"globals": {
|
||||||
"version": "9.18.0",
|
"version": "9.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
|
||||||
"integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
|
"integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"globby": {
|
"globby": {
|
||||||
|
@ -4393,14 +4405,6 @@
|
||||||
"pinkie-promise": "2.0.1"
|
"pinkie-promise": "2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"good-listener": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
|
||||||
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
|
|
||||||
"requires": {
|
|
||||||
"delegate": "3.1.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"graceful-fs": {
|
"graceful-fs": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||||
|
@ -4490,12 +4494,6 @@
|
||||||
"isarray": "0.0.1"
|
"isarray": "0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"has-color": {
|
|
||||||
"version": "0.1.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz",
|
|
||||||
"integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"has-cors": {
|
"has-cors": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||||
|
@ -4533,7 +4531,7 @@
|
||||||
"history": {
|
"history": {
|
||||||
"version": "4.7.2",
|
"version": "4.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz",
|
||||||
"integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==",
|
"integrity": "sha1-IrXH8xYzxbgCHH9KipVKwTnujVs=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"invariant": "2.2.2",
|
"invariant": "2.2.2",
|
||||||
"loose-envify": "1.3.1",
|
"loose-envify": "1.3.1",
|
||||||
|
@ -4566,7 +4564,7 @@
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
|
||||||
"integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==",
|
"integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"html-comment-regex": {
|
"html-comment-regex": {
|
||||||
|
@ -4643,10 +4641,15 @@
|
||||||
"integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=",
|
"integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"hyphenate-style-name": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es="
|
||||||
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.18",
|
"version": "0.4.18",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz",
|
||||||
"integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA=="
|
"integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI="
|
||||||
},
|
},
|
||||||
"icss-replace-symbols": {
|
"icss-replace-symbols": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
@ -4666,6 +4669,11 @@
|
||||||
"integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=",
|
"integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"immutable": {
|
||||||
|
"version": "3.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
|
||||||
|
"integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
|
||||||
|
},
|
||||||
"imurmurhash": {
|
"imurmurhash": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||||
|
@ -4709,6 +4717,15 @@
|
||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"inline-style-prefixer": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-MKA98bNGumsfuKgSvDydq+9IAi0=",
|
||||||
|
"requires": {
|
||||||
|
"bowser": "1.9.2",
|
||||||
|
"css-in-js-utils": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"inquirer": {
|
"inquirer": {
|
||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
|
||||||
|
@ -5014,42 +5031,6 @@
|
||||||
"whatwg-fetch": "2.0.3"
|
"whatwg-fetch": "2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"isparta": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/isparta/-/isparta-4.0.0.tgz",
|
|
||||||
"integrity": "sha1-HekZlvSAsi3LGsqFECVbrhV0RG4=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"babel-core": "6.26.0",
|
|
||||||
"escodegen": "1.8.1",
|
|
||||||
"esprima": "2.7.3",
|
|
||||||
"istanbul": "0.4.5",
|
|
||||||
"mkdirp": "0.5.1",
|
|
||||||
"nomnomnomnom": "2.0.1",
|
|
||||||
"object-assign": "4.1.1",
|
|
||||||
"source-map": "0.5.6",
|
|
||||||
"which": "1.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isparta-instrumenter-loader": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isparta-instrumenter-loader/-/isparta-instrumenter-loader-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-nDCMm+A6e8fjC62bbuh8ID4ClSY=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"isparta": "4.0.0",
|
|
||||||
"loader-utils": "0.2.17",
|
|
||||||
"lodash": "3.10.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": {
|
|
||||||
"version": "3.10.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
|
||||||
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isstream": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
@ -5114,6 +5095,11 @@
|
||||||
"integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=",
|
"integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"js-file-download": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.1.tgz",
|
||||||
|
"integrity": "sha1-3g3S1mHVY19QanO5YqtY3bZQvts="
|
||||||
|
},
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||||
|
@ -5142,6 +5128,11 @@
|
||||||
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
|
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"json-loader": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz",
|
||||||
|
"integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w=="
|
||||||
|
},
|
||||||
"json-schema": {
|
"json-schema": {
|
||||||
"version": "0.2.3",
|
"version": "0.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||||
|
@ -5197,9 +5188,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"jsonschema": {
|
"jsonschema": {
|
||||||
"version": "1.1.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.2.tgz",
|
||||||
"integrity": "sha1-PO3o4+QR03eHLu+8n98mODy8Ptk="
|
"integrity": "sha512-iX5OFQ6yx9NgbHCwse51ohhKgLuLL7Z5cNOeZOPIlDUtAMrxlruHLzVZxbltdHE5mEDXN+75oFOwq6Gn0MZwsA=="
|
||||||
},
|
},
|
||||||
"jsprim": {
|
"jsprim": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
|
@ -5227,6 +5218,11 @@
|
||||||
"integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
|
"integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"jwt-decode": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz",
|
||||||
|
"integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
|
||||||
|
},
|
||||||
"karma": {
|
"karma": {
|
||||||
"version": "1.7.1",
|
"version": "1.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz",
|
||||||
|
@ -5830,7 +5826,7 @@
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
"integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "5.1.1"
|
"safe-buffer": "5.1.1"
|
||||||
|
@ -5922,7 +5918,7 @@
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "1.1.8"
|
"brace-expansion": "1.1.8"
|
||||||
|
@ -6005,9 +6001,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"moment": {
|
"moment": {
|
||||||
"version": "2.18.1",
|
"version": "2.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz",
|
||||||
"integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
|
"integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ=="
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
@ -6028,6 +6024,14 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"narcissus": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/narcissus/-/narcissus-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-JGKgfEWYzpBl60Gyq72zDQ4w9G4=",
|
||||||
|
"requires": {
|
||||||
|
"inline-style-prefixer": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"natural-compare": {
|
"natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
|
@ -6114,41 +6118,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nomnomnomnom": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/nomnomnomnom/-/nomnomnomnom-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-siOfAxyNBNpn4yg24eMZnhL3qOI=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"chalk": "0.4.0",
|
|
||||||
"underscore": "1.6.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-styles": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"chalk": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz",
|
|
||||||
"integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"ansi-styles": "1.0.0",
|
|
||||||
"has-color": "0.1.7",
|
|
||||||
"strip-ansi": "0.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"strip-ansi": {
|
|
||||||
"version": "0.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz",
|
|
||||||
"integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noms": {
|
"noms": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz",
|
||||||
|
@ -6171,7 +6140,7 @@
|
||||||
"normalize-package-data": {
|
"normalize-package-data": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
|
||||||
"integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
|
"integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"hosted-git-info": "2.5.0",
|
"hosted-git-info": "2.5.0",
|
||||||
|
@ -7218,7 +7187,7 @@
|
||||||
"promise": {
|
"promise": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
"integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"asap": "2.0.6"
|
"asap": "2.0.6"
|
||||||
}
|
}
|
||||||
|
@ -7267,7 +7236,7 @@
|
||||||
"psl": {
|
"psl": {
|
||||||
"version": "1.1.20",
|
"version": "1.1.20",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.20.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.20.tgz",
|
||||||
"integrity": "sha512-JWUi+8DYZnEn9vfV0ppHFLBP0Lk7wxzpobILpBEMDV4nFket4YK+6Rn1Zn6DHmD9PqqsV96AM6l4R/2oirzkgw=="
|
"integrity": "sha1-NjOC8zI4iICxVeJQY0WVcIQojp0="
|
||||||
},
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
|
@ -7329,7 +7298,7 @@
|
||||||
"randomatic": {
|
"randomatic": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
|
||||||
"integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
|
"integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-number": "3.0.0",
|
"is-number": "3.0.0",
|
||||||
|
@ -7392,6 +7361,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rc-progress": {
|
||||||
|
"version": "2.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.2.5.tgz",
|
||||||
|
"integrity": "sha1-5h0FRL+dQgjlujL8UJYhWef5UqM=",
|
||||||
|
"requires": {
|
||||||
|
"babel-runtime": "6.25.0",
|
||||||
|
"prop-types": "15.5.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react": {
|
"react": {
|
||||||
"version": "15.6.1",
|
"version": "15.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-15.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-15.6.1.tgz",
|
||||||
|
@ -7439,6 +7417,14 @@
|
||||||
"warning": "3.0.0"
|
"warning": "3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-center-component": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-center-component/-/react-center-component-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-0omGv0NOD46/9jyRJ38b9q0YnHI=",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "4.17.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-copy-to-clipboard": {
|
"react-copy-to-clipboard": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.0.tgz",
|
||||||
|
@ -7456,17 +7442,12 @@
|
||||||
"lodash": "4.17.4"
|
"lodash": "4.17.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-data-grid": {
|
"react-dimensions": {
|
||||||
"version": "2.0.58",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-2.0.58.tgz",
|
"resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz",
|
||||||
"integrity": "sha1-g5Rr+cw00URVI0nDQ+FYiMLGKTw="
|
"integrity": "sha512-go5vMuGUxaB5PiTSIk+ZfAxLbHwcIgIfLhkBZ2SIMQjaCgnpttxa30z5ijEzfDjeOCTGRpxvkzcmE4Vt4Ppvyw==",
|
||||||
},
|
|
||||||
"react-data-grid-addons": {
|
|
||||||
"version": "2.0.58",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-data-grid-addons/-/react-data-grid-addons-2.0.58.tgz",
|
|
||||||
"integrity": "sha1-fi2f48741CjCAChuuzHBbJPQTGc=",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"react-data-grid": "2.0.58"
|
"element-resize-event": "2.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
|
@ -7544,27 +7525,39 @@
|
||||||
"react-base16-styling": "0.5.3"
|
"react-base16-styling": "0.5.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-json-view": {
|
|
||||||
"version": "1.12.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.12.1.tgz",
|
|
||||||
"integrity": "sha512-klz1PrwmMcX8tQuvutsoBOABqLtyLKudeXi9Pv0MkBF1Ja1d6JnXrZdYZxIH9WOdwLpCHA+7NnKsdFc/RGFBqg==",
|
|
||||||
"requires": {
|
|
||||||
"clipboard": "1.7.1",
|
|
||||||
"flux": "3.1.3",
|
|
||||||
"react-base16-styling": "0.5.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-jsonschema-form": {
|
"react-jsonschema-form": {
|
||||||
"version": "0.49.0",
|
"version": "0.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-0.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-0.50.1.tgz",
|
||||||
"integrity": "sha512-MTbsAY5s7hNhneVFpSfQjflLal2kuK9YYpdWdr8l54K0B6O4EDIIwpp5visc+1QCs04w2FUo8HqvItaFsQYqig==",
|
"integrity": "sha512-pjhbIQhLmaezfRXZDYgEpM3Ua5m7Pz+jGiQBmRlBjSQ056uegUCKehxY7h4C6qJL8xH3RagteYDuHXxg2yYyww==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"jsonschema": "1.1.1",
|
"jsonschema": "1.2.2",
|
||||||
"lodash.topath": "4.5.2",
|
"lodash.topath": "4.5.2",
|
||||||
"prop-types": "15.5.10",
|
"prop-types": "15.5.10",
|
||||||
"setimmediate": "1.0.5"
|
"setimmediate": "1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-modal-dialog": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-modal-dialog/-/react-modal-dialog-4.0.7.tgz",
|
||||||
|
"integrity": "sha1-OSbaLfqR/wny0xSVSejW7ly62bU=",
|
||||||
|
"requires": {
|
||||||
|
"active-event-stack": "1.0.0",
|
||||||
|
"classnames": "2.2.5",
|
||||||
|
"dynamics.js": "1.1.5",
|
||||||
|
"immutable": "3.8.2",
|
||||||
|
"keycode": "2.1.9",
|
||||||
|
"lodash": "3.10.1",
|
||||||
|
"narcissus": "1.0.0",
|
||||||
|
"react-center-component": "3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||||
|
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-overlays": {
|
"react-overlays": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.7.0.tgz",
|
||||||
|
@ -7601,7 +7594,7 @@
|
||||||
"react-router": {
|
"react-router": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz",
|
||||||
"integrity": "sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg==",
|
"integrity": "sha1-Yfez43cNrrJAYtrj7t7xsFQVWYY=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"history": "4.7.2",
|
"history": "4.7.2",
|
||||||
"hoist-non-react-statics": "2.3.1",
|
"hoist-non-react-statics": "2.3.1",
|
||||||
|
@ -7625,7 +7618,7 @@
|
||||||
"react-router-dom": {
|
"react-router-dom": {
|
||||||
"version": "4.2.2",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.2.2.tgz",
|
||||||
"integrity": "sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA==",
|
"integrity": "sha1-yKgd863Fi7qKdngulGy9Tq5km40=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"history": "4.7.2",
|
"history": "4.7.2",
|
||||||
"invariant": "2.2.2",
|
"invariant": "2.2.2",
|
||||||
|
@ -7635,6 +7628,22 @@
|
||||||
"warning": "3.0.0"
|
"warning": "3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-table": {
|
||||||
|
"version": "6.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.0.tgz",
|
||||||
|
"integrity": "sha1-XOQC63Nd9oU0wD2rs/qgMUeLalg=",
|
||||||
|
"requires": {
|
||||||
|
"classnames": "2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-toggle": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPTWnN7gQHgEAUEmjheanZXNzY5TPnQeyyHfEs3YshaiWZf5WNjfYDrglO5F1Hl/dNveX18i4l0grTEsYH2Ccw==",
|
||||||
|
"requires": {
|
||||||
|
"classnames": "2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"read-pkg": {
|
"read-pkg": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
||||||
|
@ -7781,7 +7790,7 @@
|
||||||
"redux": {
|
"redux": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
|
||||||
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
|
"integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash": "4.17.4",
|
"lodash": "4.17.4",
|
||||||
"lodash-es": "4.17.4",
|
"lodash-es": "4.17.4",
|
||||||
|
@ -7955,7 +7964,7 @@
|
||||||
"resolve-pathname": {
|
"resolve-pathname": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
|
||||||
"integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
|
"integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk="
|
||||||
},
|
},
|
||||||
"restore-cursor": {
|
"restore-cursor": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -8009,7 +8018,7 @@
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
||||||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
|
"integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"sax": {
|
"sax": {
|
||||||
|
@ -8018,11 +8027,6 @@
|
||||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"select": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
|
|
||||||
},
|
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
|
||||||
|
@ -8349,7 +8353,7 @@
|
||||||
"source-map-support": {
|
"source-map-support": {
|
||||||
"version": "0.4.17",
|
"version": "0.4.17",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.17.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.17.tgz",
|
||||||
"integrity": "sha512-30c1Ch8FSjV0FwC253iftbbj0dU/OXoSg1LAEGZJUlGgjTNj6cu+DVqJWWIZJY5RXLWV4eFtR+4ouo0VIOYOTg==",
|
"integrity": "sha1-byFQVT5jdTddDMsxgFAreMGLpDA=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"source-map": "0.5.6"
|
"source-map": "0.5.6"
|
||||||
|
@ -8511,12 +8515,6 @@
|
||||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
|
||||||
"version": "0.10.31",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||||
|
@ -8528,6 +8526,12 @@
|
||||||
"strip-ansi": "3.0.1"
|
"strip-ansi": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "0.10.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||||
|
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"stringstream": {
|
"stringstream": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
||||||
|
@ -8717,7 +8721,7 @@
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
"integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "5.1.1"
|
"safe-buffer": "5.1.1"
|
||||||
|
@ -8741,11 +8745,6 @@
|
||||||
"setimmediate": "1.0.5"
|
"setimmediate": "1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tiny-emitter": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow=="
|
|
||||||
},
|
|
||||||
"tmp": {
|
"tmp": {
|
||||||
"version": "0.0.31",
|
"version": "0.0.31",
|
||||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz",
|
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz",
|
||||||
|
@ -8895,12 +8894,6 @@
|
||||||
"invariant": "2.2.2"
|
"invariant": "2.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"underscore": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
|
|
||||||
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"uniq": {
|
"uniq": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
|
||||||
|
@ -9054,7 +9047,7 @@
|
||||||
"value-equal": {
|
"value-equal": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
|
||||||
"integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
|
"integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c="
|
||||||
},
|
},
|
||||||
"vary": {
|
"vary": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
|
@ -9095,7 +9088,7 @@
|
||||||
"emitter-component": "1.1.1",
|
"emitter-component": "1.1.1",
|
||||||
"hammerjs": "2.0.8",
|
"hammerjs": "2.0.8",
|
||||||
"keycharm": "0.2.0",
|
"keycharm": "0.2.0",
|
||||||
"moment": "2.18.1",
|
"moment": "2.21.0",
|
||||||
"propagating-hammerjs": "1.4.6"
|
"propagating-hammerjs": "1.4.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -9342,7 +9335,7 @@
|
||||||
"which": {
|
"which": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
|
||||||
"integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
|
"integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"isexe": "2.0.0"
|
"isexe": "2.0.0"
|
||||||
|
|
|
@ -38,7 +38,6 @@
|
||||||
"eslint-plugin-react": "^6.0.0",
|
"eslint-plugin-react": "^6.0.0",
|
||||||
"file-loader": "^0.9.0",
|
"file-loader": "^0.9.0",
|
||||||
"glob": "^7.0.0",
|
"glob": "^7.0.0",
|
||||||
"isparta-instrumenter-loader": "^1.0.0",
|
|
||||||
"karma": "^1.7.1",
|
"karma": "^1.7.1",
|
||||||
"karma-chai": "^0.1.0",
|
"karma-chai": "^0.1.0",
|
||||||
"karma-coverage": "^1.0.0",
|
"karma-coverage": "^1.0.0",
|
||||||
|
@ -63,10 +62,12 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^3.3.7",
|
"bootstrap": "^3.3.7",
|
||||||
"core-js": "^2.5.1",
|
"core-js": "^2.5.1",
|
||||||
|
"downloadjs": "^1.4.7",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
"js-file-download": "^0.4.1",
|
"js-file-download": "^0.4.1",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
|
"moment": "^2.21.0",
|
||||||
"normalize.css": "^4.0.0",
|
"normalize.css": "^4.0.0",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"rc-progress": "^2.2.5",
|
"rc-progress": "^2.2.5",
|
||||||
|
|
|
@ -97,7 +97,7 @@ class AppComponent extends AuthComponent {
|
||||||
<li>
|
<li>
|
||||||
<NavLink to="/" exact={true}>
|
<NavLink to="/" exact={true}>
|
||||||
<span className="number">1.</span>
|
<span className="number">1.</span>
|
||||||
Run C&C Server
|
Run Monkey Island Server
|
||||||
{this.state.completedSteps.run_server ?
|
{this.state.completedSteps.run_server ?
|
||||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||||
: ''}
|
: ''}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import {Icon} from 'react-fa';
|
import {Icon} from 'react-fa';
|
||||||
import Toggle from 'react-toggle';
|
import Toggle from 'react-toggle';
|
||||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||||
|
import download from 'downloadjs'
|
||||||
import AuthComponent from '../../AuthComponent';
|
import AuthComponent from '../../AuthComponent';
|
||||||
|
|
||||||
class PreviewPaneComponent extends AuthComponent {
|
class PreviewPaneComponent extends AuthComponent {
|
||||||
|
@ -82,16 +83,56 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
|
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
|
||||||
onChange={(e) => this.forceKill(e, asset)} />
|
onChange={(e) => this.forceKill(e, asset)}/>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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) {
|
||||||
|
fetch('/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) {
|
exploitsTimeline(asset) {
|
||||||
if (asset.exploits.length === 0) {
|
if (asset.exploits.length === 0) {
|
||||||
return (<div />);
|
return (<div/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -101,9 +142,9 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
||||||
</h4>
|
</h4>
|
||||||
<ul className="timeline">
|
<ul className="timeline">
|
||||||
{ asset.exploits.map(exploit =>
|
{asset.exploits.map(exploit =>
|
||||||
<li key={exploit.timestamp}>
|
<li key={exploit.timestamp}>
|
||||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')} />
|
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||||
<div>{exploit.origin}</div>
|
<div>{exploit.origin}</div>
|
||||||
<div>{exploit.exploiter}</div>
|
<div>{exploit.exploiter}</div>
|
||||||
|
@ -119,10 +160,10 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
<div>
|
<div>
|
||||||
<table className="table table-condensed">
|
<table className="table table-condensed">
|
||||||
<tbody>
|
<tbody>
|
||||||
{this.osRow(asset)}
|
{this.osRow(asset)}
|
||||||
{this.ipsRow(asset)}
|
{this.ipsRow(asset)}
|
||||||
{this.servicesRow(asset)}
|
{this.servicesRow(asset)}
|
||||||
{this.accessibleRow(asset)}
|
{this.accessibleRow(asset)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{this.exploitsTimeline(asset)}
|
{this.exploitsTimeline(asset)}
|
||||||
|
@ -135,12 +176,13 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
<div>
|
<div>
|
||||||
<table className="table table-condensed">
|
<table className="table table-condensed">
|
||||||
<tbody>
|
<tbody>
|
||||||
{this.osRow(asset)}
|
{this.osRow(asset)}
|
||||||
{this.statusRow(asset)}
|
{this.statusRow(asset)}
|
||||||
{this.ipsRow(asset)}
|
{this.ipsRow(asset)}
|
||||||
{this.servicesRow(asset)}
|
{this.servicesRow(asset)}
|
||||||
{this.accessibleRow(asset)}
|
{this.accessibleRow(asset)}
|
||||||
{this.forceKillRow(asset)}
|
{this.forceKillRow(asset)}
|
||||||
|
{this.downloadLogRow(asset)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{this.exploitsTimeline(asset)}
|
{this.exploitsTimeline(asset)}
|
||||||
|
@ -173,9 +215,9 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
<div>
|
<div>
|
||||||
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
||||||
<ul className="timeline">
|
<ul className="timeline">
|
||||||
{ edge.exploits.map(exploit =>
|
{edge.exploits.map(exploit =>
|
||||||
<li key={exploit.timestamp}>
|
<li key={exploit.timestamp}>
|
||||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')} />
|
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||||
<div>{exploit.origin}</div>
|
<div>{exploit.origin}</div>
|
||||||
<div>{exploit.exploiter}</div>
|
<div>{exploit.exploiter}</div>
|
||||||
|
@ -206,8 +248,8 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
|
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
|
||||||
break;
|
break;
|
||||||
case 'island_edge':
|
case 'island_edge':
|
||||||
info = this.islandEdgeInfo();
|
info = this.islandEdgeInfo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let label = '';
|
let label = '';
|
||||||
|
@ -221,12 +263,12 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="preview-pane">
|
<div className="preview-pane">
|
||||||
{ !info ?
|
{!info ?
|
||||||
<span>
|
<span>
|
||||||
<Icon name="hand-o-left" style={{'marginRight': '0.5em'}} />
|
<Icon name="hand-o-left" style={{'marginRight': '0.5em'}}/>
|
||||||
Select an item on the map for a detailed look
|
Select an item on the map for a detailed look
|
||||||
</span>
|
</span>
|
||||||
:
|
:
|
||||||
<div>
|
<div>
|
||||||
<h3>
|
<h3>
|
||||||
{label}
|
{label}
|
||||||
|
|
|
@ -611,7 +611,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
The network can probably be segmented. A monkey instance on <span
|
The network can probably be segmented. A monkey instance on <span
|
||||||
className="label label-primary">{issue.machine}</span> in the
|
className="label label-primary">{issue.machine}</span> in the
|
||||||
networks {this.generateInfoBadges(issue.networks)}
|
networks {this.generateInfoBadges(issue.networks)}
|
||||||
could directly access the Monkey Island C&C server in the
|
could directly access the Monkey Island server in the
|
||||||
networks {this.generateInfoBadges(issue.server_networks)}.
|
networks {this.generateInfoBadges(issue.server_networks)}.
|
||||||
</CollapsibleWellComponent>
|
</CollapsibleWellComponent>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -147,7 +147,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
||||||
className="btn btn-default btn-lg center-block"
|
className="btn btn-default btn-lg center-block"
|
||||||
disabled={this.state.runningOnIslandState !== 'not_running'}
|
disabled={this.state.runningOnIslandState !== 'not_running'}
|
||||||
>
|
>
|
||||||
Run on C&C Server
|
Run on Monkey Island Server
|
||||||
{ this.renderIconByState(this.state.runningOnIslandState) }
|
{ this.renderIconByState(this.state.runningOnIslandState) }
|
||||||
</button>
|
</button>
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,7 @@ class RunServerPageComponent extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Col xs={12} lg={8}>
|
<Col xs={12} lg={8}>
|
||||||
<h1 className="page-title">1. Monkey Island C&C Server</h1>
|
<h1 className="page-title">1. Monkey Island Server</h1>
|
||||||
<div style={{'fontSize': '1.2em'}}>
|
<div style={{'fontSize': '1.2em'}}>
|
||||||
<p style={{'marginTop': '30px'}}>Congrats! You have successfully set up the Monkey Island
|
<p style={{'marginTop': '30px'}}>Congrats! You have successfully set up the Monkey Island
|
||||||
server. 👏 👏</p>
|
server. 👏 👏</p>
|
||||||
|
@ -18,7 +18,7 @@ class RunServerPageComponent extends React.Component {
|
||||||
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter
|
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter
|
||||||
breaches and internal server infections.
|
breaches and internal server infections.
|
||||||
The Monkey uses various methods to propagate across a data
|
The Monkey uses various methods to propagate across a data
|
||||||
center and reports to this Command and Control (C&C) server.
|
center and reports to this Monkey Island Command and Control server.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
To read more about the Monkey, visit <a href="http://infectionmonkey.com"
|
To read more about the Monkey, visit <a href="http://infectionmonkey.com"
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 108 KiB |
|
@ -2,7 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Infection Monkey C&C</title>
|
<title>Infection Monkey Island Server</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
|
|
@ -4,5 +4,5 @@ Maintainer: Guardicore
|
||||||
Homepage: http://www.guardicore.com
|
Homepage: http://www.guardicore.com
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Version: 1.0
|
Version: 1.0
|
||||||
Description: Guardicore Infection Monkey Island (C&C) installation package
|
Description: Guardicore Infection Monkey Island installation package
|
||||||
Depends: openssl, python-pip
|
Depends: openssl, python-pip
|
||||||
|
|
|
@ -13,4 +13,5 @@ jsonschema
|
||||||
netifaces
|
netifaces
|
||||||
ipaddress
|
ipaddress
|
||||||
enum34
|
enum34
|
||||||
|
PyCrypto
|
||||||
virtualenv
|
virtualenv
|
|
@ -1,4 +1,4 @@
|
||||||
How to set C&C server:
|
How to set up the Monkey Island server:
|
||||||
|
|
||||||
---------------- On Windows ----------------:
|
---------------- On Windows ----------------:
|
||||||
1. Create folder "bin" under monkey_island
|
1. Create folder "bin" under monkey_island
|
||||||
|
@ -18,7 +18,7 @@ How to set C&C server:
|
||||||
5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572
|
5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572
|
||||||
6. Generate SSL Certificate
|
6. Generate SSL Certificate
|
||||||
6.1. run create_certificate.bat when your current working directory is monkey_island
|
6.1. run create_certificate.bat when your current working directory is monkey_island
|
||||||
7. Create the monkey_island\cc\binaries folder and put chaos monkey binaries inside
|
7. Create the monkey_island\cc\binaries folder and put Infection Monkey binaries inside
|
||||||
monkey-linux-64 - monkey binary for linux 64bit
|
monkey-linux-64 - monkey binary for linux 64bit
|
||||||
monkey-linux-32 - monkey binary for linux 32bit
|
monkey-linux-32 - monkey binary for linux 32bit
|
||||||
monkey-windows-32.exe - monkey binary for windows 32bit
|
monkey-windows-32.exe - monkey binary for windows 32bit
|
||||||
|
|
|
@ -12,4 +12,5 @@ Flask-JWT
|
||||||
jsonschema
|
jsonschema
|
||||||
netifaces
|
netifaces
|
||||||
ipaddress
|
ipaddress
|
||||||
enum34
|
enum34
|
||||||
|
PyCrypto
|
||||||
|
|
Loading…
Reference in New Issue