mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-11-05 14:57:30 +01:00
Add info -d to show module documentation in .md
This commit is contained in:
parent
95484c81fd
commit
b0cfb4aacf
2
Gemfile
2
Gemfile
@ -18,6 +18,8 @@ group :development do
|
||||
gem 'yard'
|
||||
# for development and testing purposes
|
||||
gem 'pry'
|
||||
# module documentation
|
||||
gem 'octokit', '~> 4.0'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
|
20
Gemfile.lock
20
Gemfile.lock
@ -57,9 +57,10 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (~> 0.3.37)
|
||||
addressable (2.3.8)
|
||||
arel (4.0.2)
|
||||
arel-helpers (2.1.1)
|
||||
activerecord (= 4.0.13)
|
||||
arel-helpers (2.2.0)
|
||||
activerecord (>= 3.1.0, < 5)
|
||||
aruba (0.6.2)
|
||||
childprocess (>= 0.3.6)
|
||||
cucumber (>= 1.1.1)
|
||||
@ -95,6 +96,8 @@ GEM
|
||||
factory_girl_rails (4.5.0)
|
||||
factory_girl (~> 4.5.0)
|
||||
railties (>= 3.0.0)
|
||||
faraday (0.9.2)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.9.8)
|
||||
filesize (0.1.1)
|
||||
fivemat (1.3.2)
|
||||
@ -139,17 +142,20 @@ GEM
|
||||
mime-types (2.6.1)
|
||||
mini_portile2 (2.0.0)
|
||||
minitest (4.7.5)
|
||||
msgpack (0.7.1)
|
||||
msgpack (0.7.4)
|
||||
multi_json (1.11.2)
|
||||
multi_test (0.1.2)
|
||||
multipart-post (2.0.0)
|
||||
network_interface (0.0.1)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
octokit (4.2.0)
|
||||
sawyer (~> 0.6.0, >= 0.5.3)
|
||||
openssl-ccm (1.2.1)
|
||||
packetfu (1.1.11)
|
||||
network_interface (~> 0.0)
|
||||
pcaprub (~> 0.12)
|
||||
pcaprub (0.12.0)
|
||||
pcaprub (0.12.1)
|
||||
pg (0.18.4)
|
||||
pg_array_parser (0.0.9)
|
||||
postgres_ext (2.4.1)
|
||||
@ -200,8 +206,11 @@ GEM
|
||||
rspec-mocks (~> 3.3.0)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-support (3.3.0)
|
||||
rubyntlm (0.5.2)
|
||||
rubyntlm (0.6.0)
|
||||
rubyzip (1.1.7)
|
||||
sawyer (0.6.0)
|
||||
addressable (~> 2.3.5)
|
||||
faraday (~> 0.8, < 0.10)
|
||||
shoulda-matchers (2.8.0)
|
||||
activesupport (>= 3.0.0)
|
||||
simplecov (0.9.2)
|
||||
@ -238,6 +247,7 @@ DEPENDENCIES
|
||||
factory_girl_rails (~> 4.5.0)
|
||||
fivemat (~> 1.3.1)
|
||||
metasploit-framework!
|
||||
octokit (~> 4.0)
|
||||
pry
|
||||
rake (>= 10.0.0)
|
||||
redcarpet
|
||||
|
233
data/markdown.css
Normal file
233
data/markdown.css
Normal file
@ -0,0 +1,233 @@
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #737373;
|
||||
margin: 10px 13px 10px 13px;
|
||||
}
|
||||
a {
|
||||
color: #0069d6;
|
||||
}
|
||||
a:hover {
|
||||
color: #0050a3;
|
||||
text-decoration: none;
|
||||
}
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: #404040;
|
||||
line-height: 36px;
|
||||
}
|
||||
h1 {
|
||||
margin-bottom: 18px;
|
||||
font-size: 30px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
h6 {
|
||||
font-size: 13px;
|
||||
}
|
||||
hr {
|
||||
margin: 0 0 19px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
blockquote {
|
||||
padding: 13px 13px 21px 15px;
|
||||
margin-bottom: 18px;
|
||||
font-family:georgia,serif;
|
||||
font-style: italic;
|
||||
}
|
||||
blockquote:before {
|
||||
content:"\201C";
|
||||
font-size:40px;
|
||||
margin-left:-10px;
|
||||
font-family:georgia,serif;
|
||||
color:#eee;
|
||||
}
|
||||
blockquote p {
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
line-height: 18px;
|
||||
margin-bottom: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
code, pre {
|
||||
font-family: Monaco, Andale Mono, Courier New, monospace;
|
||||
}
|
||||
code {
|
||||
background-color: #fee9cc;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
padding: 1px 3px;
|
||||
font-size: 12px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
pre {
|
||||
display: block;
|
||||
padding: 14px;
|
||||
margin: 0 0 18px;
|
||||
line-height: 16px;
|
||||
font-size: 11px;
|
||||
border: 1px solid #d9d9d9;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
pre code {
|
||||
background-color: #fff;
|
||||
color:#737373;
|
||||
font-size: 11px;
|
||||
padding: 0;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
body {
|
||||
width: 748px;
|
||||
margin:10px auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Description: Foundation 4 docs style for highlight.js
|
||||
Author: Dan Allen <dan.j.allen@gmail.com>
|
||||
Website: http://foundation.zurb.com/docs/
|
||||
Version: 1.0
|
||||
Date: 2013-04-02
|
||||
*/
|
||||
|
||||
pre code {
|
||||
display: block; padding: 0.5em;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
pre .decorator,
|
||||
pre .annotation {
|
||||
color: #000077;
|
||||
}
|
||||
|
||||
pre .attribute {
|
||||
color: #070;
|
||||
}
|
||||
|
||||
pre .value,
|
||||
pre .string,
|
||||
pre .scss .value .string {
|
||||
color: #d14;
|
||||
}
|
||||
|
||||
pre .comment {
|
||||
color: #998;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
pre .function .title {
|
||||
color: #900;
|
||||
}
|
||||
|
||||
pre .class {
|
||||
color: #458;
|
||||
}
|
||||
|
||||
pre .id,
|
||||
pre .pseudo,
|
||||
pre .constant,
|
||||
pre .hexcolor {
|
||||
color: teal;
|
||||
}
|
||||
|
||||
pre .variable {
|
||||
color: #336699;
|
||||
}
|
||||
|
||||
pre .javadoc {
|
||||
color: #997700;
|
||||
}
|
||||
|
||||
pre .pi,
|
||||
pre .doctype {
|
||||
color: #3344bb;
|
||||
}
|
||||
|
||||
pre .number {
|
||||
color: #099;
|
||||
}
|
||||
|
||||
pre .important {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
pre .label {
|
||||
color: #970;
|
||||
}
|
||||
|
||||
pre .preprocessor {
|
||||
color: #579;
|
||||
}
|
||||
|
||||
pre .reserved,
|
||||
pre .keyword,
|
||||
pre .scss .value {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
pre .regexp {
|
||||
background-color: #fff0ff;
|
||||
color: #880088;
|
||||
}
|
||||
|
||||
pre .symbol {
|
||||
color: #990073;
|
||||
}
|
||||
|
||||
pre .symbol .string {
|
||||
color: #a60;
|
||||
}
|
||||
|
||||
pre .tag {
|
||||
color: #007700;
|
||||
}
|
||||
|
||||
pre .at_rule,
|
||||
pre .at_rule .keyword {
|
||||
color: #088;
|
||||
}
|
||||
|
||||
pre .at_rule .preprocessor {
|
||||
color: #808;
|
||||
}
|
||||
|
||||
pre .scss .tag,
|
||||
pre .scss .attribute {
|
||||
color: #339;
|
||||
}
|
@ -16,6 +16,7 @@ require 'msf/ui/console/command_dispatcher/nop'
|
||||
require 'msf/ui/console/command_dispatcher/payload'
|
||||
require 'msf/ui/console/command_dispatcher/auxiliary'
|
||||
require 'msf/ui/console/command_dispatcher/post'
|
||||
require 'msf/util/document_generator'
|
||||
|
||||
module Msf
|
||||
module Ui
|
||||
@ -743,7 +744,9 @@ class Core
|
||||
def cmd_info_help
|
||||
print_line "Usage: info <module name> [mod2 mod3 ...]"
|
||||
print_line
|
||||
print_line "Optionally the flag '-j' will print the data in json format"
|
||||
print_line "Options:"
|
||||
print_line "* The flag '-j' will print the data in json format"
|
||||
print_line "* The flag '-d' will show the markdown version with a browser"
|
||||
print_line "Queries the supplied module or modules for information. If no module is given,"
|
||||
print_line "show info for the currently active module."
|
||||
print_line
|
||||
@ -754,15 +757,24 @@ class Core
|
||||
#
|
||||
def cmd_info(*args)
|
||||
dump_json = false
|
||||
show_doc = false
|
||||
|
||||
if args.include?('-j')
|
||||
args.delete('-j')
|
||||
dump_json = true
|
||||
end
|
||||
|
||||
if args.include?('-d')
|
||||
args.delete('-d')
|
||||
show_doc = true
|
||||
end
|
||||
|
||||
if (args.length == 0)
|
||||
if (active_module)
|
||||
if dump_json
|
||||
print(Serializer::Json.dump_module(active_module) + "\n")
|
||||
elsif show_doc
|
||||
Msf::Util::DocumentGenerator.get_module_document(active_module)
|
||||
else
|
||||
print(Serializer::ReadableText.dump_module(active_module))
|
||||
end
|
||||
@ -783,6 +795,8 @@ class Core
|
||||
print_error("Invalid module: #{name}")
|
||||
elsif dump_json
|
||||
print(Serializer::Json.dump_module(mod) + "\n")
|
||||
elsif show_doc
|
||||
Msf::Util::DocumentGenerator.get_module_document(mod)
|
||||
else
|
||||
print(Serializer::ReadableText.dump_module(mod))
|
||||
end
|
||||
|
285
lib/msf/util/document_generator.rb
Normal file
285
lib/msf/util/document_generator.rb
Normal file
@ -0,0 +1,285 @@
|
||||
###
|
||||
#
|
||||
# This provides methods to generate documentation for a module.
|
||||
#
|
||||
###
|
||||
|
||||
require 'octokit'
|
||||
require 'nokogiri'
|
||||
require 'redcarpet'
|
||||
require 'net/http'
|
||||
require 'erb'
|
||||
|
||||
module Redcarpet
|
||||
module Render
|
||||
class MsfMdHTML < Redcarpet::Render::HTML
|
||||
def block_code(code, language)
|
||||
"<pre>" \
|
||||
"<code>#{code}</code>" \
|
||||
"</pre>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module Msf
|
||||
module Util
|
||||
module DocumentGenerator
|
||||
|
||||
class HTMLwithPygments < Redcarpet::Render::HTML
|
||||
def block_code(code, language)
|
||||
"Nope"
|
||||
end
|
||||
end
|
||||
|
||||
class DocumentNormalizer
|
||||
|
||||
CSS_BASE_PATH = File.expand_path(File.join(Msf::Config.data_directory, 'markdown.css' ))
|
||||
|
||||
def get_md_content(items)
|
||||
md_to_html(ERB.new(%Q|## #{items[:mod_name]}
|
||||
|
||||
#{normalize_description(items[:mod_description])}
|
||||
|
||||
## Module Name
|
||||
|
||||
#{items[:mod_fullname]}
|
||||
|
||||
## Authors
|
||||
|
||||
#{normalize_authors(items[:mod_authors])}
|
||||
|
||||
<% unless items[:mod_pull_requests].empty? %>
|
||||
## Related Pull Requests
|
||||
|
||||
#{normalize_pull_requests(items[:mod_pull_requests])}
|
||||
<% end %>
|
||||
|
||||
<% unless items[:mod_refs].empty? %>
|
||||
## References
|
||||
|
||||
#{normalize_references(items[:mod_refs])}
|
||||
<% end %>
|
||||
|
||||
## Platforms
|
||||
#{normalize_platforms(items[:mod_platforms])}
|
||||
|
||||
## Reliability
|
||||
#{normalize_rank(items[:mod_rank])}
|
||||
|
||||
## Demo
|
||||
|
||||
#{normalize_demo_output(items[:mod_demo])}
|
||||
|).result(binding()))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def md_to_html(md)
|
||||
md.gsub!(/\x20{12}/, '')
|
||||
r = Redcarpet::Markdown.new(Redcarpet::Render::MsfMdHTML, fenced_code_blocks: true)
|
||||
css_path =
|
||||
%Q|
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="file://#{CSS_BASE_PATH}">
|
||||
</head>
|
||||
<body>
|
||||
#{r.render(md)}
|
||||
</body>
|
||||
</html>
|
||||
|
|
||||
end
|
||||
|
||||
def normalize_pull_requests(pull_requests)
|
||||
formatted_pr = []
|
||||
|
||||
pull_requests.each_pair do |number, pr|
|
||||
formatted_pr << "* <a href=\"https://github.com/rapid7/metasploit-framework/pull/#{number}\">##{number}</a> - #{pr[:title]}"
|
||||
end
|
||||
|
||||
formatted_pr * "\n"
|
||||
end
|
||||
|
||||
def normalize_description(description)
|
||||
Rex::Text.wordwrap(Rex::Text.compress(description))
|
||||
end
|
||||
|
||||
def normalize_authors(authors)
|
||||
if authors.kind_of?(Array)
|
||||
authors.collect { |a| "* #{a}" } * "\n"
|
||||
else
|
||||
authors
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_targets(targets)
|
||||
targets.collect { |c| "* #{c.name}" } * "\n"
|
||||
end
|
||||
|
||||
def normalize_references(refs)
|
||||
refs.collect { |r| "* <a href=\"#{r}\">#{r}</a>" } * "\n"
|
||||
end
|
||||
|
||||
def normalize_platforms(platforms)
|
||||
if platforms.kind_of?(Array)
|
||||
platforms.collect { |p| "* #{p}" } * "\n"
|
||||
else
|
||||
platforms
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_rank(rank)
|
||||
"[#{Msf::RankingName[rank].capitalize}](https://github.com/rapid7/metasploit-framework/wiki/Exploit-Ranking)"
|
||||
end
|
||||
|
||||
def normalize_demo_output(mod)
|
||||
%Q|```
|
||||
msf > use #{mod.fullname}
|
||||
msf #{mod.type}(#{mod.shortname}) > show targets
|
||||
... a list of targets ...
|
||||
msf #{mod.type}(#{mod.shortname}) > set TARGET <target-id>
|
||||
msf #{mod.type}(#{mod.shortname}) > show options
|
||||
... show and set options ...
|
||||
msf #{mod.type}(#{mod.shortname}) > run
|
||||
```|
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class PullRequestFinder
|
||||
class Exception < RuntimeError; end
|
||||
|
||||
MANUAL_BASE_PATH = File.expand_path(File.join(Msf::Config.module_directory, '..', 'documentation', 'modules' ))
|
||||
|
||||
attr_accessor :git_client
|
||||
attr_accessor :repository
|
||||
attr_accessor :branch
|
||||
attr_accessor :owner
|
||||
attr_accessor :git_access_token
|
||||
|
||||
def initialize
|
||||
unless ENV.has_key?('GITHUB_OAUTH_TOKEN')
|
||||
raise PullRequestFinder::Exception, 'GITHUB_OAUTH_TOKEN environment variable not set.'
|
||||
end
|
||||
|
||||
self.owner = 'rapid7'
|
||||
self.repository = "#{owner}/metasploit-framework"
|
||||
self.branch = 'master'
|
||||
self.git_access_token = ENV['GITHUB_OAUTH_TOKEN']
|
||||
self.git_client = Octokit::Client.new(access_token: git_access_token)
|
||||
end
|
||||
|
||||
def search(mod)
|
||||
file_name = get_normalized_module_name(mod)
|
||||
commits = get_commits_from_file(file_name)
|
||||
get_pull_requests_from_commits(commits)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_normalized_module_name(mod)
|
||||
source_fname = mod.method(:initialize).source_location.first
|
||||
source_fname.scan(/(modules.+)/).flatten.first || ''
|
||||
end
|
||||
|
||||
def get_commits_from_file(path)
|
||||
commits = git_client.commits(repository, branch, path: path)
|
||||
if commits.empty?
|
||||
# Possibly the path is wrong.
|
||||
raise PullRequestFinder::Exception, 'No commits found.'
|
||||
end
|
||||
|
||||
commits
|
||||
end
|
||||
|
||||
def get_author(commit)
|
||||
if commit.author
|
||||
return commit.author[:login].to_s
|
||||
end
|
||||
|
||||
''
|
||||
end
|
||||
|
||||
def is_author_blacklisted?(commit)
|
||||
['tabassassin'].include?(get_author(commit))
|
||||
end
|
||||
|
||||
def get_pull_requests_from_commits(commits)
|
||||
pull_requests = {}
|
||||
|
||||
commits.each do |commit|
|
||||
next if is_author_blacklisted?(commit)
|
||||
|
||||
pr = get_pull_request_from_commit(commit)
|
||||
unless pr.empty?
|
||||
pull_requests[pr[:number]] = pr
|
||||
end
|
||||
end
|
||||
|
||||
pull_requests
|
||||
end
|
||||
|
||||
def get_pull_request_from_commit(commit)
|
||||
sha = commit.sha
|
||||
url = URI.parse("https://github.com/#{repository}/branch_commits/#{sha}")
|
||||
cli = Net::HTTP.new(url.host, url.port)
|
||||
cli.use_ssl = true
|
||||
req = Net::HTTP::Get.new(url.request_uri)
|
||||
res = cli.request(req)
|
||||
n = Nokogiri::HTML(res.body)
|
||||
found_pr_link = n.at('li[@class="pull-request"]//a')
|
||||
|
||||
# If there is no PR associated with this commit, it's probably from the SVN days.
|
||||
return {} unless found_pr_link
|
||||
|
||||
href = found_pr_link.attributes['href'].text
|
||||
title = found_pr_link.attributes['title'].text
|
||||
|
||||
# Filter out all the pull requests that do not belong to rapid7.
|
||||
# If this happens, it's probably because the PR was submitted to somebody's fork.
|
||||
return {} unless /^\/#{owner}\// === href
|
||||
|
||||
{ number: href.scan(/\d+$/).flatten.first, title: title }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def self.get_module_document(mod)
|
||||
manual_path = File.join(PullRequestFinder::MANUAL_BASE_PATH, mod.fullname)
|
||||
|
||||
if File.exists?(manual_path)
|
||||
Rex::Compat.open_webrtc_browser("file://#{manual_path}")
|
||||
else
|
||||
pr_finder = PullRequestFinder.new
|
||||
pr = pr_finder.search(mod)
|
||||
n = DocumentNormalizer.new
|
||||
items = {
|
||||
mod_description: mod.description,
|
||||
mod_authors: mod.send(:module_info)['Author'],
|
||||
mod_fullname: mod.fullname,
|
||||
mod_name: mod.name,
|
||||
mod_pull_requests: pr,
|
||||
mod_refs: mod.references,
|
||||
mod_rank: mod.rank,
|
||||
mod_platforms: mod.send(:module_info)['Platform'],
|
||||
mod_options: mod.datastore,
|
||||
mod_demo: mod
|
||||
}
|
||||
|
||||
if mod.respond_to?(:targets) && mod.targets
|
||||
items[:mod_targets] = mod.targets
|
||||
end
|
||||
|
||||
md = n.get_md_content(items)
|
||||
f = Rex::Quickfile.new(["#{mod.shortname}_doc", '.html'])
|
||||
f.write(md)
|
||||
f.close
|
||||
Rex::Compat.open_webrtc_browser("file://#{f.path}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user