doc_gonzo

ShodanAPI Class

Jul 5th, 2018
342
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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
Add Comment
Please, Sign In to add comment