-
Notifications
You must be signed in to change notification settings - Fork 1
/
shodanscanner.py
154 lines (122 loc) · 4.24 KB
/
shodanscanner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import shodan
import socket
from pprint import pprint as pp
import json
from openpyxl import Workbook
from openpyxl import styles
import argparse
import sys
from dotenv import load_dotenv
import os
from tqdm import tqdm
parser = argparse.ArgumentParser(
prog='shodanscanner',
description='Simple script to bulk scan IPs on Shodan',
epilog='made by aerodiduch. https://github.com/aerodiduch'
)
parser.add_argument(
'-f', '--file', help='File containing IP address to scan. Input must be one IP per line. ',
)
parser.add_argument(
'-t', '--target', help='Scan a single target, e.g: -t 200.100.20.10'
)
parser.add_argument(
'-o', '--output', help='Name of the output file without extension. Default value is "results".'
)
def load_data(filename: str) -> list:
'''Loads data from specified target file
Args:
filename (str): File containing hosts data
Returns:
list: List contaning hosts
'''
hosts = []
with open(filename, 'r') as fh:
for line in fh:
hosts.append(
line.replace('\n', '')
)
return hosts
def save_data(data: list, workbook: Workbook, filename: str):
'''Saves parsed data to output XLSX file.
Args:
data (list): API response from Shodan
workbook (Workbook): Workbook object to write
filename (str): Output filename
'''
ws = workbook.active
if 'ports' in data:
ports = ", ".join([str(port) for port in data['ports']])
else:
ports = '----'
if 'domains' in data:
domains = ", ".join([str(domain) for domain in data['domains']])
else:
domains = '----'
if 'vulns' in data:
vulns = ", ".join([str(vuln) for vuln in data['vulns']])
else:
vulns = '----'
font = styles.Font(bold=True)
headers = ws['A1':'I1']
for column in headers[0]:
column.font = font
ws.append(
[data['ip_str'], data['isp'], data['asn'], data['city'], ports, '', vulns, data['last_update'], domains]
)
workbook.save(f'{filename}.xlsx')
def request_data(api: shodan.Shodan, data: list, output_name: str):
'''Request data to Shodan API
Args:
shodan (shodan.Shodan): Shodan object with API key
data (list): List of hosts
output_name (str): Fiilename to write to.
'''
print(f'-> Ready to scan {len(data)} hosts...\n')
failed = []
for host in tqdm(data):
try:
host_info = api.host(host)
save_data(host_info, wb, output_name)
except shodan.APIError as e:
if 'IP' in e.args[0]:
failed.append(host)
#print(f'-> No results for {host}')
continue
print(f'\n-> Finished scan. Results dumped to "{output_name}.xlsx"')
if failed:
print(f'\nNo results found for: {", ".join([i for i in failed])}.')
def set_api_key():
print('[!!!] No API KEY detected.\n')
print(
'This will be prompted only one time to set API KEY.\nA .env file will be created containing it.\nYou will find your Shodan API KEY on https://account.shodan.io/'
)
print('\nYou can change it later editing the .env file created on this directory.')
print('If no valid API KEY is provided, shodanscanner can not make requests through Shodan API.')
key = input('\n[!] Paste your API KEY: ')
while len(key) < 30:
print("\nInvalid API KEY. Shodan's API KEY must have at least 30 characters\n")
key = input('\n[!] Paste your API KEY: ')
with open('.env', 'w') as fh:
fh.write(
f"API_KEY='{key}'"
)
print('API KEY saved succesfully.\n\n')
return True
if __name__ == '__main__':
load_dotenv()
API_KEY = os.getenv('API_KEY')
if not API_KEY:
set_api_key()
api = shodan.Shodan(API_KEY)
args = parser.parse_args()
if len(sys.argv) < 2:
parser.print_help()
wb = Workbook()
wb.active.append(
['IP', 'ISP', 'ASN', 'LOCATION', 'PORTS', 'PRODUCTS', 'CVEs', 'LAST UPDATED', 'DOMAINS']
)
if args.file:
output_name = args.output if args.output is not None else 'results'
data = load_data(args.file)
request_data(api, data, output_name)