require 'redcarpet' require 'erb' module Redcarpet module Render class MsfMdHTML < Redcarpet::Render::HTML def block_code(code, language) code = $1 if code =~ /^(.+)<\/ruby>/m "
" \
          "#{CGI.escape_html(code)}" \
        "
" end def list(content, list_type) if list_type == :unordered && content.scan(/
  • /).flatten.length > 15 %Q|

      #{content}

      | elsif list_type == :unordered %Q|| elsif list_type == :ordered %Q|
        #{content}
      | else content end end def header(text, header_level) %Q|#{text}
      | end def table(header, body) %Q|#{header}#{body}

      | end end end end module Msf module Util module DocumentGenerator class DocumentNormalizer # # Markdown templates # CSS_BASE_PATH = 'markdown.css' HTML_TEMPLATE = 'html_template.erb' TEMPLATE_PATH = 'default_template.erb' # # Demo templates # REMOTE_EXPLOIT_DEMO_TEMPLATE = 'remote_exploit_demo_template.erb' BES_DEMO_TEMPLATE = 'bes_demo_template.erb' HTTPSERVER_DEMO_TEMPLATE = 'httpserver_demo_template.erb' GENERIC_DEMO_TEMPLATE = 'generic_demo_template.erb' LOCALEXPLOIT_DEMO_TEMPLATE = 'localexploit_demo_template.erb' POST_DEMO_TEMPLATE = 'post_demo_template.erb' AUXILIARY_SCANNER_DEMO_TEMPLATE = 'auxiliary_scanner_template.erb' PAYLOAD_DEMO_TEMPLATE = 'payload_demo_template.erb' EVASION_DEMO_TEMPLATE = 'evasion_demo_template.erb' # Special messages NO_CVE_MESSAGE = %Q|CVE: [Not available](https://docs.metasploit.com/docs/using-metasploit/other/why-cve-is-not-available.html)| # Returns the module document in HTML form. # # @param items [Hash] Items to be documented. # @param kb [String] Additional information to be added in the doc. # @return [String] HTML. def get_md_content(items, kb) @md_template ||= lambda { template = '' path = File.expand_path(File.join(Msf::Config.data_directory, 'markdown_doc', TEMPLATE_PATH)) File.open(path, 'rb') { |f| template = f.read } return template }.call md_to_html(ERB.new(@md_template).result(binding()), kb) end private # Returns the CSS code for the HTML document. # # @return [String] def load_css @css ||= lambda { data = '' path = File.expand_path(File.join(Msf::Config.data_directory, 'markdown_doc', CSS_BASE_PATH)) File.open(path, 'rb') { |f| data = f.read } return data }.call end # Returns the HTML document. # # @param md [String] Markdown document. # @param kb [String] Additional information to add. # @return [String] HTML document. def md_to_html(md, kb) extensions = { escape_html: true } render_options = { fenced_code_blocks: true, no_intra_emphasis: true, tables: true } html_renderer = Redcarpet::Render::MsfMdHTML.new(extensions) r = Redcarpet::Markdown.new(html_renderer, render_options) ERB.new(@html_template ||= lambda { html_template = '' path = File.expand_path(File.join(Msf::Config.data_directory, 'markdown_doc', HTML_TEMPLATE)) File.open(path, 'rb') { |f| html_template = f.read } return html_template }.call).result(binding()) end # Returns the markdown format for pull requests. # # @param pull_requests [Hash] Pull requests # @return [String] def normalize_pull_requests(pull_requests) if pull_requests.kind_of?(PullRequestFinder::Exception) error = pull_requests.message case error when /GITHUB_OAUTH_TOKEN/i error << " [See how](" error << "https://help.github.com/articles/creating-an-access-token-for-command-line-use/" error << ")" end return error end formatted_pr = [] pull_requests.each_pair do |number, pr| formatted_pr << "* [##{number} #{pr[:title]}](https://github.com/rapid7/metasploit-framework/pull/#{number})" end formatted_pr * "\n" end # Returns the markdown format for module description. # # @param description [String] Module description. # @return [String] def normalize_description(description) Rex::Text.wordwrap(Rex::Text.compress(description)) end # Returns the markdown format for module authors. # # @param authors [Array, String] Module Authors # @return [String] def normalize_authors(authors) if authors.kind_of?(Array) authors.collect { |a| "* #{CGI::escapeHTML(a)}" } * "\n" else authors end end # Returns the markdown format for module targets. # # @param targets [Array] Module targets. # @return [String] def normalize_targets(targets) targets.collect { |c| "* #{c.name}" } * "\n" end # Returns the markdown format for module AttackerKB references. # # @param refs [Array] AttackerKB references , with the CVE as the key and the link as the value. # @return [String] def normalize_attackerkb_references(refs) normalized = '' # Grabs module CVE's if available and adds each AttackerKB link to the hash refs.each do |ref| next unless ref.ctx_id == 'CVE' cve = "#{ref.ctx_id}-#{ref.ctx_val}" link = "https://attackerkb.com/topics/#{ref.ctx_id}-#{ref.ctx_val}?referrer=msfconsole" normalized << "* [#{cve}](#{link})\n" end normalized end # Returns the markdown format for module references. # # @param refs [Array] Module references. # @return [String] def normalize_references(refs) normalized = '' cve_collection = refs.select { |r| r.ctx_id.match(/^cve$/i) } if cve_collection.empty? normalized << "* #{NO_CVE_MESSAGE}\n" end refs.each do |ref| case ref.ctx_id when 'MSB' normalized << "* [#{ref.ctx_val}](#{ref.site})" when 'URL' normalized << "* [#{ref.site}](#{ref.site})" when 'OSVDB' normalized << "* #{ref.site.to_s}" when 'US-CERT-VU' normalized << "* [VU##{ref.ctx_val}](#{ref.site})" when 'CVE', 'cve' if !cve_collection.empty? && ref.ctx_val.blank? normalized << "* #{NO_CVE_MESSAGE}" else normalized << "* [#{ref.ctx_id}-#{ref.ctx_val}](#{ref.site})" end else normalized << "* [#{ref.ctx_id}-#{ref.ctx_val}](#{ref.site})" end normalized << "\n" end normalized end # Returns the markdown format for module platforms. # # @param platforms [Array, String] Module platforms. # @return [String] def normalize_platforms(platforms) if platforms.kind_of?(Array) platforms.collect { |p| "* #{p}" } * "\n" else platforms end end # Returns a parsed demo ERB template. # # @param mod [Msf::Module] Metasploit module. # @param path [String] Template path. # @return [String] def load_demo_template(mod, path) data = '' path = File.expand_path(File.join(Msf::Config.data_directory, 'markdown_doc', path)) File.open(path, 'rb') { |f| data = f.read } ERB.new(data).result(binding()) end # Returns whether the module is a remote exploit or not. # # @param mod [Msf::Module] Metasploit module. # @return [TrueClass] Module is a remote exploit. # @return [FalseClass] Module is not really a remote exploit. def is_remote_exploit?(mod) # It's actually a little tricky to determine this, so we'll try to be as # specific as possible. Rather have false negatives than false positives, # because the worst case would be using the generic demo template. mod.type == 'exploit' && # Must be an exploit mod.kind_of?(Msf::Exploit::Remote) && # Should always have this !mod.kind_of?(Msf::Exploit::FILEFORMAT) && # Definitely not a file format !mod.kind_of?(Msf::Exploit::Remote::TcpServer) && # If there is a server mixin, things might get complicated mod.options['DisablePayloadHandler'] # Must allow this option end # Returns a demo template suitable for the module. Currently supported templates: # BrowserExploitServer modules, HttpServer modules, local exploit modules, post # modules, payloads, auxiliary scanner modules. # # @param mod [Msf::Module] Metasploit module. # @return [String] def normalize_demo_output(mod) if mod.kind_of?(Msf::Exploit::Remote::BrowserExploitServer) && mod.shortname != 'browser_autopwn2' load_demo_template(mod, BES_DEMO_TEMPLATE) elsif mod.kind_of?(Msf::Exploit::Remote::HttpServer) load_demo_template(mod, HTTPSERVER_DEMO_TEMPLATE) elsif mod.kind_of?(Msf::Exploit::Local) load_demo_template(mod, LOCALEXPLOIT_DEMO_TEMPLATE) elsif mod.kind_of?(Msf::Post) load_demo_template(mod, POST_DEMO_TEMPLATE) elsif mod.kind_of?(Msf::Payload) load_demo_template(mod, PAYLOAD_DEMO_TEMPLATE) elsif mod.kind_of?(Msf::Auxiliary::Scanner) load_demo_template(mod, AUXILIARY_SCANNER_DEMO_TEMPLATE) elsif is_remote_exploit?(mod) load_demo_template(mod, REMOTE_EXPLOIT_DEMO_TEMPLATE) elsif mod.kind_of?(Msf::Evasion) load_demo_template(mod, EVASION_DEMO_TEMPLATE) else load_demo_template(mod, GENERIC_DEMO_TEMPLATE) end end end end end end