diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 0d44cb973..50ee2c060 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -157,7 +157,7 @@ class Configuration(object): retry_failed_explotation = True # addresses of internet servers to ping and check if the monkey has internet acccess. - internet_services = ["monkey.guardicore.com", "www.google.com"] + internet_services = ["updates.infectionmonkey.com", "www.google.com"] keep_tunnel_open_time = 60 diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 98ad55671..a88996069 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -9,7 +9,7 @@ from requests.exceptions import ConnectionError import infection_monkey.monkeyfs as monkeyfs import infection_monkey.tunnel as tunnel from infection_monkey.config import WormConfiguration, GUID -from infection_monkey.network.info import local_ips, check_internet_access +from infection_monkey.network.info import local_ips, check_internet_access, TIMEOUT from infection_monkey.transport.http import HTTPConnectProxy from infection_monkey.transport.tcp import TcpProxy @@ -19,9 +19,6 @@ requests.packages.urllib3.disable_warnings() LOG = logging.getLogger(__name__) DOWNLOAD_CHUNK = 1024 -# random number greater than 5, -# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere. -TIMEOUT = 15 class ControlClient(object): diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index a55dbcdc5..952a9282e 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -8,6 +8,10 @@ import itertools import netifaces from subprocess import check_output from random import randint + +import requests +from requests import ConnectionError + from common.network.network_range import CidrRange try: @@ -16,6 +20,10 @@ except NameError: long = int # Python 3 +# Timeout for monkey connections +TIMEOUT = 15 + + def get_host_subnets(): """ Returns a list of subnets visible to host (omitting loopback and auto conf networks) @@ -124,14 +132,18 @@ def get_free_tcp_port(min_range=1000, max_range=65535): def check_internet_access(services): """ - Checks if any of the services are accessible, over ICMP + Checks if any of the services are accessible, over HTTPS :param services: List of IPs/hostnames :return: boolean depending on internet access """ - ping_str = "-n 1" if sys.platform.startswith("win") else "-c 1" for host in services: - if os.system("ping " + ping_str + " " + host) == 0: + try: + requests.get("https://%s" % (host,), timeout=TIMEOUT, verify=False) return True + except ConnectionError: + # Failed connecting + pass + return False diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index b1697f5a5..205785486 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -28,6 +28,7 @@ from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.pba_file_download import PBAFileDownload +from monkey_island.cc.resources.version_update import VersionUpdate from monkey_island.cc.services.config import ConfigService from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService @@ -83,18 +84,14 @@ def output_json(obj, code, headers=None): return resp -def init_app(mongo_url): - app = Flask(__name__) - - api = flask_restful.Api(app) - api.representations = {'application/json': output_json} - +def init_app_config(app, mongo_url): app.config['MONGO_URI'] = mongo_url - app.config['SECRET_KEY'] = str(uuid.getnode()) app.config['JWT_AUTH_URL_RULE'] = '/api/auth' app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time() + +def init_app_services(app): init_jwt(app) mongo.init_app(app) @@ -105,9 +102,13 @@ def init_app(mongo_url): # If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island. RemoteRunAwsService.init() + +def init_app_url_rules(app): app.add_url_rule('/', 'serve_home', serve_home) app.add_url_rule('/', 'serve_static_file', serve_static_file) + +def init_api_resources(api): api.add_resource(Root, '/api') api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/') api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/') @@ -130,5 +131,18 @@ def init_app(mongo_url): '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') api.add_resource(AttackTelem, '/api/attack/') + api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/') + + +def init_app(mongo_url): + app = Flask(__name__) + + api = flask_restful.Api(app) + api.representations = {'application/json': output_json} + + init_app_config(app, mongo_url) + init_app_services(app) + init_app_url_rules(app) + init_api_resources(api) return app diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 62b0e9eed..1202f299d 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -13,6 +13,7 @@ class Environment(object): _MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://localhost:27017/monkeyisland") _DEBUG_SERVER = False _AUTH_EXPIRATION_TIME = timedelta(hours=1) + _MONKEY_VERSION = "1.6.3" def __init__(self): self.config = None @@ -37,6 +38,21 @@ class Environment(object): h.update(secret) return h.hexdigest() + def get_deployment(self): + return self._get_from_config('deployment', 'unknown') + + def is_develop(self): + return self.get_deployment() == 'develop' + + def get_version(self): + return self._MONKEY_VERSION + ('-dev' if self.is_develop() else '') + + def _get_from_config(self, key, default_value=None): + val = default_value + if self.config is not None: + val = self.config.get(key, val) + return val + @abc.abstractmethod def get_auth_users(self): return diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 3cd6bb587..b27880e07 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -32,6 +32,7 @@ def load_env_from_file(): config_json = load_server_configuration_from_file() return config_json['server_config'] + try: config_json = load_server_configuration_from_file() __env_type = config_json['server_config'] diff --git a/monkey/monkey_island/cc/resources/version_update.py b/monkey/monkey_island/cc/resources/version_update.py new file mode 100644 index 000000000..5b34f4206 --- /dev/null +++ b/monkey/monkey_island/cc/resources/version_update.py @@ -0,0 +1,24 @@ +import flask_restful +import logging + +from monkey_island.cc.environment.environment import env +from monkey_island.cc.auth import jwt_required +from monkey_island.cc.services.version_update import VersionUpdateService + +__author__ = 'itay.mizeretz' + +logger = logging.getLogger(__name__) + + +class VersionUpdate(flask_restful.Resource): + def __init__(self): + super(VersionUpdate, self).__init__() + + # We don't secure this since it doesn't give out any private info and we want UI to know version + # even when not authenticated + def get(self): + return { + 'current_version': env.get_version(), + 'newer_version': VersionUpdateService.get_newer_version(), + 'download_link': VersionUpdateService.get_download_link() + } diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 2d1a5995b..420f1b303 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,3 +1,4 @@ { - "server_config": "standard" -} \ No newline at end of file + "server_config": "standard", + "deployment": "develop" +} diff --git a/monkey/monkey_island/cc/services/version_update.py b/monkey/monkey_island/cc/services/version_update.py new file mode 100644 index 000000000..3d43017f6 --- /dev/null +++ b/monkey/monkey_island/cc/services/version_update.py @@ -0,0 +1,57 @@ +import logging + +import requests + +from monkey_island.cc.environment.environment import env + +__author__ = "itay.mizeretz" + +logger = logging.getLogger(__name__) + + +class VersionUpdateService: + VERSION_SERVER_URL_PREF = 'https://updates.infectionmonkey.com' + VERSION_SERVER_CHECK_NEW_URL = VERSION_SERVER_URL_PREF + '?deployment=%s&monkey_version=%s' + VERSION_SERVER_DOWNLOAD_URL = VERSION_SERVER_CHECK_NEW_URL + '&is_download=true' + + newer_version = None + + def __init__(self): + pass + + @staticmethod + def get_newer_version(): + """ + Checks for newer version if never checked before. + :return: None if failed checking for newer version, result of '_check_new_version' otherwise + """ + if VersionUpdateService.newer_version is None: + try: + VersionUpdateService.newer_version = VersionUpdateService._check_new_version() + except Exception: + logger.exception('Failed updating version number') + + return VersionUpdateService.newer_version + + @staticmethod + def _check_new_version(): + """ + Checks if newer monkey version is available + :return: False if not, version in string format ('1.6.2') otherwise + """ + url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env.get_deployment(), env.get_version()) + + reply = requests.get(url, timeout=15) + + res = reply.json().get('newer_version', None) + + if res is False: + return res + + [int(x) for x in res.split('.')] # raises value error if version is invalid format + return res + + @staticmethod + def get_download_link(): + return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env.get_deployment(), env.get_version()) + diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index da8e59113..8229133e6 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -20,6 +20,7 @@ import 'react-data-components/css/table-twbs.css'; import 'styles/App.css'; import 'react-toggle/style.css'; import 'react-table/react-table.css'; +import VersionComponent from "./side-menu/VersionComponent"; let logoImage = require('../images/monkey-icon.svg'); let infectionMonkeyImage = require('../images/infection-monkey.svg'); @@ -85,7 +86,7 @@ class AppComponent extends AuthComponent { infection_done: false, report_done: false, isLoggedIn: undefined - } + }, }; } @@ -175,6 +176,7 @@ class AppComponent extends AuthComponent {
License
+ ()}/> diff --git a/monkey/monkey_island/cc/ui/src/components/side-menu/VersionComponent.js b/monkey/monkey_island/cc/ui/src/components/side-menu/VersionComponent.js new file mode 100644 index 000000000..1246b5b94 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/side-menu/VersionComponent.js @@ -0,0 +1,46 @@ +import React from 'react'; +import {Icon} from 'react-fa'; + +class VersionComponent extends React.Component { + constructor(props) { + super(props); + this.state = { + currentVersion: undefined, + newerVersion: undefined, + downloadLink: undefined + } + } + + componentDidMount() { + fetch('/api/version-update') // This is not authenticated on purpose + .then(res => res.json()) + .then(res => { + this.setState({ + currentVersion: res['current_version'], + newerVersion: res['newer_version'], + downloadLink: res['download_link'], + }); + }); + } + + render() { + return ( +
+ Infection Monkey Version: {this.state.currentVersion} + { + this.state.newerVersion ? +
+ Newer version available! +
+ Download here +
+ : + undefined + } +
+ ); + } +} + + +export default VersionComponent; diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 6155a4dcc..b44fa4562 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -515,3 +515,13 @@ body { } } + +.version-text { + font-size: 0.9em; + position: absolute; + bottom: 5px; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; +} diff --git a/monkey/monkey_island/readme.txt b/monkey/monkey_island/readme.txt index 344e6b736..956892e23 100644 --- a/monkey/monkey_island/readme.txt +++ b/monkey/monkey_island/readme.txt @@ -65,12 +65,8 @@ How to run: 4. Setup MongoDB (Use one of the two following options): 4.a. Download MongoDB and extract it to /var/monkey_island/bin/mongodb - for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz - for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz - find more at - https://www.mongodb.org/downloads#production - untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb - (make sure the content of the mongo folder is in this directory, meaning this path exists: - /var/monkey_island/bin/mongodb/bin) + 4.a.1. Run '/var/monkey_island/linux/install_mongo.sh /var/monkey_island/bin/mongodb' + This will download and extract the relevant mongoDB for your OS. OR 4.b. Use already running instance of mongodb 4.b.1. Run 'set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"'. Replace '' with address of mongo server diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 5e0faea19..ff09c5761 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -1,3 +1,4 @@ +bson python-dateutil tornado==5.1.1 werkzeug @@ -18,7 +19,7 @@ boto3 botocore PyInstaller awscli -bson cffi virtualenv wheel +requests