Example #1
0
class HTML:
    """ class for HTML reporting """
    def __init__(self, date, opts, template_dir, report_dir, logs_dir):
        """ constructor """

        self.date = date
        self.opts = opts
        self.res = {}  # holds the results out of log dirs

        self.template_dir = template_dir
        self.report_dir = report_dir
        self.logs_dir = logs_dir

        self.file = File()

        self.file.copy_dirs(self.template_dir, self.report_dir)
        self.index_html = f'{self.report_dir}/index.html'
        self.res_html = f'{self.report_dir}/results.html'
        self.index_html_data = self.file.read_file(self.index_html)
        self.res_html_data = self.file.read_file(self.res_html)

        return

    def make_results(self):
        """ get all results from the targets log directory """

        logs = f'{self.logs_dir}/targets/'

        targets = os.listdir(logs)
        for target in targets:
            target_orig = target
            if ':' in target:
                target = target.replace(':', '-')
            self.res[target] = {}
            for moddir in os.listdir(logs + target_orig):
                if moddir in ('tcp', 'udp', 'social'):
                    submods = os.listdir(f'{logs}{target_orig}/{moddir}')
                    for s in submods:
                        for modname in os.listdir(
                                f'{logs}{target_orig}/{moddir}/{s}'):
                            if moddir == 'tcp' or moddir == 'udp':
                                module = f'{moddir}.{s}.{modname}'
                            else:
                                module = f'{moddir}.{modname}'
                            self.res[target][module] = {}
                            tool_path = f'{logs}{target_orig}/{moddir}/{s}/{modname}/'
                            for tool in os.listdir(tool_path):
                                if '.log' in tool:
                                    self.res[target][module][tool] = \
                                      '\n'.join(self.file.read_file(tool_path + tool))
                else:
                    for modname in os.listdir(f'{logs}{target_orig}/{moddir}'):
                        target = target.replace(':', '-')  # url port
                        module = f'{moddir}.{modname}'
                        self.res[target][module] = {}
                        for tool in os.listdir(
                                f'{logs}{target_orig}/{moddir}/{modname}'):
                            if '.log' in tool and '.bin' not in tool:
                                tool_path = f'{logs}{target_orig}/{moddir}/{modname}/'
                                self.res[target][module][tool] = '\n'.join(
                                    self.file.read_file(tool_path + tool))

        return

    def make_target_html(self):
        """ create $target.html with its contents/results """

        _mods = ''
        panels = []
        panel = '''
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title">$TOOL</h3>
      </div>
      <div class="panel-body">
        <pre>$RESULT</pre>
      </div>
    </div>
    '''

        for target, mods in self.res.items():
            t_html = f'{self.report_dir}/{target}.html'
            self.file.copy_files(self.res_html, t_html)
            t_html_data = self.file.read_file(t_html)
            t_html_data = [w.replace('$TARGET', target) for w in t_html_data]
            self.file.write_file(t_html, ' '.join(t_html_data))
            for mod, tools in mods.items():
                _mods += f'<h5>{mod}</h5>'
                for tool, data in tools.items():
                    tlink = tool.split('.log')[0]
                    link = f'{mod}.{tlink}'
                    _mods += f'<a href="#{link}" target="_blank">{tlink}</a><br />'
                    p1 = panel.replace(f'$TOOL',
                                       f'<a name="{link}">{link}</a>')
                    p2 = p1.replace('$RESULT', html.escape(data))
                    panels.append(p2)
            t_html_data = [w.replace('$ALLMODS', _mods) for w in t_html_data]
            _mods = ''
            t_html_data = [w.replace('$CONTENT', ' '.join(panels)) \
              for w in t_html_data]
            panels = []

            self.file.write_file(t_html, ' '.join(t_html_data))

        return

    def make_index_html(self):
        """ create the index.html """

        num_social_targets = 0
        patterns = {
            '$DATE': self.date,
            '$CMDLINE_ARGS': ' '.join(self.opts['cmdline'])
        }

        # index.html: date + cmdline args
        for k, v in patterns.items():
            self.index_html_data = [
                w.replace(k, v) for w in self.index_html_data
            ]

        # index.html: num target modes
        for k, v in self.opts['targets'].items():
            if k == 'social':
                for s in self.opts['targets'][k]:
                    if self.opts['targets'][k][s]:
                        num_social_targets += len(self.opts['targets'][k][s])
                self.index_html_data = [
                    w.replace('$NUM_SOCIAL', str(num_social_targets))
                    for w in self.index_html_data
                ]
            self.index_html_data = [w.replace(f'$NUM_{k.upper()}', str(len(v))) \
              for w in self.index_html_data]

        return

    def make_menu(self, mod, targets):
        """ create the menu and links """

        # targets menu items
        if mod == 'tcp' or mod == 'udp':
            l = []
            for target in self.opts['targets'][mod]:
                target = f"<li><a href='{target['host']}.html'>{target['host']}</a></li>"
                l.append(target)
            self.index_html_data = [
              w.replace(f'$TARGETS_{mod.upper()}', ' '.join(l)) \
              for w in self.index_html_data
            ]
            self.res_html_data = [
              w.replace(f'$TARGETS_{mod.upper()}', ' '.join(l)) \
              for w in self.res_html_data
            ]
        elif mod == 'social':
            l = []
            for part in self.opts['targets'][mod]:
                for target in self.opts['targets'][mod][part]:
                    target = f'<li><a href="{target}.html">{target}</a></li>'
                    l.append(target)
            self.index_html_data = [
              w.replace(f'$TARGETS_{mod.upper()}', ' '.join(l)) \
              for w in self.index_html_data
            ]
            self.res_html_data = [
              w.replace(f'$TARGETS_{mod.upper()}', ' '.join(l)) \
              for w in self.res_html_data
            ]
        else:
            for target in targets:
                if '://' in target:
                    target = target.split('://')[1].rstrip('/').split('/')[0]
                if ':' in target:
                    target = target.replace(':', '-')  # port
                target = f'<li><a href="{target}.html">{target}</a></li>'
                self.index_html_data = [
                  w.replace(f'$TARGETS_{mod.upper()}', target) \
                  for w in self.index_html_data
                ]
                self.res_html_data = [
                  w.replace(f'$TARGETS_{mod.upper()}', target) \
                  for w in self.res_html_data
                ]

        return

    def make_report(self):
        """ make the report """

        self.make_results()

        for mod, targets in self.opts['targets'].items():
            self.make_menu(mod, targets)
        self.make_index_html()

        self.file.write_file(self.index_html, ' '.join(self.index_html_data))
        self.file.write_file(self.res_html, ' '.join(self.res_html_data))

        self.make_target_html()
        self.file.del_file(f'{self.report_dir}/results.html')

        return
Example #2
0
class Controller:
    """ program controller class """
    def __init__(self):
        """ constructor """

        # options and modules
        self.opt = None
        self.mod = Module(MOD_PATH)

        # logger
        self.logger = Logger()
        self.log = self.logger.log

        # rest we need
        self.file = File()
        self.check = Check()
        self.misc = Misc()
        self.parser = None

        # nullscan working dir
        self.nullscan_dir = None

        return

    def prepare(self):
        """ preparation/initialization of opts and env: parsing & checks """

        # declare nullscan options
        self.opt = Option(sys.argv)

        # check argc and argc (usage)
        self.check.check_argc(len(sys.argv))
        self.check.check_argv(sys.argv)

        # check for missing libraries / deps / python modules
        self.check.check_deps(self.file.read_file(PYDEPS))

        # parse cmdline and config options, update final options dictionary
        try:
            self.parser = Parser(self.opt.opts)
            self.parser.parse_cmdline()
            self.parser.parse_config()
            self.opt.opts = self.parser.opts
        except:
            self.log('usage', _type='err', end='\n')

        # update final options dictionary
        self.opt.update_opts()

        # further checks for usage, options, env, etc.
        self.check.check_opts(self.opt.opts)

        # collect all py-files and grep the tools out of the py-files
        tools = []
        py_files = self.misc.find_py_files(MOD_PATH)
        for py in py_files:
            tools.append(self.misc.grep_tools(py))
        tools = [x for sublist in tools for x in sublist]

        # create the locks for each tool except for excluded ones
        with ThreadPoolExecutor(50) as exe:
            for tool in tools:
                if tool not in self.opt.opts['tools']['ex_tools']:
                    exe.submit(self.file.create_lock, tool)

        # copy debug flag to target_opts (for nullscan tools)
        self.opt.opts['targets_opts']['debug'] = self.opt.opts['debug']

        return

    def run_misc(self):
        """ run chosen misc options """

        if self.opt.opts['check_tools']:
            self.log('Checking for missing tools\n\n', _type='msg')
            self.check.check_tools()
            os._exit(SUCCESS)
        if self.opt.opts['print_tools']:
            self.log('Printing tools and information\n\n', _type='msg')
            self.misc.print_tool(self.opt.opts['print_tools'])
            os._exit(SUCCESS)
        if self.opt.opts['add_module']:
            self.log('Adding new module\n', _type='msg')
            self.misc.add_mod_tool('mod', self.opt.opts['add_module'])
            os._exit(SUCCESS)
        if self.opt.opts['add_tool']:
            self.log('Adding new tool\n', _type='msg')
            self.misc.add_mod_tool('tool', self.opt.opts['add_tool'])
            os._exit(SUCCESS)

        return

    def prepare_modules(self):
        """ filter in-/excluded mods + return a unique list """

        self.mod.filter_modules(self.opt.opts)
        self.mod.mods = list(set(self.mod.mods))

        return

    def run_nmap_mode(self):
        """ run nmap scan mode """

        nmap = Nmap(self.opt.opts['targets']['nmap'])

        # create nmap logdir based on scan type requested
        logpath = f'{self.nullscan_dir}/logs/nmap/{nmap.get_protocol()}'
        logfile = f'{logpath}/results'
        self.file.make_dir(logpath)
        nmap.set_logfile(logfile)

        # build nmap command line
        nmap.build_cmd()

        # start scans
        self.log('NMAP mode activated\n', _type='msg', color='blue')
        self.log('Targets added: {}\n'.format(
            len(self.opt.opts['targets']['nmap']['hosts'])),
                 _type='msg')
        if self.opt.opts['verbose']:
            self.log('\n')
            for target in self.opt.opts['targets']['nmap']['hosts']:
                self.log(f'{target}\n', _type='msg')
        nmap.scan(debug=self.opt.opts['debug'])

        return f'{logfile}.xml'

    def run_social_mode(self, target):
        """ run social mode """

        if '.social.' in str(self.mod.mods):
            # availabel social modules to import and load
            mods = [i for i in self.mod.mods if '.social.' in i]

            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for key in target.keys():
                    if target[key]:
                        for t in target[key]:
                            # default module
                            rdir = f'{self.nullscan_dir}/logs/targets/'
                            wdir = f"{rdir}{t}/social/{key}/default"
                            exe.submit(self.mod.run_module,
                                       'modules.social.default', t,
                                       self.opt.opts, wdir)

                            # non-default modules
                            for m in mods:
                                if 'default' not in m:
                                    splitted = m.split('.')
                                    moddir = splitted[1]
                                    modname = splitted[2]
                                    wdir = f"{rdir}{t}/{moddir}/{key}/{modname}"
                                    exe.submit(self.mod.run_module, m, t,
                                               self.opt.opts, wdir)

        return

    def run_wifi_mode(self, target):
        """ run wifi mode """

        if '.wifi.' in str(self.mod.mods):
            # available wifi modules to import and load
            mods = [i for i in self.mod.mods if '.wifi.' in i]

            # default module first
            rdir = f'{self.nullscan_dir}/logs/targets/'
            wdir = f'{rdir}{target}/wifi/default'
            self.mod.run_module('modules.wifi.default', target, self.opt.opts,
                                wdir)

            # non-default modules
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for m in mods:
                    if 'default' not in m:
                        splitted = m.split('.')
                        moddir = splitted[1]
                        modname = splitted[2]
                        wdir = f'{rdir}{target}/{moddir}/{modname}'
                        exe.submit(self.mod.run_module, m, target,
                                   self.opt.opts, wdir)

        return

    def run_lan_mode(self, target):
        """ run lan mode """

        if '.lan.' in str(self.mod.mods):
            # available lan modules to import and load
            mods = [i for i in self.mod.mods if '.lan.' in i]

            # default module first
            rdir = f'{self.nullscan_dir}/logs/targets/'
            wdir = f'{rdir}{target}/lan/default'
            self.mod.run_module('modules.lan.default', target, self.opt.opts,
                                wdir)

            # non-default modules
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for m in mods:
                    if 'default' not in m:
                        splitted = m.split('.')
                        moddir = splitted[1]
                        modname = splitted[2]
                        wdir = f'{rdir}{target}/{moddir}/{modname}'
                        exe.submit(self.mod.run_module, m, target,
                                   self.opt.opts, wdir)

        return

    def run_web_mode(self, target):
        """ run web mode """

        if '.web.' in str(self.mod.mods):
            # available web modules to import and load
            mods = [i for i in self.mod.mods if '.web.' in i]

            # we need host name for working directory
            host = requests.utils.urlparse(target).netloc

            # default module first
            rdir = f'{self.nullscan_dir}/logs/targets/'
            wdir = f'{rdir}{host}/web/default'
            self.mod.run_module('modules.web.default', target, self.opt.opts,
                                wdir)

            # non-default modules
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for m in mods:
                    if 'default' not in m:
                        splitted = m.split('.')
                        moddir = splitted[1]
                        modname = splitted[2]
                        wdir = f'{rdir}{host}/{moddir}/{modname}'
                        exe.submit(self.mod.run_module, m, target,
                                   self.opt.opts, wdir)

        return

    def run_udp_mode(self, target):
        """ run udp mode """

        # we need to run host modules before tcp and we need to run default
        # tool first
        self.run_host_mode(target)

        # now tcp modules
        if '.udp.' in str(self.mod.mods):
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for p in target['ports']:
                    exe.submit(self.run_tcp_udp_mode, target, p, 'udp')

        return

    def run_tcp_mode(self, target):
        """ run tcp mode """

        # we need to run host modules before tcp and we need to run default
        # module first
        self.run_host_mode(target)

        # now tcp modules
        if '.tcp.' in str(self.mod.mods):
            with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
                for p in target['ports']:
                    exe.submit(self.run_tcp_udp_mode, target, p, 'tcp')

        return

    def run_tcp_udp_mode(self, host, port, proto):
        """ wrapper for tcp/udp mode """

        # available modules
        mod = f'modules.{proto}.{port[1]}'

        # force default module for given port if module does not exist
        if mod not in self.mod.mods:
            mod = f'modules.{proto}.default'

        # new target dict as we only need the corresponding port
        t = {'host': host['host'], 'port': port[0]}

        # default module first
        rdir = f'{self.nullscan_dir}/logs/targets/'
        wdir = f"{rdir}{t['host']}/{proto}/{port[0]}/default"
        self.mod.run_module(f'modules.{proto}.default', t, self.opt.opts, wdir)

        # now non-default module
        if '.default' not in mod:
            wdir = f"{rdir}{t['host']}/{proto}/{port[0]}/{port[1]}"
            self.mod.run_module(mod, t, self.opt.opts, wdir)

        return

    def run_host_mode(self, target):
        """ run host mode """

        # available host modules to import and load
        mods = [i for i in self.mod.mods if '.host.' in i]

        # default module
        rdir = f'{self.nullscan_dir}/logs/targets/'
        wdir = f"{rdir}{target['host']}/host/default"
        self.mod.run_module('modules.host.default', target, self.opt.opts,
                            wdir)

        # non-default modules
        with ProcessPoolExecutor(self.opt.opts['m_workers']) as exe:
            for m in mods:
                if 'default' not in m:
                    splitted = m.split('.')
                    moddir = splitted[1]
                    modname = splitted[2]
                    wdir = f"{rdir}{target['host']}/{moddir}/{modname}"
                    exe.submit(self.mod.run_module, m, target, self.opt.opts,
                               wdir)

        return

    def run_modes(self):
        """ run chosen modes """

        scans = []
        modes = {
            'tcp': self.run_tcp_mode,
            'udp': self.run_udp_mode,
            'wifi': self.run_wifi_mode,
            'web': self.run_web_mode,
            'social': self.run_social_mode,
            'http': self.run_web_mode,
            'https': self.run_web_mode,
        }

        # run lan mode first if requested
        if self.opt.opts['targets']['lan']:
            ifaces = []
            self.log('LAN mode activated\n', color='blue', _type='msg')
            self.log(
                f"Targets added: {len(self.opt.opts['targets']['lan'])}\n\n",
                _type='msg')
            for iface in self.opt.opts['targets']['lan']:
                if self.opt.opts['verbose']:
                    self.log(f'{iface}\n', _type='vmsg')
                ifaces.append((self.run_lan_mode, iface))
            if self.opt.opts['verbose']:
                self.log('\n')
            with ProcessPoolExecutor(self.opt.opts['t_workers']) as exe:
                self.log('Shooting tools\n\n', color='green', _type='msg')
                for iface in ifaces:
                    exe.submit(iface[0], iface[1])
            self.log('\n')
            if not self.opt.opts['verbose']:
                self.log('\n')
            self.log('Tools done\n\n', color='green', _type='msg')
            for log in glob.glob('**/lan/portscan/*.xml', recursive=True):
                if log:
                    self.parser.parse_nmap_logfile(log, lan=True)

        # collect scan modes for each target
        for k, v in self.opt.opts['targets'].items():
            if self.opt.opts['targets'][k]:
                if k == 'lan':
                    continue
                else:
                    self.log(f'{k.upper()} mode activated\n',
                             color='blue',
                             _type='msg')
                self.log(f'Targets added: {len(v)}\n\n', _type='msg')
                if k == 'social':
                    if self.opt.opts['verbose']:
                        for targets in v.values():
                            for target in targets:
                                self.log(f'{target}\n', _type='vmsg')
                        self.log('\n')
                    scans.append((modes[k], v))
                else:
                    for target in v:
                        scans.append((modes[k], target))
                        if self.opt.opts['verbose']:
                            if 'host' in target:
                                target_head = target['host']
                            else:
                                target_head = target
                            self.log(f'{target_head}\n', _type='vmsg')
                    if self.opt.opts['verbose']:
                        self.log('\n')

        # start mode for each target
        with ProcessPoolExecutor(self.opt.opts['t_workers']) as exe:
            if scans:
                self.log('Shooting tools\n\n', color='green', _type='msg')
                for scan in scans:
                    exe.submit(scan[0], scan[1])
        if scans:
            self.log('\n')
            if not self.opt.opts['verbose']:
                self.log('\n')
            self.log('Tools done\n\n', _type='msg', color='green')

        return

    def start(self):
        """ nullscan starts here with actions """

        self.check.check_uid()
        self.log('Game Started\n\n', _type='msg')

        # create nullscan working, targets and log dir
        self.nullscan_dir = self.file.make_dir(self.opt.opts['nullscan_dir'],
                                               incr=True)
        self.file.make_dir(f'{self.nullscan_dir}/logs/targets')
        self.opt.opts['targets_opts']['nullscan_logdir'] = \
          f'{self.nullscan_dir}/logs/targets/'

        # run nmap mode first if requested
        if 'hosts' in self.opt.opts['targets']['nmap']:
            self.parser.parse_nmap_logfile(self.run_nmap_mode())
            self.log('\n')

        # delete nmap key
        del self.opt.opts['targets']['nmap']

        # prepare modules for other modes
        self.prepare_modules()

        # run the nullscan modes now
        self.run_modes()

        return

    def end(self):
        """ program ends here. clean-ups, reporting, etc. """

        # go back to root dir
        os.chdir(ROOT_PATH)

        # clean up empty directories and empty (log-)files or logfiles containing
        # a singl ebyte (newline) (failed tools)
        self.misc.remove_empty_files_dirs(f'{self.nullscan_dir}/logs/targets/')

        # just to be safe - delete all locks, in case tools didn't
        self.file.del_file('/tmp/nullscan', _dir=True)

        # create report
        if self.opt.opts['report']:
            self.log('Creating report\n', _type='msg')
            tmpl_dir = f'{ROOT_PATH}/src/report/template'
            rep_dir = f'{self.nullscan_dir}/report'
            logs_dir = f'{self.nullscan_dir}/logs'
            self.report = HTML(TODAY, self.opt.opts, tmpl_dir, rep_dir,
                               logs_dir)
            self.report.make_report()
            self.log('Report done\n\n', _type='msg')
        self.log('Game Over\n', _type='msg')

        # reset terminal to original state. sometimes f**k up occurs because of
        # color and other escape codes.
        self.misc.reset_terminal()

        return