From 33043a34995ea2748a59dadd2ade2f5e9e89a43e Mon Sep 17 00:00:00 2001 From: brent s Date: Sat, 3 Nov 2018 02:37:19 -0400 Subject: [PATCH] adding some new scripts and updated hack(le)s --- aif/scripts/post/sshkeys.py | 4 +- mysql/tblinfo.py | 84 ++++++++++++++++ net/ssh/audit.py | 132 +++++++++++++++++++++++++ ref/python.tips_tricks_and_dirty_hacks | 14 +++ 4 files changed, 232 insertions(+), 2 deletions(-) create mode 100755 mysql/tblinfo.py create mode 100755 net/ssh/audit.py diff --git a/aif/scripts/post/sshkeys.py b/aif/scripts/post/sshkeys.py index 7938549..95893d0 100644 --- a/aif/scripts/post/sshkeys.py +++ b/aif/scripts/post/sshkeys.py @@ -12,9 +12,9 @@ def copyKeys(keystring, user = 'root'): homedir = os.path.expanduser('~{0}'.format(user)) sshdir = '{0}/.ssh'.format(homedir) authfile = '{0}/authorized_keys'.format(sshdir) + os.makedirs(sshdir, mode = 0o700, exist_ok = True) with open(authfile, 'a') as f: f.write(keystring) - os.makedirs(sshdir, mode = 0o700, exist_ok = True) for basedir, dirs, files in os.walk(sshdir): os.chown(basedir, uid, gid) os.chmod(basedir, 0o700) @@ -28,4 +28,4 @@ def main(): copyKeys(keys.read().decode('utf-8')) if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/mysql/tblinfo.py b/mysql/tblinfo.py new file mode 100755 index 0000000..b311388 --- /dev/null +++ b/mysql/tblinfo.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +import argparse +import configparser +import copy +import os +import pymysql + +mysql_internal = ['information_schema', 'mysql'] + +# Not used, but could be in the future. +stat_hdrs = ['Name', 'Engine', 'Version', 'Row_format', 'Rows', 'Avg_row_length', 'Data_length', + 'Max_data_length', 'Index_length', 'Data_free', 'Auto_increment', 'Create_time', + 'Update_time', 'Check_time', 'Collation', 'Checksum', 'Create_options', 'Comment'] +tblinfo_hdrs = ['Field', 'Type', 'Null', 'Key', 'Default', 'Extra'] + +def get_info(db, internal = False): + dbs = {} + if os.path.isfile(os.path.expanduser('~/.my.cnf')): + _cfg = configparser.ConfigParser(allow_no_value = True) + _cfg.read(os.path.expanduser('~/.my.cnf')) + _cfg = dict(_cfg['client']) + _cfg['ssl'] = {} + if 'host' not in _cfg: + _cfg['host'] = 'localhost' + conn = pymysql.connect(**_cfg, cursorclass = pymysql.cursors.DictCursor) + else: + raise RuntimeError('Need mysql creds at ~/.my.cnf') + cur = conn.cursor() + if not db: + cur.execute("SHOW DATABASES") + db = [row['Database'] for row in cur.fetchall()] + if not internal: + for d in mysql_internal: + try: + db.remove(d) + except ValueError: # Not in the list; our user probably doesn't have access + pass + else: + db = [db] + for d in db: + dbs[d] = {} + cur.execute("SHOW TABLES FROM `{0}`".format(d)) + for tbl in [t['Tables_in_{0}'.format(d)] for t in cur.fetchall()]: + dbs[d][tbl] = {} + # Status + cur.execute("SHOW TABLE STATUS FROM `{0}` WHERE Name = %s".format(d), (tbl, )) + dbs[d][tbl]['_STATUS'] = copy.deepcopy(cur.fetchone()) + # Columns + dbs[d][tbl]['_COLUMNS'] = {} + #cur.execute("DESCRIBE {0}.{1}".format(d, tbl)) + cur.execute("SHOW COLUMNS IN `{0}` FROM `{1}`".format(tbl, d)) + for row in cur.fetchall(): + colNm = row['Field'] + dbs[d][tbl]['_COLUMNS'][colNm] = {} + for k in [x for x in tblinfo_hdrs if x is not 'Field']: + dbs[d][tbl]['_COLUMNS'][colNm][k] = row[k] + cur.close() + conn.close() + return(dbs) + +def parseArgs(): + args = argparse.ArgumentParser() + args.add_argument('-i', '--internal', + dest = 'internal', + action = 'store_true', + help = ('If specified, include the MySQL internal databases ' + '(mysql, information_schema, etc.); only used if -d is not specified')) + args.add_argument('-d', '--database', + dest = 'db', + default = None, + help = 'If specified, only list table info for this DB') + return(args) + +def main(): + args = vars(parseArgs().parse_args()) + dbs = get_info(args['db'], internal = args['internal']) + #import json + #print(json.dumps(dbs, indent = 4, sort_keys = True, default = str)) + import pprint + pprint.pprint(dbs) + +if __name__ == '__main__': + main() diff --git a/net/ssh/audit.py b/net/ssh/audit.py new file mode 100755 index 0000000..ba9c6ea --- /dev/null +++ b/net/ssh/audit.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 + +import argparse +import paramiko +import socket + + +class SSHAuthInfo(object): + def __init__(self, target, port = 22, banner = True, ciphers = True, digests = True, kex = True, key_types = True, + methods = True, hostkeys = True, version = True): + self.target = target + self.port = int(port) + self.info = {'target': self.target, + 'port': self.port, + 'banner': banner, + 'ciphers': ciphers, + 'digests': digests, + 'kex': kex, + 'key_types': key_types, + 'methods': methods, + 'hostkeys': hostkeys, + 'version': version} + self._ssh = None + if any((ciphers, banner, methods, digests, kex, key_types)): # These need an SSH connection. + self._ssh_dummy() + if banner: + self.getBanner() + if hostkeys: + self.getHostkeys() + if version: + self.getVersion() + self._close() + + def _ssh_dummy(self): + self._ssh = paramiko.Transport((self.target, self.port)) + self._ssh.connect() + try: + self._ssh.auth_none('') + except paramiko.ssh_exception.BadAuthenticationType as err: + secopts = self._ssh.get_security_options() + print(secopts.key_types) + if self.info['methods']: + # https://stackoverflow.com/a/1257769 + self.info['methods'] = err.allowed_types + if self.info['ciphers']: + self.info['ciphers'] = list(secopts.ciphers) + if self.info['digests']: + self.info['digests'] = list(secopts.digests) + if self.info['kex']: + self.info['kex'] = list(secopts.kex) + if self.info['key_types']: + self.info['key_types'] = list(secopts.key_types) + return() + + def getBanner(self): + self.info['banner'] = None + # https://github.com/paramiko/paramiko/issues/273#issuecomment-225058645 doesn't seem to work. + # But https://github.com/paramiko/paramiko/pull/58#issuecomment-63857078 did! + self.info['banner'] = self._ssh.get_banner() + return() + + def getHostkeys(self): + # TODO: how the hell do I get *all* hostkeys served? + self.info['hostkeys'] = {} + k = self._ssh.get_remote_server_key() + self.info['hostkeys'][k.get_name()] = k.get_base64() + return() + + def getVersion(self): + self.info['version'] = None + s = socket.socket() + s.connect((self.target, self.port)) + try: + # 8192 bytes is kind of overkill considering most are probably going to be around 20 bytes or so. + self.info['version'] = s.recv(8192) + except Exception as e: + pass + return() + + def _close(self): + if self._ssh: + self._ssh.close() + +def parseArgs(): + args = argparse.ArgumentParser() + args.add_argument('-b', '--no-banner', + action = 'store_false', + dest = 'banner', + help = 'Do not gather the SSH banner') + args.add_argument('-c', '--no-ciphers', + action = 'store_false', + dest = 'ciphers', + help = 'Do not gather supported ciphers') + args.add_argument('-d', '--no-digests', + action = 'store_false', + dest = 'digests', + help = 'Do not gather supported digests') + args.add_argument('-m', '--no-methods', + action = 'store_false', + dest = 'methods', + help = 'Do not gather supported auth methods') + args.add_argument('-k', '--no-hostkeys', + action = 'store_false', + dest = 'hostkeys', + help = 'Do not gather hostkeys') + args.add_argument('-x', '--no-kex', + action = 'store_false', + dest = 'kex', + help = 'Do not gather supported key exchanges') + args.add_argument('-t', '--no-key-types', + action = 'store_false', + dest = 'key_types', + help = 'Do not gather supported key types') + args.add_argument('-v', '--no-version', + action = 'store_false', + dest = 'version', + help = 'Do not gather SSH version') + args.add_argument('-p', '--port', + default = 22, + help = 'The port on target that the SSH daemon is running on. Default is 22') + args.add_argument('target', + help = 'The server to run the check against') + return(args) + +def main(): + args = vars(parseArgs().parse_args()) + i = SSHAuthInfo(**args) + import pprint + pprint.pprint(i.info) + +if __name__ == '__main__': + main() diff --git a/ref/python.tips_tricks_and_dirty_hacks b/ref/python.tips_tricks_and_dirty_hacks index e9ab1e1..7860e45 100644 --- a/ref/python.tips_tricks_and_dirty_hacks +++ b/ref/python.tips_tricks_and_dirty_hacks @@ -118,3 +118,17 @@ To issue an equivalent of "reset" command in linux, assuming console is ANSI-com print('\x1bc', end = '') ############################################################################### + + +To get the default route via pyroute2, +---- +import socket +from pyroute2 import IPDB +ip = IPDB() +def_rt = ip.routes['default'] # a route object +iface = ip.interfaces[def_rt.oif] # an interface object. name is e.g. iface.ifname, IPs are in tuple-of-tuples iface.ipaddr, etc. +gw = def_rt.gateway # etc. +ip.release() +---- + +###############################################################################