SHARE
TWEET

Shodan API Class, by HR

a guest Jan 8th, 2014 1,683 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
RAW Paste Data
Want to get better at Ruby?
Learn to code Ruby in 2017
Pastebin PRO Summer Special!
Get 40% OFF on Pastebin PRO accounts!
Top