Skip to content
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
39 changes: 39 additions & 0 deletions analyzers/RDAP/RDAP.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "RDAP",
"author": "yatuk",
"license": "AGPL-V3",
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
"version": "1.0",
"baseConfig": "RDAP",
"config": {
"check_tlp": false,
"max_tlp": 3
},
"description": "Look up domain and IP registration data over RDAP, the IETF successor to WHOIS. No API key required.",
"dataTypeList": ["domain", "ip"],
"command": "RDAP/RDAP_analyzer.py",
"configurationItems": [
{
"name": "timeout",
"description": "HTTP timeout in seconds",
"multi": false,
"required": false,
"type": "number",
"defaultValue": 15
}
],
"registration_required": false,
"subscription_required": false,
"free_subscription": true,
"service_homepage": "https://rdap.org/",
"service_logo": {
"path": "assets/rdap.png",
"caption": "logo"
},
"screenshots": [
{
"path": "assets/long_report.png",
"caption": "RDAP: long report"
}
]
}
143 changes: 143 additions & 0 deletions analyzers/RDAP/RDAP_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
import requests
from cortexutils.analyzer import Analyzer

# rdap.org resolves the authoritative RDAP server for a given object and
# redirects to it, so a single endpoint covers every TLD and RIR.
BASEURL = "https://rdap.org"

# Registry statuses that indicate the object is restricted, held or in the
# middle of a transfer. Worth surfacing in triage.
NOTABLE_STATUSES = [
"client hold",
"server hold",
"pending delete",
"pending transfer",
"redemption period",
]


class RDAPAnalyzer(Analyzer):
def __init__(self):
Analyzer.__init__(self)
self.timeout = int(self.get_param("config.timeout", 15))

def query(self, object_type, value):
url = "{}/{}/{}".format(BASEURL, object_type, value)

try:
response = requests.get(
url,
headers={"Accept": "application/rdap+json"},
timeout=self.timeout,
)
except requests.exceptions.RequestException as e:
self.error("Could not reach RDAP service: {}".format(e))

if response.status_code == 404:
return {"found": False}

if response.status_code == 429:
self.error("RDAP service rate limited the request.")

if not 200 <= response.status_code < 300:
self.error("RDAP service returned HTTP {}".format(response.status_code))

try:
result = response.json()
except ValueError:
self.error("RDAP service returned a non-JSON response.")

result["found"] = True
return result

def run(self):
data = self.get_data()
if not data:
self.error("No observable given.")

if self.data_type == "domain":
result = self.query("domain", data)
elif self.data_type == "ip":
result = self.query("ip", data)
else:
self.error("Data type {} not supported.".format(self.data_type))

result["queried_type"] = self.data_type
self.report(result)

def _events(self, raw):
events = {}
for event in raw.get("events") or []:
action = event.get("eventAction")
date = event.get("eventDate")
if action and date:
events[action] = date
return events

def _registrar(self, raw):
for entity in raw.get("entities") or []:
roles = entity.get("roles") or []
if "registrar" not in roles:
continue
for item in entity.get("vcardArray", [None, []])[1] or []:
if item and item[0] == "fn":
return item[3]
return None

def summary(self, raw):
taxonomies = []
namespace = "RDAP"

if not raw.get("found"):
taxonomies.append(
self.build_taxonomy("info", namespace, "Registration", "Not found")
)
return {"taxonomies": taxonomies}

statuses = [s.lower() for s in (raw.get("status") or [])]
flagged = [s for s in statuses if s in NOTABLE_STATUSES]
if flagged:
taxonomies.append(
self.build_taxonomy(
"suspicious", namespace, "Status", ", ".join(flagged)
)
)

events = self._events(raw)
registered = events.get("registration")
if registered:
taxonomies.append(
self.build_taxonomy(
"info", namespace, "Registered", registered[:10]
)
)

registrar = self._registrar(raw)
if registrar:
taxonomies.append(
self.build_taxonomy("info", namespace, "Registrar", registrar)
)

if not taxonomies:
taxonomies.append(
self.build_taxonomy("info", namespace, "Registration", "Found")
)

return {"taxonomies": taxonomies}

def artifacts(self, raw):
artifacts = []
seen = set()

for ns in raw.get("nameservers") or []:
name = ns.get("ldhName")
if name and name.lower() not in seen:
seen.add(name.lower())
artifacts.append(self.build_artifact("domain", name.lower()))

return artifacts


if __name__ == "__main__":
RDAPAnalyzer().run()
21 changes: 21 additions & 0 deletions analyzers/RDAP/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### RDAP

[RDAP](https://about.rdap.org/) (Registration Data Access Protocol) is the IETF
successor to WHOIS, standardised in RFC 7480 through RFC 7484. It returns registration
data as structured JSON over HTTPS instead of the unstructured text WHOIS returns over
port 43, and it is now the protocol registries are required to support.

The analyzer takes a domain or an IP address and returns its registration record:
registrar, registration and expiry events, registry status codes, and nameservers.
Nameservers are extracted as observables.

Queries go through https://rdap.org/, which resolves the authoritative RDAP server for
the object and redirects to it, so a single endpoint covers every TLD and regional
internet registry.

#### Requirements

None. RDAP is an open protocol and requires no account, key or subscription.

The only configuration item is an optional HTTP `timeout` in seconds, which defaults
to 15.
Binary file added analyzers/RDAP/assets/rdap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions analyzers/RDAP/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cortexutils
requests
68 changes: 68 additions & 0 deletions thehive-templates/RDAP_1_0/long.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<div class="panel panel-info" ng-if="success && content.found">
<div class="panel-heading">
RDAP registration data for <strong>{{artifact.data | fang}}</strong>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt ng-if="content.ldhName">Name</dt>
<dd ng-if="content.ldhName" class="wrap">{{content.ldhName | fang}}</dd>

<dt ng-if="content.handle">Handle</dt>
<dd ng-if="content.handle">{{content.handle}}</dd>

<dt ng-if="content.startAddress">Range</dt>
<dd ng-if="content.startAddress">
{{content.startAddress}} - {{content.endAddress}}
<span ng-if="content.ipVersion" class="label label-default">{{content.ipVersion}}</span>
</dd>

<dt ng-if="content.country">Country</dt>
<dd ng-if="content.country">{{content.country}}</dd>

<dt ng-if="content.status">Status</dt>
<dd ng-if="content.status">
<span ng-repeat="s in content.status" class="label label-default">{{s}}</span>
</dd>

<dt ng-if="content.events">Events (UTC)</dt>
<dd ng-if="content.events">
<div ng-repeat="e in content.events">
{{e.eventAction}}: {{e.eventDate}}
</div>
</dd>

<dt ng-if="content.entities">Entities</dt>
<dd ng-if="content.entities">
<div ng-repeat="ent in content.entities">
<span ng-repeat="r in ent.roles" class="label label-primary">{{r}}</span>
{{ent.handle}}
</div>
</dd>

<dt ng-if="content.nameservers.length > 0">Nameservers</dt>
<dd ng-if="content.nameservers.length > 0" class="wrap">
<div ng-repeat="ns in content.nameservers">{{ns.ldhName | fang}}</div>
</dd>
</dl>
</div>
</div>

<div class="panel panel-info" ng-if="success && !content.found">
<div class="panel-heading">
RDAP registration data for <strong>{{artifact.data | fang}}</strong>
</div>
<div class="panel-body">
No registration record found. The object may be unregistered, or the responsible
registry may not publish RDAP data for it.
</div>
</div>

<div class="panel panel-danger" ng-if="!success">
<div class="panel-heading">{{(artifact.data || fileName) | fang}}</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Error</dt>
<dd class="wrap">{{content.errorMessage}}</dd>
</dl>
</div>
</div>
3 changes: 3 additions & 0 deletions thehive-templates/RDAP_1_0/short.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<span class="label" ng-repeat="t in content.taxonomies" ng-class="{'info': 'label-info', 'safe': 'label-success', 'suspicious': 'label-warning', 'malicious':'label-danger'}[t.level]">
{{t.namespace}}:{{t.predicate}}="{{t.value}}"
</span>