intrigue-ident 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +29 -0
  4. data/ident.rb +269 -0
  5. data/intrigue-ident.gemspec +22 -0
  6. data/lib/check_factory.rb +22 -0
  7. data/lib/checks/akamai.rb +22 -0
  8. data/lib/checks/amazon.rb +26 -0
  9. data/lib/checks/aruba.rb +21 -0
  10. data/lib/checks/asp_net.rb +68 -0
  11. data/lib/checks/atlassian.rb +55 -0
  12. data/lib/checks/base.rb +13 -0
  13. data/lib/checks/chef.rb +31 -0
  14. data/lib/checks/cisco.rb +33 -0
  15. data/lib/checks/citrix.rb +24 -0
  16. data/lib/checks/cloudflare.rb +59 -0
  17. data/lib/checks/cloudfront.rb +41 -0
  18. data/lib/checks/cpanel.rb +23 -0
  19. data/lib/checks/django.rb +22 -0
  20. data/lib/checks/drupal.rb +26 -0
  21. data/lib/checks/f5.rb +24 -0
  22. data/lib/checks/fastly.rb +22 -0
  23. data/lib/checks/generic.rb +23 -0
  24. data/lib/checks/gitlab.rb +22 -0
  25. data/lib/checks/google.rb +23 -0
  26. data/lib/checks/grafana.rb +22 -0
  27. data/lib/checks/jenkins.rb +40 -0
  28. data/lib/checks/joomla.rb +23 -0
  29. data/lib/checks/limesuvey.rb +22 -0
  30. data/lib/checks/lithium.rb +30 -0
  31. data/lib/checks/magento.rb +22 -0
  32. data/lib/checks/mcafee.rb +22 -0
  33. data/lib/checks/mediawiki.rb +38 -0
  34. data/lib/checks/microsoft.rb +69 -0
  35. data/lib/checks/nagios.rb +22 -0
  36. data/lib/checks/oracle.rb +38 -0
  37. data/lib/checks/palo_alto.rb +23 -0
  38. data/lib/checks/pardot.rb +22 -0
  39. data/lib/checks/pfsense.rb +25 -0
  40. data/lib/checks/phpmyadmin.rb +22 -0
  41. data/lib/checks/rabbitmq.rb +29 -0
  42. data/lib/checks/spring.rb +31 -0
  43. data/lib/checks/team_city.rb +22 -0
  44. data/lib/checks/telerik.rb +25 -0
  45. data/lib/checks/tomcat.rb +22 -0
  46. data/lib/checks/varnish.rb +27 -0
  47. data/lib/checks/wordpress.rb +120 -0
  48. data/lib/checks/wp_engine.rb +22 -0
  49. metadata +133 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b379c724034923eb05671859aaaaa17ad8ee2be348bdf4c272c428c16121d3fb
4
+ data.tar.gz: 138245f1078a14b88fa1651fbd61fa8be202c7bc92fe13a9cae90abf91934415
5
+ SHA512:
6
+ metadata.gz: e7330591c145a0357ca6ded382d1407bff366fab69146f1cf73b8f1cb9aab1e8fca6c3798c7d462753405870def4d115f524f11a0cf022b4ee57eb82898d7c3a
7
+ data.tar.gz: 55d7000e202d10a0fb7ce64d34f12163ef73d4caf244ddab04c5d1add173e1db3caba6b5e3ada16bc9b875e74dfc096c0485e53bf60184ca11574f0f75bcf9f5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ ruby '2.5.1'
3
+
4
+ gem "rspec"
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.3)
5
+ rspec (3.7.0)
6
+ rspec-core (~> 3.7.0)
7
+ rspec-expectations (~> 3.7.0)
8
+ rspec-mocks (~> 3.7.0)
9
+ rspec-core (3.7.1)
10
+ rspec-support (~> 3.7.0)
11
+ rspec-expectations (3.7.0)
12
+ diff-lcs (>= 1.2.0, < 2.0)
13
+ rspec-support (~> 3.7.0)
14
+ rspec-mocks (3.7.0)
15
+ diff-lcs (>= 1.2.0, < 2.0)
16
+ rspec-support (~> 3.7.0)
17
+ rspec-support (3.7.1)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ rspec
24
+
25
+ RUBY VERSION
26
+ ruby 2.5.1p57
27
+
28
+ BUNDLED WITH
29
+ 1.16.1
data/ident.rb ADDED
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env ruby
2
+ require 'net/http'
3
+ require 'openssl'
4
+ require 'zlib'
5
+
6
+ require_relative 'lib/check_factory'
7
+ require_relative 'lib/checks/base'
8
+ check_folder = File.expand_path('lib/checks', File.dirname(__FILE__)) # get absolute directory
9
+ Dir["#{check_folder}/*.rb"].each { |file| require_relative file }
10
+
11
+ module Intrigue
12
+ module Ident
13
+
14
+ VERSION=0.1
15
+
16
+ def generate_requests_and_check(url)
17
+
18
+ results = []
19
+
20
+ # gather all fingeprints for each product
21
+ # this will look like an array of checks, each with a uri and a SET of checks
22
+ generated_checks = Intrigue::Ident::CheckFactory.all.map{|x| x.new.generate_checks(url) }.flatten
23
+
24
+ # group by the uris, with the associated checks
25
+ # TODO - this only currently supports the first path of the group!!!!
26
+ grouped_generated_checks = generated_checks.group_by{|x| x[:paths].first }
27
+
28
+ # call the check on each uri
29
+ grouped_generated_checks.each do |ggc|
30
+
31
+ target_url = ggc.first
32
+
33
+ # get the response
34
+ response = _http_request :get, "#{target_url}"
35
+
36
+ unless response
37
+ puts "Unable to get a response at: #{target_url}, failing"
38
+ return nil
39
+ end
40
+
41
+ # Go ahead and match it up if we got a response!
42
+ if response
43
+ # call each check, collecting the product if it's a match
44
+ ggc.last.each do |check|
45
+ results << _check_response(check, response)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Return all matches, minus the nils (non-matches)
51
+ results.compact
52
+ end
53
+
54
+ private
55
+
56
+ # this method takes a check and returns a ~match object if it matches, otherwise
57
+ # returns nil.
58
+ def _check_response(check, response)
59
+
60
+ # if type "content", do the content check
61
+ if check[:type] == :content_body
62
+ match = {
63
+ :version => (check[:dynamic_version].call(response) if check[:dynamic_version]) || check[:version],
64
+ :name => check[:name],
65
+ :match => check[:type],
66
+ :hide => check[:hide]
67
+ } if "#{response.body}" =~ check[:content]
68
+
69
+ elsif check[:type] == :content_headers
70
+
71
+ # construct the headers into a big string block
72
+ header_string = ""
73
+ response.each_header do |h,v|
74
+ header_string << "#{h}: #{v}\n"
75
+ end
76
+
77
+ match = {
78
+ :version => (check[:dynamic_version].call(response) if check[:dynamic_version]) || check[:version],
79
+ :name => check[:name],
80
+ :match => check[:type],
81
+ :hide => check[:hide]
82
+ } if header_string =~ check[:content]
83
+
84
+ elsif check[:type] == :content_cookies
85
+ # Check only the set-cookie header
86
+ match = {
87
+ :version => (check[:dynamic_version].call(response) if check[:dynamic_version]) || check[:version],
88
+ :name => check[:name],
89
+ :match => check[:type],
90
+ :hide => check[:hide]
91
+ } if response.header['set-cookie'] =~ check[:content]
92
+
93
+ elsif check[:type] == :checksum_body
94
+ match = {
95
+ :version => (check[:dynamic_version].call(response) if check[:dynamic_version]) || check[:version],
96
+ :name => check[:name],
97
+ :match => check[:type],
98
+ :hide => check[:hide]
99
+ } if Digest::MD5.hexdigest("#{response.body}") == check[:checksum]
100
+ end
101
+ match
102
+ end
103
+
104
+ def _http_request(method, uri_string, credentials=nil, headers={}, data=nil, limit = 10, open_timeout=15, read_timeout=15)
105
+
106
+ response = nil
107
+ begin
108
+
109
+ # set user agent
110
+ headers["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36"
111
+
112
+ attempts=0
113
+ max_attempts=10
114
+ found = false
115
+
116
+ uri = URI.parse uri_string
117
+
118
+ unless uri
119
+ _log error "Unable to parse URI from: #{uri_string}"
120
+ return
121
+ end
122
+
123
+ until( found || attempts >= max_attempts)
124
+ #puts "Getting #{uri}, attempt #{attempts}"
125
+ attempts+=1
126
+
127
+ # proxy configuration, disabled for now
128
+ #if $config["http_proxy"]
129
+ # proxy_addr = $config["http_proxy"]["host"]
130
+ # proxy_port = $config["http_proxy"]["port"]
131
+ # proxy_user = $config["http_proxy"]["user"]
132
+ # proxy_pass = $config["http_proxy"]["pass"]
133
+ #end
134
+ proxy_addr = nil
135
+ proxy_port = nil
136
+
137
+
138
+
139
+ # set options
140
+ opts = {}
141
+ if uri.instance_of? URI::HTTPS
142
+ opts[:use_ssl] = true
143
+ opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
144
+ end
145
+
146
+ http = Net::HTTP.start(uri.host, uri.port, proxy_addr, proxy_port, opts)
147
+ #http.set_debug_output($stdout) if _get_system_config "debug"
148
+ http.read_timeout = 20
149
+ http.open_timeout = 20
150
+
151
+ path = "#{uri.path}"
152
+ path = "/" if path==""
153
+
154
+ # add in the query parameters
155
+ if uri.query
156
+ path += "?#{uri.query}"
157
+ end
158
+
159
+ ### ALLOW DIFFERENT VERBS HERE
160
+ if method == :get
161
+ request = Net::HTTP::Get.new(uri)
162
+ elsif method == :post
163
+ # see: https://coderwall.com/p/c-mu-a/http-posts-in-ruby
164
+ request = Net::HTTP::Post.new(uri)
165
+ request.body = data
166
+ elsif method == :head
167
+ request = Net::HTTP::Head.new(uri)
168
+ elsif method == :propfind
169
+ request = Net::HTTP::Propfind.new(uri.request_uri)
170
+ request.body = "Here's the body." # Set your body (data)
171
+ request["Depth"] = "1" # Set your headers: one header per line.
172
+ elsif method == :options
173
+ request = Net::HTTP::Options.new(uri.request_uri)
174
+ elsif method == :trace
175
+ request = Net::HTTP::Trace.new(uri.request_uri)
176
+ request.body = "intrigue"
177
+ end
178
+ ### END VERBS
179
+
180
+ # set the headers
181
+ headers.each do |k,v|
182
+ request[k] = v
183
+ end
184
+
185
+ # handle credentials
186
+ #if credentials
187
+ # request.basic_auth(credentials[:username],credentials[:password])
188
+ #end
189
+
190
+ # get the response
191
+ response = http.request(request)
192
+
193
+ if response.code=="200"
194
+ break
195
+ end
196
+
197
+ if (response.header['location']!=nil)
198
+ newuri=URI.parse(response.header['location'])
199
+ if(newuri.relative?)
200
+ newuri=uri+response.header['location']
201
+ end
202
+ uri=newuri
203
+
204
+ else
205
+ found=true #resp was 404, etc
206
+ end #end if location
207
+ end #until
208
+
209
+ ### TODO - this code may be be called outside the context of a task,
210
+ ### meaning @task_result is not available to it. Below, we check to
211
+ ### make sure that it exists before attempting to log anything,
212
+ ### but there may be a cleaner way to do this (hopefully?). Maybe a
213
+ ### global logger or logging queue?
214
+ ###
215
+ #rescue TypeError
216
+ # # https://github.com/jaimeiniesta/metainspector/issues/125
217
+ # puts "TypeError - unknown failure"
218
+ rescue ArgumentError => e
219
+ puts "Unable to open connection: #{e}"
220
+ rescue Net::OpenTimeout => e
221
+ puts "Timeout : #{e}"
222
+ rescue Net::ReadTimeout => e
223
+ puts "Timeout : #{e}"
224
+ rescue Errno::ETIMEDOUT => e
225
+ puts "Timeout : #{e}"
226
+ rescue Errno::EINVAL => e
227
+ puts "Unable to connect: #{e}"
228
+ rescue Errno::ENETUNREACH => e
229
+ puts "Unable to connect: #{e}"
230
+ rescue Errno::EHOSTUNREACH => e
231
+ puts "Unable to connect: #{e}"
232
+ rescue URI::InvalidURIError => e
233
+ #
234
+ # XXX - This is an issue. We should catch this and ensure it's not
235
+ # due to an underscore / other acceptable character in the URI
236
+ # http://stackoverflow.com/questions/5208851/is-there-a-workaround-to-open-urls-containing-underscores-in-ruby
237
+ #
238
+ puts "Unable to request URI: #{uri} #{e}"
239
+ rescue OpenSSL::SSL::SSLError => e
240
+ puts "SSL connect error : #{e}"
241
+ rescue Errno::ECONNREFUSED => e
242
+ puts "Unable to connect: #{e}"
243
+ rescue Errno::ECONNRESET => e
244
+ puts "Unable to connect: #{e}"
245
+ rescue Net::HTTPBadResponse => e
246
+ puts "Unable to connect: #{e}"
247
+ rescue Zlib::BufError => e
248
+ puts "Unable to connect: #{e}"
249
+ rescue Zlib::DataError => e # "incorrect header check - may be specific to ruby 2.0"
250
+ puts "Unable to connect: #{e}"
251
+ rescue EOFError => e
252
+ puts "Unable to connect: #{e}"
253
+ rescue SocketError => e
254
+ puts "Unable to connect: #{e}"
255
+ #rescue SystemCallError => e
256
+ # puts "Unable to connect: #{e}"
257
+ #rescue ArgumentError => e
258
+ # puts "Argument Error: #{e}"
259
+ rescue Encoding::InvalidByteSequenceError => e
260
+ puts "Encoding error: #{e}"
261
+ rescue Encoding::UndefinedConversionError => e
262
+ puts "Encoding error: #{e}"
263
+ end
264
+
265
+ response
266
+ end
267
+
268
+ end
269
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ require './ident'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "intrigue-ident"
6
+ spec.version = Intrigue::Ident::VERSION
7
+ spec.authors = ["jcran"]
8
+ spec.email = ["jcran@intrigue.io"]
9
+
10
+ spec.summary = %q{Fingerprinter for Intrigue Data}
11
+ spec.description = %q{Fingerprinter for Intrigue Data}
12
+ spec.homepage = "https://intrigue.io"
13
+ spec.license = "BSD"
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ spec.bindir = "exe"
16
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.11"
20
+ spec.add_development_dependency "rake", "~> 10.0"
21
+ spec.add_development_dependency "rspec", "~> 3.0"
22
+ end
@@ -0,0 +1,22 @@
1
+ module Intrigue
2
+ module Ident
3
+ class CheckFactory
4
+
5
+ #
6
+ # Register a new handler
7
+ #
8
+ def self.register(klass)
9
+ @checks = [] unless @checks
10
+ @checks << klass if klass
11
+ end
12
+
13
+ #
14
+ # Provide the full list of checks
15
+ #
16
+ def self.all
17
+ @checks
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Intrigue
2
+ module Ident
3
+ module Check
4
+ class Akamai < Intrigue::Ident::Check::Base
5
+
6
+ def generate_checks(url)
7
+ [
8
+ {
9
+ :name => "Akamai",
10
+ :description => "Akamai Missing Uri",
11
+ :version => nil,
12
+ :type => :content_body,
13
+ :content => /The requested URL "&#91;no&#32;URL&#93;", is invalid.<p>/,
14
+ :hide => true,
15
+ :paths => ["#{url}"]
16
+ }
17
+ ]
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ module Intrigue
2
+ module Ident
3
+ module Check
4
+ class Amazon < Intrigue::Ident::Check::Base
5
+
6
+ def generate_checks(uri)
7
+ [
8
+ {
9
+ :name => "Amazon ELB",
10
+ :description => "Amazon Elastic Load Balancer",
11
+ :url => "https://aws.amazon.com/elasticloadbalancing/",
12
+ :version => nil,
13
+ :tags => ["error_page"],
14
+ :type => :content_headers,
15
+ :content => /awselb\/\d.\d/,
16
+ :hide => true,
17
+ :dynamic_version => lambda { |x| x["server"].match(/awselb\/(\d.\d)/).captures[0] },
18
+ :verify_sites => ["http://52.4.103.22:80"],
19
+ :paths => ["#{uri}"]
20
+ }
21
+ ]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module Intrigue
2
+ module Ident
3
+ module Check
4
+ class Aruba < Intrigue::Ident::Check::Base
5
+
6
+ def generate_checks(url)
7
+ [
8
+ {
9
+ :name => "Aruba Wireless Controller",
10
+ :description => "Aruba Wireless Controller",
11
+ :version => nil,
12
+ :type => :content_body,
13
+ :content => /arubalp=/,
14
+ :paths => ["#{url}"]
15
+ }
16
+ ]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,68 @@
1
+ module Intrigue
2
+ module Ident
3
+ module Check
4
+ class AspNet < Intrigue::Ident::Check::Base
5
+
6
+ def generate_checks(uri)
7
+ [
8
+ {
9
+ :name => "ASP.NET",
10
+ :description => "ASP.Net Error Message",
11
+ :version => nil,
12
+ :tags => ["error_page"],
13
+ :type => :content_body,
14
+ :content => /^.*ASP.NET is configured.*$/i,
15
+ :dynamic_version => lambda{|x| x.body.scan(/ASP.NET Version:(.*)$/)[0].first.chomp },
16
+ :paths => ["#{uri}"]
17
+ },
18
+ {
19
+ :name => "ASP.NET",
20
+ :description => "X-AspNet Header",
21
+ :version => nil,
22
+ :type => :content_headers,
23
+ :content => /^x-aspnet-version:.*$/i,
24
+ :dynamic_version => lambda{|x| x.body.scan(/ASP.NET Version:(.*)$/i)[0].first.chomp if x.body.scan(/ASP.NET Version:(.*)$/i)[0] },
25
+ :paths => ["#{uri}"]
26
+ },
27
+ {
28
+ :name => "ASP.NET",
29
+ :description => "Asp.Net Default Cookie",
30
+ :version => nil,
31
+ :type => :content_cookies,
32
+ :content => /ASPSESSIONID.*$/i,
33
+ :paths => ["#{uri}"]
34
+ #:dynamic_version => lambda{|x| x.each_header{|k,v| return v if k =~ /x-aspnet-version/ } }
35
+ },
36
+ {
37
+ :name => "ASP.NET",
38
+ :description => "Asp.Net Default Cookie",
39
+ :version => nil,
40
+ :type => :content_cookies,
41
+ :content => /ASP.NET_SessionId.*$/i,
42
+ :paths => ["#{uri}"]
43
+ #:dynamic_version => lambda{|x| x.each_header{|k,v| return v if k =~ /x-aspnet-version/ } }
44
+ },
45
+ {
46
+ :name => "ASP.NET MVC",
47
+ :description => "Asp.Net MVC Header",
48
+ :version => nil,
49
+ :type => :content_headers,
50
+ :content => /x-aspnetmvc-version/i,
51
+ :paths => ["#{uri}"]
52
+ #:dynamic_version => lambda{|x| x.each_header{|k,v| return v if k =~ /x-aspnetmvc-version/ } }
53
+ },
54
+ {
55
+ :name => "ASP.NET",
56
+ :description => "WebResource.axd link in the page",
57
+ :version => nil,
58
+ :type => :content_body,
59
+ :content => /WebResource.axd?d=/i,
60
+ :paths => ["#{uri}"]
61
+ #:dynamic_version => lambda{|x| x.each_header{|k,v| return v if k =~ /WebResource.axd?d=/ } }
62
+ }
63
+ ]
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end