Symantec Messaging Gateway

Symantec Messaging Gateway protects against spam, malware, targeted attacks and provides advanced content filtering, data loss prevention, and email encryption.

Email · Symantec Messaging Gateway

Details

IDSymantec Messaging Gateway
ProviderBroadcom
CategoryEmail
From Version5.0.0
Docker Imagedemisto/bs4-py3:1.0.0.10120494
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security

README

Use Symantec Messaging Gateway (SMG) to block and unblock domains, email addresses, and IP addresses.

This integration was integrated and tested with Symantec Messaging Gateway v10.6.4.

Use Cases

  • Block and unblock domains, email addresses and IP addresses.
  • Get blocked domains and blocked IP addresses.

Known limitations

  • SMG does not have a REST API, therefore the integration parses HTML response using the Beautiful Soup package. It also sends and gets data through it.
  • The integration adds and removes IoCs to the relevant default Bad Sender lists, and not custom ones.

Configure Symantec Messaging Gateway on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Symantec Messaging Gateway.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance
    • Server URL (for example, https://192.168.0.1:20013)
    • Username
    • Do not validate server certificate (not secure)
    • Use system proxy settings
  4. Click Test to validate URLs and connection. 

Commands

You can execute these commands from the Cortex XSOAR 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.

  1. Block an email address: smg-block-email
  2. Block a domain: smg-block-domain
  3. Block an IP address: smg-block-ip
  4. Unblock an email address: smg-unblock-email
  5. Unblock a domain: smg-unblock-domain
  6. Unblock an IP address: smg-unblock-ip
  7. Get blocked Domains: smg-get-blocked-domains
  8. Get blocked IP addresses: smg-get-blocked-ips

1. Block an email address


Blocks an email address.

Base Command

smg-block-email

Input
Parameter Description
email Email address to block

 

Context Output
Path Description
Email.Address Email address that was blocked
Email.Blocked True if blocked, False if unblocked

 

Raw Output
Email address admin@example.com was blocked successfully.

 

2. Block a domain


Block a domain.

Base Command

smg-block-domain

Input
Parameter Description
domain Domain to block

 

Context Output
Path Description
Domain.Name Name of the domain that was blocked
Domain.Blocked True if blocked, False if unblocked

 

Raw Output
Domain google.com was blocked successfully.

 

3. Block an IP address


Blocks an IP address.

Base Command

smg-block-ip

Input
Parameter Description
ip  IP address to block

 

Context Output
Path Description
IP.Address IP address that was blocked
IP.Blocked True if blocked, False if unblocked

 

Raw Output
IP address 8.8.8.8 was blocked successfully.

 

4. Unblock an email address


Unblock an email address.

Base Command

smg-unblock-email

Input
Parameter Description
email Email address to unblock

 

Context Output
Path Description
Email.Address Email address that was unblocked
Email.Blocked True if blocked, False if unblocked

 

Raw Output
Email address admin@example.com was unblocked successfully.

 

5. Unblock a domain


Unblock a domain.

Base Command

smg-unblock-domain

Input
Parameter Description
domain Domain to unblock

 

Context Output
Path Description
Domain.Name Name of the domain that was blocked
Domain.Blocked True if blocked, False if unblocked

 

Raw Output
Domain google.com was unblocked successfully.

 

6. Unblock an IP address


Unblock an IP address.

Base Command

smg-unblock-ip

Input
Parameter Description
ip  IP address to unblock

 

Context Output
Path Description
IP.Address IP address that was unblocked
IP.Blocked True if blocked, False if unblocked

 

Raw Output

IP address 8.8.8.8 was unblocked successfully.

 


7. Get a list of blocked domains

Returns a list of blocked domains.

Base Command

smg-get-blocked-domains

Input

There is no input.

Context Output

There is no context output for this command.

Raw Output
### SMG Blocked domains:
- abc.net
- abc.org

 


8. Get blocked IP addresses

Get blocked IP addresses.

Base Command

smg-get-blocked-ips

Input

There is no input.

Context Output

There is no context output for this command..

Raw Output
### SMG Blocked IP addresses:
- 1.2.3.4
- 8.8.8.8

Configuration parameters

  • server — Server URL (e.g. https://192.168.0.1:20013) (required)
  • credentials — Username (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (8)

  • smg-block-domain

    Adds domain to the Local Bad Sender Domains group.

  • smg-block-email

    Adds email address to the Local Bad Sender Domains.

  • smg-block-ip

    Adds IP address to the Local Bad Sender IPs group.

  • smg-get-blocked-domains

    Returns a list of all blocked email addresses.

  • smg-get-blocked-ips

    Returns a list of all blocked IP addresses.

  • smg-unblock-domain

    Removes domain from the Local Bad Sender Domains group.

  • smg-unblock-email

    Removes email address from the Local Bad Sender Domains.

  • smg-unblock-ip

    Removes IP address from the Local Bad Sender IPs group.

import demistomock as demisto  # noqa: F401
import urllib3
from bs4 import BeautifulSoup, Tag
from CommonServerPython import *  # noqa: F401

# disable insecure warnings
urllib3.disable_warnings()

""" GLOBAL VARS """
SERVER = demisto.params()["server"].rstrip("/")
USERNAME = demisto.params()["credentials"]["identifier"]
PASSWORD = demisto.params()["credentials"]["password"]
BASE_URL = SERVER + "/brightmail/"
USE_SSL = not demisto.params().get("insecure", False)
COOKIES = {}  # type: ignore
TOKEN: str

BAD_DOMAINS_EMAILS_GROUP = "Local Bad Sender Domains"
BAD_IPS_GROUP = "Local Bad Sender IPs"

client = BaseClient(base_url=BASE_URL, verify=USE_SSL)
""" HELPER FUNCTIONS """


def http_request(method, url_suffix, cookies=COOKIES, data=None, headers=None):
    LOG(f"running request with url={BASE_URL + url_suffix}\tdata={data}\theaders={headers}")
    try:
        res = client._session.request(method, BASE_URL + url_suffix, verify=USE_SSL, data=data, headers=headers, cookies=cookies)

        if res.status_code not in (200, 204):
            raise Exception("Your request failed with the following error: " + res.reason)
    except Exception as e:
        LOG(e)
        raise
    return res


def login():
    login_do_url = "viewLogin.do"
    login_do_response = http_request("get", login_do_url, cookies=None)
    login_jsession = login_do_response.cookies.get_dict()["JSESSIONID"]

    soup = BeautifulSoup(login_do_response.text, "lxml")
    hidden_tags = soup.find_all("input", type="hidden")  # Parse <input type=hidden>
    last_login = ""
    for tag in hidden_tags:
        name = tag.attrs.get("name", None)
        if name == "lastlogin":
            # attrs values may be multi-valued, but "value" is always single-valued here
            last_login = str(tag.attrs["value"])
    cookies = {"JSESSIONID": login_jsession}
    demisto.debug(f"{last_login=}")
    data = {"lastlogin": last_login, "username": USERNAME, "password": PASSWORD}
    login_url = "login.do"
    login_response = http_request("post", login_url, cookies=cookies, data=data)

    # if JSESSIONID doesn't exist - creds may be invalid
    if "JSESSIONID" not in login_response.cookies:
        return_error("Failed to login. Username or password may be invalid")

    jsession = login_response.cookies.get_dict()["JSESSIONID"]

    # Add Jsession ID to the cookies
    COOKIES["JSESSIONID"] = jsession

    # Get Token
    login_do_url = "admin/backup/backupNow.do"
    login_do_response = http_request("get", login_do_url)
    soup = BeautifulSoup(login_do_response.text, "lxml")
    hidden_tags = soup.find_all("input", type="hidden")  # Parse <input type=hidden>
    for tag in hidden_tags:
        name = tag.attrs.get("name", None)
        if name == "symantec.brightmail.key.TOKEN":
            token = tag.attrs["value"]
            return token
    return None


def get_selected_sender_groups(group):
    """
    Gets bad group name, i.e. Local Bad Sender Domains, and returns the bad group identifer, i.e. 1|3
    The identifier is needed in any sent query related to the bad group
    """
    cmd_url = "reputation/sender-group/viewSenderGroups.do?view=badSenders"
    groups = http_request("get", cmd_url)
    soup = BeautifulSoup(groups.text, "lxml")

    tds_group_names_array = soup.find_all("td")  # Parse <td>
    for td in tds_group_names_array:
        a_href = td.find("a")  # Parse <a>
        if a_href:
            href_string = a_href.string  # Extracts the string from the <a>string</a> tags
            if not href_string:  # <a> has no text, cannot be the group we are looking for
                continue
            checked_group = " ".join(href_string.split())  # Removes whitespaces from string
            if checked_group == group:
                previous_sibling = td.previous_sibling
                previous_td = previous_sibling.previous_sibling if previous_sibling else None
                if not isinstance(previous_td, Tag):  # No preceding <td> holding the group <input>
                    continue
                input_tag = previous_td.find("input")  # Parse <input> tags
                if isinstance(input_tag, Tag):
                    group_number = input_tag["value"]
                    return group_number
    return None


def block_request(ioc, selected_sender_groups):
    cmd_url = "reputation/sender-group/saveSender.do"
    data = {
        "pageReuseFor": "add",
        "selectedSenderGroups": selected_sender_groups,
        "view": "badSenders",
        "symantec.brightmail.key.TOKEN": TOKEN,
        "addEditSenders": ioc,
    }
    response = http_request("post", cmd_url, data=data)
    # Check if given domain/email address is valid and is not already blocked
    soup = BeautifulSoup(response.text, "lxml")
    # Look for the error message
    error = soup.find("div", class_="errorMessageText")
    if error:  # Error occured
        error_message = " ".join(error.text.split())  # Removes whitespaces from string
        return error_message
    return None


def unblock_request(selected_group_member, selected_sender_groups):
    cmd_url = "reputation/sender-group/deleteSender.do"
    data = {
        "selectedSenderGroups": selected_sender_groups,
        "view": "badSenders",
        "symantec.brightmail.key.TOKEN": TOKEN,
        "selectedGroupMembers": int(selected_group_member),
    }
    response = http_request("post", cmd_url, data=data)
    return response


def get_blocked_request(sender_group):
    selected_sender_groups = get_selected_sender_groups(sender_group)
    cmd_url = "reputation/sender-group/viewSenderGroup.do"
    data = {"selectedSenderGroups": selected_sender_groups, "view": "badSenders", "symantec.brightmail.key.TOKEN": TOKEN}
    blocked = http_request("post", cmd_url, data=data)
    return blocked


def get_next_page(selected_sender_groups):
    """
    Gets next page in bad list
    """
    cmd_url = "reputation/sender-group/viewNextPage.do"
    data = {"selectedSenderGroups": selected_sender_groups, "view": "badSenders", "symantec.brightmail.key.TOKEN": TOKEN}
    next_page = http_request("post", cmd_url, data=data)
    return next_page


def get_page_number_options(soup):
    """
    Returns the pagination <option> elements of the given parsed page.
    Raises a DemistoException when the page number selector is missing, instead of failing
    later with an obscure AttributeError.
    """
    page_number_select = soup.find("select", class_="defaultDrop", id="pageNumber")
    if not isinstance(page_number_select, Tag):
        raise DemistoException("Could not find the page number selector in the Symantec Messaging Gateway response.")
    return page_number_select.find_all("option")


""" FUNCTIONS """


def get_blocked_domains():
    selected_sender_groups = get_selected_sender_groups(BAD_DOMAINS_EMAILS_GROUP)
    blocked_domains = get_blocked_request(BAD_DOMAINS_EMAILS_GROUP)
    hr = "### SMG Blocked domains:\n"
    soup = BeautifulSoup(blocked_domains.text, "lxml")
    # Handles pagination of Local Bad Sender Domains
    pages = get_page_number_options(soup)
    for _i in range(len(pages)):  # Loop through all pages of blocked IP address
        tds_array = soup.find_all("td", class_="paddingL3")  # Parse <td>
        for td in tds_array:
            a = td.find("a")  # Parse <a>
            if a:
                s = str(a.find_next_sibling(text=True))  # Get domain
                hr += "- " + "".join(s.split()) + "\n"  # Removes whitespaces from string
        # Get next page
        next_page = get_next_page(selected_sender_groups)
        soup = BeautifulSoup(next_page.text, "lxml")

    entry = {
        "Type": entryTypes["note"],
        "Contents": hr,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": hr,
    }
    return entry


def get_blocked_ips():
    selected_sender_groups = get_selected_sender_groups(BAD_IPS_GROUP)
    blocked_emails = get_blocked_request(BAD_IPS_GROUP)
    hr = "### SMG Blocked IP addresses:\n"
    soup = BeautifulSoup(blocked_emails.text, "lxml")
    # Handles pagination of Local Bad Sender IPs
    pages = get_page_number_options(soup)
    for _i in range(len(pages)):  # Loop through all pages of blocked IP address
        tds_array = soup.find_all("td", class_="paddingL3")  # Parse <td>
        for td in tds_array:
            a = td.find("a")  # Parse <a>
            if a:
                s = str(a.find_next_sibling(text=True))  # Get IP address
                hr += "- " + "".join(s.split()) + "\n"  # Removes whitespaces from string
        # Get next page
        next_page = get_next_page(selected_sender_groups)
        soup = BeautifulSoup(next_page.text, "lxml")

    entry = {
        "Type": entryTypes["note"],
        "Contents": hr,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": hr,
    }
    return entry


def block_email(email):
    selected_sender_groups = get_selected_sender_groups(BAD_DOMAINS_EMAILS_GROUP)
    error_message = block_request(email, selected_sender_groups)
    if error_message:
        return error_message
    context = {"Address": email, "Blocked": True}
    ec = {"Email(val.Address && val.Address === obj.Address)": context}
    message = "Email address " + email + " was blocked successfully."
    entry = {
        "Type": entryTypes["note"],
        "Contents": message,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": message,
        "EntryContext": ec,
    }
    return entry


def unblock_email(email):
    selected_sender_groups = get_selected_sender_groups(BAD_DOMAINS_EMAILS_GROUP)
    blocked_emails = get_blocked_request(BAD_DOMAINS_EMAILS_GROUP)
    # Email member number is required in order to send it in the unblock query
    soup = BeautifulSoup(blocked_emails.text, "lxml")
    # Handles pagination of Local Bad Sender Domains
    pages = get_page_number_options(soup)
    for _i in range(len(pages)):  # Loop through all pages of blocked email addresses
        tds_array = soup.find_all("td", class_="paddingL3")  # Parse <td>
        for td in tds_array:
            a = td.find("a")  # Parse <a>
            if a:
                s = str(a.find_next_sibling(text=True))  # Get checked email address
                checked_email = "".join(s.split())  # Removes whitespaces from string
                if checked_email == email:
                    href = str(a["href"])  # Get <a href=...>, always a single-valued attribute
                    comma_index = href.find(",")  # Get comma sign index in string
                    selected_group_member = href[comma_index + 1 : -2]  # Get email member number
                    break
        # Get next page
        next_page = get_next_page(selected_sender_groups)
        soup = BeautifulSoup(next_page.text, "lxml")

    if "selected_group_member" not in locals():
        return "Could not find given email address in " + BAD_DOMAINS_EMAILS_GROUP

    unblock_request(selected_group_member, selected_sender_groups)
    context = {"Address": email, "Blocked": False}
    ec = {"Email(val.Address && val.Address === obj.Address)": context}
    message = "Email address " + email + " was unblocked successfully."
    entry = {
        "Type": entryTypes["note"],
        "Contents": message,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": message,
        "EntryContext": ec,
    }
    return entry


def block_domain(domain):
    selected_sender_groups = get_selected_sender_groups(BAD_DOMAINS_EMAILS_GROUP)
    error_message = block_request(domain, selected_sender_groups)
    if error_message:
        return error_message
    context = {"Name": domain, "Blocked": True}
    ec = {"Domain(val.Name && val.Name === obj.Name)": context}
    message = "Domain " + domain + " was blocked successfully."
    entry = {
        "Type": entryTypes["note"],
        "Contents": message,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": message,
        "EntryContext": ec,
    }
    return entry


def unblock_domain(domain):
    selected_sender_groups = get_selected_sender_groups(BAD_DOMAINS_EMAILS_GROUP)
    blocked_domains = get_blocked_request(BAD_DOMAINS_EMAILS_GROUP)
    # Domain member number is required in order to send it in the unblock query
    soup = BeautifulSoup(blocked_domains.text, "lxml")
    # Handles pagination of Local Bad Sender Domains
    pages = get_page_number_options(soup)
    for _i in range(len(pages)):  # Loop through all pages of blocked domains
        tds_array = soup.find_all("td", class_="paddingL3")  # Parse <td>
        for td in tds_array:
            a = td.find("a")  # Parse <a>
            if a:
                s = str(a.find_next_sibling(text=True))  # Get checked domain
                checked_domain = "".join(s.split())  # Removed whitespaces from string
                if checked_domain == domain:
                    href = str(a["href"])  # Get <a href=...>, always a single-valued attribute
                    comma_index = href.find(",")  # Get comma sign index in string
                    selected_group_member = href[comma_index + 1 : -2]  # Get domain member number
                    break
        # Get next page
        next_page = get_next_page(selected_sender_groups)
        soup = BeautifulSoup(next_page.text, "lxml")

    if "selected_group_member" not in locals():
        return "Could not find given domain in " + BAD_DOMAINS_EMAILS_GROUP

    unblock_request(selected_group_member, selected_sender_groups)
    context = {"Name": domain, "Blocked": False}
    ec = {"Domain(val.Name && val.Name === obj.Name)": context}
    message = "Domain " + domain + " was unblocked successfully."
    entry = {
        "Type": entryTypes["note"],
        "Contents": message,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": message,
        "EntryContext": ec,
    }
    return entry


def block_ip(ip):
    selected_sender_groups = get_selected_sender_groups(BAD_IPS_GROUP)
    error_message = block_request(ip, selected_sender_groups)
    if error_message:
        return error_message
    context = {"Address": ip, "Blocked": True}
    ec = {"IP(val.Address && val.Address === obj.Address)": context}
    message = "IP address " + ip + " was blocked successfully."
    entry = {
        "Type": entryTypes["note"],
        "Contents": message,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": message,
        "EntryContext": ec,
    }
    return entry


def unblock_ip(ip):
    selected_sender_groups = get_selected_sender_groups(BAD_IPS_GROUP)
    blocked_ips = get_blocked_request(BAD_IPS_GROUP)
    # Domain member number is required in order to send it in the unblock query
    soup = BeautifulSoup(blocked_ips.text, "lxml")
    # Handles pagination of Local Bad Sender IPs
    pages = get_page_number_options(soup)
    for _i in range(len(pages)):  # Loop through all pages of blocked IP address
        tds_array = soup.find_all("td", class_="paddingL3")  # Parse <td>
        for td in tds_array:
            a = td.find("a")  # Parse <a>
            if a:
                s = str(a.find_next_sibling(text=True))  # Get checked IP address
                checked_ip = "".join(s.split())  # Removed whitespaces from string
                if checked_ip == ip:
                    href = str(a["href"])  # Get <a href=...>, always a single-valued attribute
                    comma_index = href.find(",")  # Get comma sign index in string
                    selected_group_member = href[comma_index + 1 : -2]  # Get IP member number
                    break
        next_page = get_next_page(selected_sender_groups)
        soup = BeautifulSoup(next_page.text, "lxml")

    if "selected_group_member" not in locals():
        return "Could not find given IP address in " + BAD_IPS_GROUP

    unblock_request(selected_group_member, selected_sender_groups)
    context = {"Address": ip, "Blocked": False}
    ec = {"IP(val.Address && val.Address === obj.Address)": context}
    message = "IP address " + ip + " was unblocked successfully."
    entry = {
        "Type": entryTypes["note"],
        "Contents": message,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": message,
        "EntryContext": ec,
    }
    return entry


def main():
    global TOKEN
    handle_proxy()
    TOKEN = login()

    try:
        if demisto.command() == "test-module":
            # Checks authentication and connectivity in login() function
            demisto.results("ok")
        elif demisto.command() == "smg-block-email":
            # demisto.results(get_selected_sender_groups())
            demisto.results(block_email(demisto.args()["email"]))
        elif demisto.command() == "smg-unblock-email":
            demisto.results(unblock_email(demisto.args()["email"]))
        elif demisto.command() == "smg-block-domain":
            demisto.results(block_domain(demisto.args()["domain"]))
        elif demisto.command() == "smg-block-ip":
            demisto.results(block_ip(demisto.args()["ip"]))
        elif demisto.command() == "smg-unblock-ip":
            demisto.results(unblock_ip(demisto.args()["ip"]))
        elif demisto.command() == "smg-unblock-domain":
            demisto.results(unblock_domain(demisto.args()["domain"]))
        elif demisto.command() == "smg-get-blocked-domains":
            demisto.results(get_blocked_domains())
        elif demisto.command() == "smg-get-blocked-ips":
            demisto.results(get_blocked_ips())

    except Exception as e:
        return_error(str(e))


if __name__ in ("__main__", "builtin", "builtins"):
    main()