Skip to content

Added Shodan Alert API Collector #2618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Please refer to the [NEWS](NEWS.md) for a list of changes which have an affect o

### Bots
#### Collectors
- `intelmq.bots.collectors.shodan.collector_alert`: Added a new collector to query the Shodan Alert API (PR#2618 by Sebastian Wagner and Malawi CERT).

#### Parsers
- `intelmq.bots.parsers.cymru.parser_cap_program`: Add mapping for TOR and ipv6-icmp protocol (PR#2621 by Mikk Margus Möll).
Expand Down
28 changes: 28 additions & 0 deletions docs/user/bots.md
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,10 @@ Requires the shodan library to be installed:

Only the proxy is used (requires `shodan-python > 1.8.1`). Certificate is always verified.

**`api_key`**

Your Shodan API Key.

**`countries`**

() A list of countries to query for. If it is a string, it will be spit by `,`.
Expand All @@ -1021,6 +1025,30 @@ applies, if not null.

---

### Shodan Alert <div id="intelmq.bots.collectors.shodan.collector_alert" />

Queries the Shodan Alert Streaming API.

Configure Alerts in the Shodan Interface (Website or CLI tool), then receive the data on the alerts via the Streaming service.

Requires the shodan library to be installed:

- <https://github.com/achillean/shodan-python/>

- <https://pypi.org/project/shodan/>

**Module:** `intelmq.bots.collectors.shodan.collector_alert`

**Parameters (also expects [feed parameters](#feed-parameters) and [HTTP parameters](#http-parameters)):**

Of the generic HTTP parameters, only the proxy is used (requires `shodan-python > 1.8.1`). The API endpoint certificate is always verified.

**`api_key`**

Your Shodan API Key.

---

### TCP <div id="intelmq.bots.collectors.tcp.collector" />

TCP is the bot responsible to receive events on a TCP port (ex: from TCP Output of another IntelMQ instance). Might not
Expand Down
69 changes: 69 additions & 0 deletions intelmq/bots/collectors/shodan/collector_alert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
SPDX-FileCopyrightText: 2025 Institute for Common Good Technology & Malawi CERT
SPDX-License-Identifier: AGPL-3.0-or-later
"""

from pkg_resources import get_distribution
from json import dumps as json_dumps
from typing import Optional

from intelmq.lib.bot import CollectorBot

try:
from shodan import Shodan
except ImportError:
Shodan = None


class ShodanAlertCollectorBot(CollectorBot):
"""
Stream Listener for Shodan Alerts
"""

api_key: str = None
# Shodan only uses HTTPS, so it's fine to only offer the HTTPS proxy parameter, not also the HTTPS proxy
https_proxy: Optional[str] = None

def init(self):
if Shodan is None:
raise ValueError("Library 'shodan' is needed but not installed.")

self.proxy = ({'http': self.https_proxy, # just for safety, also add the proxy for http, although it should never be in use
'https': self.https_proxy}
if self.https_proxy
else {})
if tuple(int(v) for v in get_distribution("shodan").version.split('.')) <= (1, 8, 1):
if self.proxy:
raise ValueError('Proxies are given but shodan-python > 1.8.1 is needed for proxy support.')
else:
self.api = Shodan(self.api_key)
else:
self.api = Shodan(self.api_key,
proxies=self.proxy)

def process(self):
for alert in self.api.stream.alert():
report = self.new_report()
report['raw'] = json_dumps(alert)
self.send_message(report)

@staticmethod
def check(parameters: dict) -> Optional[list[list[str]]]:
messages = []
if 'api_key' not in parameters or not parameters['api_key']:
messages.append(["error", "Parameter 'api_key' not given."])

if not Shodan:
if 'https_proxy' in parameters:
messages.append(["error", "Library 'shodan' is needed but not installed. Required version: At least 1.8.1 for HTTPS Proxy support."])
else:
messages.append(["error", "Library 'shodan' is needed but not installed."])
else:
shodan_version = tuple(int(v) for v in get_distribution("shodan").version.split('.'))
if 'https_proxy' in parameters and shodan_version <= (1, 8, 1):
messages.append(["error", "Library 'shodan' needs to be updated. At least version 1.8.1 is required for HTTPS Proxy support."])

return messages


BOT = ShodanAlertCollectorBot
24 changes: 24 additions & 0 deletions intelmq/tests/bots/collectors/shodan/test_collector_alert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
SPDX-FileCopyrightText: 2025 Institute for Common Good Technology & Malawi CERT
SPDX-License-Identifier: AGPL-3.0-or-later
"""

import os
import unittest

import intelmq.lib.test as test

if os.environ.get('INTELMQ_TEST_EXOTIC'):
from intelmq.bots.collectors.shodan.collector_alert import ShodanAlertCollectorBot


@test.skip_exotic()
class TestShodanAlertCollectorBot(test.BotTestCase, unittest.TestCase):

@classmethod
def set_bot(cls):
cls.bot_reference = ShodanAlertCollectorBot


if __name__ == '__main__':
unittest.main()
Loading