File: magictreexml.rb

package info (click to toggle)
whatweb 0.6.1-1
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 23,948 kB
  • sloc: ruby: 43,493; sh: 213; makefile: 41
file content (148 lines) | stat: -rw-r--r-- 5,815 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

# MagicTree #
# Output XML file in MagicTree XML format
class LoggingMagicTreeXML < Logging
  def initialize(f = STDOUT)
    super
    @substitutions = { '&' => '&amp;', '"' => '&quot;', '<' => '&lt;', '>' => '&gt;' }

    # only output <?xml line if it's a new file or STDOUT
    @f.puts '<?xml version="1.0" encoding="UTF-8"?>' if @f.empty?
    @f.puts '<magictree class="MtBranchObject">'
  end

  def close
    @f.puts '</magictree>'
    @f.close
  end

  def escape(t)
    text = t.to_s.dup
    # use sort_by so that & is before &quot;, etc.
    @substitutions.sort_by { |a, _| a == '&' ? 0 : 1 }.map { |from, to| text.gsub!(from, to) }

    # Encode all special characters
    # More info: http://www.asciitable.com/
    r = /[^\x20-\x5A\x5E-\x7E]/

    # based on code for CGI.escape
    text.gsub!(r) { |x| "%#{x.unpack('H2' * x.size).join('%').upcase}" }
    text
  end

  def out(target, _status, results)
    $semaphore.synchronize do
      # Parse target URL and initialize host node details
      uri = URI.parse(target.to_s)
      @host_os = []
      @host_port = uri.port
      @host_scheme = uri.scheme

      # Set host node details
      if uri.host =~ /^[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}$/i
        @host_ip = uri.host
        @host_name = nil
      else
        @host_name = uri.host
        @host_ip = nil
      end

      # Loop through plugin results # get host IP, country and OS
      results.each do |plugin_name, plugin_results|
        next if plugin_results.empty?
        # Host IP
        @host_ip = plugin_results.map { |x| x[:string] unless x[:string].nil? }.to_s if plugin_name =~ /^IP$/
        # Host Country
        @host_country = plugin_results.map { |x| x[:string] unless x[:string].nil? }.to_s if plugin_name =~ /^Country$/
        # Host OS
        @host_os << plugin_results.map { |x| x[:os] unless x[:os].class == Regexp }.to_s
      end

      # testdata branch
      @f.write "<testdata class=\"MtBranchObject\"><host>#{escape(@host_ip)}"

      # hostname
      @f.write "<hostname>#{escape(@host_name)}</hostname>" unless @host_name.nil?

      # os
      @host_os.compact.sort.uniq.map { |x| @f.write "<os>#{escape(x.to_s)}</os>" unless x.empty? } unless @host_os.empty?

      # country and port nodes
      @f.write "<country>#{escape(@host_country)}</country><ipproto>tcp<port>#{escape(@host_port)}<state>open</state>"

      # https
      @f.write '<tunnel>ssl' if @host_scheme == 'https'

      # Service node # Loop through remaining results
      # software, headers, firmware, modules, etc. are all related to a specific URL and therefore are placed under the url node
      @f.puts '<service>http'
      results.each do |plugin_name, plugin_results|
        next unless !plugin_results.empty? && plugin_name !~ /^IP$/ && plugin_name !~ /^Country$/
        certainty = plugin_results.map { |x| x[:certainty] unless x[:certainty].class == Regexp }.flatten.compact.sort.uniq.last
        versions = plugin_results.map { |x| x[:version] unless x[:version].class == Regexp }.flatten.compact.sort.uniq
        strings = plugin_results.map { |x| x[:string] unless x[:string].class == Regexp }.flatten.compact.sort.uniq
        models = plugin_results.map { |x| x[:model] unless x[:model].class == Regexp }.flatten.compact.sort.uniq
        firmwares = plugin_results.map { |x| x[:firmware] unless x[:firmware].class == Regexp }.flatten.compact.sort.uniq
        filepaths = plugin_results.map { |x| x[:filepath] unless x[:filepath].class == Regexp }.flatten.compact.sort.uniq
        accounts = plugin_results.map { |x| x[:account] unless x[:account].class == Regexp }.flatten.compact.sort.uniq
        modules = plugin_results.map { |x| x[:module] unless x[:module].class == Regexp }.flatten.compact.sort.uniq

        # URL node # plugin node
        @f.write "<url>#{escape(target)}<#{escape(plugin_name)}>"

        # Print certainty if certainty < 100
        if certainty && certainty < 100
          @f.write "<certainty>#{escape(certainty)}</certainty>"
        end

        # Strings
        unless strings.empty?
          strings.map { |x| @f.write escape(x).to_s } unless plugin_name =~ /^IP$/ || plugin_name =~ /^Country$/
        end

        # Versions
        unless versions.empty?
          versions.map { |x| @f.write "<version>#{escape(x)}</version>" }
        end

        # Models
        unless models.empty?
          models.map { |x| @f.puts "<model>#{escape(x)}</model>" }
        end

        # Firmware
        unless firmwares.empty?
          firmwares.map { |x| @f.write "<firmware>#{escape(x)}</firmware>" }
        end

        # Modules
        unless modules.empty?
          modules.map { |x| @f.write "<module>#{escape(x)}</module>" } unless plugin_name =~ /^Country$/
        end

        # Accounts # MagicTree generally uses "user" nodes for account
        unless accounts.empty?
          accounts.map { |x| @f.write "<user>#{escape(x)}</user>" }
        end

        # Local File Filepaths # Not to be confused with file paths in the web root which are returned in Strings
        unless filepaths.empty?
          filepaths.map { |x| @f.write "<filepath>#{escape(x)}</filepath>" }
        end

        # debug node # Uncomment to debug
        # @f.write "<debug title=\"WhatWeb\" class=\"MtTextObject\">Identifying: #{escape(target)}\nHTTP-Status: #{escape(status)}"
        # @f.write "#{escape(results.pretty_inspect)}" unless results.empty?
        # @f.write "</debug>"

        # Close plugin name and URL nodes
        @f.write "</#{escape(plugin_name)}></url>"
      end
      @f.write '</service>'
      # end https node
      @f.write '</tunnel>' if @host_scheme == 'https'
      # testdata # close port, host and testdata nodes
      @f.write '</port></ipproto></host></testdata>'
    end
  end
end