Skip to content

Commit 816f9d3

Browse files
Merge pull request #1411 from TheHive-Project/abuseipdb-improvements
AbuseIPDB - Report Responder & improvements
2 parents e6a3e83 + ad96fb4 commit 816f9d3

File tree

6 files changed

+220
-1
lines changed

6 files changed

+220
-1
lines changed

analyzers/AbuseIPDB/abuseipdb.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def run(self):
5959
"Accept": "application/json",
6060
"Content-Type": "application/x-www-form-urlencoded",
6161
"Key": "%s" % api_key,
62+
"User-Agent": "strangebee-thehive/1.0",
6263
}
6364
params = {
6465
"maxAgeInDays": days_to_check,
@@ -172,7 +173,7 @@ def summary(self, raw):
172173
if "abuseConfidenceScore" in data:
173174
score = int(data.get("abuseConfidenceScore") or 0)
174175
level = (
175-
"malicious" if score >= 80 else ("suspicious" if score > 0 else "safe")
176+
"malicious" if score >= 75 else ("suspicious" if score > 0 else "safe")
176177
)
177178
taxonomies.append(
178179
self.build_taxonomy(level, "AbuseIPDB", "Abuse Confidence Score", score)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "AbuseIPDB_Report",
3+
"version": "1.0",
4+
"author": "Fabien Bloume, StrangeBee",
5+
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
6+
"license": "AGPL-V3",
7+
"description": "Report an IP address to AbuseIPDB for abuse tracking and community sharing. Please, make sure to use the correct category in your Cortex responder configuration.",
8+
"dataTypeList": ["thehive:case_artifact"],
9+
"command": "AbuseIPDB/abuseipdb_responder.py",
10+
"baseConfig": "AbuseIPDB",
11+
"config": {
12+
"service": "report"
13+
},
14+
"configurationItems": [
15+
{
16+
"name": "key",
17+
"description": "API key for AbuseIPDB",
18+
"type": "string",
19+
"multi": false,
20+
"required": true
21+
},
22+
{
23+
"name": "categories",
24+
"description": "Select one or more abuse categories: DNS Compromise, DNS Poisoning, Fraud Orders, DDoS Attack, FTP Brute-Force, Ping of Death, Phishing, Fraud VoIP, Open Proxy, Web Spam, Email Spam, Blog Spam, VPN IP, Port Scan, Hacking, SQL Injection, Spoofing, Brute Force, Bad Web Bot, Exploited Host, Web App Attack, SSH, IoT Targeted",
25+
"type": "string",
26+
"multi": true,
27+
"required": true,
28+
"defaultValue": ["Hacking"]
29+
},
30+
{
31+
"name": "comment",
32+
"description": "Optional comment describing the abuse (max 1024 characters)",
33+
"type": "string",
34+
"multi": false,
35+
"required": false
36+
}
37+
],
38+
"registration_required": true,
39+
"subscription_required": true,
40+
"free_subscription": true,
41+
"integration_type": "external_api",
42+
"service_homepage": "https://www.abuseipdb.com/",
43+
"service_logo": {
44+
"path": "assets/abuseipdb.png",
45+
"caption": "AbuseIPDB logo"
46+
}
47+
}

responders/AbuseIPDB/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# AbuseIPDB Responder
2+
3+
Reports IP addresses to AbuseIPDB.
4+
5+
## Configuration
6+
7+
- **key**: Your AbuseIPDB API key
8+
- **categories**: One or more categories (see below)
9+
- **comment**: Optional comment (max 1024 chars)
10+
11+
### Categories
12+
13+
DNS Compromise, DNS Poisoning, Fraud Orders, DDoS Attack, FTP Brute-Force, Ping of Death, Phishing, Fraud VoIP, Open Proxy, Web Spam, Email Spam, Blog Spam, VPN IP, Port Scan, Hacking, SQL Injection, Spoofing, Brute Force, Bad Web Bot, Exploited Host, Web App Attack, SSH, IoT Targeted
14+
15+
## Before you use this
16+
17+
Everytime you run it, configure the responder in Cortex with the correct categories **before** running it from TheHive. Categories cannot be changed at runtime, as of today.
18+
19+
Wrong categories = bad data in AbuseIPDB. Always validate Cortex configuration before using.
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
# encoding: utf-8
3+
4+
import requests
5+
from cortexutils.responder import Responder
6+
7+
8+
class AbuseIPDBResponder(Responder):
9+
"""
10+
AbuseIPDB Responder - Report malicious IP addresses to AbuseIPDB
11+
API documentation: https://docs.abuseipdb.com/#report-endpoint
12+
"""
13+
14+
# Mapping from category ID to name (for display)
15+
CATEGORIES = {
16+
"1": "DNS Compromise",
17+
"2": "DNS Poisoning",
18+
"3": "Fraud Orders",
19+
"4": "DDoS Attack",
20+
"5": "FTP Brute-Force",
21+
"6": "Ping of Death",
22+
"7": "Phishing",
23+
"8": "Fraud VoIP",
24+
"9": "Open Proxy",
25+
"10": "Web Spam",
26+
"11": "Email Spam",
27+
"12": "Blog Spam",
28+
"13": "VPN IP",
29+
"14": "Port Scan",
30+
"15": "Hacking",
31+
"16": "SQL Injection",
32+
"17": "Spoofing",
33+
"18": "Brute Force",
34+
"19": "Bad Web Bot",
35+
"20": "Exploited Host",
36+
"21": "Web App Attack",
37+
"22": "SSH",
38+
"23": "IoT Targeted",
39+
}
40+
41+
# Reverse mapping from category name to ID (for API calls)
42+
CATEGORY_NAME_TO_ID = {name: id for id, name in CATEGORIES.items()}
43+
44+
def __init__(self):
45+
Responder.__init__(self)
46+
self.api_key = self.get_param("config.key", None, "Missing AbuseIPDB API key")
47+
self.categories = self.get_param("config.categories", ["Brute Force"])
48+
self.comment = self.get_param("config.comment", "")
49+
self.service = self.get_param("config.service", "report")
50+
51+
def run(self):
52+
Responder.run(self)
53+
54+
if self.service == "report":
55+
self.report_ip()
56+
else:
57+
self.error(f"Unknown service: {self.service}")
58+
59+
def report_ip(self):
60+
"""Report an IP address to AbuseIPDB"""
61+
data_type = self.get_param("data.dataType", None)
62+
63+
if data_type != "ip":
64+
self.error("This responder only supports IP addresses")
65+
return
66+
67+
ip_address = self.get_param("data.data", None, "No IP address provided")
68+
69+
# Ensure categories is a list
70+
category_list = self.categories if isinstance(self.categories, list) else [self.categories]
71+
72+
# Validate and translate category names to IDs
73+
category_ids = []
74+
invalid_categories = []
75+
for cat_name in category_list:
76+
cat_name = cat_name.strip()
77+
if cat_name in self.CATEGORY_NAME_TO_ID:
78+
category_ids.append(self.CATEGORY_NAME_TO_ID[cat_name])
79+
else:
80+
invalid_categories.append(cat_name)
81+
82+
if invalid_categories:
83+
valid_names = ", ".join(self.CATEGORY_NAME_TO_ID.keys())
84+
self.error(f"Invalid category names: {', '.join(invalid_categories)}. Valid categories are: {valid_names}")
85+
return
86+
87+
if not category_ids:
88+
self.error("At least one category must be specified")
89+
return
90+
91+
# Convert category IDs to comma-separated string for API
92+
categories_str = ",".join(category_ids)
93+
94+
# Prepare the API request
95+
url = "https://api.abuseipdb.com/api/v2/report"
96+
headers = {
97+
"Accept": "application/json",
98+
"Key": self.api_key,
99+
"User-Agent": "strangebee-thehive/1.0",
100+
}
101+
payload = {
102+
"ip": ip_address,
103+
"categories": categories_str,
104+
}
105+
106+
if self.comment:
107+
# Truncate comment to 1024 characters as per API limit
108+
payload["comment"] = self.comment[:1024]
109+
110+
try:
111+
response = requests.post(url, headers=headers, data=payload)
112+
113+
if response.status_code == 200:
114+
result = response.json()
115+
data = result.get("data", {})
116+
117+
self.report({
118+
"success": True,
119+
"message": f"IP {ip_address} reported successfully to AbuseIPDB",
120+
"ip_address": data.get("ipAddress", ip_address),
121+
"abuse_confidence_score": data.get("abuseConfidenceScore"),
122+
"categories_reported": category_list,
123+
})
124+
elif response.status_code == 422:
125+
# Validation error from API
126+
error_detail = response.json().get("errors", [{}])[0].get("detail", "Validation error")
127+
self.error(f"AbuseIPDB validation error: {error_detail}")
128+
elif response.status_code == 429:
129+
self.error("AbuseIPDB rate limit exceeded. Please try again later.")
130+
elif response.status_code == 401:
131+
self.error("Invalid AbuseIPDB API key")
132+
else:
133+
self.error(f"AbuseIPDB API error (HTTP {response.status_code}): {response.text}")
134+
135+
except requests.exceptions.RequestException as e:
136+
self.error(f"Network error while contacting AbuseIPDB: {str(e)}")
137+
138+
def operations(self, raw):
139+
"""Add a tag to the artifact after successful reporting"""
140+
operations = []
141+
if raw.get("success"):
142+
operations.append(
143+
self.build_operation("AddTagToArtifact", tag="AbuseIPDB:reported")
144+
)
145+
return operations
146+
147+
148+
if __name__ == "__main__":
149+
AbuseIPDBResponder().run()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cortexutils
2+
requests

thehive-templates/AbuseIPDB_1_1/long.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
'fa-check-circle': content.values[0].data.abuseConfidenceScore < 1}"></i>
1010
AbuseIPDB Verdict:
1111
<strong>{{content.values[0].data.ipAddress}}</strong>
12+
<a ng-href="https://www.abuseipdb.com/check/{{content.values[0].data.ipAddress}}" target="_blank" title="View on AbuseIPDB" style="margin-left:6px;"><i class="fa fa-external-link"></i></a>
1213

1314
<span class="pull-right">
1415
<!-- compact context badges, subtle but handy -->

0 commit comments

Comments
 (0)