Merge branch 'develop' into SSH_key_stealing

This commit is contained in:
Daniel Goldberg 2018-06-05 16:59:28 +03:00 committed by GitHub
commit ecdd2e8762
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1044 additions and 495 deletions

23
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us fix things!
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Configure the Monkey with X settings
2. Run the monkey on specific machine
3. See error
**Expected behavior**
A description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Machine version(please complete the following information):**
- OS: Windows or Linux

27
.travis.yml Normal file
View File

@ -0,0 +1,27 @@
group: travis_latest
language: python
cache: pip
python:
- 2.7
- 3.6
#- nightly
#- pypy
#- pypy3
matrix:
allow_failures:
- python: nightly
- python: pypy
- python: pypy3
install:
#- pip install -r requirements.txt
- pip install flake8 # pytest # add another testing frameworks later
before_script:
# stop the build if there are Python syntax errors or undefined names
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
script:
- true # pytest --capture=sys # add other tests here
notifications:
on_success: change
on_failure: change # `always` will be the setting once code changes slow down

View File

@ -20,7 +20,7 @@ The following is a *short* list of recommendations. PRs that don't match these c
* **Don't** leave your pull request description blank. * **Don't** leave your pull request description blank.
* **Do** license your code as GPLv3. * **Do** license your code as GPLv3.
Also, please submit PRs to the develop branch.
## Issues ## Issues
* **Do** write a detailed description of your bug and use a descriptive title. * **Do** write a detailed description of your bug and use a descriptive title.

View File

@ -4,6 +4,7 @@ import struct
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import ipaddress import ipaddress
from six import text_type
__author__ = 'itamar' __author__ = 'itamar'
@ -65,7 +66,7 @@ class CidrRange(NetworkRange):
def __init__(self, cidr_range, shuffle=True): def __init__(self, cidr_range, shuffle=True):
super(CidrRange, self).__init__(shuffle=shuffle) super(CidrRange, self).__init__(shuffle=shuffle)
self._cidr_range = cidr_range.strip() self._cidr_range = cidr_range.strip()
self._ip_network = ipaddress.ip_network(unicode(self._cidr_range), strict=False) self._ip_network = ipaddress.ip_network(text_type(self._cidr_range), strict=False)
def __repr__(self): def __repr__(self):
return "<CidrRange %s>" % (self._cidr_range,) return "<CidrRange %s>" % (self._cidr_range,)

19
docker/Dockerfile Normal file
View File

@ -0,0 +1,19 @@
FROM debian:jessie-slim
LABEL MAINTAINER="theonlydoo <theonlydoo@gmail.com>"
WORKDIR /app
ADD https://github.com/guardicore/monkey/releases/download/1.5.2/infection_monkey_1.5.2_deb.tgz .
RUN tar xvf infection_monkey_1.5.2_deb.tgz \
&& apt-get -yqq update \
&& apt-get -yqq upgrade \
&& apt-get -yqq install python-pip \
libssl-dev \
supervisor \
&& dpkg -i *.deb
COPY stack.conf /etc/supervisor/conf.d/stack.conf
ENTRYPOINT [ "supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf" ]

11
docker/README.md Normal file
View File

@ -0,0 +1,11 @@
# Improvements needed
* Remove embedded mongodb from .deb, it forbids installation on a `debian:stretch` distro.
* Package monkey for system's python usage.
* Fix package number: (I installed the 1.5.2)
```
ii gc-monkey-island 1.0 amd64 Guardicore Infection Monkey Island installation package
```
* Use .deb dependencies for mongodb setup?
* Use docker-compose for stack construction.
* Remove the .sh script from the systemd unit file (`/var/monkey_island/ubuntu/systemd/start_server.sh`) which only does a `cd && localpython run`

4
docker/stack.conf Normal file
View File

@ -0,0 +1,4 @@
[program:mongod]
command=/var/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey_island/db
[program:monkey]
command=/var/monkey_island/ubuntu/systemd/start_server.sh

View File

@ -40,7 +40,7 @@ def _cast_by_example(value, example):
return int(value) return int(value)
elif example_type is float: elif example_type is float:
return float(value) return float(value)
elif example_type is types.ClassType or example_type is ABCMeta: elif example_type in (type, ABCMeta):
return globals()[value] return globals()[value]
else: else:
return None return None
@ -84,10 +84,10 @@ class Configuration(object):
if val_type is types.FunctionType or val_type is types.MethodType: if val_type is types.FunctionType or val_type is types.MethodType:
continue continue
if val_type is types.ClassType or val_type is ABCMeta: if val_type in (type, ABCMeta):
value = value.__name__ value = value.__name__
elif val_type is tuple or val_type is list: elif val_type is tuple or val_type is list:
if len(value) != 0 and (type(value[0]) is types.ClassType or type(value[0]) is ABCMeta): if len(value) != 0 and type(value[0]) in (type, ABCMeta):
value = val_type([x.__name__ for x in value]) value = val_type([x.__name__ for x in value])
result[key] = value result[key] = value

View File

@ -3,9 +3,9 @@
"41.50.73.31:5000" "41.50.73.31:5000"
], ],
"internet_services": [ "internet_services": [
"monkey.guardicore.com", "monkey.guardicore.com",
"www.google.com" "www.google.com"
], ],
"keep_tunnel_open_time": 60, "keep_tunnel_open_time": 60,
"subnet_scan_list": [ "subnet_scan_list": [
"" ""
@ -36,15 +36,15 @@
"WmiExploiter", "WmiExploiter",
"ShellShockExploiter", "ShellShockExploiter",
"ElasticGroovyExploiter", "ElasticGroovyExploiter",
"SambaCryExploiter", "SambaCryExploiter"
], ],
"finger_classes": [ "finger_classes": [
"SSHFinger", "SSHFinger",
"PingScanner", "PingScanner",
"HTTPFinger", "HTTPFinger",
"SMBFinger", "SMBFinger",
"MySQLFinger" "MySQLFinger",
"ElasticFinger", "ElasticFinger"
], ],
"max_iterations": 3, "max_iterations": 3,
"monkey_log_path_windows": "%temp%\\~df1563.tmp", "monkey_log_path_windows": "%temp%\\~df1563.tmp",
@ -91,4 +91,4 @@
"use_file_logging": true, "use_file_logging": true,
"victims_max_exploit": 7, "victims_max_exploit": 7,
"victims_max_find": 30 "victims_max_find": 30
} }

View File

@ -27,7 +27,7 @@ LOG = getLogger(__name__)
def twisted_log_func(*message, **kw): def twisted_log_func(*message, **kw):
if kw.has_key('isError') and kw['isError']: if kw.get('isError'):
error_msg = 'Unknown' error_msg = 'Unknown'
if 'failure' in kw: if 'failure' in kw:
error_msg = kw['failure'].getErrorMessage() error_msg = kw['failure'].getErrorMessage()

View File

@ -8,7 +8,7 @@ import requests
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
from model import MONKEY_ARG from model import DROPPER_ARG
from shellshock_resources import CGI_FILES from shellshock_resources import CGI_FILES
from tools import build_monkey_commandline from tools import build_monkey_commandline
@ -133,7 +133,7 @@ class ShellShockExploiter(HostExploiter):
self.attack_page(url, header, run_path) self.attack_page(url, header, run_path)
# run the monkey # run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & '
run_path = exploit + cmdline run_path = exploit + cmdline
self.attack_page(url, header, run_path) self.attack_page(url, header, run_path)

View File

@ -405,7 +405,7 @@ def get_interface_to_target(dst):
for d, m, gw, i, a in routes: for d, m, gw, i, a in routes:
aa = atol(a) aa = atol(a)
if aa == dst: if aa == dst:
pathes.append((0xffffffffL, ("lo", a, "0.0.0.0"))) pathes.append((0xffffffff, ("lo", a, "0.0.0.0")))
if (dst & m) == (d & m): if (dst & m) == (d & m):
pathes.append((m, (i, a, gw))) pathes.append((m, (i, a, gw)))
if not pathes: if not pathes:

View File

@ -12,6 +12,7 @@ from control import ControlClient
from model import DELAY_DELETE_CMD from model import DELAY_DELETE_CMD
from network.firewall import app as firewall from network.firewall import app as firewall
from network.network_scanner import NetworkScanner from network.network_scanner import NetworkScanner
from six.moves import xrange
from system_info import SystemInfoCollector from system_info import SystemInfoCollector
from system_singleton import SystemSingleton from system_singleton import SystemSingleton
from windows_upgrader import WindowsUpgrader from windows_upgrader import WindowsUpgrader

View File

@ -10,6 +10,11 @@ from subprocess import check_output
from random import randint from random import randint
from common.network.network_range import CidrRange from common.network.network_range import CidrRange
try:
long # Python 2
except NameError:
long = int # Python 3
def get_host_subnets(): def get_host_subnets():
""" """
@ -93,8 +98,8 @@ else:
ifaddr = socket.inet_ntoa(ifreq[20:24]) ifaddr = socket.inet_ntoa(ifreq[20:24])
else: else:
continue continue
routes.append((socket.htonl(long(dst, 16)) & 0xffffffffL, routes.append((socket.htonl(long(dst, 16)) & 0xffffffff,
socket.htonl(long(msk, 16)) & 0xffffffffL, socket.htonl(long(msk, 16)) & 0xffffffff,
socket.inet_ntoa(struct.pack("I", long(gw, 16))), socket.inet_ntoa(struct.pack("I", long(gw, 16))),
iff, ifaddr)) iff, ifaddr))

View File

@ -144,13 +144,13 @@ class SMBFinger(HostFinger):
host.os['type'] = 'linux' host.os['type'] = 'linux'
host.services[SMB_SERVICE]['name'] = service_client host.services[SMB_SERVICE]['name'] = service_client
if not host.os.has_key('version'): if 'version' not in host.os:
host.os['version'] = os_version host.os['version'] = os_version
else: else:
host.services[SMB_SERVICE]['os-version'] = os_version host.services[SMB_SERVICE]['os-version'] = os_version
return True return True
except Exception, exc: except Exception as exc:
LOG.debug("Error getting smb fingerprint: %s", exc) LOG.debug("Error getting smb fingerprint: %s", exc)
return False return False

View File

@ -10,8 +10,7 @@ odict
paramiko paramiko
psutil==3.4.2 psutil==3.4.2
PyInstaller PyInstaller
six
ecdsa ecdsa
netifaces netifaces
mock ipaddress
nose
ipaddress

View File

@ -24,7 +24,7 @@ class MimikatzCollector(object):
self._collect = collect_proto(("collect", self._dll)) self._collect = collect_proto(("collect", self._dll))
self._get = get_proto(("get", self._dll)) self._get = get_proto(("get", self._dll))
self._isInit = True self._isInit = True
except StandardError: except Exception:
LOG.exception("Error initializing mimikatz collector") LOG.exception("Error initializing mimikatz collector")
def get_logon_info(self): def get_logon_info(self):
@ -71,7 +71,7 @@ class MimikatzCollector(object):
logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash
return logon_data_dictionary return logon_data_dictionary
except StandardError: except Exception:
LOG.exception("Error getting logon info") LOG.exception("Error getting logon info")
return {} return {}

View File

@ -1,45 +0,0 @@
# -*- coding: UTF-8 -*-
# NOTE: Launch all tests with `nosetests` command from infection_monkey dir.
import json
import unittest
from mock import Mock, patch
import control
from config import GUID
class ReportConfigErrorTestCase(unittest.TestCase):
"""
When unknown config variable received form the island server, skip it and report config
error back to the server.
"""
config_response = Mock(json=Mock(return_value={'config': {'blah': 'blah'}}))
def teardown(self):
patch.stopall()
def test_config(self):
patch('control.requests.patch', Mock()).start()
patch('control.WormConfiguration', Mock(current_server='127.0.0.1:123')).start()
# GIVEN the server with uknown config variable
patch('control.requests.get', Mock(return_value=self.config_response)).start()
# WHEN monkey tries to load config from server
control.ControlClient.load_control_config()
# THEN she reports config error back to the server
control.requests.patch.assert_called_once_with(
"https://127.0.0.1:123/api/monkey/%s" % GUID,
data=json.dumps({'config_error': True}),
headers={'content-type': 'application/json'},
verify=False,
proxies=control.ControlClient.proxies)
if __name__ == '__main__':
unittest.main()

View File

@ -32,12 +32,12 @@ class FTPServer(threading.Thread):
try: try:
func=getattr(self,cmd[:4].strip().upper()) func=getattr(self,cmd[:4].strip().upper())
func(cmd) func(cmd)
except Exception,e: except Exception as e:
self.conn.send('500 Sorry.\r\n') self.conn.send('500 Sorry.\r\n')
break break
self.conn.close() self.conn.close()
self.sock.close() self.sock.close()
def SYST(self,cmd): def SYST(self,cmd):
self.conn.send('215 UNIX Type: L8\r\n') self.conn.send('215 UNIX Type: L8\r\n')
@ -83,7 +83,7 @@ class FTPServer(threading.Thread):
def PASV(self,cmd): def PASV(self,cmd):
self.pasv_mode = True self.pasv_mode = True
self.servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) self.servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.servsock.bind((local_ip,0)) self.servsock.bind((self.local_ip,0))
self.servsock.listen(1) self.servsock.listen(1)
ip, port = self.servsock.getsockname() ip, port = self.servsock.getsockname()
self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' % self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' %

View File

@ -122,7 +122,7 @@ class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
address = (u.hostname, u.port or 443) address = (u.hostname, u.port or 443)
try: try:
conn = socket.create_connection(address) conn = socket.create_connection(address)
except socket.error, e: except socket.error as e:
LOG.debug("HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" % (repr(address), e)) LOG.debug("HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" % (repr(address), e))
self.send_error(504) # 504 Gateway Timeout self.send_error(504) # 504 Gateway Timeout
return return

View File

@ -63,7 +63,7 @@ class TcpProxy(TransportProxyBase):
try: try:
dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
dest.connect((self.dest_host, self.dest_port)) dest.connect((self.dest_host, self.dest_port))
except socket.error, ex: except socket.error as ex:
source.close() source.close()
dest.close() dest.close()
continue continue

View File

@ -14,6 +14,7 @@ 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.log import Log
from cc.resources.island_logs import IslandLog
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
@ -104,5 +105,6 @@ def init_app(mongo_url):
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/') api.add_resource(Log, '/api/log', '/api/log/')
api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/')
return app return app

View File

@ -1,7 +1,11 @@
import json import json
import logging
import standard import standard
import aws import aws
logger = logging.getLogger(__name__)
ENV_DICT = { ENV_DICT = {
'standard': standard.StandardEnvironment, 'standard': standard.StandardEnvironment,
'aws': aws.AwsEnvironment 'aws': aws.AwsEnvironment
@ -18,6 +22,7 @@ def load_env_from_file():
try: try:
__env_type = load_env_from_file() __env_type = load_env_from_file()
env = ENV_DICT[__env_type]() env = ENV_DICT[__env_type]()
logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
except Exception: except Exception:
print('Failed initializing environment: %s' % __env_type) logger.error('Failed initializing environment', exc_info=True)
raise raise

View File

@ -0,0 +1,26 @@
import os
import json
import logging.config
__author__ = 'Maor.Rayzin'
def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'):
"""
Setup the logging configuration
:param default_path: the default log configuration file path
:param default_level: Default level to log from
:param env_key: SYS ENV key to use for external configuration file path
:return:
"""
path = default_path
value = os.getenv(env_key, None)
if value:
path = value
if os.path.exists(path):
with open(path, 'rt') as f:
config = json.load(f)
logging.config.dictConfig(config)
else:
logging.basicConfig(level=default_level)

View File

@ -0,0 +1,33 @@
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
},
"info_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "simple",
"filename": "info.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "info_file_handler"]
}
}

View File

@ -2,19 +2,25 @@ from __future__ import print_function # In python 2.7
import os import os
import sys import sys
import time import time
import logging
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if BASE_PATH not in sys.path: if BASE_PATH not in sys.path:
sys.path.insert(0, BASE_PATH) sys.path.insert(0, BASE_PATH)
from cc.island_logger import json_setup_logging
# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top.
json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG)
logger = logging.getLogger(__name__)
from cc.app import init_app from cc.app import init_app
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
from cc.environment.environment import env from cc.environment.environment import env
from cc.database import is_db_server_up from cc.database import is_db_server_up
if __name__ == '__main__':
def main():
from tornado.wsgi import WSGIContainer from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
@ -22,7 +28,7 @@ if __name__ == '__main__':
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
while not is_db_server_up(mongo_url): while not is_db_server_up(mongo_url):
print('Waiting for MongoDB server') logger.info('Waiting for MongoDB server')
time.sleep(1) time.sleep(1)
app = init_app(mongo_url) app = init_app(mongo_url)
@ -33,6 +39,10 @@ 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 Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) logger.info(
'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
IOLoop.instance().start() IOLoop.instance().start()
if __name__ == '__main__':
main()

View File

@ -1,3 +1,4 @@
import logging
from flask import request, jsonify from flask import request, jsonify
import flask_restful import flask_restful
@ -5,6 +6,8 @@ from cc.services.node import NodeService
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
logger = logging.getLogger(__name__)
class ClientRun(flask_restful.Resource): class ClientRun(flask_restful.Resource):
def get(self): def get(self):
@ -17,6 +20,7 @@ class ClientRun(flask_restful.Resource):
if monkey is not None: if monkey is not None:
is_monkey_running = not monkey["dead"] is_monkey_running = not monkey["dead"]
else: else:
logger.info("Monkey is not running")
is_monkey_running = False is_monkey_running = False
return jsonify(is_running=is_monkey_running) return jsonify(is_running=is_monkey_running)

View File

@ -0,0 +1,19 @@
import logging
import flask_restful
from cc.auth import jwt_required
from cc.services.island_logs import IslandLogService
__author__ = "Maor.Rayzin"
logger = logging.getLogger(__name__)
class IslandLog(flask_restful.Resource):
@jwt_required()
def get(self):
try:
return IslandLogService.get_log_file()
except Exception as e:
logger.error('Monkey Island logs failed to download', exc_info=True)

View File

@ -13,6 +13,8 @@ from cc.utils import local_ip_addresses
__author__ = 'Barak' __author__ = 'Barak'
import logging
logger = logging.getLogger(__name__)
def run_local_monkey(): def run_local_monkey():
import platform import platform
@ -32,6 +34,7 @@ def run_local_monkey():
copyfile(monkey_path, target_path) copyfile(monkey_path, target_path)
os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG) os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG)
except Exception as exc: except Exception as exc:
logger.error('Copy file failed', exc_info=True)
return False, "Copy file failed: %s" % exc return False, "Copy file failed: %s" % exc
# run the monkey # run the monkey
@ -41,6 +44,7 @@ def run_local_monkey():
args = "".join(args) args = "".join(args)
pid = subprocess.Popen(args, shell=True).pid pid = subprocess.Popen(args, shell=True).pid
except Exception as exc: except Exception as exc:
logger.error('popen failed', exc_info=True)
return False, "popen failed: %s" % exc return False, "popen failed: %s" % exc
return True, "pis: %s" % pid return True, "pis: %s" % pid

View File

@ -17,7 +17,7 @@ class MonkeyConfiguration(flask_restful.Resource):
@jwt_required() @jwt_required()
def post(self): def post(self):
config_json = json.loads(request.data) config_json = json.loads(request.data)
if config_json.has_key('reset'): if 'reset' in config_json:
ConfigService.reset_config() ConfigService.reset_config()
else: else:
ConfigService.update_config(config_json, should_encrypt=True) ConfigService.update_config(config_json, should_encrypt=True)

View File

@ -1,3 +1,4 @@
import logging
import json import json
import os import os
@ -6,6 +7,8 @@ import flask_restful
__author__ = 'Barak' __author__ = 'Barak'
logger = logging.getLogger(__name__)
MONKEY_DOWNLOADS = [ MONKEY_DOWNLOADS = [
{ {
@ -42,7 +45,10 @@ MONKEY_DOWNLOADS = [
def get_monkey_executable(host_os, machine): def get_monkey_executable(host_os, machine):
for download in MONKEY_DOWNLOADS: for download in MONKEY_DOWNLOADS:
if host_os == download.get('type') and machine == download.get('machine'): if host_os == download.get('type') and machine == download.get('machine'):
logger.info('Monkey exec found for os: {0} and machine: {1}'.format(host_os, machine))
return download return download
logger.warning('No monkey executables could be found for the host os or machine or both: host_os: {0}, machine: {1}'
.format(host_os, machine))
return None return None

View File

@ -1,4 +1,5 @@
from datetime import datetime from datetime import datetime
import logging
import flask_restful import flask_restful
from flask import request, make_response, jsonify from flask import request, make_response, jsonify
@ -12,6 +13,8 @@ from cc.utils import local_ip_addresses
__author__ = 'Barak' __author__ = 'Barak'
logger = logging.getLogger(__name__)
class Root(flask_restful.Resource): class Root(flask_restful.Resource):
@ -42,6 +45,7 @@ class Root(flask_restful.Resource):
# We can't drop system collections. # We can't drop system collections.
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
ConfigService.init_config() ConfigService.init_config()
logger.info('DB was reset')
return jsonify(status='OK') return jsonify(status='OK')
@staticmethod @staticmethod
@ -50,6 +54,7 @@ class Root(flask_restful.Resource):
mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}},
upsert=False, upsert=False,
multi=True) multi=True)
logger.info('Kill all monkeys was called')
return jsonify(status='OK') return jsonify(status='OK')
@staticmethod @staticmethod
@ -59,6 +64,7 @@ class Root(flask_restful.Resource):
infection_done = NodeService.is_monkey_finished_running() infection_done = NodeService.is_monkey_finished_running()
if not infection_done: if not infection_done:
report_done = False report_done = False
logger.info('Report generation cannot be completed, infection is not done.')
else: else:
report_done = ReportService.is_report_generated() report_done = ReportService.is_report_generated()
return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done)

View File

@ -1,4 +1,5 @@
import json import json
import logging
import traceback import traceback
import copy import copy
from datetime import datetime from datetime import datetime
@ -17,6 +18,9 @@ from cc.encryptor import encryptor
__author__ = 'Barak' __author__ = 'Barak'
logger = logging.getLogger(__name__)
class Telemetry(flask_restful.Resource): class Telemetry(flask_restful.Resource):
@jwt_required() @jwt_required()
def get(self, **kw): def get(self, **kw):
@ -52,10 +56,9 @@ class Telemetry(flask_restful.Resource):
if telem_type in TELEM_PROCESS_DICT: if telem_type in TELEM_PROCESS_DICT:
TELEM_PROCESS_DICT[telem_type](telemetry_json) TELEM_PROCESS_DICT[telem_type](telemetry_json)
else: else:
print('Got unknown type of telemetry: %s' % telem_type) logger.info('Got unknown type of telemetry: %s' % telem_type)
except StandardError as ex: except Exception as ex:
print("Exception caught while processing telemetry: %s" % str(ex)) logger.error("Exception caught while processing telemetry", exc_info=True)
traceback.print_exc()
telem_id = mongo.db.telemetry.insert(telemetry_json) telem_id = mongo.db.telemetry.insert(telemetry_json)
return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) return mongo.db.telemetry.find_one_or_404({"_id": telem_id})

View File

@ -1,7 +1,9 @@
import copy import copy
import collections import collections
import functools import functools
import logging
from jsonschema import Draft4Validator, validators from jsonschema import Draft4Validator, validators
from six import string_types
from cc.database import mongo from cc.database import mongo
from cc.encryptor import encryptor from cc.encryptor import encryptor
@ -10,6 +12,8 @@ from cc.utils import local_ip_addresses
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
logger = logging.getLogger(__name__)
WARNING_SIGN = u" \u26A0" WARNING_SIGN = u" \u26A0"
SCHEMA = { SCHEMA = {
@ -916,6 +920,7 @@ class ConfigService:
if should_encrypt: if should_encrypt:
ConfigService.encrypt_config(config_json) 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)
logger.info('monkey config was updated')
@staticmethod @staticmethod
def init_default_config(): def init_default_config():
@ -931,6 +936,7 @@ class ConfigService:
config = copy.deepcopy(ConfigService.default_config) config = copy.deepcopy(ConfigService.default_config)
if should_encrypt: if should_encrypt:
ConfigService.encrypt_config(config) ConfigService.encrypt_config(config)
logger.info("Default config was called")
return config return config
@staticmethod @staticmethod
@ -944,6 +950,7 @@ class ConfigService:
config = ConfigService.get_default_config(True) config = ConfigService.get_default_config(True)
ConfigService.set_server_ips_in_config(config) ConfigService.set_server_ips_in_config(config)
ConfigService.update_config(config, should_encrypt=False) ConfigService.update_config(config, should_encrypt=False)
logger.info('Monkey config reset was called')
@staticmethod @staticmethod
def set_server_ips_in_config(config): def set_server_ips_in_config(config):
@ -960,6 +967,7 @@ class ConfigService:
initial_config['name'] = 'initial' initial_config['name'] = 'initial'
initial_config.pop('_id') initial_config.pop('_id')
mongo.db.config.insert(initial_config) mongo.db.config.insert(initial_config)
logger.info('Monkey config was inserted to mongo and saved')
@staticmethod @staticmethod
def _extend_config_with_default(validator_class): def _extend_config_with_default(validator_class):
@ -1001,7 +1009,7 @@ class ConfigService:
""" """
keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS]
for key in keys: for key in keys:
if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], basestring): if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types):
# Check if we are decrypting ssh key pair # Check if we are decrypting ssh key pair
if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]: if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]:
flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]] flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]]

View File

@ -0,0 +1,32 @@
import logging
__author__ = "Maor.Rayzin"
logger = logging.getLogger(__name__)
class IslandLogService:
def __init__(self):
pass
@staticmethod
def get_log_file():
"""
This static function is a helper function for the monkey island log download function.
It finds the logger handlers and checks if one of them is a fileHandler of any kind by checking if the handler
has the property handler.baseFilename.
:return:
a dict with the log file content.
"""
logger_handlers = logger.parent.handlers
for handler in logger_handlers:
if hasattr(handler, 'baseFilename'):
logger.info('Log file found: {0}'.format(handler.baseFilename))
log_file_path = handler.baseFilename
with open(log_file_path, 'rt') as f:
log_file = f.read()
return {
'log_file': log_file
}
logger.warning('No log file could be found, check logger config.')
return None

View File

@ -1,6 +1,9 @@
import ipaddress import ipaddress
import logging
from enum import Enum from enum import Enum
from six import text_type
from cc.database import mongo 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
@ -10,6 +13,9 @@ from cc.utils import local_ip_addresses, get_subnets
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
logger = logging.getLogger(__name__)
class ReportService: class ReportService:
def __init__(self): def __init__(self):
pass pass
@ -78,6 +84,8 @@ class ReportService:
creds = ReportService.get_azure_creds() creds = ReportService.get_azure_creds()
machines = set([instance['origin'] for instance in creds]) machines = set([instance['origin'] for instance in creds])
logger.info('Azure issues generated for reporting')
return [ return [
{ {
'type': 'azure_password', 'type': 'azure_password',
@ -104,6 +112,8 @@ class ReportService:
} }
for node in nodes] for node in nodes]
logger.info('Scanned nodes generated for reporting')
return nodes return nodes
@staticmethod @staticmethod
@ -125,6 +135,8 @@ class ReportService:
} }
for monkey in exploited] for monkey in exploited]
logger.info('Exploited nodes generated for reporting')
return exploited return exploited
@staticmethod @staticmethod
@ -148,6 +160,7 @@ class ReportService:
'origin': origin 'origin': origin
} }
) )
logger.info('Stolen creds generated for reporting')
return creds return creds
@staticmethod @staticmethod
@ -189,6 +202,8 @@ class ReportService:
azure_leaked_users = [{'username': user.replace(',', '.'), 'type': 'Clear Password', azure_leaked_users = [{'username': user.replace(',', '.'), 'type': 'Clear Password',
'origin': origin} for user in azure_users] 'origin': origin} for user in azure_users]
creds.extend(azure_leaked_users) creds.extend(azure_leaked_users)
logger.info('Azure machines creds generated for reporting')
return creds return creds
@staticmethod @staticmethod
@ -311,7 +326,7 @@ class ReportService:
return \ return \
[ [
ipaddress.ip_interface(unicode(network['addr'] + '/' + network['netmask'])).network ipaddress.ip_interface(text_type(network['addr'] + '/' + network['netmask'])).network
for network in network_info['data']['network_info']['networks'] for network in network_info['data']['network_info']['networks']
] ]
@ -324,7 +339,7 @@ class ReportService:
monkey_subnets = ReportService.get_monkey_subnets(monkey['guid']) monkey_subnets = ReportService.get_monkey_subnets(monkey['guid'])
for subnet in monkey_subnets: for subnet in monkey_subnets:
for ip in island_ips: for ip in island_ips:
if ipaddress.ip_address(unicode(ip)) in subnet: if ipaddress.ip_address(text_type(ip)) in subnet:
found_good_ip = True found_good_ip = True
break break
if found_good_ip: if found_good_ip:
@ -348,6 +363,7 @@ class ReportService:
if machine not in issues_dict: if machine not in issues_dict:
issues_dict[machine] = [] issues_dict[machine] = []
issues_dict[machine].append(issue) issues_dict[machine].append(issue)
logger.info('Issues generated for reporting')
return issues_dict return issues_dict
@staticmethod @staticmethod
@ -437,6 +453,7 @@ class ReportService:
{'name': 'generated_report'}, {'name': 'generated_report'},
{'$set': {'value': True}}, {'$set': {'value': True}},
upsert=True) upsert=True)
logger.info("Report marked as generated.")
@staticmethod @staticmethod
def get_report(): def get_report():

File diff suppressed because it is too large Load Diff

View File

@ -346,7 +346,7 @@ class ReportPageComponent extends AuthComponent {
<li>Weak segmentation - Machines from different segments are able to <li>Weak segmentation - Machines from different segments are able to
communicate.</li> : null} communicate.</li> : null}
{this.state.report.overview.warnings[this.Warning.TUNNEL] ? {this.state.report.overview.warnings[this.Warning.TUNNEL] ?
<li>Weak segmentation - machines were able to communicate over unused ports.</li> : null} <li>Weak segmentation - Machines were able to communicate over unused ports.</li> : null}
</ul> </ul>
</div> </div>
: :

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import {Col} from 'react-bootstrap'; import {Button, Col} from 'react-bootstrap';
import JSONTree from 'react-json-tree' import JSONTree from 'react-json-tree'
import {DataTable} from 'react-data-components'; import {DataTable} from 'react-data-components';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import download from 'downloadjs'
const renderJson = (val) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true} />; const renderJson = (val) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true} />;
const renderTime = (val) => val.split('.')[0]; const renderTime = (val) => val.split('.')[0];
@ -28,21 +29,47 @@ class TelemetryPageComponent extends AuthComponent {
.then(res => this.setState({data: res.objects})); .then(res => this.setState({data: res.objects}));
}; };
downloadIslandLog = () => {
this.authFetch('/api/log/island/download')
.then(res => res.json())
.then(res => {
let filename = 'Island_log'
let logContent = (res['log_file']);
download(logContent, filename, 'text/plain');
});
};
render() { render() {
return ( return (
<Col xs={12} lg={8}> <div>
<h1 className="page-title">Log</h1> <div>
<div className="data-table-container"> <Col xs={12} lg={8}>
<DataTable <h1 className="page-title">Log</h1>
keys="name" <div className="data-table-container">
columns={columns} <DataTable
initialData={this.state.data} keys="name"
initialPageLength={20} columns={columns}
initialSortBy={{ prop: 'timestamp', order: 'descending' }} initialData={this.state.data}
pageLengthOptions={[ 20, 50, 100 ]} initialPageLength={20}
/> initialSortBy={{ prop: 'timestamp', order: 'descending' }}
</div> pageLengthOptions={[ 20, 50, 100 ]}
</Col> />
</div>
</Col>
</div>
<div>
<Col xs={12} lg={8}>
<h1 className="page-title"> Monkey Island Logs </h1>
<div className="text-center" style={{marginBottom: '20px'}}>
<p style={{'marginBottom': '2em', 'fontSize': '1.2em'}}> Download Monkey Island internal log file </p>
<Button bsSize="large" onClick={()=> {
this.downloadIslandLog();
}}>
<i className="glyphicon glyphicon-download"/> Download </Button>
</div>
</Col>
</div>
</div>
); );
} }
} }

View File

@ -5,4 +5,4 @@ Homepage: http://www.guardicore.com
Priority: optional Priority: optional
Version: 1.0 Version: 1.0
Description: Guardicore Infection Monkey Island installation package Description: Guardicore Infection Monkey Island installation package
Depends: openssl, python-pip Depends: openssl, python-pip, python-dev