diff --git a/README.md b/README.md index bb331007e..63c52e0cb 100644 --- a/README.md +++ b/README.md @@ -150,11 +150,12 @@ Copyright (c) 2016 Guardicore Ltd See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3). + Dependent packages --------------------- -Dependency | License | -----------------------------|---------------------------- +Dependency | License | Notes +----------------------------|----------------------------|---------------------------- libffi-dev | https://github.com/atgreen/libffi/blob/master/LICENSE PyCrypto | Public domain upx | Custom license, http://upx.sourceforge.net/upx-license.html @@ -188,3 +189,4 @@ Dependency | License | winbind | GPL-3 pyinstaller | GPL Celery | BSD + mimikatz | CC BY 4.0 | We use an altered version of mimikatz. Original: https://github.com/gentilkiwi/mimikatz diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index a5e99ccd0..c2eae0c33 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -208,8 +208,8 @@ class Configuration(object): rdp_use_vbs_download = True # User and password dictionaries for exploits. - exploit_user_list = ['Administrator', 'root', 'user'] - exploit_password_list = ["Password1!", "1234", "password", "12345678"] + exploit_user_list = [] + exploit_password_list = [] # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds diff --git a/chaos_monkey/example.conf b/chaos_monkey/example.conf index 6250460b6..b8131fc61 100644 --- a/chaos_monkey/example.conf +++ b/chaos_monkey/example.conf @@ -59,8 +59,8 @@ "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", "skip_exploit_if_file_exist": true, "local_network_scan": true, - "exploit_user_list": ["Administrator", "root", "user"], - "exploit_password_list" = ["Password1!", "1234", "password", "12345678"] + "exploit_user_list": [], + "exploit_password_list" = [] "tcp_scan_get_banner": true, "tcp_scan_interval": 200, "tcp_scan_timeout": 10000, diff --git a/chaos_monkey/exploit/sshexec.py b/chaos_monkey/exploit/sshexec.py index b827c86df..42913e1f8 100644 --- a/chaos_monkey/exploit/sshexec.py +++ b/chaos_monkey/exploit/sshexec.py @@ -46,12 +46,6 @@ class SSHExploiter(HostExploiter): passwords = list(self._config.exploit_password_list[:]) users = list(self._config.exploit_user_list) - known_passwords = [host.get_credentials(x) for x in users] - if len(known_passwords) > 0: - for known_pass in known_passwords: - if known_pass in passwords: - passwords.remove(known_pass) - passwords.insert(0, known_pass) #try first user_pass = product(users,passwords) exploited = False diff --git a/chaos_monkey/exploit/win_ms08_067.py b/chaos_monkey/exploit/win_ms08_067.py index 02f144851..48348d266 100644 --- a/chaos_monkey/exploit/win_ms08_067.py +++ b/chaos_monkey/exploit/win_ms08_067.py @@ -233,6 +233,7 @@ class Ms08_067_Exploiter(HostExploiter): src_path, self._config.dropper_target_path) + # TODO: why are we doing this? Isn't that smbexec's job? if not remote_full_path: # try other passwords for administrator for password in self._config.exploit_password_list: diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index e3af0e012..6e128ed67 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -88,14 +88,10 @@ class ChaosMonkey(object): LOG.debug("default server: %s" % self._default_server) ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https')) - additional_creds = {} - if WormConfiguration.collect_system_info: LOG.debug("Calling system info collection") system_info_collector = SystemInfoCollector() system_info = system_info_collector.get_info() - if system_info.has_key('credentials'): - additional_creds = system_info['credentials'] ControlClient.send_telemetry("system_info_collection", system_info) if 0 == WormConfiguration.depth: @@ -105,14 +101,10 @@ class ChaosMonkey(object): else: LOG.debug("Running with depth: %d" % WormConfiguration.depth) - - for _ in xrange(WormConfiguration.max_iterations): ControlClient.keepalive() ControlClient.load_control_config() - self.update_config_creds(additional_creds) - LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list)) LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list)) @@ -133,9 +125,6 @@ class ChaosMonkey(object): if ControlClient.check_for_stop(): break - # TODO: Consider removing later - self.update_config_creds(additional_creds) - is_empty = False for finger in self._fingerprint: LOG.info("Trying to get OS fingerprint from %r with module %s", @@ -254,16 +243,4 @@ class ChaosMonkey(object): except Exception, exc: LOG.error("Exception in self delete: %s", exc) - def update_config_creds(self, additional_creds): - # TODO: this is temporary until we change server to support changing of config. - for user in list(additional_creds): - if user not in WormConfiguration.exploit_user_list: - WormConfiguration.exploit_user_list.append(user) - - creds = additional_creds[user] - if creds.has_key('password'): - password = creds['password'] - if password not in WormConfiguration.exploit_password_list: - WormConfiguration.exploit_password_list.append(password) - LOG.info("Monkey is shutting down") diff --git a/chaos_monkey/system_info/mimikatz_collector.py b/chaos_monkey/system_info/mimikatz_collector.py index 1fd40480c..a069a50ab 100644 --- a/chaos_monkey/system_info/mimikatz_collector.py +++ b/chaos_monkey/system_info/mimikatz_collector.py @@ -43,7 +43,6 @@ class MimikatzCollector: for i in range(entry_count): entry = self._get() - # TODO: Consider what to do when string can't be ascii username = str(entry.username) password = str(entry.password) lm_hash = binascii.hexlify(bytearray(entry.lm_hash)) diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index b05c42e70..9641e2ae3 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -48,6 +48,9 @@ MONKEY_DOWNLOADS = [ }, ] +INITIAL_USERNAMES = ['Administrator', 'root', 'user'] +INITIAL_PASSWORDS = ["Password1!", "1234", "password", "12345678"] + MONGO_URL = os.environ.get('MONGO_URL') if not MONGO_URL: MONGO_URL = "mongodb://localhost:27017/monkeyisland" @@ -65,7 +68,12 @@ class Monkey(restful.Resource): timestamp = request.args.get('timestamp') if guid: - return mongo.db.monkey.find_one_or_404({"guid": guid}) + monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) + monkey_json['config']['exploit_user_list'] = \ + map(lambda x: x['username'], mongo.db.usernames.find({}, {'_id': 0, 'username': 1}).sort([('count', -1)])) + monkey_json['config']['exploit_password_list'] = \ + map(lambda x: x['password'], mongo.db.passwords.find({}, {'_id': 0, 'password': 1}).sort([('count', -1)])) + return monkey_json else: result = {'timestamp': datetime.now().isoformat()} find_filter = {} @@ -195,6 +203,18 @@ class Telemetry(restful.Resource): except: pass + # Update credentials DB + try: + if (telemetry_json.get('telem_type') == 'system_info_collection') and (telemetry_json['data'].has_key('credentials')): + creds = telemetry_json['data']['credentials'] + for user in creds: + creds_add_username(user) + + if creds[user].has_key('password'): + creds_add_password(creds[user]['password']) + except StandardError as ex: + print("Exception caught while updating DB credentials: %s" % str(ex)) + return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) @@ -259,6 +279,9 @@ class Root(restful.Resource): mongo.db.config.drop() mongo.db.monkey.drop() mongo.db.telemetry.drop() + mongo.db.usernames.drop() + mongo.db.passwords.drop() + init_db() return { 'status': 'OK', } @@ -347,13 +370,25 @@ def run_local_monkey(island_address): return (True, "pis: %s" % pid) +def creds_add_username(username): + mongo.db.usernames.update( + {'username': username}, + {'$inc': {'count': 1}}, + upsert=True + ) + +def creds_add_password(password): + mongo.db.passwords.update( + {'password': password}, + {'$inc': {'count': 1}}, + upsert=True + ) ### Local ips function if sys.platform == "win32": def local_ips(): local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] - else: import fcntl def local_ips(): @@ -398,6 +433,17 @@ def send_to_default(): return redirect('/admin/index.html') +def init_db(): + if not "usernames" in mongo.db.collection_names(): + mongo.db.usernames.create_index([( "username", 1 )], unique= True) + for username in INITIAL_USERNAMES: + creds_add_username(username) + + if not "passwords" in mongo.db.collection_names(): + mongo.db.passwords.create_index([( "password", 1 )], unique= True) + for password in INITIAL_PASSWORDS: + creds_add_password(password) + DEFAULT_REPRESENTATIONS = {'application/json': output_json} api = restful.Api(app) api.representations = DEFAULT_REPRESENTATIONS @@ -414,6 +460,8 @@ if __name__ == '__main__': from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop + with app.app_context(): + init_db() http_server = HTTPServer(WSGIContainer(app), ssl_options={'certfile': 'server.crt', 'keyfile': 'server.key'}) http_server.listen(ISLAND_PORT) IOLoop.instance().start()