Skip to content

Commit f073744

Browse files
committed
Added Shodan Alert API Collector
In cooperation with Malawi CERT
1 parent a105925 commit f073744

File tree

4 files changed

+122
-0
lines changed

4 files changed

+122
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Please refer to the [NEWS](NEWS.md) for a list of changes which have an affect o
2525

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

2930
#### Parsers
3031
- `intelmq.bots.parsers.cymru.parser_cap_program`: Add mapping for TOR and ipv6-icmp protocol (PR#2621 by Mikk Margus Möll).

docs/user/bots.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,10 @@ Requires the shodan library to be installed:
10041004

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

1007+
**`api_key`**
1008+
1009+
Your Shodan API Key.
1010+
10071011
**`countries`**
10081012

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

10221026
---
10231027

1028+
### Shodan Alert <div id="intelmq.bots.collectors.shodan.collector_alert" />
1029+
1030+
Queries the Shodan Alert Streaming API.
1031+
1032+
Configure Alerts in the Shodan Interface (Website or CLI tool), then receive the data on the alerts via the Streaming service.
1033+
1034+
Requires the shodan library to be installed:
1035+
1036+
- <https://github.com/achillean/shodan-python/>
1037+
1038+
- <https://pypi.org/project/shodan/>
1039+
1040+
**Module:** `intelmq.bots.collectors.shodan.collector_alert`
1041+
1042+
**Parameters (also expects [feed parameters](#feed-parameters) and [HTTP parameters](#http-parameters)):**
1043+
1044+
Of the generic HTTP parameters, only the proxy is used (requires `shodan-python > 1.8.1`). The API endpoint certificate is always verified.
1045+
1046+
**`api_key`**
1047+
1048+
Your Shodan API Key.
1049+
1050+
---
1051+
10241052
### TCP <div id="intelmq.bots.collectors.tcp.collector" />
10251053

10261054
TCP is the bot responsible to receive events on a TCP port (ex: from TCP Output of another IntelMQ instance). Might not
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
SPDX-FileCopyrightText: 2025 Institute for Common Good Technology & Malawi CERT
3+
SPDX-License-Identifier: AGPL-3.0-or-later
4+
"""
5+
6+
from pkg_resources import get_distribution
7+
from json import dumps as json_dumps
8+
from typing import Optional
9+
10+
from intelmq.lib.bot import CollectorBot
11+
12+
try:
13+
from shodan import Shodan
14+
except ImportError:
15+
Shodan = None
16+
17+
18+
class ShodanAlertCollectorBot(CollectorBot):
19+
"""
20+
Stream Listener for Shodan Alerts
21+
"""
22+
23+
api_key: str = None
24+
# Shodan only uses HTTPS, so it's fine to only offer the HTTPS proxy parameter, not also the HTTPS proxy
25+
https_proxy: Optional[str] = None
26+
27+
def init(self):
28+
if Shodan is None:
29+
raise ValueError("Library 'shodan' is needed but not installed.")
30+
31+
self.proxy = ({'http': self.https_proxy, # just for safety, also add the proxy for http, although it should never be in use
32+
'https': self.https_proxy}
33+
if self.https_proxy
34+
else {})
35+
if tuple(int(v) for v in get_distribution("shodan").version.split('.')) <= (1, 8, 1):
36+
if self.proxy:
37+
raise ValueError('Proxies are given but shodan-python > 1.8.1 is needed for proxy support.')
38+
else:
39+
self.api = Shodan(self.api_key)
40+
else:
41+
self.api = Shodan(self.api_key,
42+
proxies=self.proxy)
43+
44+
def process(self):
45+
for alert in self.api.stream.alert():
46+
report = self.new_report()
47+
report['raw'] = json_dumps(alert)
48+
self.send_message(report)
49+
50+
@staticmethod
51+
def check(parameters: dict) -> Optional[list[list[str]]]:
52+
messages = []
53+
if 'api_key' not in parameters or not parameters['api_key']:
54+
messages.append(["error", "Parameter 'api_key' not given."])
55+
56+
if not Shodan:
57+
if 'https_proxy' in parameters:
58+
messages.append(["error", "Library 'shodan' is needed but not installed. Required version: At least 1.8.1 for HTTPS Proxy support."])
59+
else:
60+
messages.append(["error", "Library 'shodan' is needed but not installed."])
61+
else:
62+
shodan_version = tuple(int(v) for v in get_distribution("shodan").version.split('.'))
63+
if 'https_proxy' in parameters and shodan_version <= (1, 8, 1):
64+
messages.append(["error", "Library 'shodan' needs to be updated. At least version 1.8.1 is required for HTTPS Proxy support."])
65+
66+
return messages
67+
68+
69+
BOT = ShodanAlertCollectorBot
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
SPDX-FileCopyrightText: 2025 Institute for Common Good Technology & Malawi CERT
3+
SPDX-License-Identifier: AGPL-3.0-or-later
4+
"""
5+
6+
import os
7+
import unittest
8+
9+
import intelmq.lib.test as test
10+
11+
if os.environ.get('INTELMQ_TEST_EXOTIC'):
12+
from intelmq.bots.collectors.shodan.collector_alert import ShodanAlertCollectorBot
13+
14+
15+
@test.skip_exotic()
16+
class TestShodanAlertCollectorBot(test.BotTestCase, unittest.TestCase):
17+
18+
@classmethod
19+
def set_bot(cls):
20+
cls.bot_reference = ShodanAlertCollectorBot
21+
22+
23+
if __name__ == '__main__':
24+
unittest.main()

0 commit comments

Comments
 (0)