@@ -22,6 +22,8 @@ def initialize(info = {})
2222 see the Shodan site for more information.
2323 Shodan website: https://www.shodan.io/
2424 API: https://developer.shodan.io/api
25+ Filters: https://www.shodan.io/search/filters
26+ Facets: https://www.shodan.io/search/facet (from the scrollbox)
2527 } ,
2628 'Author' =>
2729 [
@@ -36,6 +38,7 @@ def initialize(info = {})
3638 [
3739 OptString . new ( 'SHODAN_APIKEY' , [ true , 'The SHODAN API key' ] ) ,
3840 OptString . new ( 'QUERY' , [ true , 'Keywords you want to search for' ] ) ,
41+ OptString . new ( 'FACETS' , [ false , 'List of facets' ] ) ,
3942 OptString . new ( 'OUTFILE' , [ false , 'A filename to store the list of IPs' ] ) ,
4043 OptBool . new ( 'DATABASE' , [ false , 'Add search results to the database' , false ] ) ,
4144 OptInt . new ( 'MAXPAGE' , [ true , 'Max amount of pages to collect' , 1 ] ) ,
@@ -55,7 +58,7 @@ def initialize(info = {})
5558 end
5659
5760 # create our Shodan query function that performs the actual web request
58- def shodan_query ( apikey , query , page )
61+ def shodan_query ( apikey , query , facets , page )
5962 # send our query to Shodan
6063 res = send_request_cgi ( {
6164 'method' => 'GET' ,
@@ -66,6 +69,7 @@ def shodan_query(apikey, query, page)
6669 'vars_get' => {
6770 'key' => apikey ,
6871 'query' => query ,
72+ 'facets' => facets ,
6973 'page' => page . to_s
7074 }
7175 } )
@@ -117,92 +121,113 @@ def run
117121
118122 # create our Shodan request parameters
119123 query = datastore [ 'QUERY' ]
124+ facets = datastore [ 'FACETS' ]
120125 apikey = datastore [ 'SHODAN_APIKEY' ]
121126 maxpage = datastore [ 'MAXPAGE' ]
122127
123128 # results gets our results from shodan_query
124129 results = [ ]
125- results [ 0 ] = shodan_query ( apikey , query , 1 )
130+ first_page = 0
131+ results [ first_page ] = shodan_query ( apikey , query , facets , 1 )
126132
127- if results [ 0 ] [ 'total' ] . nil? || results [ 0 ] [ 'total' ] == 0
133+ if results [ first_page ] [ 'total' ] . nil? || results [ first_page ] [ 'total' ] == 0
128134 msg = "No results."
129- if results [ 0 ] [ 'error' ] . to_s . length > 0
130- msg << " Error: #{ results [ 0 ] [ 'error' ] } "
135+ if results [ first_page ] [ 'error' ] . to_s . length > 0
136+ msg << " Error: #{ results [ first_page ] [ 'error' ] } "
131137 end
132138 print_error ( msg )
133139 return
134140 end
135141
136142 # Determine page count based on total results
137- if results [ 0 ] [ 'total' ] % 100 == 0
138- tpages = results [ 0 ] [ 'total' ] / 100
143+ if results [ first_page ] [ 'total' ] % 100 == 0
144+ tpages = results [ first_page ] [ 'total' ] / 100
139145 else
140- tpages = results [ 0 ] [ 'total' ] / 100 + 1
146+ tpages = results [ first_page ] [ 'total' ] / 100 + 1
141147 end
142148 maxpage = tpages if datastore [ 'MAXPAGE' ] > tpages
143149
144- # start printing out our query statistics
145- print_status ( "Total: #{ results [ 0 ] [ 'total' ] } on #{ tpages } " +
146- "pages. Showing: #{ maxpage } page(s)" )
147-
148- # If search results greater than 100, loop & get all results
149- print_status ( 'Collecting data, please wait...' )
150-
151- if results [ 0 ] [ 'total' ] > 100
152- page = 1
153- while page < maxpage
154- page_result = shodan_query ( apikey , query , page +1 )
155- if page_result [ 'matches' ] . nil?
156- next
150+ if facets
151+ facets_tbl = Rex ::Text ::Table . new (
152+ 'Header' => 'Facets' ,
153+ 'Indent' => 1 ,
154+ 'Columns' => [ 'Facet' , 'Name' , 'Count' ]
155+ )
156+ print_status ( "Total: #{ results [ first_page ] [ 'total' ] } on #{ tpages } " \
157+ 'pages. Showing facets' )
158+ facet = results . dig ( first_page , 'facets' )
159+ facet . each do |name , list |
160+ list . each do |f |
161+ facets_tbl << [ name . to_s , ( f [ 'value' ] ) . to_s , ( f [ 'count' ] ) . to_s ]
157162 end
158- results [ page ] = page_result
159- page += 1
160163 end
161- end
162-
163- # Save the results to this table
164- tbl = Rex ::Text ::Table . new (
165- 'Header' => 'Search Results' ,
166- 'Indent' => 1 ,
167- 'Columns' => [ 'IP:Port' , 'City' , 'Country' , 'Hostname' ]
168- )
164+ else
165+ # start printing out our query statistics
166+ print_status ( "Total: #{ results [ first_page ] [ 'total' ] } on #{ tpages } " +
167+ "pages. Showing: #{ maxpage } page(s)" )
168+
169+ # If search results greater than 100, loop & get all results
170+ print_status ( 'Collecting data, please wait...' )
171+
172+ if results [ first_page ] [ 'total' ] > 100
173+ page = 1
174+ while page < maxpage
175+ page_result = shodan_query ( apikey , query , facets , page +1 )
176+ if page_result [ 'matches' ] . nil?
177+ next
178+ end
179+ results [ page ] = page_result
180+ page += 1
181+ end
182+ end
183+ # Save the results to this table
184+ tbl = Rex ::Text ::Table . new (
185+ 'Header' => 'Search Results' ,
186+ 'Indent' => 1 ,
187+ 'Columns' => [ 'IP:Port' , 'City' , 'Country' , 'Hostname' ]
188+ )
169189
170- # Organize results and put them into the table and database
171- regex = datastore [ 'REGEX' ] if datastore [ 'REGEX' ]
172- results . each do |page |
173- page [ 'matches' ] . each do |host |
174- city = host [ 'location' ] [ 'city' ] || 'N/A'
175- ip = host [ 'ip_str' ] || 'N/A'
176- port = host [ 'port' ] || ''
177- country = host [ 'location' ] [ 'country_name' ] || 'N/A'
178- hostname = host [ 'hostnames' ] [ 0 ]
179- data = host [ 'data' ]
180-
181- report_host ( :host => ip ,
182- :name => hostname ,
183- :comments => 'Added from Shodan' ,
184- :info => host [ 'info' ]
185- ) if datastore [ 'DATABASE' ]
186-
187- report_service ( :host => ip ,
188- :port => port ,
189- :info => 'Added from Shodan'
190- ) if datastore [ 'DATABASE' ]
191-
192- if ip =~ regex ||
193- city =~ regex ||
194- country =~ regex ||
195- hostname =~ regex ||
196- data =~ regex
197- # Unfortunately we cannot display the banner properly,
198- # because it messes with our output format
199- tbl << [ "#{ ip } :#{ port } " , city , country , hostname ]
190+ # Organize results and put them into the table and database
191+ regex = datastore [ 'REGEX' ] if datastore [ 'REGEX' ]
192+ results . each do |page |
193+ page [ 'matches' ] . each do |host |
194+ city = host . dig ( 'location' , 'city' ) || 'N/A'
195+ ip = host . fetch ( 'ip_str' , 'N/A' )
196+ port = host . fetch ( 'port' , '' )
197+ country = host . dig ( 'location' , 'country_name' ) || 'N/A'
198+ hostname = host . dig ( 'hostnames' , 0 )
199+ data = host . dig ( 'data' )
200+
201+ report_host ( :host => ip ,
202+ :name => hostname ,
203+ :comments => 'Added from Shodan' ,
204+ :info => host . dig ( 'info' )
205+ ) if datastore [ 'DATABASE' ]
206+
207+ report_service ( :host => ip ,
208+ :port => port ,
209+ :info => 'Added from Shodan'
210+ ) if datastore [ 'DATABASE' ]
211+
212+ if ip =~ regex ||
213+ city =~ regex ||
214+ country =~ regex ||
215+ hostname =~ regex ||
216+ data =~ regex
217+ # Unfortunately we cannot display the banner properly,
218+ # because it messes with our output format
219+ tbl << [ "#{ ip } :#{ port } " , city , country , hostname ]
220+ end
200221 end
201222 end
223+ #Show data and maybe save it if needed
224+ print_line ( )
225+ print_line ( "#{ tbl } " )
226+ save_output ( tbl ) if datastore [ 'OUTFILE' ]
227+ end
228+ if datastore [ 'FACETS' ]
229+ print_line ( facets_tbl . to_s )
230+ save_output ( facets_tbl ) if datastore [ 'OUTFILE' ]
202231 end
203- #Show data and maybe save it if needed
204- print_line ( )
205- print_line ( "#{ tbl } " )
206- save_output ( tbl ) if datastore [ 'OUTFILE' ]
207232 end
208233end
0 commit comments