├── .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 | 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 | 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 | --------------------------------------------------------------------------------