CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/docs/_plugins/metasploit_stats.rb
Views: 11766
1
require 'jekyll'
2
require 'json'
3
require 'pathname'
4
5
#
6
# Helper class for extracting information related to Metasploit framework's stats
7
#
8
class MetasploitStats
9
def total_module_count
10
modules.length
11
end
12
13
# @return [Hash<String, Integer>] A map of module type to the amount of modules
14
def module_counts
15
module_counts_by_type = modules.group_by { |mod| mod['type'].to_s }.transform_values { |mods| mods.count }.sort_by(&:first).to_h
16
module_counts_by_type
17
end
18
19
# @return [Array<Hash<String, Hash>>] A nested array of module metadata, containing at least the keys :name, :total, :children
20
def nested_module_counts
21
create_nested_module_counts(modules)
22
end
23
24
protected
25
26
# @param [Array<Hash>] modules
27
# @param [String] parent_path The parent path to track the nesting depth when called recursively
28
# i.e. auxiliary, then auxiliary/admin, then auxiliary/admin/foo, etc
29
def create_nested_module_counts(modules, parent_path = '')
30
# Group the modules by their prefix, i.e. auxiliary/payload/encoder/etc
31
top_level_buckets = modules.select { |mod| mod['fullname'].start_with?(parent_path) }.group_by do |mod|
32
remaining_paths = mod['fullname'].gsub(parent_path.empty? ? '' : %r{^#{parent_path}/}, '').split('/')
33
remaining_paths[0]
34
end.sort.to_h
35
36
top_level_buckets.map do |(prefix, children)|
37
current_path = parent_path.empty? ? prefix : "#{parent_path}/#{prefix}"
38
mod = modules_by_fullname[current_path]
39
{
40
name: prefix,
41
total: children.count,
42
module_fullname: mod ? mod['fullname'] : nil,
43
module_path: mod ? mod['path'] : nil,
44
children: mod.nil? ? create_nested_module_counts(children, current_path) : []
45
}
46
end
47
end
48
49
# @return [Array<Hash>] An array of Hashes containing each Metasploit module's metadata
50
def modules
51
return @modules if @modules
52
53
module_metadata_path = '../db/modules_metadata_base.json'
54
unless File.exist?(module_metadata_path)
55
raise "Unable to find Metasploit module data, expected it to be at #{module_metadata_path}"
56
end
57
58
@modules = JSON.parse(File.binread(module_metadata_path)).values
59
@modules
60
end
61
62
# @return [Hash<String, Hash>] A mapping of module name to Metasploit module metadata
63
def modules_by_fullname
64
@modules_by_fullname ||= @modules.each_with_object({}) do |mod, hash|
65
fullname = mod['fullname']
66
hash[fullname] = mod
67
end
68
end
69
end
70
71
# Custom liquid filter implementation for visualizing nested Metasploit module metadata
72
#
73
# Intended usage:
74
# {{ site.metasploit_nested_module_counts | module_tree }}
75
module ModuleFilter
76
# @param [Array<Hash>] modules The array of Metasploit cache information
77
# @return [String] The module tree HTML representation of the given modules
78
def module_tree(modules, title = 'Modules', show_controls = false)
79
rendered_children = render_modules(modules)
80
controls = <<~EOF
81
<div class="module-controls">
82
<span><a href="#" data-expand-all>Expand All</a></span>
83
<span><a href="#" data-collapse-all>Collapse All</a></span>
84
</div>
85
EOF
86
87
<<~EOF
88
<div class="module-list">
89
#{show_controls ? controls : ''}
90
91
<ul class="module-structure">
92
<li class="folder"><a href=\"#\"><div class=\"target\">#{title}</div></a>
93
<ul class="open">
94
#{rendered_children}
95
</ul>
96
</li>
97
</ul>
98
</div>
99
EOF
100
end
101
102
module_function
103
104
# @param [Array<Hash>] modules The array of Metasploit cache information
105
# @return [String] The rendered tree HTML representation of the given modules
106
def render_modules(modules)
107
modules.map do |mod|
108
classes = render_child_modules?(mod) ? ' class="folder"' : ''
109
result = "<li#{classes}>#{heading_for_mod(mod)}"
110
if render_child_modules?(mod)
111
result += "\n<ul>#{render_modules(mod[:children].sort_by { |mod| "#{render_child_modules?(mod) ? 0 : 1}-#{mod[:name]}" })}</ul>\n"
112
end
113
result += "</li>"
114
result
115
end.join("\n")
116
end
117
118
# @param [Hash] mod The module metadata object
119
# @return [String] Human readable string for a module list such as `- <a>Auxiliary (1234)</a>` or `- Other (50)`
120
def heading_for_mod(mod)
121
if render_child_modules?(mod)
122
"<a href=\"#\"><div class=\"target\">#{mod[:name]} (#{mod[:total]})</div></a>"
123
else
124
config = Jekyll.sites.first.config
125
# Preference linking to module documentation over the module implementation
126
module_docs_path = Pathname.new("documentation").join(mod[:module_path].gsub(/^\//, '')).sub_ext(".md")
127
link_path = File.exist?(File.join('..', module_docs_path)) ? "/#{module_docs_path}" : mod[:module_path]
128
docs_link = "#{config['gh_edit_repository']}/#{config['gh_edit_view_mode']}/#{config['gh_edit_branch']}#{link_path}"
129
"<a href=\"#{docs_link}\" target=\"_blank\"><div class=\"target\">#{mod[:module_fullname]}</div></a>"
130
end
131
end
132
133
# @param [Hash] mod The module metadata object
134
# @return [TrueClass, FalseClass]
135
def render_child_modules?(mod)
136
mod[:children].length >= 1 && mod[:module_path].nil?
137
end
138
end
139
140
# Register the Liquid filter so any Jekyll page can render module information
141
Liquid::Template.register_filter(ModuleFilter)
142
143
# Register the site initialization hook to populate global site information so any Jekyll page can access Metasploit stats information
144
Jekyll::Hooks.register :site, :after_init do |site|
145
begin
146
Jekyll.logger.info 'Calculating module stats'
147
148
metasploit_stats = MetasploitStats.new
149
150
site.config['metasploit_total_module_count'] = metasploit_stats.total_module_count
151
site.config['metasploit_module_counts'] = metasploit_stats.module_counts
152
site.config['metasploit_nested_module_counts'] = metasploit_stats.nested_module_counts
153
154
Jekyll.logger.info 'Finished calculating module stats'
155
rescue
156
Jekyll.logger.error "Unable to to extractMetasploit stats"
157
raise
158
end
159
end
160
161