Multiple enhancements to networkConfigurationCredentialsExtract.py
This commit is contained in:
parent
6a02dbe886
commit
f31930c7c8
|
@ -3,8 +3,9 @@
|
||||||
#
|
#
|
||||||
# Script intendend to sweep Cisco, Huawei and possibly other network devices
|
# Script intendend to sweep Cisco, Huawei and possibly other network devices
|
||||||
# configuration files in order to extract plain and cipher passwords out of them.
|
# configuration files in order to extract plain and cipher passwords out of them.
|
||||||
|
# Equipped with functionality to decrypt Cisco Type 7 passwords.
|
||||||
#
|
#
|
||||||
# Mariusz B., mgeeky '18
|
# Mariusz B., mgeeky '18-20
|
||||||
#
|
#
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -45,6 +46,10 @@ regexes = {
|
||||||
'ISAKMP Pre-Shared Key' : r'crypto isakmp key \password(?: address \ip)?',
|
'ISAKMP Pre-Shared Key' : r'crypto isakmp key \password(?: address \ip)?',
|
||||||
'SNMP-Server User Auth & Encr keys' : r'snmp-server user \name .* encrypted auth md5 ([0-9a-f\:]+) priv aes \d+ ([0-9a-f\:]+)',
|
'SNMP-Server User Auth & Encr keys' : r'snmp-server user \name .* encrypted auth md5 ([0-9a-f\:]+) priv aes \d+ ([0-9a-f\:]+)',
|
||||||
'PPP PAP Sent Username & Password' : r'ppp pap sent-username \name password \password',
|
'PPP PAP Sent Username & Password' : r'ppp pap sent-username \name password \password',
|
||||||
|
'AAA TACACS+/RADIUS Server Private' : r'server-private \ip key \password',
|
||||||
|
'AAA TACACS+ Server Private' : r'tacacs-server key \password',
|
||||||
|
'SNMP Server Community string' : r'snmp-server community \password',
|
||||||
|
'IPSec VPN ISAKMP Pre-Shared Key' : r'pre-shared-key address \ip key \password'
|
||||||
},
|
},
|
||||||
|
|
||||||
'Cisco ASA' : {
|
'Cisco ASA' : {
|
||||||
|
@ -88,6 +93,7 @@ regexes = {
|
||||||
'Other uncategorized XML password' : r'password>([^<]+)<',
|
'Other uncategorized XML password' : r'password>([^<]+)<',
|
||||||
'Other uncategorized authentication string' : r'.* authentication \password.*',
|
'Other uncategorized authentication string' : r'.* authentication \password.*',
|
||||||
'Other hash-key related' : r'.* key \hash',
|
'Other hash-key related' : r'.* key \hash',
|
||||||
|
'Cisco 7 Password' : r'\cisco7',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,9 +101,12 @@ config = {
|
||||||
'verbose' : False,
|
'verbose' : False,
|
||||||
'debug' : False,
|
'debug' : False,
|
||||||
'lines' : 0,
|
'lines' : 0,
|
||||||
'output' : 'normal',
|
'format' : 'normal',
|
||||||
'csv_delimiter' : ';',
|
'csv_delimiter' : ';',
|
||||||
'no_others' : False,
|
'no_others' : False,
|
||||||
|
'filename' : False,
|
||||||
|
'nonunique' : False,
|
||||||
|
'output' : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
markers = {
|
markers = {
|
||||||
|
@ -106,7 +115,8 @@ markers = {
|
||||||
'domain' : r'(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})',
|
'domain' : r'(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})',
|
||||||
'hash' : r'([a-fA-F0-9]{20,})',
|
'hash' : r'([a-fA-F0-9]{20,})',
|
||||||
'bcrypt' : r'([\$\w\.\/]+)',
|
'bcrypt' : r'([\$\w\.\/]+)',
|
||||||
'password': r'(?:\d\s+)?([^\s]+)',
|
'password': r'(?:(?:\d\s+)?([^\s]+))',
|
||||||
|
'cisco7' : r'\b(?:7 ([0-9a-f]{4,}))|(?:([0-9a-f]{4,}) 7)\b',
|
||||||
'keystring': r'([a-f0-9]+)',
|
'keystring': r'([a-f0-9]+)',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +125,7 @@ foundCreds = set()
|
||||||
maxTechnologyWidth = 0
|
maxTechnologyWidth = 0
|
||||||
maxRegexpWidth = 0
|
maxRegexpWidth = 0
|
||||||
|
|
||||||
results = set()
|
results = []
|
||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -156,7 +166,53 @@ def processRegex(inputRegex):
|
||||||
inputRegex = '^\\s*{}\\s*.*$'.format(inputRegex)
|
inputRegex = '^\\s*{}\\s*.*$'.format(inputRegex)
|
||||||
return inputRegex
|
return inputRegex
|
||||||
|
|
||||||
def matchLines(lines, technology):
|
def cisco7Decrypt(data):
|
||||||
|
# source: https://github.com/theevilbit/ciscot7
|
||||||
|
xlat = [
|
||||||
|
0x64, 0x73, 0x66, 0x64, 0x3b, 0x6b, 0x66, 0x6f, 0x41, 0x2c, 0x2e,
|
||||||
|
0x69, 0x79, 0x65, 0x77, 0x72, 0x6b, 0x6c, 0x64, 0x4a, 0x4b, 0x44,
|
||||||
|
0x48, 0x53, 0x55, 0x42, 0x73, 0x67, 0x76, 0x63, 0x61, 0x36, 0x39,
|
||||||
|
0x38, 0x33, 0x34, 0x6e, 0x63, 0x78, 0x76, 0x39, 0x38, 0x37, 0x33,
|
||||||
|
0x32, 0x35, 0x34, 0x6b, 0x3b, 0x66, 0x67, 0x38, 0x37
|
||||||
|
]
|
||||||
|
|
||||||
|
dp = ''
|
||||||
|
regex = re.compile(r'(^[0-9A-Fa-f]{2})([0-9A-Fa-f]+)')
|
||||||
|
result = regex.search(data)
|
||||||
|
try:
|
||||||
|
if result:
|
||||||
|
s, e = int(result.group(1)), result.group(2)
|
||||||
|
for pos in range(0, len(e), 2):
|
||||||
|
magic = int(e[pos] + e[pos+1], 16)
|
||||||
|
newchar = ''
|
||||||
|
if s <= 50:
|
||||||
|
# xlat length is 51
|
||||||
|
newchar = '%c' % (magic ^ xlat[s])
|
||||||
|
s += 1
|
||||||
|
if s == 51: s = 0
|
||||||
|
dp += newchar
|
||||||
|
return dp
|
||||||
|
return ''
|
||||||
|
except:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def tryToCisco7Decrypt(creds):
|
||||||
|
if not len(creds):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
decrypted = []
|
||||||
|
for m in re.finditer(markers['cisco7'], creds, re.I):
|
||||||
|
f = m.group(2) if m.group(2) != None else m.group(1)
|
||||||
|
out = cisco7Decrypt(f)
|
||||||
|
if out:
|
||||||
|
decrypted.append(out)
|
||||||
|
|
||||||
|
if len(decrypted):
|
||||||
|
return " (decrypted cisco 7: '" + "', '".join(decrypted) + "')"
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def matchLines(file, lines, technology):
|
||||||
global foundCreds
|
global foundCreds
|
||||||
global results
|
global results
|
||||||
|
|
||||||
|
@ -166,7 +222,7 @@ def matchLines(lines, technology):
|
||||||
for idx in range(len(lines)):
|
for idx in range(len(lines)):
|
||||||
line = lines[idx].strip()
|
line = lines[idx].strip()
|
||||||
|
|
||||||
if line in foundCreds:
|
if not config['nonunique'] and line in foundCreds:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
processedRex = processRegex(regexes[technology][rex])
|
processedRex = processRegex(regexes[technology][rex])
|
||||||
|
@ -175,28 +231,28 @@ def matchLines(lines, technology):
|
||||||
num += 1
|
num += 1
|
||||||
|
|
||||||
foundCreds.add(line)
|
foundCreds.add(line)
|
||||||
creds = '", "'.join(matched.groups(1))
|
f = [x for x in matched.groups(1) if type(x) == str]
|
||||||
|
creds = '", "'.join(f)
|
||||||
|
creds += tryToCisco7Decrypt(line)
|
||||||
|
|
||||||
results.add((
|
results.append((
|
||||||
technology, rex, creds
|
file, technology, rex, creds
|
||||||
))
|
))
|
||||||
|
|
||||||
Logger._out('[+] {}: {}: {}'.format(
|
|
||||||
technology, rex, creds
|
|
||||||
))
|
|
||||||
|
|
||||||
if idx - config['lines'] >= 0:
|
|
||||||
for i in range(idx - config['lines'], idx):
|
|
||||||
Logger._out('[{:04}]\t\t{}'.format(i, lines[i]))
|
|
||||||
|
|
||||||
if config['lines'] != 0:
|
if config['lines'] != 0:
|
||||||
Logger._out('[{:04}]==>\t{}'.format(idx, line))
|
Logger._out('\n[+] {}: {}: {}'.format(
|
||||||
else:
|
technology, rex, creds
|
||||||
Logger._out('[{:04}]\t\t{}'.format(idx, line))
|
))
|
||||||
|
|
||||||
if idx + 1 + config['lines'] < len(lines):
|
if idx - config['lines'] >= 0:
|
||||||
for i in range(idx + 1, idx + config['lines'] + 1):
|
for i in range(idx - config['lines'], idx):
|
||||||
Logger._out('[{:04}]\t\t{}'.format(i, lines[i]))
|
Logger._out('[{:04}]\t\t{}'.format(i, lines[i]))
|
||||||
|
|
||||||
|
Logger._out('[{:04}]==>\t{}'.format(idx, line))
|
||||||
|
|
||||||
|
if idx + 1 + config['lines'] < len(lines):
|
||||||
|
for i in range(idx + 1, idx + config['lines'] + 1):
|
||||||
|
Logger._out('[{:04}]\t\t{}'.format(i, lines[i]))
|
||||||
|
|
||||||
Logger.dbg('\tRegex used: [ {} ]'.format(processedRex))
|
Logger.dbg('\tRegex used: [ {} ]'.format(processedRex))
|
||||||
return num
|
return num
|
||||||
|
@ -205,21 +261,23 @@ def processFile(file):
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
Logger.info('Processing file: "{}"'.format(file))
|
Logger.info('Processing file: "{}"'.format(file))
|
||||||
with open(file, 'r') as f:
|
try:
|
||||||
lines = [ line.strip() for line in f.readlines()]
|
with open(file, 'r') as f:
|
||||||
|
lines = [ line.strip() for line in f.readlines()]
|
||||||
|
except Exception as e:
|
||||||
|
Logger.err("Parsing file '{}' failed: {}.".format(file, str(e)))
|
||||||
|
return 0
|
||||||
|
|
||||||
num = 0
|
num = 0
|
||||||
for technology in regexes:
|
for technology in regexes:
|
||||||
if technology == 'Others':
|
if technology == 'Others':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
num0 = matchLines(lines, technology)
|
num0 = matchLines(file, lines, technology)
|
||||||
num += num0
|
num += num0
|
||||||
|
|
||||||
if not config['no_others']:
|
if not config['no_others']:
|
||||||
num0 = matchLines(lines, 'Others')
|
num0 = matchLines(file, lines, 'Others')
|
||||||
if num0 == 0:
|
|
||||||
print('<none>')
|
|
||||||
num += num0
|
num += num0
|
||||||
|
|
||||||
return num
|
return num
|
||||||
|
@ -237,11 +295,14 @@ def processDir(dirname):
|
||||||
def parseOptions(argv):
|
def parseOptions(argv):
|
||||||
parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options] <file>')
|
parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options] <file>')
|
||||||
parser.add_argument('file', metavar='<file>', type=str, help='Config file or directory to process.')
|
parser.add_argument('file', metavar='<file>', type=str, help='Config file or directory to process.')
|
||||||
|
parser.add_argument('-o', '--output', help = 'Output file.')
|
||||||
|
parser.add_argument('-H', '--with-filename', action='store_true', help = 'Print file name next to the results')
|
||||||
|
parser.add_argument('-R', '--show-nonunique', action='store_true', help = 'Print repeated, non unique credentials found. By default only unique references are returned.')
|
||||||
parser.add_argument('-C', '--lines', metavar='N', type=int, default=0, help='Display N lines around matched credential if verbose output is enabled.')
|
parser.add_argument('-C', '--lines', metavar='N', type=int, default=0, help='Display N lines around matched credential if verbose output is enabled.')
|
||||||
parser.add_argument('-f', '--format', choices=['raw', 'normal', 'tabular', 'csv'], default='normal', help="Specifies output format: 'raw' (only hashes), 'tabular', 'normal', 'csv'. Default: 'normal'")
|
parser.add_argument('-f', '--format', choices=['raw', 'normal', 'tabular', 'csv'], default='normal', help="Specifies output format: 'raw' (only hashes), 'tabular', 'normal', 'csv'. Default: 'normal'")
|
||||||
parser.add_argument('-N', '--no-others', dest='no_others', action='store_true', help='Don\'t match "Others" category which is false-positives prone.')
|
parser.add_argument('-N', '--no-others', dest='no_others', action='store_true', help='Don\'t match "Others" category which is false-positives prone.')
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose output.')
|
parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Display verbose output.')
|
||||||
parser.add_argument('-d', '--debug', action='store_true', help='Display debug output.')
|
parser.add_argument('-d', '--debug', action='store_true', default=False, help='Display debug output.')
|
||||||
|
|
||||||
if len(argv) < 2:
|
if len(argv) < 2:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
@ -253,32 +314,40 @@ def parseOptions(argv):
|
||||||
config['debug'] = args.debug
|
config['debug'] = args.debug
|
||||||
config['lines'] = args.lines
|
config['lines'] = args.lines
|
||||||
config['no_others'] = args.no_others
|
config['no_others'] = args.no_others
|
||||||
|
config['filename'] = args.with_filename
|
||||||
|
config['nonunique'] = args.show_nonunique
|
||||||
|
config['output'] = args.output
|
||||||
|
|
||||||
if args.format == 'raw':
|
if args.format == 'raw':
|
||||||
config['output'] = 'raw'
|
config['format'] = 'raw'
|
||||||
elif args.format == 'tabular':
|
elif args.format == 'tabular':
|
||||||
config['output'] = 'tabular'
|
config['format'] = 'tabular'
|
||||||
elif args.format == 'csv':
|
elif args.format == 'csv':
|
||||||
config['output'] = 'csv'
|
config['format'] = 'csv'
|
||||||
else:
|
else:
|
||||||
config['output'] == 'normal'
|
config['format'] == 'normal'
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def printResults():
|
def printResults():
|
||||||
global maxTechnologyWidth
|
global maxTechnologyWidth
|
||||||
global maxRegexpWidth
|
global maxRegexpWidth
|
||||||
|
global results
|
||||||
|
|
||||||
# CSV Columns
|
# CSV Columns
|
||||||
cols = ['technology', 'name', 'hashes']
|
cols = ['file', 'technology', 'name', 'hashes']
|
||||||
|
|
||||||
def _print(technology, rex, creds):
|
if not config['nonunique']:
|
||||||
if config['output'] == 'tabular':
|
results = set(results)
|
||||||
print('[+] {0: <{width1}} {1:^{width2}}: "{2:}"'.format(
|
|
||||||
|
def _print(file, technology, rex, creds):
|
||||||
|
out = ''
|
||||||
|
if config['format'] == 'tabular':
|
||||||
|
out += '[+] {0: <{width1}} {1:^{width2}}: "{2:}"\n'.format(
|
||||||
technology, rex, creds,
|
technology, rex, creds,
|
||||||
width1 = maxTechnologyWidth, width2 = maxRegexpWidth
|
width1 = maxTechnologyWidth, width2 = maxRegexpWidth
|
||||||
))
|
)
|
||||||
elif config['output'] == 'raw':
|
elif config['format'] == 'raw':
|
||||||
credstab = creds.split('", "')
|
credstab = creds.split('", "')
|
||||||
longest = ''
|
longest = ''
|
||||||
|
|
||||||
|
@ -286,24 +355,26 @@ def printResults():
|
||||||
if len(passwd) > len(longest):
|
if len(passwd) > len(longest):
|
||||||
longest = passwd
|
longest = passwd
|
||||||
|
|
||||||
print('{}'.format(
|
out += '{}\n'.format(
|
||||||
passwd
|
passwd
|
||||||
))
|
)
|
||||||
elif config['output'] == 'csv':
|
elif config['format'] == 'csv':
|
||||||
creds = '"{}"'.format(creds)
|
creds = '"{}"'.format(creds)
|
||||||
rex = rex.replace(config['csv_delimiter'], ' ')
|
rex = rex.replace(config['csv_delimiter'], ' ')
|
||||||
#creds = creds.replace(config['csv_delimiter'], ' ')
|
out += config['csv_delimiter'].join([file, technology, rex, creds])
|
||||||
print(config['csv_delimiter'].join([technology, rex, creds]))
|
out += '\n'
|
||||||
else:
|
else:
|
||||||
print('[+] {}: {}: "{}"'.format(
|
out += '[+] {}: {}: "{}"\n'.format(
|
||||||
technology, rex, creds
|
technology, rex, creds
|
||||||
))
|
)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
maxTechnologyWidth = 0
|
maxTechnologyWidth = 0
|
||||||
maxRegexpWidth = 0
|
maxRegexpWidth = 0
|
||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
technology, rex, creds = result
|
file, technology, rex, creds = result
|
||||||
if len(technology) > maxTechnologyWidth:
|
if len(technology) > maxTechnologyWidth:
|
||||||
maxTechnologyWidth = len(technology)
|
maxTechnologyWidth = len(technology)
|
||||||
|
|
||||||
|
@ -313,23 +384,45 @@ def printResults():
|
||||||
maxTechnologyWidth = maxTechnologyWidth + 3
|
maxTechnologyWidth = maxTechnologyWidth + 3
|
||||||
maxRegexpWidth = maxRegexpWidth + 3
|
maxRegexpWidth = maxRegexpWidth + 3
|
||||||
|
|
||||||
if config['output'] == 'normal' or config['output'] == 'tabular':
|
outputToPrint = ''
|
||||||
print('\n=== CREDENTIALS FOUND:')
|
|
||||||
elif config['output'] == 'csv':
|
|
||||||
print(config['csv_delimiter'].join(cols))
|
|
||||||
|
|
||||||
|
if config['format'] == 'normal' or config['format'] == 'tabular':
|
||||||
|
outputToPrint += '\n=== CREDENTIALS FOUND:\n'
|
||||||
|
elif config['format'] == 'csv':
|
||||||
|
outputToPrint += config['csv_delimiter'].join(cols)
|
||||||
|
outputToPrint += '\n'
|
||||||
|
|
||||||
|
resultsPerFile = {}
|
||||||
|
otherResultsPerFile = {}
|
||||||
for result in results:
|
for result in results:
|
||||||
technology, rex, creds = result
|
file, technology, rex, creds = result
|
||||||
if technology == 'Others': continue
|
if technology == 'Others':
|
||||||
_print(technology, rex, creds)
|
if file not in otherResultsPerFile.keys():
|
||||||
|
otherResultsPerFile[file] = []
|
||||||
|
otherResultsPerFile[file].append((technology, rex, creds))
|
||||||
|
else:
|
||||||
|
if file not in resultsPerFile.keys():
|
||||||
|
resultsPerFile[file] = []
|
||||||
|
resultsPerFile[file].append((technology, rex, creds))
|
||||||
|
|
||||||
if not config['no_others'] and (config['output'] == 'normal' or config['output'] == 'tabular'):
|
for file, _results in resultsPerFile.items():
|
||||||
print('\n=== BELOW LINES MAY BE FALSE POSITIVES:')
|
if config['filename'] and config['format'] in ['raw', 'normal', 'tabular']:
|
||||||
|
outputToPrint += '\nResults from file: "{}"\n'.format(file)
|
||||||
|
for result in _results:
|
||||||
|
technology, rex, creds = result
|
||||||
|
outputToPrint += _print(file, technology, rex, creds)
|
||||||
|
|
||||||
for result in results:
|
if not config['no_others'] and (config['format'] == 'normal' or config['format'] == 'tabular'):
|
||||||
technology, rex, creds = result
|
outputToPrint += '\n\n=== BELOW LINES MAY BE FALSE POSITIVES:\n'
|
||||||
if technology != 'Others': continue
|
|
||||||
_print(technology, rex, creds)
|
for file, _results in otherResultsPerFile.items():
|
||||||
|
if config['filename'] and config['format'] in ['raw', 'normal', 'tabular']:
|
||||||
|
outputToPrint += '\nResults from file: "{}"\n'.format(file)
|
||||||
|
for result in _results:
|
||||||
|
technology, rex, creds = result
|
||||||
|
outputToPrint += _print(file, technology, rex, creds)
|
||||||
|
|
||||||
|
return outputToPrint
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
Logger._out('''
|
Logger._out('''
|
||||||
|
@ -356,9 +449,16 @@ def main(argv):
|
||||||
Logger.err('Please provide either file or directory on input.')
|
Logger.err('Please provide either file or directory on input.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
printResults()
|
out = printResults()
|
||||||
|
|
||||||
if config['output'] == 'normal' or config['output'] == 'tabular':
|
if config['output']:
|
||||||
|
Logger.info("Dumping credentials to the output file: '{}'".format(config['output']))
|
||||||
|
with open(config['output'], 'w') as f:
|
||||||
|
f.write(out)
|
||||||
|
else:
|
||||||
|
print(out)
|
||||||
|
|
||||||
|
if config['format'] == 'normal' or config['format'] == 'tabular':
|
||||||
print('\n[>] Found: {} credentials.'.format(num))
|
print('\n[>] Found: {} credentials.'.format(num))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in New Issue