PhishTank V2

PhishTank is a free community site where anyone can submit, verify, track, and share phishing data.

Data Enrichment & Threat Intelligence · PhishTank

Details

IDPhishTank V2
ProviderOpen Source
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10325753
Supported ModulesAgentix XSIAM

README

PhishTank is a free community site where anyone can submit, verify, track and share phishing data.
This integration was integrated and tested with version 1.0.1 of PhishTank.

Configure PhishTankV2 in Cortex

Parameter Description Required
use_https Use HTTPS connection False
Source Reliability Reliability of the source providing the intelligence data. B - Usually reliable
proxy Use system proxy settings False
insecure Trust any certificate (not secure) False
fetchIntervalHours Database refresh interval (hours) False

Best Practice

When using the PhishTank V2 integration, we recommend that you use an engine to run the integration instance,
and to use different engines for different tenants.
You should open a platform feature request (FR) to request separate egress IPs for the different tenants.

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

url


Checks the reputation of the supplied URLs.

Notice: Submitting indicators using this command might make the indicator data publicly available. See the vendor’s documentation for more details.

Base Command

url

Input

Argument Name Description Required
url A comma-separated list of URLs to check the reputation of. Required

Context Output

Path Type Description
URL.Data String A list of URLs with a bad reputation.
URL.Malicious.Vendor String For malicious URLs, the vendor that tagged the URL as malicious.
URL.Malicious.Description String For malicious URLs, the reason the vendor tagged the URL as malicious.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Reliability String Reliability of the source providing the intelligence data.

Command Example

!url url=hxxp://login.rakuten.co.jp.reise

Human Readable Output

PhishTankV2 Database - URL Query

Found matches for URL hxxp://login.rakuten.co.jp.reise

online phish_id submission_time target verification_time verified
yes 6784982 2020-09-27T19:04:35+00:00 Other 2020-09-27T19:10:20+00:00 yes

Additional details at http://www.phishtank.com/phish_detail.php?phish_id=6784982

phishtank-reload


Reload PhishTank database

Base Command

phishtank-reload

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command Example

!phishtank-reload

Human Readable Output

PhishTankV2 Database reloaded

Total 13181 URLs loaded

phishtank-status


Show PhishTank database status

Base Command

phishtank-status

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command Example

!phishtank-status

Human Readable Output

PhishTankV2 Database Status

Total 13181 URLs loaded

Last Load time Sun Oct 04 2020 09:43:01 (UTC)

Configuration parameters

  • username — Username
  • use_https — Use HTTPS connection
  • integrationReliability — Source Reliability (required)
  • fetchIntervalHours — Database refresh interval (number of hours to wait between each loading)
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)

Commands (3)

  • phishtank-reload

    Reloads the PhishTank downloadable database. PhishTank database is an up to date phishing detection file (updated hourly) .

  • phishtank-status

    Shows the status (timestamp) of the last time that PhishTank database was loaded.

  • url

    Checks the reputation of the supplied URLs.

from datetime import datetime

import demistomock as demisto
import pytest
from CommonServerPython import DBotScoreReliability
from freezegun import freeze_time
from PhishTankV2 import Client, is_number, is_reload_needed, phishtank_status_command, reload, remove_last_slash, url_command


def create_client(
    proxy: bool = False,
    verify: bool = False,
    fetch_interval_hours: str = "1",
    reliability: str = DBotScoreReliability.A_PLUS,
    username: str = "",
):
    return Client(
        proxy=proxy,
        verify=verify,
        fetch_interval_hours=fetch_interval_hours,
        use_https=False,
        reliability=reliability,
        username=username,
    )


@pytest.mark.parametrize(
    "number, output",
    [
        ("True", False),
        ("432", True),
        ("str", False),
        ("455.55", True),
        ("-1", False),
        ("0", False),
        ("fd.f", False),
        ("1", True),
        ("", False),
    ],
)
def test_is_number(number, output):
    """
    Given:
        - fetchIntervalHours as integration param

    When:
        - At the beginning of the integration

    Then:
        - Returns True if fetchIntervalHours is a positive number (float)
    """
    assert is_number(number) == output


@pytest.mark.parametrize(
    "url, output", [(r"hxxp://www.com/", r"hxxp://www.com"), (r"hxxp://www.com", r"hxxp://www.com"), ("", "")]
)
def test_remove_last_slash(url, output):
    """
    Given:
        - Given url from an API response file or from integration args

    When:
        - Before operation with given url

    Then:
        - Return url without '/' in the end (if not exists return the given url)
    """
    assert remove_last_slash(url) == output


@pytest.mark.parametrize(
    "client,data,output",
    [
        (Client(False, False, "2", False, DBotScoreReliability.B), {}, True),
        (Client(False, False, "1", False, DBotScoreReliability.A), {"list": {"id": 200}, "timestamp": 1601542800000}, False),
        (Client(False, False, "2", False, DBotScoreReliability.C), {"list": {"id": 200}, "timestamp": 1601542800000}, False),
        (Client(False, False, "0.5", False, DBotScoreReliability.B), {"list": {"id": 200}, "timestamp": 1601542800000}, True),
    ],
)
def test_is_reloaded_needed(client, data, output):
    """
    Given:
        - data as IntegrationContext

    When:
        - reload command was required

    Then:
        - Returns False if last reload occurred in the past fetch_interval_hours. True otherwise

    """
    with freeze_time(datetime(2020, 10, 1, 10, 00, 00, 0)):
        assert is_reload_needed(client, data) == output


FETCH_INDICATORS_PACKAGE = [
    (
        "http://url.example",
        200,
        "phish_id,url,phish_detail_url,submission_time,verified,verification_time,online,target\n"
        "1,http://url.example1,http://url.example1,2019-10-20T23:54:13+00:00,yes,2019-10-20T23:54:13+00:00,yes,Other\n"
        "2,http://url.example2,http://url.example2,2019-10-20T23:54:14+00:00,yes,2019-10-20T23:54:14+00:00,yes,Target"
        "\n",
        {
            "http://url.example1": {
                "phish_id": "1",
                "submission_time": "2019-10-20T23:54:13+00:00",
                "verified": "yes",
                "verification_time": "2019-10-20T23:54:13+00:00",
                "online": "yes",
                "target": "Other",
            },
            "http://url.example2": {
                "phish_id": "2",
                "submission_time": "2019-10-20T23:54:14+00:00",
                "verified": "yes",
                "verification_time": "2019-10-20T23:54:14+00:00",
                "online": "yes",
                "target": "Target",
            },
        },
    ),
    (
        "http://url.example/",
        200,
        "phish_id,url,phish_detail_url,submission_time,verified,verification_time,online,target\n"
        "1,http://url.example1,2019-10-20T23:54:13+00:00,yes,2019-10-20T23:54:13+00:00,yes,Other\n"
        "2,http://url.example2,"
        "http://url.example2,2019-10-20T23:54:14+00:00,yes,2019-10-20T23:54:14+00:00,yes,Target\n",
        {
            "http://url.example2": {
                "phish_id": "2",
                "submission_time": "2019-10-20T23:54:14+00:00",
                "verified": "yes",
                "verification_time": "2019-10-20T23:54:14+00:00",
                "online": "yes",
                "target": "Target",
            }
        },
    ),
    (
        "http://url.example/",
        509,
        "['You have exceeded the request rate limit for this method. Please see the response headers for usage stats."
        " For more information about rate limiting on Phishtank, please see our developer site: "
        "http://www.phishtank.com/developer_info.php']",
        {},
    ),
]


@pytest.mark.parametrize("url, status_code, data, expected_result", FETCH_INDICATORS_PACKAGE)
def test_reload(mocker, url, status_code, data, expected_result):
    """
    Given:
        - url:
            - on reload command : url is the end point to get all data
            - on url command : given url has to be checked

    When:
        - After reload or url command

    Then:
        - Returns the processed dictionary
    """
    mocker.patch.object(Client, "get_http_request", return_value=data)
    client = create_client(False, False, "1", DBotScoreReliability.B)
    if status_code == 200 or status_code == 509:
        got_data = reload(client)
        assert got_data == expected_result
    else:
        pytest.fail()


CONTEXT_LIST = [
    ({}, "PhishTankV2 Database Status\nDatabase not loaded.\n"),
    ({}, "PhishTankV2 Database Status\nDatabase not loaded.\n"),
    (
        {
            "list": {
                "http://url.example1": {
                    "phish_id": "1",
                    "submission_time": "2019-10-20T23:54:13+00:00",
                    "verified": "yes",
                    "verification_time": "2019-10-20T23:54:13+00:00",
                    "online": "yes",
                    "target": "Other",
                },
                "http://url.example2": {
                    "phish_id": "2",
                    "submission_time": "2019-10-20T23:54:14+00:00",
                    "verified": "yes",
                    "verification_time": "2019-10-20T23:54:14+00:00",
                    "online": "yes",
                    "target": "Target",
                },
            },
            "timestamp": 1601969897 * 1000,
        },
        "PhishTankV2 Database Status\nTotal **2** URLs loaded.\nLast Load time **Tue Oct 06 2020 07:38:17 (UTC)**\n",
    ),
]


@pytest.mark.parametrize("data,expected_result", CONTEXT_LIST)
@freeze_time("1993-06-17 11:00:00 GMT")
def test_phishtank_status_command(mocker, data, expected_result):
    """
    Given:
        - Integration context

    When:
        - After asked fot status command

    Then:
        - Returns number of loaded urls if data was loaded.
        - Otherwise, returns Database not loaded.
    """
    mocker.patch.object(demisto, "getIntegrationContext", return_value=data)
    status = phishtank_status_command()
    assert status.readable_output == expected_result


URL_COMMAND_LIST = [
    (  # valid data , verified = yes
        {
            "phish_id": "1",
            "submission_time": "2019-10-20T23:54:13+00:00",
            "verified": "yes",
            "verification_time": "2019-10-20T23:54:13+00:00",
            "online": "yes",
            "target": "Other",
        },
        ["http://url.example1"],
        3,
        "### PhishTankV2 Database - URL Query \n#### Found matches for URL http://url.example1 \n|online"
        "|phish_id|submission_time|target|verification_time|verified|\n|---|---|---|---|---|---|\n| yes | 1 | "
        "2019-10-20T23:54:13+00:00 | Other | 2019-10-20T23:54:13+00:00 | yes |\nAdditional details at "
        "[http://www.phishtank.com/phish_detail.php?phish_id=1](http://www.phishtank.com/phish_detail.php?phish_id=1) \n",
    ),
    (  # no exists key verified
        {
            "phish_id": "1",
            "submission_time": "2019-10-20T23:54:13+00:00",
            "verification_time": "2019-10-20T23:54:13+00:00",
            "online": "yes",
            "target": "Other",
        },
        ["http://url.example1"],
        0,
        "### PhishTankV2 Database - URL Query \n#### No matches for URL http://url.example1 \n",
    ),
    (  # valid data , verified = no
        {
            "phish_id": "1",
            "submission_time": "2019-10-20T23:54:13+00:00",
            "verified": "no",
            "verification_time": "2019-10-20T23:54:13+00:00",
            "online": "yes",
            "target": "Other",
        },
        ["http://url.example1"],
        2,
        "### PhishTankV2 Database - URL Query \n#### Found matches for URL http://url.example1 \n|online"
        "|phish_id|submission_time|target|verification_time|verified|\n|---|---|---|---|---|---|\n| yes | 1 | "
        "2019-10-20T23:54:13+00:00 | Other | 2019-10-20T23:54:13+00:00 | no |\nAdditional details at "
        "[http://www.phishtank.com/phish_detail.php?phish_id=1](http://www.phishtank.com/phish_detail.php?phish_id=1) \n",
    ),
    (  # no data
        {},
        ["http://url.example1"],
        0,
        "### PhishTankV2 Database - URL Query \n#### No matches for URL http://url.example1 \n",
    ),
]


@pytest.mark.parametrize("data,url,expected_score,expected_table", URL_COMMAND_LIST)
def test_url_command(mocker, data, url, expected_score, expected_table):
    """
    Given:
        - Got url to check for scores

    When:
        - After asked fot url command

    Then:
        - validating that the IOC score is as expected
        - validating the returned human readable
    """
    client = create_client(False, False, "1", DBotScoreReliability.C)
    mocker.patch.object(demisto, "results")
    mocker.patch("PhishTankV2.get_url_data", return_value=(data, url[0]))
    command_results = url_command(client, url)

    # validate score
    output = command_results[0].to_context().get("EntryContext", {})
    dbot_key = "DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)"
    assert output.get(dbot_key, [])[0].get("Score") == expected_score
    assert output.get(dbot_key, [])[0].get("Reliability") == DBotScoreReliability.C

    # validate human readable
    hr_ = command_results[0].to_context().get("HumanReadable", {})
    assert hr_ == expected_table


@pytest.mark.parametrize("username, expected_headers", [("test", {"User-Agent": "phishtank/test"}), ("", {})])
def test_user_agent_header(mocker, username, expected_headers):
    """
    Given:
        - phishtank username

    When:
        - After reload or url command

    Then:
        - validating that the User-Agent header is populated as expected
    """
    http_request = mocker.patch.object(Client, "_http_request", return_value="")
    client = create_client(False, False, "1", DBotScoreReliability.B, username)
    reload(client)
    assert http_request.call_args.kwargs["headers"] == expected_headers