├── Readme.md ├── ShodanAPI.py └── ips.txt /Readme.md: -------------------------------------------------------------------------------- 1 | Tool name : Shodan API - Tool Search Assistant 2 | 3 | Author : Kyxrec0n 4 | ----------------------------------- 5 | Have you ever needed to write a quick script to download data from Shodan? 6 | Okey 7 | Installation 8 | Okey shodan API tool wrote in python & i class it on information gathering tool , with the official Python library for Shodan, which means 9 | if you’re running the latest version of the 10 | library you already have access to the CLI. To install the new tool in Linux simply execute : 11 | 12 | python shodanAPI.py 13 | Type (-h) or (H) or ? to display the options & usage .. 14 | 15 | And this is Demo video of my tool : 16 | https://www.youtube.com/watch?v=bto2pCuqw9U 17 | 18 | This another api-Shodan class : 19 | # The simplejson library has better JSON-parsing than the standard library and is more often updated 20 | from simplejson import dumps, loads 21 | 22 | try: 23 | # Python 2 24 | from urllib2 import urlopen 25 | from urllib import urlencode 26 | except: 27 | # Python 3 28 | from urllib.request import urlopen 29 | from urllib.parse import urlencode 30 | 31 | __all__ = ['WebAPI'] 32 | 33 | class WebAPIError(Exception): 34 | def __init__(self, value): 35 | self.value = value 36 | 37 | def __str__(self): 38 | return self.value 39 | 40 | 41 | class WebAPI: 42 | """Wrapper around the SHODAN webservices API""" 43 | 44 | class Exploits: 45 | 46 | def __init__(self, parent): 47 | self.parent = parent 48 | 49 | def search(self, query, sources=[], cve=None, osvdb=None, msb=None, bid=None): 50 | """Search the entire Shodan Exploits archive using the same query syntax 51 | as the website. 52 | 53 | Arguments: 54 | query -- exploit search query; same syntax as website 55 | 56 | Optional arguments: 57 | sources -- metasploit, cve, osvdb, exploitdb 58 | cve -- CVE identifier (ex. 2010-0432) 59 | osvdb -- OSVDB identifier (ex. 11666) 60 | msb -- Microsoft Security Bulletin ID (ex. MS05-030) 61 | bid -- Bugtraq identifier (ex. 13951) 62 | 63 | """ 64 | if sources: 65 | query += ' source:' + ','.join(sources) 66 | if cve: 67 | query += ' cve:%s' % (str(cve).strip()) 68 | if osvdb: 69 | query += ' osvdb:%s' % (str(osvdb).strip()) 70 | if msb: 71 | query += ' msb:%s' % (str(msb).strip()) 72 | if bid: 73 | query += ' bid:%s' % (str(bid).strip()) 74 | return self.parent._request('api', {'q': query}, service='exploits') 75 | 76 | class ExploitDb: 77 | 78 | def __init__(self, parent): 79 | self.parent = parent 80 | 81 | def download(self, id): 82 | """DEPRECATED 83 | Download the exploit code from the ExploitDB archive. 84 | 85 | Arguments: 86 | id -- ID of the ExploitDB entry 87 | """ 88 | query = '_id:%s' % id 89 | return self.parent.search(query, sources=['exploitdb']) 90 | 91 | def search(self, query, **kwargs): 92 | """Search the ExploitDB archive. 93 | 94 | Arguments: 95 | query -- Search terms 96 | 97 | Returns: 98 | A dictionary with 2 main items: matches (list) and total (int). 99 | """ 100 | return self.parent.search(query, sources=['exploitdb']) 101 | 102 | class Msf: 103 | 104 | def __init__(self, parent): 105 | self.parent = parent 106 | 107 | def download(self, id): 108 | """Download a metasploit module given the fullname (id) of it. 109 | 110 | Arguments: 111 | id -- fullname of the module (ex. auxiliary/admin/backupexec/dump) 112 | 113 | Returns: 114 | A dictionary with the following fields: 115 | filename -- Name of the file 116 | content-type -- Mimetype 117 | data -- File content 118 | """ 119 | query = '_id:%s' % id 120 | return self.parent.search(query, sources=['metasploit']) 121 | 122 | def search(self, query, **kwargs): 123 | """Search for a Metasploit module. 124 | """ 125 | return self.parent.search(query, sources=['metasploit']) 126 | 127 | def __init__(self, key): 128 | """Initializes the API object. 129 | 130 | Arguments: 131 | key -- your API key 132 | 133 | """ 134 | self.api_key = key 135 | self.base_url = 'http://www.shodanhq.com/api/' 136 | self.base_exploits_url = 'https://exploits.shodan.io/' 137 | self.exploits = self.Exploits(self) 138 | self.exploitdb = self.ExploitDb(self.exploits) 139 | self.msf = self.Msf(self.exploits) 140 | 141 | def _request(self, function, params, service='shodan'): 142 | """General-purpose function to create web requests to SHODAN. 143 | 144 | Arguments: 145 | function -- name of the function you want to execute 146 | params -- dictionary of parameters for the function 147 | 148 | Returns 149 | A JSON string containing the function's results. 150 | 151 | """ 152 | # Add the API key parameter automatically 153 | params['key'] = self.api_key 154 | 155 | # Determine the base_url based on which service we're interacting with 156 | base_url = { 157 | 'shodan': self.base_url, 158 | 'exploits': self.base_exploits_url, 159 | }.get(service, 'shodan') 160 | 161 | # Send the request 162 | try: 163 | data = urlopen(base_url + function + '?' + urlencode(params)).read().decode('utf-8') 164 | except: 165 | raise WebAPIError('Unable to connect to Shodan') 166 | 167 | # Parse the text into JSON 168 | data = loads(data) 169 | 170 | # Raise an exception if an error occurred 171 | if data.get('error', None): 172 | raise WebAPIError(data['error']) 173 | 174 | # Return the data 175 | return data 176 | 177 | def count(self, query): 178 | """Returns the total number of search results for the query. 179 | """ 180 | return self._request('count', {'q': query}) 181 | 182 | def locations(self, query): 183 | """Return a break-down of all the countries and cities that the results for 184 | the given search are located in. 185 | """ 186 | return self._request('locations', {'q': query}) 187 | 188 | def fingerprint(self, banner): 189 | """Determine the software based on the banner. 190 | 191 | Arguments: 192 | banner - HTTP banner 193 | 194 | Returns: 195 | A list of software that matched the given banner. 196 | """ 197 | return self._request('fingerprint', {'banner': banner}) 198 | 199 | def host(self, ip): 200 | """Get all available information on an IP. 201 | Arguments: 202 | ip -- IP of the computer 203 | Returns: 204 | All available information SHODAN has on the given IP, 205 | subject to API key restrictions. 206 | """ 207 | return self._request('host', {'ip': ip}) 208 | 209 | def info(self): 210 | """Returns information about the current API key, such as a list of add-ons 211 | and other features that are enabled for the current user's API plan. 212 | """ 213 | return self._request('info', {}) 214 | 215 | def search(self, query, page=1, limit=None, offset=None): 216 | """Search the SHODAN database. 217 | 218 | Arguments: 219 | query -- search query; identical syntax to the website 220 | 221 | Optional arguments: 222 | page -- page number of the search results 223 | limit -- number of results to return 224 | offset -- search offset to begin getting results from 225 | 226 | Returns: 227 | A dictionary with 3 main items: matches, countries and total. 228 | Visit the website for more detailed information. 229 | 230 | """ 231 | args = { 232 | 'q': query, 233 | 'p': page, 234 | } 235 | if limit: 236 | args['l'] = limit 237 | if offset: 238 | args['o'] = offset 239 | 240 | return self._request('search', args) 241 | 242 | #Enjoy ~ 243 | H.R 244 | -------------------------------------------------------------------------------- /ShodanAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Shodan API - By Lucifer HR 3 | # THanks to the legend Hood3dRob1n 4 | 5 | # in case you got shodan error use command below 6 | # apt-get install python-shodan python-netaddr 7 | # This is my API key for you => api_key = "pHHlgpFt8Ka3Stb5UlTxcaEwciOeF2QM" 8 | # You can change the key ;) 9 | 10 | class bcolors: # 11 | OKWHITE="\033[0;37m" # 12 | OKBLUE = '\033[94m' # 13 | OKGREEN = '\033[92m' # 14 | WARNING = '\033[93m' # 15 | FAIL = '\033[91m' # 16 | ENDC = '\033[0m' # 17 | 18 | def disable(self): # 19 | self.HEADER = '' # 20 | self.OKBLUE = '' # 21 | self.OKGREEN = '' # 22 | self.WARNING = '' # 23 | self.FAIL = '' # 24 | self.ENDC = '' # 25 | 26 | import argparse 27 | from netaddr import IPNetwork 28 | import os 29 | import re 30 | import shodan 31 | import sys 32 | 33 | # 34 | # class ShodanAPI 35 | # Initialize ShodanAPI via passed API Key 36 | # def initialize(apikey) 37 | # @url="http://www.shodanhq.com/api/" 38 | # if shodan_connect(apikey) 39 | # @key=apikey 40 | # end 41 | # end 42 | # 43 | # # Check API Key against API Info Query 44 | # # Return True on success, False on Error or Failure 45 | # def shodan_connect(apikey) 46 | # url = @url + "info?key=#{apikey}" 47 | # begin 48 | # c = Curl::Easy.perform(url) 49 | # if c.body_str =~ /"unlocked_left": \d+, "telnet": .+, "plan": ".+", "https": .+, "unlocked": .+/i 50 | # results = JSON.parse(c.body_str) 51 | # @plan = results['plan'] 52 | # @unlocked = results['unlocked'] 53 | # @unlocks = results['unlocked_left'] 54 | # @https = results['https'] 55 | # @telnet = results['telnet'] 56 | # return true 57 | # elsif c.body_str =~ /"error": "API access denied"/i 58 | # puts "Access Denied using API Key '#{apikey}'!" 59 | # puts "Check Key & Try Again...." 60 | # return false 61 | # else 62 | # puts "Unknown Problem with Connection to Shodan API!" 63 | # 64 | 65 | def cli_parser(): 66 | 67 | # Command line argument parser 68 | parser = argparse.ArgumentParser( 69 | add_help=False, 70 | description="\033[0;37mShodan API - Search Assistant Searching shodan via API.\033[92m \n By : Alexcerus") 71 | parser.add_argument( 72 | "-search", metavar="Apache server", default=False, 73 | help="when searching Shodan for a string.") 74 | parser.add_argument( 75 | "-f", metavar="ips.txt", default=None, 76 | help="Using THe Ips List - File containing IPs to search shodan for.") 77 | parser.add_argument( 78 | "-ip", metavar='217.140.75.46', default=False, 79 | help="Shodan Host Search against IP & return results from Shodan about a specific IP.") 80 | parser.add_argument( 81 | "-iprg", metavar='217.140.75.46/24', default=False, 82 | help="Used to return results from Shodan about a specific CIDR to IP range .") 83 | parser.add_argument( 84 | 85 | "--hostnameonly", action='store_true', 86 | help="[Optional] Only provide results with a Shodan stored hostname.") 87 | parser.add_argument( 88 | "--page", metavar='1', default=1, 89 | help="Page number of results to return (default 1 (first page)).") 90 | parser.add_argument( 91 | '-H','-h', '-?', '--h', '-help', '--help', action="store_true", 92 | help=argparse.SUPPRESS) 93 | args = parser.parse_args() 94 | 95 | if args.h: 96 | parser.print_help() 97 | sys.exit() 98 | 99 | return args.search, args.ip, args.iprg, args.hostnameonly, args.page, args.f 100 | 101 | 102 | def create_shodan_object(): 103 | # Add your shodan API key here 104 | # api_key = "TYPE API SHODAN HQ" 105 | api_key = "pHHlgpFt8Ka3Stb5UlTxcaEwciOeF2QM" 106 | 107 | shodan_object = shodan.WebAPI(api_key) 108 | 109 | return shodan_object 110 | 111 | 112 | def shodan_iprg_search(shodan_search_object, shodan_search_iprg, input_file_ips): 113 | 114 | title() 115 | 116 | if shodan_search_iprg is not False: 117 | 118 | if not validate_iprg(shodan_search_iprg): 119 | print "[*] ERROR: Please provide valid iprg notation!" 120 | sys.exit() 121 | 122 | else: 123 | #### 124 | #def info 125 | # url = @url + 'info?key=' + @key 126 | # begin 127 | # c = Curl::Easy.perform(url) 128 | # results = JSON.parse(c.body_str) 129 | # puts 130 | # puts "Shodan API Key Confirmed!" 131 | # puts "API Key: #{@key}" 132 | # puts "Plan Type: #{results['plan']}" 133 | # puts "Unlocked: #{results['unlocked']}" 134 | # puts "Unlocks Remaining: #{results['unlocked_left']}" 135 | # puts "HTTPS Enabled: #{results['https']}" 136 | # puts "Telnet Enabled: #{results['telnet']}" 137 | # return true 138 | # rescue => e 139 | #### 140 | print "[*] Searching Shodan for info about " + shodan_search_iprg 141 | 142 | # Create iprg notated list 143 | network = IPNetwork(shodan_search_iprg) 144 | 145 | elif input_file_ips is not False: 146 | try: 147 | with open(input_file_ips, 'r') as ips_provided: 148 | network = ips_provided.readlines() 149 | except IOError: 150 | print "[*] ERROR: You didn't provide a valid input file." 151 | print "[*] ERROR: Please re-run and provide a valid file." 152 | sys.exit() 153 | 154 | # search shodan for each IP 155 | for ip in network: 156 | 157 | print "\n[+] Searching specifically for: " + str(ip) 158 | 159 | try: 160 | # Search Shodan 161 | result = shodan_search_object.host(ip) 162 | 163 | # Display basic info of result 164 | print "\n*** RESULT ***" 165 | print "IP: " + result['ip'] 166 | print "Country: " + result['country_name'] 167 | if result['city'] is not None: 168 | print "City: " + result['city'] 169 | print "\n" 170 | 171 | # Loop through other info 172 | for item in result['data']: 173 | print "Port: " + str(item['port']) 174 | print "Banner: " + item['banner'] 175 | 176 | except Exception, e: 177 | if str(e).strip() == "API access denied": 178 | print "You provided an invalid API Key!" 179 | print "Please provide a valid API Key and re-run!" 180 | sys.exit() 181 | elif str(e).strip() == "No information available for that IP.": 182 | print "No information is available for " + str(ip) 183 | else: 184 | print "[*]Unknown Error: " + str(e) 185 | 186 | 187 | def shodan_ip_search(shodan_search_object, shodan_search_ip): 188 | 189 | title() 190 | 191 | if validate_ip(shodan_search_ip): 192 | 193 | print "[*] Searching Shodan for info about " + shodan_search_ip + "..." 194 | 195 | try: 196 | # Search Shodan 197 | result = shodan_search_object.host(shodan_search_ip) 198 | 199 | # Display basic info of result 200 | print "\n*** RESULT ***" 201 | print "IP: " + result['ip'] 202 | print "Country: " + result['country_name'] 203 | if result['city'] is not None: 204 | print "City: " + result['city'] 205 | print "\n" 206 | 207 | # Loop through other info 208 | for item in result['data']: 209 | print "Port: " + str(item['port']) 210 | print "Banner: " + item['banner'] 211 | 212 | except Exception, e: 213 | if str(e).strip() == "API access denied": 214 | print "You provided an invalid API Key!" 215 | print "Please provide a valid API Key and re-run!" 216 | sys.exit() 217 | elif str(e).strip() == "No information available for that IP.": 218 | print "No information on Shodan about " +\ 219 | str(shodan_search_ip) 220 | else: 221 | print "[*]Unknown Error: " + str(e) 222 | 223 | else: 224 | print "[*]ERROR: You provided an invalid IP address!" 225 | print "[*]ERROR: Please re-run and provide a valid IP." 226 | sys.exit() 227 | 228 | 229 | def shodan_string_search(shodan_search_object, shodan_search_string, 230 | hostname_only, page_to_return): 231 | 232 | title() 233 | 234 | # Try/catch for searching the shodan api 235 | print "[*] Searching Shodan...\n" 236 | 237 | try: 238 | # Time to search Shodan 239 | results = shodan_search_object.search( 240 | shodan_search_string, page=page_to_return) 241 | 242 | if not hostname_only: 243 | print "Total number of results back: " +\ 244 | str(results['total']) + "\n" 245 | 246 | for result in results['matches']: 247 | if hostname_only: 248 | for item in result['hostnames']: 249 | if item is None: 250 | pass 251 | else: 252 | print "*** RESULT ***" 253 | print "IP Address: " + result['ip'] 254 | if result['country_name'] is not None: 255 | print "Country: " + result['country_name'] 256 | if result['updated'] is not None: 257 | print "Last updated: " + result['updated'] 258 | if result['port'] is not None: 259 | print "Port: " + str(result['port']) 260 | print "Data: " + result['data'] 261 | for item in result['hostnames']: 262 | print "Hostname: " + item 263 | print 264 | else: 265 | print "*** RESULT ***" 266 | print "IP Address: " + result['ip'] 267 | if result['country_name'] is not None: 268 | print "Country: " + result['country_name'] 269 | if result['updated'] is not None: 270 | print "Last updated: " + result['updated'] 271 | if result['port'] is not None: 272 | print "Port: " + str(result['port']) 273 | print "Data: " + result['data'] 274 | for item in result['hostnames']: 275 | print "Hostname: " + item 276 | print 277 | 278 | except Exception, e: 279 | if str(e).strip() == "API access denied": 280 | print "You provided an invalid API Key!" 281 | print "Please provide a valid API Key and re-run!" 282 | sys.exit() 283 | 284 | 285 | def title(): 286 | os.system('clear') 287 | print "+------------------------------------------------------------+" 288 | print "| - Shodan Search - |" 289 | print "+------------------------------------------------------------+" 290 | 291 | return 292 | 293 | 294 | def validate_iprg(val_iprg): 295 | # This came from (Mult-line link for pep8 compliance) 296 | # http://python-iptools.googlecode.com/svn-history/r4 297 | # /trunk/iptools/__init__.py 298 | iprg_re = re.compile(r'^(\d{1,3}\.){0,3}\d{1,3}/\d{1,2}$') 299 | if iprg_re.match(val_iprg): 300 | ip, mask = val_iprg.split('/') 301 | if validate_ip(ip): 302 | if int(mask) > 32: 303 | return False 304 | else: 305 | return False 306 | return True 307 | return False 308 | 309 | 310 | def validate_ip(val_ip): 311 | # This came from (Mult-line link for pep8 compliance) 312 | # http://python-iptools.googlecode.com/svn-history/r4 313 | # /trunk/iptools/__init__.py 314 | ip_re = re.compile(r'^(\d{1,3}\.){0,3}\d{1,3}$') 315 | if ip_re.match(val_ip): 316 | quads = (int(q) for q in val_ip.split('.')) 317 | for q in quads: 318 | if q > 255: 319 | return False 320 | return True 321 | return False 322 | 323 | 324 | if __name__ == '__main__': 325 | 326 | # Parse command line options 327 | search_string, search_ip, search_iprg, search_hostnameonly,\ 328 | search_page_number, search_file = cli_parser() 329 | 330 | # Create object used to search Shodan 331 | shodan_api_object = create_shodan_object() 332 | 333 | # Determine which action will be performed 334 | if search_string is not False: 335 | shodan_string_search(shodan_api_object, search_string, 336 | search_hostnameonly, search_page_number) 337 | 338 | elif search_ip is not False: 339 | shodan_ip_search(shodan_api_object, search_ip) 340 | 341 | elif search_iprg is not False or search_file is not None: 342 | shodan_iprg_search(shodan_api_object, search_iprg, search_file) 343 | 344 | else: 345 | print(bcolors.WARNING + "Shodan API - Search Assistant" + bcolors.ENDC) 346 | 347 | print(bcolors.OKWHITE + "By : Alexcerus HR ~\n" + bcolors.ENDC) 348 | print(bcolors.OKGREEN + "Usage: ./ShodanAPI.py [Options]\n" + bcolors.ENDC) 349 | print(bcolors.OKWHITE + "Ex: ./ShodanAPI.py -h to display options & usage" + bcolors.ENDC) 350 | print(bcolors.OKWHITE + "Ex: ./ShodanAPI.py --help to display full options " + bcolors.ENDC) 351 | print(bcolors.OKWHITE + "Ex: ./ShodanAPI.py -search Apache server " + bcolors.ENDC) 352 | print(bcolors.OKWHITE + "Ex: ./ShodanAPI.py -f list of sites directory list ips.txt " + bcolors.ENDC) 353 | print(bcolors.OKWHITE + "Ex: ./ShodanAPI.py --hostnameonly [Optional] Only provide results with a Shodan stored hostname. " + bcolors.ENDC) 354 | print(bcolors.OKWHITE + "Ex: ./ShodanAPI.py --help disply the full command's\n " + bcolors.ENDC) 355 | print(bcolors.OKGREEN + "Options: \n " + bcolors.ENDC) 356 | print(bcolors.OKGREEN + " -f ips.txt" + bcolors.ENDC) 357 | print(bcolors.OKWHITE + " shodan search with ipts.txt list " + bcolors.ENDC) 358 | print(bcolors.OKGREEN + " -search Apache server" + bcolors.ENDC) 359 | print(bcolors.OKWHITE + " Use this when searching Shodan for a string. " + bcolors.ENDC) 360 | print(bcolors.OKGREEN + " -ip 217.140.75.46" + bcolors.ENDC) 361 | print(bcolors.OKWHITE + " Used to return results from Shodan about a specific IP. " + bcolors.ENDC) 362 | print(bcolors.OKGREEN + " -h " + bcolors.ENDC) 363 | print(bcolors.OKWHITE + " Help Menu " + bcolors.ENDC) 364 | print(bcolors.OKGREEN + " --help " + bcolors.ENDC) 365 | print(bcolors.OKWHITE + " see full Help Options " + bcolors.ENDC) 366 | 367 | # 368 | # 369 | #END ~ 370 | # 371 | -------------------------------------------------------------------------------- /ips.txt: -------------------------------------------------------------------------------- 1 | Put list of IP here :) --------------------------------------------------------------------------------