Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class DomainToolsClient:
DOMAINDISCOVERY = "domaindiscovery"
DOMAINRISK = "domainrisk"
DOMAINHOTLIST = "domainhotlist"
IPHOTLIST = "iphotlist"
IPRISK = "iprisk"

FEED_METHOD_MAP = {
"nod": "nod",
Expand All @@ -37,6 +39,8 @@ class DomainToolsClient:
"domaindiscovery": "domaindiscovery",
"domainrisk": "realtime_domain_risk",
"domainhotlist": "domainhotlist",
"iphotlist": "iphotlist",
"iprisk": "iprisk",
}

def __init__(
Expand Down Expand Up @@ -169,8 +173,13 @@ def build_iterator(self, feed_type: str = "nod", dt_feed_kwargs: dict = {}) -> I
json_feed = json.loads(feed)

timestamp = json_feed.get("timestamp", "")
indicator = json_feed.get("domain")
indicator_type = FeedIndicatorType.Domain

if feed_type in (self.IPHOTLIST, self.IPRISK):
indicator = json_feed.get("ip")
indicator_type = FeedIndicatorType.IP
else:
indicator = json_feed.get("domain")
indicator_type = FeedIndicatorType.Domain

# for `domainrdap` feed, we have more data to display including the parsed data.
parsed_record = json_feed.get("parsed_record", {})
Expand Down Expand Up @@ -203,6 +212,22 @@ def build_iterator(self, feed_type: str = "nod", dt_feed_kwargs: dict = {}) -> I
# update the parsed dt feed data
dt_feed_data["risk_score_details"] = risk_score_details

if feed_type in (self.IPHOTLIST, self.IPRISK):
ip_threat_data = {
"asn": json_feed.get("asn"),
"organization": json_feed.get("organization"),
"city": json_feed.get("city"),
"country": json_feed.get("country"),
"latitude": json_feed.get("latitude"),
"longitude": json_feed.get("longitude"),
"pdns_resolutions": json_feed.get("pdns_resolutions"),
"bad_pdns_resolutions": json_feed.get("bad_pdns_resolutions"),
"total_domains": json_feed.get("total_domains"),
"all_threats_combined_count": json_feed.get("all_threats_combined_count"),
"third_party_threats": json_feed.get("third_party_threats"),
}
dt_feed_data["ip_threat_data"] = ip_threat_data

if indicator and indicator_type:
yield dt_feed_data

Expand Down Expand Up @@ -278,6 +303,7 @@ def fetch_indicators(client: DomainToolsClient, feed_type: str = "nod", dt_feed_
parsed_record_ = item.get("parsed_record")
overall_risk_score_ = item.get("overall_risk_score")
risk_score_details_ = item.get("risk_score_details")
ip_threat_data_ = item.get("ip_threat_data")

indicator_tags = ",".join(tags_).rstrip(",")

Expand All @@ -293,6 +319,9 @@ def fetch_indicators(client: DomainToolsClient, feed_type: str = "nod", dt_feed_
if risk_score_details_:
raw_data["risk_score_details"] = risk_score_details_

if ip_threat_data_:
raw_data["ip_threat_data"] = ip_threat_data_

# Create indicator object for each value.
indicator_obj = {
"value": value_,
Expand Down Expand Up @@ -384,6 +413,8 @@ def fetch_indicators_command(client: DomainToolsClient, params: dict[str, Any] =
client.DOMAINDISCOVERY,
client.DOMAINRISK,
client.DOMAINHOTLIST,
client.IPHOTLIST,
client.IPRISK,
]

dt_feed_kwargs = {"top": top, "after": after, "session_id": session_id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ configuration:
- domaindiscovery
- domainrisk
- domainhotlist
- iphotlist
- iprisk
additionalinfo: The DomainTools feed type fo fetch. Defaults to 'ALL'.
section: Collect
- display: Fetch indicators
Expand Down Expand Up @@ -160,6 +162,8 @@ script:
- "domaindiscovery"
- "domainrisk"
- "domainhotlist"
- "iphotlist"
- "iprisk"
defaultValue: "nod"
description: The DomainTools integration feed type to fetch.
isArray: false
Expand Down Expand Up @@ -199,7 +203,7 @@ script:
default: false
required: false
secret: false
dockerimage: demisto/vendors-sdk:1.0.0.10470199
dockerimage: demisto/vendors-sdk:1.0.0.11778894
feed: true
isfetch: false
longRunning: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,55 @@ def test_get_dbot_score(overall_riskscore, expected_dbot_score):
assert actual_dbot_score == expected_dbot_score


class TestIPFeedsBuildIterator:
def test_iphotlist_build_iterator(self, mocker, dt_feeds_client):
"""
Given:
- Output of the IP hotlist feed API
When:
- When calling fetch_indicators or get_indicators
Then:
- Returns an iterator of IP indicators parsed from the API's response
"""
mocker.patch.object(
dt_feeds_client,
"_get_dt_feeds",
return_value=feed_mock_response.IPHOTLIST_RESPONSE,
)
indicators = list(dt_feeds_client.build_iterator(feed_type="iphotlist"))
ips = [indicator.get("value") for indicator in indicators]

assert "203.0.113.5" in ips
assert len(indicators) == 3
assert indicators[0].get("type") == FeedIndicatorType.IP
assert indicators[0].get("ip_threat_data", {}).get("asn") == 12345

def test_iprisk_build_iterator(self, mocker, dt_feeds_client):
"""
Given:
- Output of the IP risk feed API
When:
- When calling fetch_indicators or get_indicators
Then:
- Returns an iterator of IP indicators parsed from the API's response
"""
mocker.patch.object(
dt_feeds_client,
"_get_dt_feeds",
return_value=feed_mock_response.IPRISK_RESPONSE,
)
indicators = list(dt_feeds_client.build_iterator(feed_type="iprisk"))
ips = [indicator.get("value") for indicator in indicators]

assert "203.0.113.10" in ips
assert len(indicators) == 2
assert indicators[0].get("type") == FeedIndicatorType.IP
assert indicators[0].get("ip_threat_data", {}).get("asn") == 22222


@pytest.mark.parametrize(
"feed_type",
["nod", "nad", "noh", "domaindiscovery", "domainrdap", "domainrisk", "domainhotlist"],
["nod", "nad", "noh", "domaindiscovery", "domainrdap", "domainrisk", "domainhotlist", "iphotlist", "iprisk"],
)
def test_get_indicators_command(mocker, dt_feeds_client, feed_type):
"""
Expand All @@ -156,6 +202,8 @@ def test_get_indicators_command(mocker, dt_feeds_client, feed_type):
"domainrdap": feed_mock_response.DOMAINRDAP_RESPONSE,
"domainrisk": feed_mock_response.DOMAINRISK_RESPONSE,
"domainhotlist": feed_mock_response.DOMAINHOTLIST_RESPONSE,
"iphotlist": feed_mock_response.IPHOTLIST_RESPONSE,
"iprisk": feed_mock_response.IPRISK_RESPONSE,
}

mocker.patch.object(
Expand All @@ -173,6 +221,8 @@ def test_get_indicators_command(mocker, dt_feeds_client, feed_type):
"domainrdap": feed_mock_response.DOMAINRDAP_PARSED_INDICATOR_RESPONSE,
"domainrisk": feed_mock_response.DOMAINRISK_PARSED_INDICATOR_RESPONSE,
"domainhotlist": feed_mock_response.DOMAINHOTLIST_PARSED_INDICATOR_RESPONSE,
"iphotlist": feed_mock_response.IPHOTLIST_PARSED_INDICATOR_RESPONSE,
"iprisk": feed_mock_response.IPRISK_PARSED_INDICATOR_RESPONSE,
}

human_readable = tableToMarkdown(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Gets indicators from the feed.

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| feed_type | The DomainTools integration feed type to fetch. Possible values are: nod, nad, noh, domainrdap, domaindiscovery, domainrisk, domainhotlist. Default is nod. | Optional |
| feed_type | The DomainTools integration feed type to fetch. Possible values are: nod, nad, noh, domainrdap, domaindiscovery, domainrisk, domainhotlist, iphotlist, iprisk. Default is nod. | Optional |
| session_id | The session id to serve as unique indentifier. On it's initial use, it will retrieve data from the past 5 days. Default is dt-cortex-feeds. | Optional |
| domain | The top level domain to query (e.g. `*.com`). | Optional |
| after | The start of the query window in seconds, relative to the current time, inclusive. Defaults to 3600 seconds (1h). Default is -3600. | Optional |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
!dtfeeds-get-indicators limit="10" feed_type="nod" session_id="dt-nod-cortex-integrations"
!dtfeeds-get-indicators limit="10" feed_type="nad" session_id="dt-nad-cortex-integrations"
!dtfeeds-get-indicators limit="10" feed_type="nad" session_id="dt-nad-cortex-integrations"
!dtfeeds-get-indicators limit="10" feed_type="iphotlist" session_id="dt-iphotlist-cortex-integrations"
!dtfeeds-get-indicators limit="10" feed_type="iprisk" session_id="dt-iprisk-cortex-integrations"
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,163 @@
}
]

IPHOTLIST_RESPONSE = [
'{"timestamp":"2025-09-06T22:00:00Z","ip":"203.0.113.5","asn":12345,"organization":"Evil Corp","city":"Moscow","country":"RU","latitude":55.7558,"longitude":37.6173,"pdns_resolutions":150,"bad_pdns_resolutions":120,"total_domains":80,"all_threats_combined_count":95,"third_party_threats":10}',
'{"timestamp":"2025-09-06T22:01:00Z","ip":"198.51.100.22","asn":67890,"organization":"BadNet","city":"Beijing","country":"CN","latitude":39.9042,"longitude":116.4074,"pdns_resolutions":200,"bad_pdns_resolutions":180,"total_domains":60,"all_threats_combined_count":75,"third_party_threats":5}',
'{"timestamp":"2025-09-06T22:02:00Z","ip":"192.0.2.100","asn":11111,"organization":"SpamCo","city":"Lagos","country":"NG","latitude":6.5244,"longitude":3.3792,"pdns_resolutions":50,"bad_pdns_resolutions":45,"total_domains":30,"all_threats_combined_count":40,"third_party_threats":2}',
]

IPHOTLIST_PARSED_INDICATOR_RESPONSE = [
{
"value": "203.0.113.5",
"type": "IP",
"fields": {
"tags": "DomainToolsFeeds,iphotlist",
"service": "DomainTools Feeds",
"firstseenbysource": "2025-09-06T22:00:00Z",
"sourcebrands": "FeedDomainTools",
},
"rawJSON": {
"value": "203.0.113.5",
"type": "IP",
"timestamp": "2025-09-06T22:00:00Z",
"ip_threat_data": {
"asn": 12345,
"organization": "Evil Corp",
"city": "Moscow",
"country": "RU",
"latitude": 55.7558,
"longitude": 37.6173,
"pdns_resolutions": 150,
"bad_pdns_resolutions": 120,
"total_domains": 80,
"all_threats_combined_count": 95,
"third_party_threats": 10,
},
},
},
{
"value": "198.51.100.22",
"type": "IP",
"fields": {
"tags": "DomainToolsFeeds,iphotlist",
"service": "DomainTools Feeds",
"firstseenbysource": "2025-09-06T22:01:00Z",
"sourcebrands": "FeedDomainTools",
},
"rawJSON": {
"value": "198.51.100.22",
"type": "IP",
"timestamp": "2025-09-06T22:01:00Z",
"ip_threat_data": {
"asn": 67890,
"organization": "BadNet",
"city": "Beijing",
"country": "CN",
"latitude": 39.9042,
"longitude": 116.4074,
"pdns_resolutions": 200,
"bad_pdns_resolutions": 180,
"total_domains": 60,
"all_threats_combined_count": 75,
"third_party_threats": 5,
},
},
},
{
"value": "192.0.2.100",
"type": "IP",
"fields": {
"tags": "DomainToolsFeeds,iphotlist",
"service": "DomainTools Feeds",
"firstseenbysource": "2025-09-06T22:02:00Z",
"sourcebrands": "FeedDomainTools",
},
"rawJSON": {
"value": "192.0.2.100",
"type": "IP",
"timestamp": "2025-09-06T22:02:00Z",
"ip_threat_data": {
"asn": 11111,
"organization": "SpamCo",
"city": "Lagos",
"country": "NG",
"latitude": 6.5244,
"longitude": 3.3792,
"pdns_resolutions": 50,
"bad_pdns_resolutions": 45,
"total_domains": 30,
"all_threats_combined_count": 40,
"third_party_threats": 2,
},
},
},
]

IPRISK_RESPONSE = [
'{"timestamp":"2025-09-06T22:10:00Z","ip":"203.0.113.10","asn":22222,"organization":"RiskNet","city":"Tehran","country":"IR","latitude":35.6892,"longitude":51.3890,"pdns_resolutions":300,"bad_pdns_resolutions":250,"total_domains":90,"all_threats_combined_count":110,"third_party_threats":15}',
'{"timestamp":"2025-09-06T22:11:00Z","ip":"198.51.100.50","asn":33333,"organization":"MalHost","city":"Pyongyang","country":"KP","latitude":39.0194,"longitude":125.7381,"pdns_resolutions":180,"bad_pdns_resolutions":160,"total_domains":55,"all_threats_combined_count":85,"third_party_threats":8}',
]

IPRISK_PARSED_INDICATOR_RESPONSE = [
{
"value": "203.0.113.10",
"type": "IP",
"fields": {
"tags": "DomainToolsFeeds,iprisk",
"service": "DomainTools Feeds",
"firstseenbysource": "2025-09-06T22:10:00Z",
"sourcebrands": "FeedDomainTools",
},
"rawJSON": {
"value": "203.0.113.10",
"type": "IP",
"timestamp": "2025-09-06T22:10:00Z",
"ip_threat_data": {
"asn": 22222,
"organization": "RiskNet",
"city": "Tehran",
"country": "IR",
"latitude": 35.6892,
"longitude": 51.3890,
"pdns_resolutions": 300,
"bad_pdns_resolutions": 250,
"total_domains": 90,
"all_threats_combined_count": 110,
"third_party_threats": 15,
},
},
},
{
"value": "198.51.100.50",
"type": "IP",
"fields": {
"tags": "DomainToolsFeeds,iprisk",
"service": "DomainTools Feeds",
"firstseenbysource": "2025-09-06T22:11:00Z",
"sourcebrands": "FeedDomainTools",
},
"rawJSON": {
"value": "198.51.100.50",
"type": "IP",
"timestamp": "2025-09-06T22:11:00Z",
"ip_threat_data": {
"asn": 33333,
"organization": "MalHost",
"city": "Pyongyang",
"country": "KP",
"latitude": 39.0194,
"longitude": 125.7381,
"pdns_resolutions": 180,
"bad_pdns_resolutions": 160,
"total_domains": 55,
"all_threats_combined_count": 85,
"third_party_threats": 8,
},
},
},
]

DOMAINHOTLIST_PARSED_INDICATOR_RESPONSE = [
{
"value": "scmipgf.icu",
Expand Down
Loading