1
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:
wchen-r7 2016-02-16 22:44:03 -06:00
parent 95484c81fd
commit b0cfb4aacf
5 changed files with 550 additions and 6 deletions

View File

@ -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

View File

@ -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
View 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;
}

View File

@ -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

View 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