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/plugins/wiki.rb
Views: 11705
##1#2# This plugin requires Metasploit: https://metasploit.com/download3# Current source: https://github.com/rapid7/metasploit-framework4#5##67module Msf8###9#10# This plugin extends the Rex::Text::Table class and provides commands11# that output database information for the current workspace in a wiki12# friendly format13#14# @author Trenton Ivey15# * *email:* ("[email protected]").gsub(/example/,"gmail")16# * *github:* kn017# * *twitter:* trentonivey18###19class Plugin::Wiki < Msf::Plugin2021###22#23# This class implements a command dispatcher that provides commands to24# output database information in a wiki friendly format.25#26###27class WikiCommandDispatcher28include Msf::Ui::Console::CommandDispatcher2930#31# The dispatcher's name.32#33def name34'Wiki'35end3637#38# Returns the hash of commands supported by the wiki dispatcher.39#40def commands41{42'dokuwiki' => 'Outputs data from the current workspace in dokuwiki markup.',43'mediawiki' => 'Outputs data from the current workspace in mediawiki markup.'44}45end4647#48# Outputs database entries as Dokuwiki formatted text by passing the49# arguments to the wiki method with a wiki_type of 'dokuwiki'50# @param [Array<String>] args the arguments passed when the command is51# called52# @see #wiki53#54def cmd_dokuwiki(*args)55wiki('dokuwiki', *args)56end5758#59# Outputs database entries as Mediawiki formatted text by passing the60# arguments to the wiki method with a wiki_type of 'mediawiki'61# @param [Array<String>] args the arguments passed when the command is62# called63# @see #wiki64#65def cmd_mediawiki(*args)66wiki('mediawiki', *args)67end6869#70# This method parses arguments passed from the wiki output commands71# and then formats and displays or saves text according to the72# provided wiki type73#74# @param [String] wiki_type selects the wiki markup lanuguage output to75# use, it can be:76# * dokuwiki77# * mediawiki78#79# @param [Array<String>] args the arguments passed when the command is80# called81#82def wiki(wiki_type, *args)83# Create a table options hash84tbl_opts = {}85# Set some default options for the table hash86tbl_opts[:hosts] = []87tbl_opts[:links] = false88tbl_opts[:wiki_type] = wiki_type89tbl_opts[:heading_size] = 590case wiki_type91when 'dokuwiki'92tbl_opts[:namespace] = 'notes:targets:hosts:'93else94tbl_opts[:namespace] = ''95end9697# Get the table we should be looking at98command = args.shift99if command.nil? || !['creds', 'hosts', 'loot', 'services', 'vulns'].include?(command.downcase)100usage(wiki_type)101return102end103104# Parse the rest of the arguments105while (arg = args.shift)106case arg107when '-o', '--output'108tbl_opts[:file_name] = next_opt(args)109when '-h', '--help'110usage(wiki_type)111return112when '-l', '-L', '--link', '--links'113tbl_opts[:links] = true114when '-n', '-N', '--namespace'115tbl_opts[:namespace] = next_opt(args)116when '-p', '-P', '--port', '--ports'117tbl_opts[:ports] = next_opts(args)118tbl_opts[:ports].map!(&:to_i)119when '-s', '-S', '--search'120tbl_opts[:search] = next_opt(args)121when '-i', '-I', '--heading-size'122heading_size = next_opt(args)123tbl_opts[:heading_size] = heading_size.to_i unless heading_size.nil?124else125# Assume it is a host126rw = Rex::Socket::RangeWalker.new(arg)127if rw.valid?128rw.each do |ip|129tbl_opts[:hosts] << ip130end131else132print_warning "#{arg} is an invalid hostname"133end134end135end136137# Output the table138if respond_to? "#{command}_to_table", true139table = send "#{command}_to_table", tbl_opts140if table.respond_to? "to_#{wiki_type}", true141if tbl_opts[:file_name]142print_status("Wrote the #{command} table to a file as a #{wiki_type} formatted table")143File.open(tbl_opts[:file_name], 'wb') do |f|144f.write(table.send("to_#{wiki_type}"))145end146else147print_line table.send "to_#{wiki_type}"148end149return150end151end152usage(wiki_type)153end154155#156# Gets the next set of arguments when parsing command options157#158# *Note:* This will modify the provided argument list159#160# @param [Array] args the list of unparsed arguments161# @return [Array] the unique list of items before the next '-' in the162# provided array163#164def next_opts(args)165opts = []166while (opt = args.shift)167if opt =~ /^-/168args.unshift opt169break170end171opts.concat(opt.split(','))172end173return opts.uniq174end175176#177# Gets the next argument when parsing command options178#179# *Note:* This will modify the provided argument list180#181# @param [Array] args the list of unparsed arguments182# @return [String, nil] the argument or nil if the argument starts with a '-'183#184def next_opt(args)185return nil if args[0] =~ /^-/186187args.shift188end189190#191# Outputs the help message192#193# @param [String] cmd_name the type of the wiki output command to display194# help for195#196def usage(cmd_name = '<wiki cmd>')197print_line "Usage: #{cmd_name} <table> [options] [IP1 IP2,IPn]"198print_line199print_line 'The first argument must be the type of table to retrieve:'200print_line ' creds, hosts, loot, services, vulns'201print_line202print_line 'OPTIONS:'203print_line ' -l,--link Enables links for host addresses'204print_line ' -n,--namespace <ns> Changes the default namespace for host links'205print_line ' -o,--output <file> Write output to a file'206print_line ' -p,--port <ports> Only return results that relate to given ports'207print_line ' -s,--search <search> Only show results that match the provided text'208print_line ' -i,--heading-size <1-6> Changes the heading size'209print_line ' -h,--help Displays this menu'210print_line211end212213#214# Outputs credentials in the database (within the current workspace) as a Rex table object215# @param [Hash] opts216# @option opts [Array<String>] :hosts contains list of hosts used to limit results217# @option opts [Array<Integer>] :ports contains list of ports used to limit results218# @option opts [String] :search limits results to those containing a provided string219# @return [Rex::Text::Table] table containing credentials220#221def creds_to_table(opts = {})222tbl = Rex::Text::Table.new({ 'Columns' => ['host', 'port', 'user', 'pass', 'type', 'proof', 'active?'] })223tbl.header = 'Credentials'224tbl.headeri = opts[:heading_size]225framework.db.creds.each do |cred|226if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? cred.service.host.address)227next228end229if !opts[:ports].nil? && opts[:ports].none? { |p| cred.service.port.eql? p }230next231end232233address = cred.service.host.address234address = to_wikilink(address, opts[:namespace]) if opts[:links]235row = [236address,237cred.service.port,238cred.user,239cred.pass,240cred.ptype,241cred.proof,242cred.active243]244if opts[:search]245tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }246else247tbl << row248end249end250return tbl251end252253#254# Outputs host information stored in the database (within the current255# workspace) as a Rex table object256# @param [Hash] opts257# @option opts [Array<String>] :hosts contains list of hosts used to limit results258# @option opts [Array<String>] :ports contains list of ports used to limit results259# @option opts [String] :search limits results to those containing a provided string260# @return [Rex::Text::Table] table containing credentials261#262def hosts_to_table(opts = {})263tbl = Rex::Text::Table.new({ 'Columns' => ['address', 'mac', 'name', 'os_name', 'os_flavor', 'os_sp', 'purpose', 'info', 'comments'] })264tbl.header = 'Hosts'265tbl.headeri = opts[:heading_size]266framework.db.hosts.each do |host|267if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? host.address)268next269end270if !opts[:ports].nil? && (host.services.map { |s| s[:port] }).none? { |p| opts[:ports].include? p }271next272end273274address = host.address275address = to_wikilink(address, opts[:namespace]) if opts[:links]276row = [277address,278host.mac,279host.name,280host.os_name,281host.os_flavor,282host.os_sp,283host.purpose,284host.info,285host.comments286]287if opts[:search]288tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }289else290tbl << row291end292end293return tbl294end295296#297# Outputs loot information stored in the database (within the current298# workspace) as a Rex table object299# @param [Hash] opts300# @option opts [Array<String>] :hosts contains list of hosts used to limit results301# @option opts [Array<String>] :ports contains list of ports used to limit results302# @option opts [String] :search limits results to those containing a provided string303# @return [Rex::Text::Table] table containing credentials304#305def loot_to_table(opts = {})306tbl = Rex::Text::Table.new({ 'Columns' => ['host', 'service', 'type', 'name', 'content', 'info', 'path'] })307tbl.header = 'Loot'308tbl.headeri = opts[:heading_size]309framework.db.loots.each do |loot|310if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? loot.host.address)311next312end313if !(opts[:ports].nil? || opts[:ports].empty?) && (loot.service.nil? || loot.service.port.nil? || !opts[:ports].include?(loot.service.port))314next315end316317if loot.service318svc = (loot.service.name || "#{loot.service.port}/#{loot.service.proto}")319end320address = loot.host.address321address = to_wikilink(address, opts[:namespace]) if opts[:links]322row = [323address,324svc || '',325loot.ltype,326loot.name,327loot.content_type,328loot.info,329loot.path330]331if opts[:search]332tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }333else334tbl << row335end336end337return tbl338end339340#341# Outputs service information stored in the database (within the current342# workspace) as a Rex table object343# @param [Hash] opts344# @option opts [Array<String>] :hosts contains list of hosts used to limit results345# @option opts [Array<String>] :ports contains list of ports used to limit results346# @option opts [String] :search limits results to those containing a provided string347# @return [Rex::Text::Table] table containing credentials348#349def services_to_table(opts = {})350tbl = Rex::Text::Table.new({ 'Columns' => ['host', 'port', 'proto', 'name', 'state', 'info'] })351tbl.header = 'Services'352tbl.headeri = opts[:heading_size]353framework.db.services.each do |service|354if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? service.host.address)355next356end357if !(opts[:ports].nil? || opts[:ports].empty?) && opts[:ports].none? { |p| service[:port].eql? p }358next359end360361address = service.host.address362address = to_wikilink(address, opts[:namespace]) if opts[:links]363row = [364address,365service.port,366service.proto,367service.name,368service.state,369service.info370]371if opts[:search]372tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }373else374tbl << row375end376end377return tbl378end379380#381# Outputs vulnerability information stored in the database (within the current382# workspace) as a Rex table object383# @param [Hash] opts384# @option opts [Array<String>] :hosts contains list of hosts used to limit results385# @option opts [Array<String>] :ports contains list of ports used to limit results386# @option opts [String] :search limits results to those containing a provided string387# @return [Rex::Text::Table] table containing credentials388#389def vulns_to_table(opts = {})390tbl = Rex::Text::Table.new({ 'Columns' => ['Title', 'Host', 'Port', 'Info', 'Detail Count', 'Attempt Count', 'Exploited At', 'Updated At'] })391tbl.header = 'Vulns'392tbl.headeri = opts[:heading_size]393framework.db.vulns.each do |vuln|394if !(opts[:hosts].nil? || opts[:hosts].empty?) && !(opts[:hosts].include? vuln.host.address)395next396end397if !(opts[:ports].nil? || opts[:ports].empty?) && opts[:ports].none? { |p| vuln.service.port.eql? p }398next399end400401address = vuln.host.address402address = to_wikilink(address, opts[:namespace]) if opts[:links]403row = [404vuln.name,405address,406(vuln.service ? vuln.service.port : ''),407vuln.info,408vuln.vuln_detail_count,409vuln.vuln_attempt_count,410vuln.exploited_at,411vuln.updated_at,412]413if opts[:search]414tbl << row if row.any? { |r| /#{opts[:search]}/i.match r.to_s }415else416tbl << row417end418end419return tbl420end421422#423# Converts a value to a wiki link424# @param [String] text value to convert to a link425# @param [String] namespace optional namespace to set for the link426# @return [String] the formatted wiki link427def to_wikilink(text, namespace = '')428return '[[' + namespace + text + ']]'429end430431end432433#434# Plugin Initialization435#436437#438# Constructs a new instance of the plugin and registers the console439# dispatcher. It also extends Rex by adding the following methods:440# * Rex::Text::Table.to_dokuwiki441# * Rex::Text::Table.to_mediawiki442#443def initialize(framework, opts)444super445446# Extend Rex::Text::Table class so it can output wiki formats447add_dokuwiki_to_rex448add_mediawiki_to_rex449450# Add the console dispatcher451add_console_dispatcher(WikiCommandDispatcher)452end453454#455# The cleanup routine removes the methods added to Rex by the plugin456# initialization and then removes the console dispatcher457#458def cleanup459# Cleanup methods added to Rex::Text::Table460Rex::Text::Table.class_eval { undef :to_dokuwiki }461Rex::Text::Table.class_eval { undef :to_mediawiki }462# Deregister the console dispatcher463remove_console_dispatcher('Wiki')464end465466#467# Returns the plugin's name.468#469def name470'wiki'471end472473#474# This method returns a brief description of the plugin. It should be no475# more than 60 characters, but there are no hard limits.476#477def desc478'Outputs stored database values from the current workspace into DokuWiki or MediaWiki format'479end480481#482# The following methods are added here to keep the initialize method483# readable484#485486#487# Extends Rex tables to be able to create Dokuwiki tables488#489def add_dokuwiki_to_rex490Rex::Text::Table.class_eval do491def to_dokuwiki492str = prefix.dup493# Print the header if there is one. Use headeri to determine wiki paragraph level494if header495level = '=' * headeri496str << level + header + level + "\n"497end498# Add the column names to the top of the table499columns.each do |col|500str << '^ ' + col.to_s + ' '501end502str << "^\n" unless columns.count.eql? 0503# Fill out the rest of the table with rows504rows.each do |row|505row.each do |val|506cell = val.to_s507cell = "<nowiki>#{cell}</nowiki>" if cell.include? '|'508str << '| ' + cell + ' '509end510str << "|\n" unless rows.count.eql? 0511end512return str513end514end515end516517#518# Extends Rex tables to be able to create Mediawiki tables519#520def add_mediawiki_to_rex521Rex::Text::Table.class_eval do522def to_mediawiki523str = prefix.dup524# Print the header if there is one. Use headeri to determine wiki525# headline level. Mediawiki does headlines a bit backwards so that526# the header level isn't limited. This results in the need to 'flip'527# the headline length to standardize it.528if header529if headeri <= 6530level = '=' * (-headeri + 7)531str << "#{level} #{header} #{level}"532else533str << header.to_s534end535str << "\n"536end537# Setup the table with some standard formatting options538str << "{|class=\"wikitable\"\n"539# Output formatted column names as the first row540unless columns.count.eql? 0541str << '!'542str << columns.join('!!')543str << "\n"544end545# Add the rows to the table546unless rows.count.eql? 0547rows.each do |row|548str << "|-\n|"549# Try and prevent formatting tags from causing problems550bad = ['&', '<', '>', '"', "'", '/']551r = row.join('|| ')552r.each_char do |c|553if bad.include? c554str << Rex::Text.html_encode(c)555else556str << c557end558end559str << "\n"560end561end562# Finish up the table563str << '|}'564return str565end566end567end568569end570end571572573