def alert_list_triggers(): """List the available notification triggers""" key = get_api_key() # Get the list api = shodan.Shodan(key) try: results = api.alert_triggers() except shodan.APIError as e: raise click.ClickException(e.value) if len(results) > 0: click.secho('The following triggers can be enabled on alerts:', dim=True) click.echo('') for trigger in sorted(results, key=itemgetter('name')): click.secho('{:<12} '.format('Name'), dim=True, nl=False) click.secho(trigger['name'], fg='yellow') click.secho('{:<12} '.format('Description'), dim=True, nl=False) click.secho(trigger['description'], fg='cyan') click.secho('{:<12} '.format('Rule'), dim=True, nl=False) click.echo(trigger['rule']) click.echo('') else: click.echo("No triggers currently available.")
def main(argv=None): """ Main function / entry point """ from thirdparty.shodan import Shodan from thirdparty.shodan.cli.helpers import get_api_key api = Shodan(get_api_key()) return launch_map(api)
def alert_list(expired): """List all the active alerts""" key = get_api_key() # Get the list api = shodan.Shodan(key) try: results = api.alerts(include_expired=expired) except shodan.APIError as e: raise click.ClickException(e.value) if len(results) > 0: click.echo(u'# {:14} {:<21} {:<15s}'.format('Alert ID', 'Name', 'IP/ Network')) for alert in results: click.echo(u'{:16} {:<30} {:<35} '.format( click.style(alert['id'], fg='yellow'), click.style(alert['name'], fg='cyan'), click.style(', '.join(alert['filters']['ip']), fg='white')), nl=False) if 'triggers' in alert and alert['triggers']: click.secho('Triggers: ', fg='magenta', nl=False) click.echo(', '.join(alert['triggers'].keys()), nl=False) if 'expired' in alert and alert['expired']: click.secho('expired', fg='red') else: click.echo('') else: click.echo("You haven't created any alerts yet.")
def alert_domain(domain, triggers): """Create a network alert based on a domain name""" key = get_api_key() api = shodan.Shodan(key) try: # Grab a list of IPs for the domain domain = domain.lower() click.secho('Looking up domain information...', dim=True) info = api.dns.domain_info(domain, type='A') domain_ips = set([record['value'] for record in info['data']]) # Create the actual alert click.secho('Creating alert...', dim=True) alert = api.create_alert('__domain: {}'.format(domain), list(domain_ips)) # Enable the triggers so it starts getting managed by Shodan Monitor click.secho('Enabling triggers...', dim=True) api.enable_alert_trigger(alert['id'], triggers) except shodan.APIError as e: raise click.ClickException(e.value) click.secho('Successfully created domain alert!', fg='green') click.secho('Alert ID: {}'.format(alert['id']), fg='cyan')
def alert_info(alert): """Show information about a specific alert""" key = get_api_key() api = shodan.Shodan(key) try: info = api.alerts(aid=alert) except shodan.APIError as e: raise click.ClickException(e.value) click.secho(info['name'], fg='cyan') click.secho('Created: ', nl=False, dim=True) click.secho(info['created'], fg='magenta') click.secho('Notifications: ', nl=False, dim=True) if 'triggers' in info and info['triggers']: click.secho('enabled', fg='green') else: click.echo('disabled') click.echo('') click.secho('Network Range(s):', dim=True) for network in info['filters']['ip']: click.echo(u' > {}'.format(click.style(network, fg='yellow'))) click.echo('') if 'triggers' in info and info['triggers']: click.secho('Triggers:', dim=True) for trigger in info['triggers']: click.echo(u' > {}'.format(click.style(trigger, fg='yellow'))) click.echo('')
def data_list(dataset): """List available datasets or the files within those datasets.""" # Setup the API connection key = get_api_key() api = shodan.Shodan(key) if dataset: # Show the files within this dataset files = api.data.list_files(dataset) for file in files: click.echo(click.style(u'{:20s}'.format(file['name']), fg='cyan'), nl=False) click.echo(click.style('{:10s}'.format(helpers.humanize_bytes(file['size'])), fg='yellow'), nl=False) # Show the SHA1 checksum if available if file.get('sha1'): click.echo(click.style('{:42s}'.format(file['sha1']), fg='green'), nl=False) click.echo('{}'.format(file['url'])) else: # If no dataset was provided then show a list of all datasets datasets = api.data.list_datasets() for ds in datasets: click.echo(click.style('{:15s}'.format(ds['name']), fg='cyan'), nl=False) click.echo('{}'.format(ds['description']))
def info(): """Show an overview of the organization""" key = get_api_key() api = shodan.Shodan(key) try: organization = api.org.info() except shodan.APIError as e: raise click.ClickException(e.value) click.secho(organization['name'], fg='cyan') click.secho('Access Level: ', nl=False, dim=True) click.secho(humanize_api_plan(organization['upgrade_type']), fg='magenta') if organization['domains']: click.secho('Authorized Domains: ', nl=False, dim=True) click.echo(', '.join(organization['domains'])) click.echo('') click.secho('Administrators:', dim=True) for admin in organization['admins']: click.echo(u' > {:30}\t{:30}'.format( click.style(admin['username'], fg='yellow'), admin['email'])) click.echo('') if organization['members']: click.secho('Members:', dim=True) for member in organization['members']: click.echo(u' > {:30}\t{:30}'.format( click.style(member['username'], fg='yellow'), member['email'])) else: click.secho('No members yet', dim=True)
def host(format, history, filename, save, ip): """View all available information for an IP address""" key = get_api_key() api = shodan.Shodan(key) try: host = api.host(ip, history=history) # Print the host information to the terminal using the user-specified format HOST_PRINT[format](host, history=history) # Store the results if filename or save: if save: filename = '{}.json.gz'.format(ip) # Add the appropriate extension if it's not there atm if not filename.endswith('.json.gz'): filename += '.json.gz' # Create/ append to the file fout = helpers.open_file(filename) for banner in sorted(host['data'], key=lambda k: k['port']): if 'placeholder' not in banner: helpers.write_banner(fout, banner) except shodan.APIError as e: raise click.ClickException(e.value)
def scan_status(scan_id): """Check the status of an on-demand scan.""" key = get_api_key() api = shodan.Shodan(key) try: scan = api.scan_status(scan_id) click.echo(scan['status']) except shodan.APIError as e: raise click.ClickException(e.value)
def alert_remove(alert_id): """Remove the specified alert""" key = get_api_key() # Get the list api = shodan.Shodan(key) try: api.delete_alert(alert_id) except shodan.APIError as e: raise click.ClickException(e.value) click.echo("Alert deleted")
def remove(user): """Remove and downgrade a member""" key = get_api_key() api = shodan.Shodan(key) try: api.org.remove_member(user) except shodan.APIError as e: raise click.ClickException(e.value) click.secho('Successfully removed the member', fg='green')
def add(silent, user): """Add a new member""" key = get_api_key() api = shodan.Shodan(key) try: api.org.add_member(user, notify=not silent) except shodan.APIError as e: raise click.ClickException(e.value) click.secho('Successfully added the new member', fg='green')
def scan_protocols(): """List the protocols that you can scan with using Shodan.""" key = get_api_key() api = shodan.Shodan(key) try: protocols = api.protocols() for name, description in iter(protocols.items()): click.echo( click.style('{0:<30}'.format(name), fg='cyan') + description) except shodan.APIError as e: raise click.ClickException(e.value)
def info(): """Shows general information about your account""" key = get_api_key() api = shodan.Shodan(key) try: results = api.info() except shodan.APIError as e: raise click.ClickException(e.value) click.echo("""Query credits available: {0} Scan credits available: {1} """.format(results['query_credits'], results['scan_credits']))
def alert_create(name, netblocks): """Create a network alert to monitor an external network""" key = get_api_key() # Get the list api = shodan.Shodan(key) try: alert = api.create_alert(name, netblocks) except shodan.APIError as e: raise click.ClickException(e.value) click.secho('Successfully created network alert!', fg='green') click.secho('Alert ID: {}'.format(alert['id']), fg='cyan')
def alert_disable_trigger(alert_id, trigger): """Disable a trigger for the alert""" key = get_api_key() # Get the list api = shodan.Shodan(key) try: api.disable_alert_trigger(alert_id, trigger) except shodan.APIError as e: raise click.ClickException(e.value) click.secho('Successfully disabled the trigger: {}'.format(trigger), fg='green')
def radar(): """Real-Time Map of some results as Shodan finds them.""" key = get_api_key() api = shodan.Shodan(key) from shodan.cli.worldmap import launch_map try: launch_map(api) except shodan.APIError as e: raise click.ClickException(e.value) except Exception as e: raise click.ClickException(u'{}'.format(e))
def myip(ipv6): """Print your external IP address""" key = get_api_key() api = shodan.Shodan(key) # Use the IPv6-enabled domain if requested if ipv6: api.base_url = 'https://apiv6.shodan.io' try: click.echo(api.tools.myip()) except shodan.APIError as e: raise click.ClickException(e.value)
def alert_clear(): """Remove all alerts""" key = get_api_key() # Get the list api = shodan.Shodan(key) try: alerts = api.alerts() for alert in alerts: click.echo(u'Removing {} ({})'.format(alert['name'], alert['id'])) api.delete_alert(alert['id']) except shodan.APIError as e: raise click.ClickException(e.value) click.echo("Alerts deleted")
def data_download(chunksize, filename, dataset, name): # Setup the API connection key = get_api_key() api = shodan.Shodan(key) # Get the file object that the user requested which will contain the URL and total file size file = None try: files = api.data.list_files(dataset) for tmp in files: if tmp['name'] == name: file = tmp break except shodan.APIError as e: raise click.ClickException(e.value) # The file isn't available if not file: raise click.ClickException('File not found') # Start downloading the file response = requests.get(file['url'], stream=True) # Figure out the size of the file based on the headers filesize = response.headers.get('content-length', None) if not filesize: # Fall back to using the filesize provided by the API filesize = file['size'] else: filesize = int(filesize) chunk_size = 1024 limit = filesize / chunk_size # Create a default filename based on the dataset and the filename within that dataset if not filename: filename = '{}-{}'.format(dataset, name) # Open the output file and start writing to it in chunks with open(filename, 'wb') as fout: with click.progressbar(response.iter_content(chunk_size=chunk_size), length=limit) as bar: for chunk in bar: if chunk: fout.write(chunk) click.echo(click.style('Download completed: {}'.format(filename), 'green'))
def honeyscore(ip): """Check whether the IP is a honeypot or not.""" key = get_api_key() api = shodan.Shodan(key) try: score = api.labs.honeyscore(ip) if score == 1.0: click.echo(click.style('Honeypot detected', fg='red')) elif score > 0.5: click.echo(click.style('Probably a honeypot', fg='yellow')) else: click.echo(click.style('Not a honeypot', fg='green')) click.echo('Score: {}'.format(score)) except Exception: raise click.ClickException('Unable to calculate honeyscore')
def count(query): """Returns the number of results for a search""" key = get_api_key() # Create the query string out of the provided tuple query = ' '.join(query).strip() # Make sure the user didn't supply an empty string if query == '': raise click.ClickException('Empty search query') # Perform the search api = shodan.Shodan(key) try: results = api.count(query) except shodan.APIError as e: raise click.ClickException(e.value) click.echo(results['total'])
def scan_list(): """Show recently launched scans""" key = get_api_key() # Get the list api = shodan.Shodan(key) try: scans = api.scans() except shodan.APIError as e: raise click.ClickException(e.value) if len(scans) > 0: click.echo(u'# {} Scans Total - Showing 10 most recent scans:'.format( scans['total'])) click.echo(u'# {:20} {:<15} {:<10} {:<15s}'.format( 'Scan ID', 'Status', 'Size', 'Timestamp')) # click.echo('#' * 65) for scan in scans['matches'][:10]: click.echo(u'{:31} {:<24} {:<10} {:<15s}'.format( click.style(scan['id'], fg='yellow'), click.style(scan['status'], fg='cyan'), scan['size'], scan['created'])) else: click.echo("You haven't yet launched any scans.")
def scan_submit(wait, filename, force, verbose, netblocks): """Scan an IP/ netblock using Shodan.""" key = get_api_key() api = shodan.Shodan(key) alert = None # Submit the IPs for scanning try: # Submit the scan scan = api.scan(netblocks, force=force) now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M') click.echo('') click.echo('Starting Shodan scan at {} - {} scan credits left'.format( now, scan['credits_left'])) if verbose: click.echo('# Scan ID: {}'.format(scan['id'])) # Return immediately if wait <= 0: click.echo( 'Exiting now, not waiting for results. Use the API or website to retrieve the results of the scan.' ) else: # Setup an alert to wait for responses alert = api.create_alert('Scan: {}'.format(', '.join(netblocks)), netblocks) # Create the output file if necessary filename = filename.strip() fout = None if filename != '': # Add the appropriate extension if it's not there atm if not filename.endswith('.json.gz'): filename += '.json.gz' fout = helpers.open_file(filename, 'w') # Start a spinner finished_event = threading.Event() progress_bar_thread = threading.Thread(target=async_spinner, args=(finished_event, )) progress_bar_thread.start() # Now wait a few seconds for items to get returned hosts = collections.defaultdict(dict) done = False scan_start = time.time() cache = {} while not done: try: for banner in api.stream.alert(aid=alert['id'], timeout=wait): ip = banner.get('ip', banner.get('ipv6', None)) if not ip: continue # Don't show duplicate banners cache_key = '{}:{}'.format(ip, banner['port']) if cache_key not in cache: hosts[helpers.get_ip(banner)][ banner['port']] = banner cache[cache_key] = True # If we've grabbed data for more than 60 seconds it might just be a busy network and we should move on if time.time() - scan_start >= 60: scan = api.scan_status(scan['id']) if verbose: click.echo('# Scan status: {}'.format( scan['status'])) if scan['status'] == 'DONE': done = True break except shodan.APIError: # If the connection timed out before the timeout, that means the streaming server # that the user tried to reach is down. In that case, lets wait briefly and try # to connect again! if (time.time() - scan_start) < wait: time.sleep(0.5) continue # Exit if the scan was flagged as done somehow if done: break scan = api.scan_status(scan['id']) if scan['status'] == 'DONE': done = True if verbose: click.echo('# Scan status: {}'.format(scan['status'])) except socket.timeout: # If the connection timed out before the timeout, that means the streaming server # that the user tried to reach is down. In that case, lets wait a second and try # to connect again! if (time.time() - scan_start) < wait: continue done = True except Exception as e: finished_event.set() progress_bar_thread.join() raise click.ClickException(repr(e)) finished_event.set() progress_bar_thread.join() def print_field(name, value): click.echo(' {:25s}{}'.format(name, value)) def print_banner(banner): click.echo(' {:20s}'.format( click.style(str(banner['port']), fg='green') + '/' + banner['transport']), nl=False) if 'product' in banner: click.echo(banner['product'], nl=False) if 'version' in banner: click.echo(' ({})'.format(banner['version']), nl=False) click.echo('') # Show optional ssl info if 'ssl' in banner: if 'versions' in banner['ssl']: # Only print SSL versions if they were successfully tested versions = [ version for version in sorted(banner['ssl']['versions']) if not version.startswith('-') ] if len(versions) > 0: click.echo(' |-- SSL Versions: {}'.format( ', '.join(versions))) if 'dhparams' in banner['ssl'] and banner['ssl'][ 'dhparams']: click.echo(' |-- Diffie-Hellman Parameters:') click.echo(' {:15s}{}\n {:15s}{}'.format( 'Bits:', banner['ssl']['dhparams']['bits'], 'Generator:', banner['ssl']['dhparams']['generator'])) if 'fingerprint' in banner['ssl']['dhparams']: click.echo(' {:15s}{}'.format( 'Fingerprint:', banner['ssl']['dhparams']['fingerprint'])) if hosts: # Remove the remaining spinner character click.echo('\b ') for ip in sorted(hosts): host = next(iter(hosts[ip].items()))[1] click.echo(click.style(ip, fg='cyan'), nl=False) if 'hostnames' in host and host['hostnames']: click.echo(' ({})'.format(', '.join( host['hostnames'])), nl=False) click.echo('') if 'location' in host and 'country_name' in host[ 'location'] and host['location']['country_name']: print_field('Country', host['location']['country_name']) if 'city' in host['location'] and host['location'][ 'city']: print_field('City', host['location']['city']) if 'org' in host and host['org']: print_field('Organization', host['org']) if 'os' in host and host['os']: print_field('Operating System', host['os']) click.echo('') # Output the vulnerabilities the host has if 'vulns' in host and len(host['vulns']) > 0: vulns = [] for vuln in host['vulns']: if vuln.startswith('!'): continue if vuln.upper() == 'CVE-2014-0160': vulns.append( click.style('Heartbleed', fg='red')) else: vulns.append(click.style(vuln, fg='red')) if len(vulns) > 0: click.echo(' {:25s}'.format('Vulnerabilities:'), nl=False) for vuln in vulns: click.echo(vuln + '\t', nl=False) click.echo('') # Print all the open ports: click.echo(' Open Ports:') for port in sorted(hosts[ip]): print_banner(hosts[ip][port]) # Save the banner in a file if necessary if fout: helpers.write_banner(fout, hosts[ip][port]) click.echo('') else: # Prepend a \b to remove the spinner click.echo( '\bNo open ports found or the host has been recently crawled and cant get scanned again so soon.' ) except shodan.APIError as e: raise click.ClickException(e.value) finally: # Remove any alert if alert: api.delete_alert(alert['id'])
def stream(color, fields, separator, limit, datadir, ports, quiet, timeout, streamer, countries, asn, alert, tags, compresslevel, vulns): """Stream data in real-time.""" # Setup the Shodan API key = get_api_key() api = shodan.Shodan(key) # Temporarily change the baseurl api.stream.base_url = streamer # Strip out any whitespace in the fields and turn them into an array fields = [item.strip() for item in fields.split(',')] if len(fields) == 0: raise click.ClickException( 'Please define at least one property to show') # The user must choose "ports", "countries", "asn" or nothing - can't select multiple # filtered streams at once. stream_type = [] if ports: stream_type.append('ports') if countries: stream_type.append('countries') if asn: stream_type.append('asn') if alert: stream_type.append('alert') if tags: stream_type.append('tags') if vulns: stream_type.append('vulns') if len(stream_type) > 1: raise click.ClickException( 'Please use --ports, --countries, --tags, --vulns OR --asn. You cant subscribe to multiple filtered streams at once.' ) stream_args = None # Turn the list of ports into integers if ports: try: stream_args = [int(item.strip()) for item in ports.split(',')] except ValueError: raise click.ClickException('Invalid list of ports') if alert: alert = alert.strip() if alert.lower() != 'all': stream_args = alert if asn: stream_args = asn.split(',') if countries: stream_args = countries.split(',') if tags: stream_args = tags.split(',') if vulns: stream_args = vulns.split(',') # Flatten the list of stream types # Possible values are: # - all # - asn # - countries # - ports if len(stream_type) == 1: stream_type = stream_type[0] else: stream_type = 'all' # Decide which stream to subscribe to based on whether or not ports were selected def _create_stream(name, args, timeout): return { 'all': api.stream.banners(timeout=timeout), 'alert': api.stream.alert(args, timeout=timeout), 'asn': api.stream.asn(args, timeout=timeout), 'countries': api.stream.countries(args, timeout=timeout), 'ports': api.stream.ports(args, timeout=timeout), 'tags': api.stream.tags(args, timeout=timeout), 'vulns': api.stream.vulns(args, timeout=timeout), }.get(name, 'all') stream = _create_stream(stream_type, stream_args, timeout=timeout) counter = 0 quit = False last_time = timestr() fout = None if datadir: fout = open_streaming_file(datadir, last_time, compresslevel) while not quit: try: for banner in stream: # Limit the number of results to output if limit > 0: counter += 1 if counter > limit: quit = True break # Write the data to the file if datadir: cur_time = timestr() if cur_time != last_time: last_time = cur_time fout.close() fout = open_streaming_file(datadir, last_time) helpers.write_banner(fout, banner) # Print the banner information to stdout if not quiet: row = u'' # Loop over all the fields and print the banner as a row for field in fields: tmp = u'' value = get_banner_field(banner, field) if value: field_type = type(value) # If the field is an array then merge it together if field_type == list: tmp = u';'.join(value) elif field_type in [int, float]: tmp = u'{}'.format(value) else: tmp = escape_data(value) # Colorize certain fields if the user wants it if color: tmp = click.style(tmp, fg=COLORIZE_FIELDS.get( field, 'white')) # Add the field information to the row row += tmp row += separator click.echo(row) except requests.exceptions.Timeout: raise click.ClickException('Connection timed out') except KeyboardInterrupt: quit = True except shodan.APIError as e: raise click.ClickException(e.value) except Exception: # For other errors lets just wait a bit and try to reconnect again time.sleep(1) # Create a new stream object to subscribe to stream = _create_stream(stream_type, stream_args, timeout=timeout)
def stats(limit, facets, filename, query): """Provide summary information about a search query""" # Setup Shodan key = get_api_key() api = shodan.Shodan(key) # Create the query string out of the provided tuple query = ' '.join(query).strip() # Make sure the user didn't supply an empty string if query == '': raise click.ClickException('Empty search query') facets = facets.split(',') facets = [(facet, limit) for facet in facets] # Perform the search try: results = api.count(query, facets=facets) except shodan.APIError as e: raise click.ClickException(e.value) # Print the stats tables for facet in results['facets']: click.echo('Top {} Results for Facet: {}'.format( len(results['facets'][facet]), facet)) for item in results['facets'][facet]: # Force the value to be a string - necessary because some facet values are numbers value = u'{}'.format(item['value']) click.echo(click.style(u'{:28s}'.format(value), fg='cyan'), nl=False) click.echo( click.style(u'{:12,d}'.format(item['count']), fg='green')) click.echo('') # Create the output file if requested fout = None if filename: if not filename.endswith('.csv'): filename += '.csv' fout = open(filename, 'w') writer = csv.writer(fout, dialect=csv.excel) # Write the header writer.writerow(['Query', query]) # Add an empty line to separate rows writer.writerow([]) # Write the header that contains the facets row = [] for facet in results['facets']: row.append(facet) row.append('') writer.writerow(row) # Every facet has 2 columns (key, value) counter = 0 has_items = True while has_items: # pylint: disable=W0612 row = ['' for i in range(len(results['facets']) * 2)] pos = 0 has_items = False for facet in results['facets']: values = results['facets'][facet] # Add the values for the facet into the current row if len(values) > counter: has_items = True row[pos] = values[counter]['value'] row[pos + 1] = values[counter]['count'] pos += 2 # Write out the row if has_items: writer.writerow(row) # Move to the next row of values counter += 1
def search(color, fields, limit, separator, query): """Search the Shodan database""" key = get_api_key() # Create the query string out of the provided tuple query = ' '.join(query).strip() # Make sure the user didn't supply an empty string if query == '': raise click.ClickException('Empty search query') # For now we only allow up to 1000 results at a time if limit > 1000: raise click.ClickException( 'Too many results requested, maximum is 1,000') # Strip out any whitespace in the fields and turn them into an array fields = [item.strip() for item in fields.split(',')] if len(fields) == 0: raise click.ClickException( 'Please define at least one property to show') # Perform the search api = shodan.Shodan(key) try: results = api.search(query, limit=limit) except shodan.APIError as e: raise click.ClickException(e.value) # Error out if no results were found if results['total'] == 0: raise click.ClickException('No search results found') # We buffer the entire output so we can use click's pager functionality output = u'' for banner in results['matches']: row = u'' # Loop over all the fields and print the banner as a row for field in fields: tmp = u'' value = get_banner_field(banner, field) if value: field_type = type(value) # If the field is an array then merge it together if field_type == list: tmp = u';'.join(value) elif field_type in [int, float]: tmp = u'{}'.format(value) else: tmp = escape_data(value) # Colorize certain fields if the user wants it if color: tmp = click.style(tmp, fg=COLORIZE_FIELDS.get(field, 'white')) # Add the field information to the row row += tmp row += separator # click.echo(out + separator, nl=False) output += row + u'\n' # click.echo('') click.echo_via_pager(output)
def download(limit, skip, filename, query): """Download search results and save them in a compressed JSON file.""" key = get_api_key() # Create the query string out of the provided tuple query = ' '.join(query).strip() # Make sure the user didn't supply an empty string if query == '': raise click.ClickException('Empty search query') filename = filename.strip() if filename == '': raise click.ClickException('Empty filename') # Add the appropriate extension if it's not there atm if not filename.endswith('.json.gz'): filename += '.json.gz' # Perform the search api = shodan.Shodan(key) try: total = api.count(query)['total'] info = api.info() except Exception: raise click.ClickException( 'The Shodan API is unresponsive at the moment, please try again later.' ) # Print some summary information about the download request click.echo('Search query:\t\t\t%s' % query) click.echo('Total number of results:\t%s' % total) click.echo('Query credits left:\t\t%s' % info['unlocked_left']) click.echo('Output file:\t\t\t%s' % filename) if limit > total: limit = total # A limit of -1 means that we should download all the data if limit <= 0: limit = total # Adjust the total number of results we should expect to download if the user is skipping results if skip > 0: limit -= skip with helpers.open_file(filename, 'w') as fout: count = 0 try: cursor = api.search_cursor(query, minify=False, skip=skip) with click.progressbar(cursor, length=limit) as bar: for banner in bar: helpers.write_banner(fout, banner) count += 1 if count >= limit: break except Exception: pass # Let the user know we're done if count < limit: click.echo( click.style('Notice: fewer results were saved than requested', 'yellow')) click.echo( click.style( u'Saved {} results into file {}'.format(count, filename), 'green'))
def scan_internet(quiet, port, protocol): """Scan the Internet for a specific port and protocol using the Shodan infrastructure.""" key = get_api_key() api = shodan.Shodan(key) try: # Submit the request to Shodan click.echo('Submitting Internet scan to Shodan...', nl=False) scan = api.scan_internet(port, protocol) click.echo('Done') # If the requested port is part of the regular Shodan crawling, then # we don't know when the scan is done so lets return immediately and # let the user decide when to stop waiting for further results. official_ports = api.ports() if port in official_ports: click.echo( 'The requested port is already indexed by Shodan. A new scan for the port has been launched, please subscribe to the real-time stream for results.' ) else: # Create the output file filename = '{0}-{1}.json.gz'.format(port, protocol) counter = 0 with helpers.open_file(filename, 'w') as fout: click.echo('Saving results to file: {0}'.format(filename)) # Start listening for results done = False # Keep listening for results until the scan is done click.echo('Waiting for data, please stand by...') while not done: try: for banner in api.stream.ports([port], timeout=90): counter += 1 helpers.write_banner(fout, banner) if not quiet: click.echo('{0:<40} {1:<20} {2}'.format( click.style(helpers.get_ip(banner), fg=COLORIZE_FIELDS['ip_str']), click.style(str(banner['port']), fg=COLORIZE_FIELDS['port']), ';'.join(banner['hostnames']))) except shodan.APIError: # We stop waiting for results if the scan has been processed by the crawlers and # there haven't been new results in a while if done: break scan = api.scan_status(scan['id']) if scan['status'] == 'DONE': done = True except socket.timeout: # We stop waiting for results if the scan has been processed by the crawlers and # there haven't been new results in a while if done: break scan = api.scan_status(scan['id']) if scan['status'] == 'DONE': done = True except Exception as e: raise click.ClickException(repr(e)) click.echo('Scan finished: {0} devices found'.format(counter)) except shodan.APIError as e: raise click.ClickException(e.value)
def domain_info(domain, details, save, history, type): """View all available information for a domain""" key = get_api_key() api = shodan.Shodan(key) try: info = api.dns.domain_info(domain, history=history, type=type) except shodan.APIError as e: raise click.ClickException(e.value) # Grab the host information for any IP records that were returned hosts = {} if details: ips = [ record['value'] for record in info['data'] if record['type'] in ['A', 'AAAA'] ] ips = set(ips) fout = None if save: filename = u'{}-hosts.json.gz'.format(domain) fout = helpers.open_file(filename) for ip in ips: try: hosts[ip] = api.host(ip) # Store the banners if requested if fout: for banner in hosts[ip]['data']: if 'placeholder' not in banner: helpers.write_banner(fout, banner) except shodan.APIError: pass # Ignore any API lookup errors as this isn't critical information # Save the DNS data if save: filename = u'{}.json.gz'.format(domain) fout = helpers.open_file(filename) for record in info['data']: helpers.write_banner(fout, record) click.secho(info['domain'].upper(), fg='green') click.echo('') for record in info['data']: click.echo( u'{:32} {:14} {}'.format( click.style(record['subdomain'], fg='cyan'), click.style(record['type'], fg='yellow'), record['value']), nl=False, ) if record['value'] in hosts: host = hosts[record['value']] click.secho(u' Ports: {}'.format(', '.join( [str(port) for port in sorted(host['ports'])])), fg='blue', nl=False) click.echo('')