├── .gitignore
├── README.md
├── shodan.tgz
├── shodan_playbook
└── shodan_example.py
├── shodanapp
├── __init__.py
├── exclude_files.txt
├── readme.html
├── shodan.json
├── shodan_connector.py
├── shodan_consts.py
└── shodan_logo.png
└── test_jsons
├── query_domain.json
├── query_ip.json
└── test_connectivity.json
/.gitignore:
--------------------------------------------------------------------------------
1 | **.pyc
2 | **.bak
3 |
4 | *.swp
5 |
6 | shodanapp.tgz
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shodan.io API connector for Phantom
2 |
3 | This App for [Phantom security orchestrator](https://www.phantom.us/product.html) provides access to
4 | information from [Shodan.io](https://www.shodan.io).
5 |
6 |
7 |
8 | ## Suggested Use-Cases
9 |
10 | This app can be used to check whether or not a given IP address is listening given ports. This allows us to gain information that is: credible, publicly accessible, and does not require a single packet to be sent to the target IP address.
11 |
12 | For Example:
13 |
14 | * For alerts about an inbound connection, phantom can validate whether or not the service is publicly accessible.
15 | * For alerts regarding outbound connections (irc, smtp, ntp, etc.) this can be used to verify whether or not the host is hosting that service and what service is listening on that port.
16 | * Perform reconnaissance on internet hosts
17 |
18 | ## Current Actions
19 |
20 | * `query ip` - Query shodan for observed services for a given IP
21 | * `query domain` - Query shodan for observed services for a given fqdn
22 |
23 | ## Future Features
24 |
25 | Currently, this app performs a lookup against a domain or ip address for open ports and services.
26 |
27 | Future areas of expansion include:
28 |
29 | * Implement more specific checks to look for specific IP _and_ Port
30 | * Implement checks to support IP ranges / CIDR blocks
31 | * Triggering on-demand scan for paid developer accounts
32 | * DNS forward and reverse resolution
33 | * Implement widgets (Webpage Thumbnails, etc.)
34 |
35 | ## Setup
36 |
37 | 1. Download a [Phantom](https://www.phantom.us/product.html) appliance from Phantom Cyber.
38 | 2. Select "Import App" from the *Administration / Apps* tab.
39 | * Select the `shodan.tgz` file
40 | * Check "Replace an Existing app" if an older version is installed
41 | 3. Obtain an API key from [Shodan.io](https://www.shodan.io). A free API key can be obtained from the Shodan.io website by registering and visiting your [account page](https://account.shodan.io)
42 | 4. Configure the Shodan asset.
43 | * Product Vendor = "Shodan.io"
44 | * Product Name = "Shodan.io"
45 | * Set the "Shodan API Key" field in the phantom "Asset Settings" tab
46 | 5. Configure the `api_key` in the json files located in the `test_json` directory (for CLI debugging)
47 |
48 | ## References
49 |
50 | * Phantom Cyber Product Page [https://www.phantom.us/product.html](https://www.phantom.us/product.html)
51 | * Shodan Developer Reference: [https://developer.shodan.io/api](https://developer.shodan.io/api)
52 | * Shodan Search Page: [https://www.shodan.io](https://www.shodan.io)
53 |
--------------------------------------------------------------------------------
/shodan.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kranzrm/PhantomShodan/492a0e20dbc7b52980f533441f49bebcd2342913/shodan.tgz
--------------------------------------------------------------------------------
/shodan_playbook/shodan_example.py:
--------------------------------------------------------------------------------
1 | import phantom.rules as phantom
2 | import json
3 |
4 | def query_ip_cb(action, success, container, results, handle):
5 |
6 | if not success:
7 | return
8 |
9 | return
10 |
11 | def query_ip1_cb(action, success, container, results, handle):
12 |
13 | if not success:
14 | return
15 |
16 | return
17 |
18 |
19 | def on_start(container):
20 |
21 | sourceAddress = set(phantom.collect(container, 'artifact:*.cef.sourceAddress'))
22 |
23 | parameters = []
24 |
25 | for ip in sourceAddress:
26 | parameters.append({"ip": ip,})
27 |
28 | phantom.act("query ip", parameters=parameters, assets=["shodan"]) # callback=query_ip_cb
29 |
30 | destinationAddress = set(phantom.collect(container, 'artifact:*.cef.destinationAddress'))
31 |
32 | parameters = []
33 |
34 | for ip in destinationAddress:
35 | parameters.append({"ip": ip,})
36 |
37 | phantom.act("query ip", parameters=parameters, assets=["shodan"])
38 |
39 | destinationDomains = set(phantom.collect(container, 'artifact:*.cef.destinationDnsDomain'))
40 |
41 | parameters = []
42 |
43 | for domain in destinationDomains:
44 | parameters.append({"domain": domain,})
45 |
46 | phantom.act("query domain", parameters=parameters, assets=["shodan"])
47 |
48 | return
49 |
50 | def on_finish(container, summary):
51 |
52 | # This function is called after all actions are completed.
53 | # Summary and/or action results can be collected here.
54 |
55 | # summary_json = phantom.get_summary()
56 | # summary_results = summary_json['result']
57 | # for result in summary_results:
58 | # app_runs = result['app_runs']
59 | # for app_run in app_runs:
60 | # app_run_id = app_run['app_run_id']
61 | # action_results = phantom.get_action_results(app_run_id=app_run_id)
62 | return
63 |
--------------------------------------------------------------------------------
/shodanapp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kranzrm/PhantomShodan/492a0e20dbc7b52980f533441f49bebcd2342913/shodanapp/__init__.py
--------------------------------------------------------------------------------
/shodanapp/exclude_files.txt:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
--------------------------------------------------------------------------------
/shodanapp/readme.html:
--------------------------------------------------------------------------------
1 |
Shodan collects data mostly on web servers (HTTP, port 80), as well as FTP (port 21), SSH (port 22) Telnet (port 23), SNMP (port 161), SIP (port 5060),[2] and Real Time Streaming Protocol (RTSP, port 554).
2 | Suggested Use-cases
3 | This app can be used to check whether or not a given IP address is listening given ports. This allows us to gain information that is: credible, publicly accessible, and does not require a single packet to be sent to the target IP address.
4 | For Example:
5 |
6 | - For alerts about an inbound connection, phantom can validate whether or not the service is publicly accessible.
7 | - For alerts regarding outbound connections (irc, smtp, ntp, etc.) this can be used to verify whether or not the host is hosting that service and what service is listening on that port.
8 | - Perform reconnaissance on internet hosts
9 |
10 | Future Features
11 | Currently, this app performs a lookup against a domain or ip address for open ports and services.
12 | Future areas of expansion include:
13 |
14 | - Implement more specific checks to look for specific IP and Port
15 | - Implement checks to support IP ranges / CIDR blocks
16 | - Triggering on-demand scan for paid developer accounts
17 | - DNS forward and reverse resolution
18 | - Implement widgets (Webpage Thumbnails, etc.)
19 |
20 | Requirement
21 | In order to use this app, you must obtain an API key from Shodan.io. A free API key can be obtained from the Shodan.io website by registering and visiting your account page
22 | References
23 |
27 |
--------------------------------------------------------------------------------
/shodanapp/shodan.json:
--------------------------------------------------------------------------------
1 | {
2 | "appid" : "d0e7f2cb-2826-42e4-a4ae-2a2cbe64802c",
3 | "name" : "Shodan",
4 | "description" : "This app implements investigative actions like query ip and query domain to get information from the shodan search engine.",
5 | "publisher": "Ryan Kranz",
6 | "package_name": "phantom_shodan",
7 | "type": "information",
8 | "license": "The GNU General Public License v3.0",
9 | "main_module" : "shodan_connector.py",
10 | "app_version": "1.0.5",
11 | "utctime_updated": "2016-05-26T04:20:36.000000Z",
12 | "product_vendor": "Shodan",
13 | "product_name": "Shodan",
14 | "product_version_regex": ".*",
15 | "min_phantom_version": "1.0.240",
16 | "logo": "shodan_logo.png",
17 | "configuration": {
18 | "api_key": {
19 | "description": "Shodan API Key",
20 | "data_type": "string",
21 | "required": true
22 | }
23 | },
24 | "actions": [
25 | {
26 | "action": "test connectivity",
27 | "description": "Validate the asset configuration for connectivity.",
28 | "type": "test",
29 | "identifier": "test_asset_connectivity",
30 | "read_only": true,
31 | "parameters": {
32 | },
33 | "output": [],
34 | "versions":"EQ(*)"
35 | },
36 | {
37 | "action": "query ip",
38 | "description": "Search Shodan.io for discovered service info",
39 | "type": "investigate",
40 | "identifier": "query_ip",
41 | "read_only": true,
42 | "parameters": {
43 | "ip": {
44 | "description": "IP to query",
45 | "data_type": "string",
46 | "contains": ["ip"],
47 | "primary": true,
48 | "required": true
49 | }
50 | },
51 | "render": {
52 | "type": "table",
53 | "width": 12,
54 | "height": 5,
55 | "title": "Shodan IP Details"
56 | },
57 | "output": [
58 | {
59 | "data_path": "action_result.parameter.ip",
60 | "data_type": "string",
61 | "contains": [
62 | "ip"
63 | ],
64 | "column_name": "IP Address",
65 | "column_order": 0
66 | },
67 | {
68 | "data_path": "action_result.data.*.os",
69 | "data_type": "string"
70 | },
71 | {
72 | "data_path": "action_result.data.*.opts",
73 | "data_type": "string"
74 | },
75 | {
76 | "data_path": "action_result.data.*.domains.*.",
77 | "data_type": "string"
78 | },
79 | {
80 | "data_path": "action_result.data.*.deprecated.html.eol",
81 | "data_type": "string"
82 | },
83 | {
84 | "data_path": "action_result.data.*.deprecated.html.new",
85 | "data_type": "string"
86 | },
87 | {
88 | "data_path": "action_result.data.*.deprecated.title.eol",
89 | "data_type": "string"
90 | },
91 | {
92 | "data_path": "action_result.data.*.deprecated.title.new",
93 | "data_type": "string"
94 | },
95 | {
96 | "data_path": "action_result.data.*.deprecated.opts.pem.eol",
97 | "data_type": "string"
98 | },
99 | {
100 | "data_path": "action_result.data.*.deprecated.opts.pem.new",
101 | "data_type": "string"
102 | },
103 | {
104 | "data_path": "action_result.data.*.deprecated.opts.robots.eol",
105 | "data_type": "string"
106 | },
107 | {
108 | "data_path": "action_result.data.*.deprecated.opts.robots.new",
109 | "data_type": "string"
110 | },
111 | {
112 | "data_path": "action_result.data.*.deprecated.opts.sitemap.eol",
113 | "data_type": "string"
114 | },
115 | {
116 | "data_path": "action_result.data.*.deprecated.opts.sitemap.new",
117 | "data_type": "string"
118 | },
119 | {
120 | "data_path": "action_result.data.*.html",
121 | "data_type": "string"
122 | },
123 | {
124 | "data_path": "action_result.data.*.http.host",
125 | "data_type": "string"
126 | },
127 | {
128 | "data_path": "action_result.data.*.http.html",
129 | "data_type": "string"
130 | },
131 | {
132 | "data_path": "action_result.data.*.http.title",
133 | "data_type": "string"
134 | },
135 | {
136 | "data_path": "action_result.data.*.http.robots",
137 | "data_type": "string"
138 | },
139 | {
140 | "data_path": "action_result.data.*.http.server",
141 | "data_type": "string"
142 | },
143 | {
144 | "data_path": "action_result.data.*.http.location",
145 | "data_type": "string"
146 | },
147 | {
148 | "data_path": "action_result.data.*.http.html_hash",
149 | "data_type": "numeric"
150 | },
151 | {
152 | "data_path": "action_result.data.*.http.redirects.*.data",
153 | "data_type": "string"
154 | },
155 | {
156 | "data_path": "action_result.data.*.http.redirects.*.host",
157 | "data_type": "string"
158 | },
159 | {
160 | "data_path": "action_result.data.*.http.redirects.*.location",
161 | "data_type": "string"
162 | },
163 | {
164 | "data_path": "action_result.data.*.ssl.cert.issued",
165 | "data_type": "string"
166 | },
167 | {
168 | "data_path": "action_result.data.*.ssl.cert.issuer.C",
169 | "data_type": "string"
170 | },
171 | {
172 | "data_path": "action_result.data.*.ssl.cert.issuer.O",
173 | "data_type": "string"
174 | },
175 | {
176 | "data_path": "action_result.data.*.ssl.cert.issuer.CN",
177 | "data_type": "string"
178 | },
179 | {
180 | "data_path": "action_result.data.*.ssl.cert.issuer.OU",
181 | "data_type": "string"
182 | },
183 | {
184 | "data_path": "action_result.data.*.ssl.cert.pubkey.bits",
185 | "data_type": "numeric"
186 | },
187 | {
188 | "data_path": "action_result.data.*.ssl.cert.pubkey.type",
189 | "data_type": "string"
190 | },
191 | {
192 | "data_path": "action_result.data.*.ssl.cert.serial",
193 | "data_type": "numeric"
194 | },
195 | {
196 | "data_path": "action_result.data.*.ssl.cert.expired",
197 | "data_type": "boolean"
198 | },
199 | {
200 | "data_path": "action_result.data.*.ssl.cert.expires",
201 | "data_type": "string"
202 | },
203 | {
204 | "data_path": "action_result.data.*.ssl.cert.sig_alg",
205 | "data_type": "string"
206 | },
207 | {
208 | "data_path": "action_result.data.*.ssl.cert.subject.C",
209 | "data_type": "string"
210 | },
211 | {
212 | "data_path": "action_result.data.*.ssl.cert.subject.L",
213 | "data_type": "string"
214 | },
215 | {
216 | "data_path": "action_result.data.*.ssl.cert.subject.O",
217 | "data_type": "string"
218 | },
219 | {
220 | "data_path": "action_result.data.*.ssl.cert.subject.CN",
221 | "data_type": "string"
222 | },
223 | {
224 | "data_path": "action_result.data.*.ssl.cert.subject.OU",
225 | "data_type": "string"
226 | },
227 | {
228 | "data_path": "action_result.data.*.ssl.cert.subject.ST",
229 | "data_type": "string"
230 | },
231 | {
232 | "data_path": "action_result.data.*.ssl.cert.version",
233 | "data_type": "numeric"
234 | },
235 | {
236 | "data_path": "action_result.data.*.ssl.cert.extensions.*.data",
237 | "data_type": "string"
238 | },
239 | {
240 | "data_path": "action_result.data.*.ssl.cert.extensions.*.name",
241 | "data_type": "string"
242 | },
243 | {
244 | "data_path": "action_result.data.*.ssl.cert.extensions.*.critical",
245 | "data_type": "boolean"
246 | },
247 | {
248 | "data_path": "action_result.data.*.ssl.cert.fingerprint.sha1",
249 | "data_type": "string"
250 | },
251 | {
252 | "data_path": "action_result.data.*.ssl.cert.fingerprint.sha256",
253 | "data_type": "string"
254 | },
255 | {
256 | "data_path": "action_result.data.*.ssl.chain.*.",
257 | "data_type": "string"
258 | },
259 | {
260 | "data_path": "action_result.data.*.ssl.cipher.bits",
261 | "data_type": "numeric"
262 | },
263 | {
264 | "data_path": "action_result.data.*.ssl.cipher.name",
265 | "data_type": "string"
266 | },
267 | {
268 | "data_path": "action_result.data.*.ssl.cipher.version",
269 | "data_type": "string"
270 | },
271 | {
272 | "data_path": "action_result.data.*.ssl.tlsext.*.id",
273 | "data_type": "numeric"
274 | },
275 | {
276 | "data_path": "action_result.data.*.ssl.tlsext.*.name",
277 | "data_type": "string"
278 | },
279 | {
280 | "data_path": "action_result.data.*.ssl.versions.*.",
281 | "data_type": "string"
282 | },
283 | {
284 | "data_path": "action_result.data.*.title",
285 | "data_type": "string"
286 | },
287 | {
288 | "data_path": "action_result.data.*.transport",
289 | "data_type": "string",
290 | "column_name": "Protocol",
291 | "column_order": 2
292 | },
293 | {
294 | "data_path": "action_result.data.*.port",
295 | "data_type": "numeric",
296 | "contains": ["port"],
297 | "column_name": "Port",
298 | "column_order": 3
299 | },
300 | {
301 | "data_path": "action_result.data.*._shodan.options",
302 | "data_type": "string"
303 | },
304 | {
305 | "data_path": "action_result.data.*._shodan.module",
306 | "data_type": "string",
307 | "column_name": "Service",
308 | "column_order": 4
309 | },
310 | {
311 | "data_path": "action_result.data.*.data",
312 | "data_type": "string",
313 | "column_name": "Data",
314 | "column_order": 5
315 | },
316 | {
317 | "data_path": "action_result.data.*.asn",
318 | "data_type": "string",
319 | "contains": ["asn"],
320 | "column_name": "ASN",
321 | "column_order": 6
322 | },
323 | {
324 | "data_path": "action_result.data.*.timestamp",
325 | "data_type": "string",
326 | "column_name": "Time Discovered",
327 | "column_order": 7
328 | },
329 | {
330 | "data_path": "action_result.data.*.ip",
331 | "data_type": "string"
332 | },
333 | {
334 | "data_path": "action_result.summary.results",
335 | "data_type": "numeric"
336 | },
337 | {
338 | "data_path": "action_result.summary.hostnames.*.",
339 | "data_type": "string"
340 | },
341 | {
342 | "data_path": "action_result.summary.country",
343 | "data_type": "string"
344 | },
345 | {
346 | "data_path": "action_result.summary.open_ports",
347 | "data_type": "string"
348 | },
349 | {
350 | "data_path": "action_result.data.*.isp",
351 | "data_type": "string"
352 | },
353 | {
354 | "data_path": "action_result.data.*.org",
355 | "data_type": "string"
356 | },
357 | {
358 | "data_path": "action_result.data.*.hash",
359 | "data_type": "numeric"
360 | },
361 | {
362 | "data_path": "action_result.data.*.ip_str",
363 | "data_type": "string",
364 | "map_info": "name",
365 | "contains": ["ip"]
366 | },
367 | {
368 | "data_path": "action_result.data.*._shodan.crawler",
369 | "data_type": "string"
370 | },
371 | {
372 | "data_path": "action_result.data.*.location.city",
373 | "data_type": "string"
374 | },
375 | {
376 | "data_path": "action_result.data.*.location.area_code",
377 | "data_type": "numeric"
378 | },
379 | {
380 | "data_path": "action_result.data.*.location.postal_code",
381 | "data_type": "string"
382 | },
383 | {
384 | "data_path": "action_result.data.*.location.dma_code",
385 | "data_type": "numeric"
386 | },
387 | {
388 | "data_path": "action_result.data.*.location.region_code",
389 | "data_type": "string"
390 | },
391 | {
392 | "data_path": "action_result.data.*.location.country_code",
393 | "data_type": "string"
394 | },
395 | {
396 | "data_path": "action_result.data.*.location.country_name",
397 | "data_type": "string"
398 | },
399 | {
400 | "data_path": "action_result.data.*.location.country_code3",
401 | "data_type": "string"
402 | },
403 | {
404 | "data_path": "action_result.data.*.location.latitude",
405 | "data_type": "numeric"
406 | },
407 | {
408 | "data_path": "action_result.data.*.location.longitude",
409 | "data_type": "numeric"
410 | },
411 | {
412 | "data_path": "action_result.status",
413 | "data_type": "string"
414 | },
415 | {
416 | "data_path": "action_result.message",
417 | "data_type": "string"
418 | },
419 | {
420 | "data_path": "summary.total_objects",
421 | "data_type": "numeric"
422 | },
423 | {
424 | "data_path": "summary.total_objects_successful",
425 | "data_type": "numeric"
426 | }
427 | ],
428 | "versions":"EQ(*)"
429 | },
430 | {
431 | "action" : "query domain",
432 | "description": "Search Shodan.io for discovered service info",
433 | "type": "investigate",
434 | "identifier": "query_domain",
435 | "read_only": true,
436 | "parameters": {
437 | "domain": {
438 | "description": "Domain to query",
439 | "data_type": "string",
440 | "contains": ["domain"],
441 | "primary": true,
442 | "required": true
443 | }
444 | },
445 | "render": {
446 | "type": "table",
447 | "width": 12,
448 | "height": 5,
449 | "title": "Shodan Domain Details"
450 | },
451 | "output": [
452 | {
453 | "data_path": "action_result.parameter.domain",
454 | "data_type": "string",
455 | "contains": [
456 | "domain"
457 | ],
458 | "column_name": "Domain",
459 | "column_order": 0
460 | },
461 | {
462 | "data_path": "action_result.data.*.ip_str",
463 | "data_type": "string",
464 | "contains": ["ip"],
465 | "column_name": "IP Address",
466 | "column_order": 1
467 | },
468 | {
469 | "data_path": "action_result.data.*.product",
470 | "data_type": "string"
471 | },
472 | {
473 | "data_path": "action_result.data.*.cpe",
474 | "data_type": "string"
475 | },
476 | {
477 | "data_path": "action_result.data.*.device_type",
478 | "data_type": "string"
479 | },
480 | {
481 | "data_path": "action_result.data.*.os",
482 | "data_type": "string"
483 | },
484 | {
485 | "data_path": "action_result.data.*.transport",
486 | "data_type": "string",
487 | "column_name": "Protocol",
488 | "column_order": 2
489 | },
490 | {
491 | "data_path": "action_result.data.*.port",
492 | "data_type": "numeric",
493 | "contains": ["port"],
494 | "column_name": "Port",
495 | "column_order": 3
496 | },
497 | {
498 | "data_path": "action_result.data.*._shodan.module",
499 | "data_type": "string",
500 | "column_name": "Service",
501 | "column_order": 4
502 | },
503 | {
504 | "data_path": "action_result.data.*.data",
505 | "data_type": "string",
506 | "column_name": "Data",
507 | "column_order": 5
508 | },
509 | {
510 | "data_path": "action_result.data.*.asn",
511 | "data_type": "string",
512 | "contains": ["asn"],
513 | "column_name": "ASN",
514 | "column_order": 6
515 | },
516 | {
517 | "data_path": "action_result.data.*.timestamp",
518 | "data_type": "string",
519 | "column_name": "Time Discovered",
520 | "column_order": 7
521 | },
522 | {
523 | "data_path": "action_result.data.*.ssl.cert.issued",
524 | "data_type": "string"
525 | },
526 | {
527 | "data_path": "action_result.data.*.ssl.cert.issuer.C",
528 | "data_type": "string"
529 | },
530 | {
531 | "data_path": "action_result.data.*.ssl.cert.issuer.O",
532 | "data_type": "string"
533 | },
534 | {
535 | "data_path": "action_result.data.*.ssl.cert.issuer.CN",
536 | "data_type": "string"
537 | },
538 | {
539 | "data_path": "action_result.data.*.ssl.cert.issuer.OU",
540 | "data_type": "string"
541 | },
542 | {
543 | "data_path": "action_result.data.*.ssl.cert.pubkey.bits",
544 | "data_type": "numeric"
545 | },
546 | {
547 | "data_path": "action_result.data.*.ssl.cert.pubkey.type",
548 | "data_type": "string"
549 | },
550 | {
551 | "data_path": "action_result.data.*.ssl.cert.serial",
552 | "data_type": "numeric"
553 | },
554 | {
555 | "data_path": "action_result.data.*.ssl.cert.expired",
556 | "data_type": "boolean"
557 | },
558 | {
559 | "data_path": "action_result.data.*.ssl.cert.expires",
560 | "data_type": "string"
561 | },
562 | {
563 | "data_path": "action_result.data.*.ssl.cert.sig_alg",
564 | "data_type": "string"
565 | },
566 | {
567 | "data_path": "action_result.data.*.ssl.cert.subject.C",
568 | "data_type": "string"
569 | },
570 | {
571 | "data_path": "action_result.data.*.ssl.cert.subject.L",
572 | "data_type": "string"
573 | },
574 | {
575 | "data_path": "action_result.data.*.ssl.cert.subject.O",
576 | "data_type": "string"
577 | },
578 | {
579 | "data_path": "action_result.data.*.ssl.cert.subject.CN",
580 | "data_type": "string"
581 | },
582 | {
583 | "data_path": "action_result.data.*.ssl.cert.subject.OU",
584 | "data_type": "string"
585 | },
586 | {
587 | "data_path": "action_result.data.*.ssl.cert.subject.ST",
588 | "data_type": "string"
589 | },
590 | {
591 | "data_path": "action_result.data.*.ssl.cert.version",
592 | "data_type": "numeric"
593 | },
594 | {
595 | "data_path": "action_result.data.*.ssl.cert.extensions.*.data",
596 | "data_type": "string"
597 | },
598 | {
599 | "data_path": "action_result.data.*.ssl.cert.extensions.*.name",
600 | "data_type": "string"
601 | },
602 | {
603 | "data_path": "action_result.data.*.ssl.cert.extensions.*.critical",
604 | "data_type": "boolean"
605 | },
606 | {
607 | "data_path": "action_result.data.*.ssl.cert.fingerprint.sha1",
608 | "data_type": "string"
609 | },
610 | {
611 | "data_path": "action_result.data.*.ssl.cert.fingerprint.sha256",
612 | "data_type": "string"
613 | },
614 | {
615 | "data_path": "action_result.data.*.ssl.chain.*.",
616 | "data_type": "string"
617 | },
618 | {
619 | "data_path": "action_result.data.*.ssl.cipher.bits",
620 | "data_type": "numeric"
621 | },
622 | {
623 | "data_path": "action_result.data.*.ssl.cipher.name",
624 | "data_type": "string"
625 | },
626 | {
627 | "data_path": "action_result.data.*.ssl.cipher.version",
628 | "data_type": "string"
629 | },
630 | {
631 | "data_path": "action_result.data.*.ssl.tlsext.*.id",
632 | "data_type": "numeric"
633 | },
634 | {
635 | "data_path": "action_result.data.*.ssl.tlsext.*.name",
636 | "data_type": "string"
637 | },
638 | {
639 | "data_path": "action_result.data.*.ssl.versions.*.",
640 | "data_type": "string"
641 | },
642 | {
643 | "data_path": "action_result.data.*.deprecated.html.eol",
644 | "data_type": "string"
645 | },
646 | {
647 | "data_path": "action_result.data.*.deprecated.html.new",
648 | "data_type": "string"
649 | },
650 | {
651 | "data_path": "action_result.data.*.deprecated.title.eol",
652 | "data_type": "string"
653 | },
654 | {
655 | "data_path": "action_result.data.*.deprecated.title.new",
656 | "data_type": "string"
657 | },
658 | {
659 | "data_path": "action_result.data.*.deprecated.opts.pem.eol",
660 | "data_type": "string"
661 | },
662 | {
663 | "data_path": "action_result.data.*.deprecated.opts.pem.new",
664 | "data_type": "string"
665 | },
666 | {
667 | "data_path": "action_result.data.*.deprecated.opts.robots.eol",
668 | "data_type": "string"
669 | },
670 | {
671 | "data_path": "action_result.data.*.deprecated.opts.robots.new",
672 | "data_type": "string"
673 | },
674 | {
675 | "data_path": "action_result.data.*.deprecated.opts.sitemap.eol",
676 | "data_type": "string"
677 | },
678 | {
679 | "data_path": "action_result.data.*.deprecated.opts.sitemap.new",
680 | "data_type": "string"
681 | },
682 | {
683 | "data_path": "action_result.data.*.total",
684 | "data_type": "numeric"
685 | },
686 | {
687 | "data_path": "action_result.data.*.ip",
688 | "data_type": "numeric"
689 | },
690 | {
691 | "data_path": "action_result.data.*.isp",
692 | "data_type": "string"
693 | },
694 | {
695 | "data_path": "action_result.data.*.org",
696 | "data_type": "string"
697 | },
698 | {
699 | "data_path": "action_result.data.*.hash",
700 | "data_type": "numeric"
701 | },
702 | {
703 | "data_path": "action_result.data.*.isakmp.flags.commit",
704 | "data_type": "boolean"
705 | },
706 | {
707 | "data_path": "action_result.data.*.isakmp.flags.encryption",
708 | "data_type": "boolean"
709 | },
710 | {
711 | "data_path": "action_result.data.*.isakmp.flags.authentication",
712 | "data_type": "boolean"
713 | },
714 | {
715 | "data_path": "action_result.data.*.isakmp.length",
716 | "data_type": "numeric"
717 | },
718 | {
719 | "data_path": "action_result.data.*.isakmp.msg_id",
720 | "data_type": "string"
721 | },
722 | {
723 | "data_path": "action_result.data.*.isakmp.version",
724 | "data_type": "string"
725 | },
726 | {
727 | "data_path": "action_result.data.*.isakmp.next_payload",
728 | "data_type": "numeric"
729 | },
730 | {
731 | "data_path": "action_result.data.*.isakmp.exchange_type",
732 | "data_type": "numeric"
733 | },
734 | {
735 | "data_path": "action_result.data.*.isakmp.initiator_spi",
736 | "data_type": "string"
737 | },
738 | {
739 | "data_path": "action_result.data.*.isakmp.responder_spi",
740 | "data_type": "string"
741 | },
742 | {
743 | "data_path": "action_result.data.*._shodan.crawler",
744 | "data_type": "string"
745 | },
746 | {
747 | "data_path": "action_result.data.*.domains.*.",
748 | "data_type": "string"
749 | },
750 | {
751 | "data_path": "action_result.data.*.location.city",
752 | "data_type": "string"
753 | },
754 | {
755 | "data_path": "action_result.data.*.location.area_code",
756 | "data_type": "numeric"
757 | },
758 | {
759 | "data_path": "action_result.data.*.location.postal_code",
760 | "data_type": "string"
761 | },
762 | {
763 | "data_path": "action_result.data.*.location.dma_code",
764 | "data_type": "numeric"
765 | },
766 | {
767 | "data_path": "action_result.data.*.location.region_code",
768 | "data_type": "string"
769 | },
770 | {
771 | "data_path": "action_result.data.*.location.country_code",
772 | "data_type": "string"
773 | },
774 | {
775 | "data_path": "action_result.data.*.location.country_name",
776 | "data_type": "string"
777 | },
778 | {
779 | "data_path": "action_result.data.*.location.country_code3",
780 | "data_type": "string"
781 | },
782 | {
783 | "data_path": "action_result.data.*.location.latitude",
784 | "data_type": "numeric"
785 | },
786 | {
787 | "data_path": "action_result.data.*.location.longitude",
788 | "data_type": "numeric"
789 | },
790 | {
791 | "data_path": "action_result.data.*.hostnames.*.",
792 | "data_type": "string"
793 | },
794 | {
795 | "data_path": "action_result.data.*.ssh.kex.unused",
796 | "data_type": "numeric"
797 | },
798 | {
799 | "data_path": "action_result.data.*.ssh.kex.languages.*.",
800 | "data_type": "string"
801 | },
802 | {
803 | "data_path": "action_result.data.*.ssh.kex.kex_follows",
804 | "data_type": "boolean"
805 | },
806 | {
807 | "data_path": "action_result.data.*.ssh.kex.kex_algorithms.*.",
808 | "data_type": "string"
809 | },
810 | {
811 | "data_path": "action_result.data.*.ssh.kex.mac_algorithms.*.",
812 | "data_type": "string"
813 | },
814 | {
815 | "data_path": "action_result.data.*.ssh.kex.encryption_algorithms.*.",
816 | "data_type": "string"
817 | },
818 | {
819 | "data_path": "action_result.data.*.ssh.kex.compression_algorithms.*.",
820 | "data_type": "string"
821 | },
822 | {
823 | "data_path": "action_result.data.*.ssh.kex.server_host_key_algorithms.*.",
824 | "data_type": "string"
825 | },
826 | {
827 | "data_path": "action_result.data.*.ssh.key",
828 | "data_type": "string"
829 | },
830 | {
831 | "data_path": "action_result.data.*.ssh.mac",
832 | "data_type": "string"
833 | },
834 | {
835 | "data_path": "action_result.data.*.ssh.type",
836 | "data_type": "string"
837 | },
838 | {
839 | "data_path": "action_result.data.*.ssh.cipher",
840 | "data_type": "string"
841 | },
842 | {
843 | "data_path": "action_result.data.*.ssh.fingerprint",
844 | "data_type": "string"
845 | },
846 | {
847 | "data_path": "action_result.data.*.info",
848 | "data_type": "string"
849 | },
850 | {
851 | "data_path": "action_result.status",
852 | "data_type": "string"
853 | },
854 | {
855 | "data_path": "action_result.message",
856 | "data_type": "string"
857 | },
858 | {
859 | "data_path": "action_result.summary.results",
860 | "data_type": "numeric"
861 | },
862 | {
863 | "data_path": "summary.total_objects",
864 | "data_type": "numeric"
865 | },
866 | {
867 | "data_path": "summary.total_objects_successful",
868 | "data_type": "numeric"
869 | }
870 | ],
871 | "versions":"EQ(*)"
872 | }
873 | ]
874 | }
875 |
--------------------------------------------------------------------------------
/shodanapp/shodan_connector.py:
--------------------------------------------------------------------------------
1 | # -----------------------------------------
2 | # Shodan Search APP
3 | # -----------------------------------------
4 |
5 | # Phantom App imports
6 | import phantom.app as phantom
7 |
8 | from phantom.base_connector import BaseConnector
9 | from phantom.action_result import ActionResult
10 |
11 | # Imports local to this App
12 | from shodan_consts import *
13 |
14 | import simplejson as json
15 | import requests
16 |
17 | requests.packages.urllib3.disable_warnings()
18 |
19 |
20 | # Define the App Class
21 | class ShodanConnector(BaseConnector):
22 |
23 | ACTION_ID_SEARCH_DOMAIN = "query_domain"
24 | ACTION_ID_SEARCH_IP = "query_ip"
25 |
26 | def __init__(self):
27 |
28 | super(ShodanConnector, self).__init__()
29 |
30 | def _query_shodan(self, endpoint, result, params={}):
31 |
32 | config = self.get_config()
33 |
34 | # Get the API Key, it's marked as required in the json, so the platform/BaseConnector will fail if
35 | # not found in the input asset config
36 | api_key = config[SHODAN_JSON_APIKEY]
37 |
38 | params.update({'key': api_key})
39 |
40 | url = SHODAN_BASE_URL + endpoint
41 |
42 | try:
43 | r = requests.get(url, params=params)
44 | except Exception as e:
45 | return (result.set_status(phantom.APP_ERROR, SHODAN_ERR_SERVER_CONNECTION, e), None)
46 |
47 | # The result object can be either self (i.e. BaseConnector) or ActionResult
48 | if (hasattr(result, 'add_debug_data')):
49 | result.add_debug_data({'r_text': r.text if r else 'r is None'})
50 |
51 | # shodan gives back a json even in case of error, so parse the json before
52 | # checking for the http
53 | try:
54 | resp = r.json()
55 | except Exception as e:
56 | return (result.set_status(phantom.APP_ERROR, SHODAN_ERR_RESPONSE_IS_NOT_JSON, e), None)
57 |
58 | if 'error' in resp:
59 | return (result.set_status(phantom.APP_ERROR, resp['error']), resp)
60 |
61 | # Usually should not come here _and_ has encountered an HTTP error, but still look for errors
62 | if (r.status_code != requests.codes.ok): # pylint: disable=E1101
63 | return (result.set_status(phantom.APP_ERROR, "REST Api Call returned error, status_code: {0}, data: {1}".format(r.status_code, r.text)), r.text)
64 |
65 | # Success, return the data retrieved
66 | return (phantom.APP_SUCCESS, resp)
67 |
68 | def _test_connectivity(self, param):
69 |
70 | self.save_progress("Testing Shodan API Key")
71 |
72 | ret_val, resp = self._query_shodan('/api-info', self)
73 |
74 | if (not ret_val):
75 | self.append_to_message(SHODAN_ERR_API_TEST)
76 | return self.get_status()
77 |
78 | return self.set_status_save_progress(phantom.APP_SUCCESS, SHODAN_SUCC_API_TEST)
79 |
80 | def _handle_query_domain(self, param):
81 |
82 | # Add an action result to the App Run
83 | action_result = self.add_action_result(ActionResult(dict(param)))
84 | self.save_progress("Querying Domain")
85 |
86 | # Setup Rest Query
87 | target = param[SHODAN_JSON_DOMAIN]
88 | params = {'query': "hostname:{0}".format(target)}
89 |
90 | endpoint = "shodan/host/search"
91 |
92 | ret_val, shodan_response = self._query_shodan(endpoint, action_result, params)
93 |
94 | if (not ret_val):
95 | return action_result.get_status()
96 |
97 | if (not shodan_response):
98 | # There was an error, no results
99 | action_result.append_to_message(SHODAN_ERR_QUERY)
100 | return action_result.get_status()
101 |
102 | self.debug_print('resp', shodan_response)
103 |
104 | matches = shodan_response.get('matches', [])
105 |
106 | if (not matches):
107 | self.save_progress("Did not find any info on the domain, doing a complete search")
108 | params = {'query': "{0}".format(target)}
109 | ret_val, shodan_response = self._query_shodan(endpoint, action_result, params)
110 |
111 | if (not ret_val):
112 | return action_result.get_status()
113 |
114 | if (not shodan_response):
115 | # There was an error, no results
116 | action_result.append_to_message(SHODAN_ERR_QUERY)
117 | return action_result.get_status()
118 |
119 | matches = shodan_response.get('matches', [])
120 |
121 | for match in matches:
122 | action_result.add_data(match)
123 |
124 | # Create the summary as a normal dictionary, so that parsing it from the playbooks is easy.
125 | # The BaseConnector does the job of Capitalizing the dictionary keys to display them properly
126 | # in the UI.
127 | summary = {
128 | 'results': len(matches)
129 | }
130 |
131 | action_result.update_summary(summary)
132 |
133 | return action_result.set_status(phantom.APP_SUCCESS)
134 |
135 | def _handle_query_ip(self, param):
136 |
137 | # Add an action result to the App Run
138 | action_result = self.add_action_result(ActionResult(dict(param)))
139 | self.save_progress("Querying IP")
140 |
141 | # Setup Rest Query
142 | target = param[SHODAN_JSON_IP]
143 | endpoint = "shodan/host/{0}".format(target)
144 |
145 | ret_val, shodan_response = self._query_shodan(endpoint, action_result)
146 |
147 | if (not ret_val):
148 | return action_result.get_status()
149 |
150 | if (not shodan_response):
151 | # There was an error, no results
152 | action_result.append_to_message(SHODAN_ERR_QUERY)
153 | return action_result.get_status()
154 |
155 | # Sanitize/Add data and summary
156 | data = shodan_response.get('data', [])
157 | for record in data:
158 | action_result.add_data(record)
159 |
160 | # Create the summary as a normal dictionary, it helps in parsing it from the playbooks
161 | # easier. The BaseConnector does the job of Capitalizing the dictionary
162 | open_ports = shodan_response.get('ports')
163 | open_ports = ", ".join(str(x) for x in shodan_response.get('ports', [])) if open_ports else None
164 |
165 | hostnames = shodan_response.get('hostnames')
166 | hostnames = ", ".join(str(x) for x in shodan_response.get('hostnames', [])) if hostnames else None
167 |
168 | summary = {
169 | 'results': len(data),
170 | 'country': shodan_response.get('country_name', ''),
171 | 'open_ports': open_ports,
172 | 'hostnames': hostnames}
173 |
174 | action_result.update_summary(summary)
175 |
176 | return action_result.set_status(phantom.APP_SUCCESS)
177 |
178 | def handle_action(self, param):
179 |
180 | ret_val = phantom.APP_SUCCESS
181 |
182 | # Get the action that we are supposed to execute for this App Run
183 | action_id = self.get_action_identifier()
184 |
185 | self.debug_print("action_id", self.get_action_identifier())
186 |
187 | if (action_id == self.ACTION_ID_SEARCH_DOMAIN):
188 | ret_val = self._handle_query_domain(param)
189 | elif (action_id == self.ACTION_ID_SEARCH_IP):
190 | ret_val = self._handle_query_ip(param)
191 | elif (action_id == phantom.ACTION_ID_TEST_ASSET_CONNECTIVITY):
192 | ret_val = self._test_connectivity(param)
193 |
194 | return ret_val
195 |
196 | if __name__ == '__main__':
197 |
198 | import sys
199 | import pudb
200 | pudb.set_trace()
201 |
202 | if (len(sys.argv) < 2):
203 | print "No test json specified as input"
204 | exit(0)
205 |
206 | with open(sys.argv[1]) as f:
207 | in_json = f.read()
208 | in_json = json.loads(in_json)
209 | print(json.dumps(in_json, indent=4))
210 |
211 | connector = ShodanConnector()
212 | connector.print_progress_message = True
213 | ret_val = connector._handle_action(json.dumps(in_json), None)
214 | print (json.dumps(json.loads(ret_val), indent=4))
215 |
216 | exit(0)
217 |
--------------------------------------------------------------------------------
/shodanapp/shodan_consts.py:
--------------------------------------------------------------------------------
1 | SHODAN_JSON_APIKEY = "api_key"
2 | SHODAN_JSON_DOMAIN = "domain"
3 | SHODAN_JSON_IP = "ip"
4 |
5 | SHODAN_ERR_QUERY = "Query failed"
6 | SHODAN_SUCC_QUERY = "Query successful"
7 | SHODAN_ERR_SERVER_CONNECTION = "Connection to server failed"
8 | SHODAN_ERR_API_TEST = "API Key is not valid"
9 | SHODAN_SUCC_API_TEST = "API Key test successful"
10 | SHODAN_ERR_RESPONSE_IS_NOT_JSON = "Response is not a valid JSON"
11 |
12 | SHODAN_BASE_URL = "https://api.shodan.io/"
13 |
--------------------------------------------------------------------------------
/shodanapp/shodan_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kranzrm/PhantomShodan/492a0e20dbc7b52980f533441f49bebcd2342913/shodanapp/shodan_logo.png
--------------------------------------------------------------------------------
/test_jsons/query_domain.json:
--------------------------------------------------------------------------------
1 | {"action":"query_domain",
2 | "app_config":null,
3 | "asset_id":"5",
4 | "config":{
5 | "api_key":"",
6 | "ingest":{"start_time_epoch_utc":null},
7 | "main_module":"shodan_connector.pyc"
8 | },
9 | "debug_level":3,
10 | "dec_key":"8",
11 | "identifier":"query_domain",
12 | "ipc_version":1,
13 | "parameters": [{"domain":"www.yahoo.com"}],
14 | "type":"response"
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/test_jsons/query_ip.json:
--------------------------------------------------------------------------------
1 | {"action":"query ip",
2 | "app_config":null,
3 | "asset_id":"5",
4 | "config":{
5 | "api_key":"",
6 | "appname":"shodan",
7 | "ingest":{"start_time_epoch_utc":null},
8 | "main_module":"shodan_connector.pyc"
9 | },
10 | "debug_level":3,
11 | "dec_key":"8",
12 | "identifier":"query_ip",
13 | "ipc_version":1,
14 | "parameters": [{"ip":"1.2.3.4"}],
15 | "type":"response"
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/test_jsons/test_connectivity.json:
--------------------------------------------------------------------------------
1 | {"action":"test connectivity",
2 | "app_config":null,
3 | "asset_id":"5",
4 | "config":{
5 | "api_key":"",
6 | "appname":"shodan",
7 | "ingest":{"start_time_epoch_utc":null},
8 | "main_module":"shodan_connector.pyc"
9 | },
10 | "debug_level":3,
11 | "dec_key":"8",
12 | "identifier":"test_asset_connectivity",
13 | "ipc_version":1,
14 | "parameters": [],
15 | "phantom_version":"1.1.72",
16 | "type":"response"
17 | }
18 |
19 |
--------------------------------------------------------------------------------