View difference between Paste ID: 9C3snNgj and q6LZJqcD
SHOW: | | - or go back to the newest paste.
1
# Custom ShodanAPI Class :)
2
# The pre-built option is broken and doesn't work in several places....
3
# So we re-wrote it!
4
class ShodanAPI
5
  # Initialize ShodanAPI via passed API Key
6
  def initialize(apikey)
7
    @url="http://www.shodanhq.com/api/"
8
    if shodan_connect(apikey)
9
        @key=apikey
10
    end
11
  end
12
13
  # Check API Key against API Info Query
14
  # Return True on success, False on Error or Failure
15
  def shodan_connect(apikey)
16
    url = @url + "info?key=#{apikey}"
17
    begin
18
      c = Curl::Easy.perform(url)
19
      if c.body_str =~ /"unlocked_left": \d+, "telnet": .+, "plan": ".+", "https": .+, "unlocked": .+/i
20
        results = JSON.parse(c.body_str)
21
        @plan = results['plan']
22
        @unlocked = results['unlocked']
23
        @unlocks = results['unlocked_left']
24
        @https = results['https']
25
        @telnet = results['telnet']
26
        return true
27
      elsif c.body_str =~ /"error": "API access denied"/i
28
        puts "Access Denied using API Key '#{apikey}'".light_red + "!".white
29
        puts "Check Key & Try Again".light_red + "....".white
30
        return false
31
      else
32
        puts "Unknown Problem with Connection to Shodan API".light_green + "!".white
33
        return false
34
      end
35
    rescue => e
36
      puts "Problem with Connection to Shodan API".light_red + "!".white
37
      puts "\t=> #{e}"
38
      return false
39
    end
40
  end
41
42
  # Just checks our key is working (re-using shodan_connect so updates @unlocks)
43
  # Returns True or False
44
  def connected?
45
    if shodan_connect(@key)
46
      return true
47
    else
48
      return  false
49
    end
50
  end
51
52
  # Return the number of unlocks remaining
53
  def unlocks
54
    if shodan_connect(@key)
55
      return @unlocks.to_i
56
    else
57
      return nil
58
    end
59
  end
60
61
  # Check if HTTPS is Enabled
62
  def https?
63
    if shodan_connect(@key)
64
      if @https
65
        return true
66
      else
67
        return false
68
      end
69
    else
70
      return false
71
    end
72
  end
73
74
  # Check if Telnet is Enabled
75
  def telnet?
76
    if shodan_connect(@key)
77
      if @telnet
78
        return true
79
      else
80
        return false
81
      end
82
    else
83
      return false
84
    end
85
  end
86
87
  # Actually display Basic Info for current API Key
88
  def info
89
    url = @url + 'info?key=' + @key
90
    begin
91
      c = Curl::Easy.perform(url)
92
      results = JSON.parse(c.body_str)
93
      puts
94
      puts "Shodan API Key Confirmed".light_green + "!".white
95
      puts "API Key".light_green + ": #{@key}".white
96
      puts "Plan Type".light_green + ": #{results['plan']}".white
97
      puts "Unlocked".light_green + ": #{results['unlocked']}".white
98
      puts "Unlocks Remaining".light_green + ": #{results['unlocked_left']}".white
99
      puts "HTTPS Enabled".light_green + ": #{results['https']}".white
100
      puts "Telnet Enabled".light_green + ": #{results['telnet']}".white
101
      return true
102
    rescue => e
103
      puts "Problem with Connection to Shodan API".light_red + "!".white
104
      puts "\t=> #{e}".white
105
      return false
106
    end
107
  end
108
109
  # Lookup all available information for a specific IP address
110
  # Returns results hash or nil
111
  def host(ip)
112
    url = @url + 'host?ip=' + ip + '&key=' + @key
113
    begin
114
      c = Curl::Easy.perform(url)
115
      results = JSON.parse(c.body_str)
116
      return results
117
    rescue => e
118
      puts "Problem running Host Search".light_red + "!".white
119
      puts "\t=> #{e}".white
120
      return nil
121
    end
122
  end
123
124
  # Returns the number of devices that a search query found
125
  # Unrestricted usage of all advanced filters
126
  # Return results count or nil on failure
127
  def count(string)
128
    url = @url + 'count?q=' + string + '&key=' + @key
129
    begin
130
      c = Curl::Easy.perform(url)
131
      results = JSON.parse(c.body_str)
132
      return results['total']
133
    rescue => e
134
      puts "Problem grabbing results count".light_red + "!".white
135
      puts "\t=> #{e}".white
136
      return nil
137
    end
138
  end
139
140
  # Search Shodan for devices using a search query
141
  # Returns results hash or nil
142
  def search(string, filters={})
143
    prem_filters =  [ 'city', 'country', 'geo', 'net', 'before', 'after', 'org', 'isp', 'title', 'html' ]
144
    cheap_filters = [ 'hostname', 'os', 'port' ]
145
    url = @url + 'search?q=' + string
146
    if not filters.empty?
147
      filters.each do |k, v|
148
        if cheap_filters.include?(k)
149
          url += ' ' + k + ":\"#{v}\""
150
        end
151
        if prem_filters.include?(k)
152
          if @unlocks.to_i > 1
153
            url += ' ' + k + ":\"#{v}\""
154
            @unlocks = @unlocks.to_i - 1 # Remove an unlock for use of filter
155
          else
156
            puts "Not Enough Unlocks Left to run Premium Filter Search".light_red + "!".white
157
            puts "Try removing '#{k}' filter and trying again".light_red + "....".white
158
            return nil
159
          end
160
        end
161
      end
162
    end
163
    url += '&key=' + @key
164
    begin
165
      c = Curl::Easy.perform(url)
166
      results = JSON.parse(c.body_str)
167
      return results
168
    rescue => e
169
      puts "Problem running Shodan Search".light_red + "!".white
170
      puts "\t=> #{e}".white
171
      return nil
172
    end
173
  end
174
175
  # Quick Search Shodan for devices using a search query
176
  # Results are limited to only the IP addresses
177
  # Returns results array or nil
178
  def quick_search(string, filters={})
179
    prem_filters =  [ 'city', 'country', 'geo', 'net', 'before', 'after', 'org', 'isp', 'title', 'html' ]
180
    cheap_filters = [ 'hostname', 'os', 'port' ]
181
    url = @url + 'search?q=' + string
182
    if not filters.empty?
183
      filters.each do |k, v|
184
        if cheap_filters.include?(k)
185
          url += ' ' + k + ":\"#{v}\""
186
        end
187
        if prem_filters.include?(k)
188
          if @unlocks.to_i > 1
189
            url += ' ' + k + ":\"#{v}\""
190
            @unlocks = @unlocks.to_i - 1
191
          else
192
            puts "Not Enough Unlocks Left to run Premium Filter Search".light_red + "!".white
193
            puts "Try removing '#{k}' filter and trying again".light_red + "....".white
194
            return nil
195
          end
196
        end
197
      end
198
    end
199
    url += '&key=' + @key
200
    begin
201
      ips=[]
202
      c = Curl::Easy.perform(url)
203
      results = JSON.parse(c.body_str)
204
      results['matches'].each do |host|
205
       ips << host['ip']
206
      end
207
      return ips
208
    rescue => e
209
      puts "Problem running Shodan Quick Search".light_red + "!".white
210
      puts "\t=> #{e}".white
211
      return nil
212
    end
213
  end
214
215
  # Perform Shodan Exploit Search as done on Web
216
  # Provide Search String and source
217
  # Source can be: metasploit, exploitdb, or cve
218
  # Returns results hash array on success: { downloadID => { link => description } }
219
  # Returns nil on failure
220
  def sploit_search(string, source)
221
    sources = [ "metasploit", "exploitdb", "cve" ]
222
    if sources.include?(source.downcase)
223
      sploits = 'https://exploits.shodan.io/?q=' + string + ' source:"' + source.downcase + '"'
224
      begin
225
        results={}
226
        c = Curl::Easy.perform(sploits)
227
        page = Nokogiri::HTML(c.body_str) # Parsable doc object now
228
        # Enumerate target section, parse out link & description
229
        page.css('div[class="search-result well"]').each do |linematch|
230
          if linematch.to_s =~ /<div class="search-result well">\s+<a href="(.+)"\s/
231
            link=$1
232
          end
233
          if linematch.to_s =~ /class="title">(.+)\s+<\/a>/
234
            desc=$1.gsub('<em>', '').gsub('</em>', '')
235
          end
236
          case source.downcase
237
          when 'cve'
238
            dl_id = 'N/A for CVE Search'
239
          when 'exploitdb'
240
            dl_id = link.split('/')[-1] unless link.nil?
241
          when 'metasploit'
242
            dl_id = link.sub('http://www.metasploit.com/', '').sub(/\/$/, '') unless link.nil?
243
          end
244
          results.store(dl_id, { link => desc}) unless (link.nil? or link == '') or (desc.nil? or desc == '') or (dl_id.nil? or dl_id == 'N/A for CVE Search')
245
        end
246
        return results
247
      rescue Curl::Err::ConnectionFailedError => e
248
        puts "Shitty connection yo".light_red + ".....".white
249
        return nil
250
      rescue => e
251
        puts "Unknown connection problem".light_red + ".....".white
252
        puts "\t=> #{e}".white
253
        return nil
254
      end
255
    else
256
      puts "Invalid Search Source Requested".light_red + "!".white
257
      return nil
258
    end
259
  end
260
261
  # Download Exploit Code from Exploit-DB or MSF Github Page
262
  # By passing in the Download ID (which can be seen in sploit_search() results)
263
  # Return { 'Download' => dl_link, 'Viewing' => v_link, 'Exploit' => c.body_str }
264
  # or nil on failure
265
  def sploit_download(id, source)
266
    sources = [ "metasploit", "exploitdb" ]
267
    if sources.include?(source.downcase)
268
      case source.downcase
269
      when 'exploitdb'
270
        dl_link = "http://www.exploit-db.com/download/#{id}/"
271
        v_link = "http://www.exploit-db.com/exploits/#{id}/"
272
      when 'metasploit'
273
        dl_link = "https://raw.github.com/rapid7/metasploit-framework/master/#{id.sub('/exploit/', '/exploits/')}.rb"
274
        v_link = "http://www.rapid7.com/db/#{id}/"
275
      end
276
      begin
277
        c = Curl::Easy.perform(dl_link)
278
        page = Nokogiri::HTML(c.body_str) # Parsable doc object now
279
        results = { 'Download' => dl_link, 'Viewing' => v_link, 'Exploit' => c.body_str }
280
        return results
281
      rescue Curl::Err::ConnectionFailedError => e
282
        puts "Shitty connection yo".light_red + ".....".white
283
        return false
284
      rescue => e
285
        puts "Unknown connection problem".light_red + ".....".white
286
        puts "#{e}".light_red
287
        return false
288
      end
289
    else
290
      puts "Invalid Download Source Requested".light_red + "!".white
291
      return false
292
    end
293
  end
294
end