CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/gather/censys_search.rb
Views: 11623
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Auxiliary::Report
8
9
CENSYS_SEARCH_API = 'search.censys.io'.freeze
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Censys Search',
16
'Description' => %q{
17
The module uses the Censys REST API to access the same data accessible
18
through the web interface. The search endpoint allows queries using
19
the Censys Search Language against the Hosts dataset. Setting the
20
CERTIFICATES option will also retrieve the certificate details for each
21
relevant service by querying the Certificates dataset.
22
},
23
'Author' => [
24
'Nixawk', # original Metasploit module
25
'e2002e', # rework to use the API v2
26
'Christophe De La Fuente' # rework to use the API v2
27
],
28
'References' => [
29
['URL', 'https://search.censys.io']
30
],
31
'License' => MSF_LICENSE,
32
'Notes' => {
33
'Stability' => [CRASH_SAFE],
34
'SideEffects' => [],
35
'Reliability' => []
36
}
37
)
38
)
39
40
register_options([
41
OptString.new('CENSYS_UID', [true, 'The Censys API UID']),
42
OptString.new('CENSYS_SECRET', [true, 'The Censys API SECRET']),
43
OptString.new('QUERY', [true, 'The Censys search query']),
44
OptBool.new('CERTIFICATES', [false, 'Query infos about certificates', false])
45
])
46
end
47
48
def basic_auth_header
49
auth_str = datastore['CENSYS_UID'].to_s + ':' + datastore['CENSYS_SECRET'].to_s
50
'Basic ' + Rex::Text.encode_base64(auth_str)
51
end
52
53
def search(keyword)
54
begin
55
@cli = Rex::Proto::Http::Client.new(CENSYS_SEARCH_API, 443, {}, true)
56
@cli.connect
57
58
response = @cli.request_cgi(
59
'method' => 'GET',
60
'uri' => "/api/v2/hosts/search?q=#{keyword}",
61
'headers' => { 'Authorization' => basic_auth_header }
62
)
63
res = @cli.send_recv(response)
64
rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
65
fail_with(Failure::Unreachable, "#search: HTTP Connection Failed: #{e}")
66
end
67
fail_with(Failure::Unreachable, '#search: HTTP Connection Failed') unless res
68
69
records = ActiveSupport::JSON.decode(res.body)
70
if records['code'] == 200
71
parse_record(records['result'])
72
else
73
fail_with(Failure::UnexpectedReply, "Error returned by '/api/v2/hosts/search': code=#{records['code']}, status=#{records['status']}, error=#{records['error']}")
74
end
75
end
76
77
def get_certificate_details(cert_fingerprint)
78
return if cert_fingerprint.nil?
79
80
begin
81
response = @cli.request_cgi(
82
'method' => 'GET',
83
'uri' => "/api/v1/view/certificates/#{cert_fingerprint}",
84
'headers' => { 'Authorization' => basic_auth_header }
85
)
86
res = @cli.send_recv(response)
87
rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
88
print_error('#get_certificate_details - HTTP Connection Failed')
89
return
90
end
91
return unless res
92
93
cert_details = ActiveSupport::JSON.decode(res.body)
94
subject = cert_details.dig('parsed', 'subject_dn')
95
return unless subject
96
97
issuer = cert_details.dig('parsed', 'issuer_dn')
98
cert_details = subject
99
cert_details << " (Issuer: #{issuer})" if issuer
100
cert_details
101
end
102
103
def parse_record(records)
104
unless records&.dig('hits')&.any?
105
print_error('The query did not return any records')
106
return
107
end
108
records['hits'].each do |hit|
109
ip = hit['ip']
110
services = hit['services']
111
ports = []
112
certs = []
113
services.each do |service|
114
port = service['port']
115
name = service['service_name']
116
ports << "#{port}/#{name}"
117
cert_details = nil
118
if datastore['CERTIFICATES'] && service['certificate']
119
cert_details = get_certificate_details(service['certificate'])
120
if cert_details
121
certs << "Certificate for #{port}/#{name}: #{cert_details}"
122
else
123
vprint_error("Unable to get certificate details for #{port}/#{name}")
124
end
125
end
126
if cert_details
127
report_service(host: ip, port: port, name: name, info: cert_details)
128
else
129
report_service(host: ip, port: port, name: name)
130
end
131
end
132
print_good("#{ip} - #{ports.join(',')}")
133
certs.each { |cert| print_status(cert) }
134
end
135
end
136
137
# Check to see if Censys Search API host resolves properly
138
def censys_resolvable?
139
begin
140
Rex::Socket.resolv_to_dotted(CENSYS_SEARCH_API)
141
rescue RuntimeError, SocketError
142
return false
143
end
144
true
145
end
146
147
def run
148
unless censys_resolvable?
149
fail_with(Failure::Unreachable, "Unable to resolve #{CENSYS_SEARCH_API}")
150
end
151
152
search(datastore['QUERY'])
153
end
154
end
155
156