Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/docs/_plugins/metasploit_stats.rb
Views: 11766
require 'jekyll'1require 'json'2require 'pathname'34#5# Helper class for extracting information related to Metasploit framework's stats6#7class MetasploitStats8def total_module_count9modules.length10end1112# @return [Hash<String, Integer>] A map of module type to the amount of modules13def module_counts14module_counts_by_type = modules.group_by { |mod| mod['type'].to_s }.transform_values { |mods| mods.count }.sort_by(&:first).to_h15module_counts_by_type16end1718# @return [Array<Hash<String, Hash>>] A nested array of module metadata, containing at least the keys :name, :total, :children19def nested_module_counts20create_nested_module_counts(modules)21end2223protected2425# @param [Array<Hash>] modules26# @param [String] parent_path The parent path to track the nesting depth when called recursively27# i.e. auxiliary, then auxiliary/admin, then auxiliary/admin/foo, etc28def create_nested_module_counts(modules, parent_path = '')29# Group the modules by their prefix, i.e. auxiliary/payload/encoder/etc30top_level_buckets = modules.select { |mod| mod['fullname'].start_with?(parent_path) }.group_by do |mod|31remaining_paths = mod['fullname'].gsub(parent_path.empty? ? '' : %r{^#{parent_path}/}, '').split('/')32remaining_paths[0]33end.sort.to_h3435top_level_buckets.map do |(prefix, children)|36current_path = parent_path.empty? ? prefix : "#{parent_path}/#{prefix}"37mod = modules_by_fullname[current_path]38{39name: prefix,40total: children.count,41module_fullname: mod ? mod['fullname'] : nil,42module_path: mod ? mod['path'] : nil,43children: mod.nil? ? create_nested_module_counts(children, current_path) : []44}45end46end4748# @return [Array<Hash>] An array of Hashes containing each Metasploit module's metadata49def modules50return @modules if @modules5152module_metadata_path = '../db/modules_metadata_base.json'53unless File.exist?(module_metadata_path)54raise "Unable to find Metasploit module data, expected it to be at #{module_metadata_path}"55end5657@modules = JSON.parse(File.binread(module_metadata_path)).values58@modules59end6061# @return [Hash<String, Hash>] A mapping of module name to Metasploit module metadata62def modules_by_fullname63@modules_by_fullname ||= @modules.each_with_object({}) do |mod, hash|64fullname = mod['fullname']65hash[fullname] = mod66end67end68end6970# Custom liquid filter implementation for visualizing nested Metasploit module metadata71#72# Intended usage:73# {{ site.metasploit_nested_module_counts | module_tree }}74module ModuleFilter75# @param [Array<Hash>] modules The array of Metasploit cache information76# @return [String] The module tree HTML representation of the given modules77def module_tree(modules, title = 'Modules', show_controls = false)78rendered_children = render_modules(modules)79controls = <<~EOF80<div class="module-controls">81<span><a href="#" data-expand-all>Expand All</a></span>82<span><a href="#" data-collapse-all>Collapse All</a></span>83</div>84EOF8586<<~EOF87<div class="module-list">88#{show_controls ? controls : ''}8990<ul class="module-structure">91<li class="folder"><a href=\"#\"><div class=\"target\">#{title}</div></a>92<ul class="open">93#{rendered_children}94</ul>95</li>96</ul>97</div>98EOF99end100101module_function102103# @param [Array<Hash>] modules The array of Metasploit cache information104# @return [String] The rendered tree HTML representation of the given modules105def render_modules(modules)106modules.map do |mod|107classes = render_child_modules?(mod) ? ' class="folder"' : ''108result = "<li#{classes}>#{heading_for_mod(mod)}"109if render_child_modules?(mod)110result += "\n<ul>#{render_modules(mod[:children].sort_by { |mod| "#{render_child_modules?(mod) ? 0 : 1}-#{mod[:name]}" })}</ul>\n"111end112result += "</li>"113result114end.join("\n")115end116117# @param [Hash] mod The module metadata object118# @return [String] Human readable string for a module list such as `- <a>Auxiliary (1234)</a>` or `- Other (50)`119def heading_for_mod(mod)120if render_child_modules?(mod)121"<a href=\"#\"><div class=\"target\">#{mod[:name]} (#{mod[:total]})</div></a>"122else123config = Jekyll.sites.first.config124# Preference linking to module documentation over the module implementation125module_docs_path = Pathname.new("documentation").join(mod[:module_path].gsub(/^\//, '')).sub_ext(".md")126link_path = File.exist?(File.join('..', module_docs_path)) ? "/#{module_docs_path}" : mod[:module_path]127docs_link = "#{config['gh_edit_repository']}/#{config['gh_edit_view_mode']}/#{config['gh_edit_branch']}#{link_path}"128"<a href=\"#{docs_link}\" target=\"_blank\"><div class=\"target\">#{mod[:module_fullname]}</div></a>"129end130end131132# @param [Hash] mod The module metadata object133# @return [TrueClass, FalseClass]134def render_child_modules?(mod)135mod[:children].length >= 1 && mod[:module_path].nil?136end137end138139# Register the Liquid filter so any Jekyll page can render module information140Liquid::Template.register_filter(ModuleFilter)141142# Register the site initialization hook to populate global site information so any Jekyll page can access Metasploit stats information143Jekyll::Hooks.register :site, :after_init do |site|144begin145Jekyll.logger.info 'Calculating module stats'146147metasploit_stats = MetasploitStats.new148149site.config['metasploit_total_module_count'] = metasploit_stats.total_module_count150site.config['metasploit_module_counts'] = metasploit_stats.module_counts151site.config['metasploit_nested_module_counts'] = metasploit_stats.nested_module_counts152153Jekyll.logger.info 'Finished calculating module stats'154rescue155Jekyll.logger.error "Unable to to extractMetasploit stats"156raise157end158end159160161