Skip to content

Commit

Permalink
Cybereason: Fix for reported issue - Ungroup Malwares from Splunk SOAR (
Browse files Browse the repository at this point in the history
#7)

* Malware ungrouping
Malware ungrouping using guid+timestamp as the container id

* comment fix

* Resolving issues

* PR fix

* Fixing Static test errors

* PR comment fixed

* PR fix - Connector code merge

* standard dev check changes

* phantom to SOAR variable name changes

* Addressed review comments

Co-authored-by: Ritesh Saste <[email protected]>
Co-authored-by: kapil-metron <[email protected]>
Co-authored-by: suraj-metron <[email protected]>
Co-authored-by: ritesh-metron <[email protected]>
  • Loading branch information
5 people authored Aug 4, 2022
1 parent fbe4f91 commit df25050
Show file tree
Hide file tree
Showing 25 changed files with 112 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/linting.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Linting
on: [push, pull_request]
jobs:
lint:
lint:
# Run per push for internal contributers. This isn't possible for forked pull requests,
# so we'll need to run on PR events for external contributers.
# String comparison below is case insensitive.
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Semgrep
on:
on:
pull_request_target:
branches:
- next
Expand All @@ -21,8 +21,8 @@ jobs:
echo "REPOSITORY=${{ github.event.pull_request.head.repo.full_name }}" >> $GITHUB_ENV
echo "REF=${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV
- uses: 'phantomcyber/dev-cicd-tools/github-actions/semgrep@main'
with:
with:
SEMGREP_DEPLOYMENT_ID: ${{ secrets.SEMGREP_DEPLOYMENT_ID }}
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
REPOSITORY: ${{ github.repository }}
REPOSITORY: ${{ github.repository }}
REF: ${{ github.ref }}
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
repos:
- repo: https://github.com/phantomcyber/dev-cicd-tools
rev: v1.9
rev: v1.13
hooks:
- id: org-hook
- id: package-app-dependencies
- repo: https://github.com/Yelp/detect-secrets
rev: v1.1.0
rev: v1.3.0
hooks:
- id: detect-secrets
args: ['--no-verify', '--exclude-files', '^cybereason.json$']
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
23 changes: 12 additions & 11 deletions cybereason.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
"product_version_regex": ".*",
"publisher": "Cybereason",
"license": "Copyright (c) Cybereason, 2018-2021",
"app_version": "2.1.4",
"app_version": "2.2.0",
"utctime_updated": "2022-01-07T20:19:08.000000Z",
"package_name": "phantom_cybereason",
"main_module": "cybereason_connector.py",
"min_phantom_version": "5.0.0",
"min_phantom_version": "5.3.0",
"app_wizard_version": "1.0.0",
"fips_compliant": false,
"configuration": {
"base_url": {
"description": "The URL of the Cybereason server to connect to. This should be of the form 'https://<server name or ip>:<port>'",
Expand Down Expand Up @@ -1817,7 +1818,7 @@
{
"data_path": "action_result.data.*.dns_query",
"data_type": "string",
"column_name": "DNS Query",
"column_name": "Dns Query",
"column_order": 10
},
{
Expand Down Expand Up @@ -2017,32 +2018,32 @@
"wheel": [
{
"module": "beautifulsoup4",
"input_file": "wheels/beautifulsoup4-4.9.1-py3-none-any.whl"
"input_file": "wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl"
},
{
"module": "certifi",
"input_file": "wheels/certifi-2021.10.8-py2.py3-none-any.whl"
"input_file": "wheels/py3/certifi-2022.6.15-py3-none-any.whl"
},
{
"module": "chardet",
"input_file": "wheels/chardet-3.0.4-py2.py3-none-any.whl"
"input_file": "wheels/shared/chardet-3.0.4-py2.py3-none-any.whl"
},
{
"module": "idna",
"input_file": "wheels/idna-2.10-py2.py3-none-any.whl"
"input_file": "wheels/shared/idna-2.10-py2.py3-none-any.whl"
},
{
"module": "requests",
"input_file": "wheels/requests-2.25.0-py2.py3-none-any.whl"
"input_file": "wheels/shared/requests-2.25.0-py2.py3-none-any.whl"
},
{
"module": "soupsieve",
"input_file": "wheels/soupsieve-2.3-py3-none-any.whl"
"input_file": "wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl"
},
{
"module": "urllib3",
"input_file": "wheels/urllib3-1.26.7-py2.py3-none-any.whl"
"input_file": "wheels/shared/urllib3-1.26.11-py2.py3-none-any.whl"
}
]
}
}
}
88 changes: 66 additions & 22 deletions cybereason_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def _validate_integer(self, action_result, parameter, key):
return phantom.APP_SUCCESS, parameter

def _handle_test_connectivity(self, param):
self.save_progress("In action handler for: {0}".format(self.get_action_identifier()))
# Add an action result object to self (BaseConnector) to represent the action for this param
action_result = self.add_action_result(ActionResult(dict(param)))

Expand All @@ -143,6 +144,7 @@ def _handle_test_connectivity(self, param):
'Successfully connected to the Cybereason console and verified session cookie'
)
else:
self.debug_print('Failure to verify session cookie.')
return action_result.set_status(
phantom.APP_ERROR,
'Connectivity failed. Unable to get session cookie from Cybereason console'
Expand Down Expand Up @@ -235,6 +237,7 @@ def _handle_delete_registry_key(self, param):
})
except Exception as e:
err = self._get_error_message_from_exception(e)
self.debug_print("Error occurred: {}".format(err))
return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err))

return action_result.set_status(phantom.APP_SUCCESS)
Expand Down Expand Up @@ -350,8 +353,8 @@ def _handle_update_malop_status(self, param):

malop_id = self._get_string_param(param['malop_id'])

phantom_status = param['status']
cybereason_status = PHANTOM_TO_CYBEREASON_STATUS.get(phantom_status)
soar_status = param['status']
cybereason_status = SOAR_TO_CYBEREASON_STATUS.get(soar_status)
if not cybereason_status:
self.save_progress("Invalid status selected")
return action_result.set_status(
Expand Down Expand Up @@ -945,6 +948,54 @@ def _get_machine_name_by_machine_ip(self, machine_ip, action_result):

return RetVal(action_result.set_status(phantom.APP_SUCCESS), machine_names)

def on_poll(self, param):
self.save_progress("Entered the on_poll function")
self.save_progress("processing")
poller = CybereasonPoller()
return poller.do_poll(self, param)

def _handle_query_processes(self, param):
self.save_progress("Entered the _handle_query_processes function")
self.save_progress("processing")
query_action = CybereasonQueryActions()
return query_action._handle_query_processes(self, param)

def _handle_query_machine(self, param):
self.save_progress("Entered the _handle_query_machine function")
self.save_progress("processing")
query_action = CybereasonQueryActions()
return query_action._handle_query_machine(self, param)

def _handle_query_machine_ip(self, param):
self.save_progress("Entered the _handle_query_machine_ip function")
self.save_progress("processing")
query_action = CybereasonQueryActions()
return query_action._handle_query_machine_ip(self, param)

def _handle_query_users(self, param):
self.save_progress("Entered the _handle_query_users function")
self.save_progress("processing")
query_action = CybereasonQueryActions()
return query_action._handle_query_users(self, param)

def _handle_query_files(self, param):
self.save_progress("Entered the _handle_query_files function")
self.save_progress("processing")
query_action = CybereasonQueryActions()
return query_action._handle_query_files(self, param)

def _handle_query_domain(self, param):
self.save_progress("Entered the _handle_query_domain function")
self.save_progress("processing")
query_action = CybereasonQueryActions()
return query_action._handle_query_domain(self, param)

def _handle_query_connections(self, param):
self.save_progress("Entered the _handle_query_connections function")
self.save_progress("processing")
query_action = CybereasonQueryActions()
return query_action._handle_query_connections(self, param)

def handle_action(self, param):
ret_val = phantom.APP_SUCCESS

Expand All @@ -963,8 +1014,7 @@ def handle_action(self, param):
ret_val = self._handle_get_sensor_status(param)

elif action_id == 'on_poll':
poller = CybereasonPoller()
ret_val = poller.do_poll(self, param)
ret_val = self.on_poll(param)

elif action_id == 'add_malop_comment':
ret_val = self._handle_add_malop_comment(param)
Expand Down Expand Up @@ -1000,32 +1050,25 @@ def handle_action(self, param):
ret_val = self._handle_restart_sensor(param)

elif action_id == 'query_processes':
query_action = CybereasonQueryActions()
ret_val = query_action._handle_query_processes(self, param)
ret_val = self._handle_query_processes(param)

elif action_id == 'query_machine':
query_action = CybereasonQueryActions()
ret_val = query_action._handle_query_machine(self, param)
ret_val = self._handle_query_machine(param)

elif action_id == 'query_machine_ip':
query_action = CybereasonQueryActions()
ret_val = query_action._handle_query_machine_ip(self, param)
ret_val = self._handle_query_machine_ip(param)

elif action_id == 'query_users':
query_action = CybereasonQueryActions()
ret_val = query_action._handle_query_users(self, param)
ret_val = self._handle_query_users(param)

elif action_id == 'query_files':
query_action = CybereasonQueryActions()
ret_val = query_action._handle_query_files(self, param)
ret_val = self._handle_query_files(param)

elif action_id == 'query_domain':
query_action = CybereasonQueryActions()
ret_val = query_action._handle_query_domain(self, param)
ret_val = self._handle_query_domain(param)

elif action_id == 'query_connections':
query_action = CybereasonQueryActions()
ret_val = query_action._handle_query_connections(self, param)
ret_val = self._handle_query_connections(param)

return ret_val

Expand Down Expand Up @@ -1055,6 +1098,7 @@ def finalize(self):

def main():
import argparse
import sys

import pudb

Expand Down Expand Up @@ -1083,7 +1127,7 @@ def main():
login_url = CybereasonConnector._get_phantom_base_url() + '/login'

print("Accessing the Login page")
r = requests.get(login_url, verify=False)
r = requests.get(login_url, timeout=DEFAULT_REQUEST_TIMEOUT)
csrftoken = r.cookies['csrftoken']

data = dict()
Expand All @@ -1096,11 +1140,11 @@ def main():
headers['Referer'] = login_url

print("Logging into Platform to get the session id")
r2 = requests.post(login_url, verify=False, data=data, headers=headers)
r2 = requests.post(login_url, data=data, headers=headers, timeout=DEFAULT_REQUEST_TIMEOUT)
session_id = r2.cookies['sessionid']
except Exception as e:
print("Unable to get session id from the platform. Error: " + str(e))
exit(1)
sys.exit(1)

with open(args.input_test_json) as f:
in_json = f.read()
Expand All @@ -1117,7 +1161,7 @@ def main():
ret_val = connector._handle_action(json.dumps(in_json), None)
print(json.dumps(json.loads(ret_val), indent=4))

exit(0)
sys.exit(0)


if __name__ == '__main__':
Expand Down
4 changes: 3 additions & 1 deletion cybereason_consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# either express or implied. See the License for the specific language governing permissions
# and limitations under the License.

PHANTOM_TO_CYBEREASON_STATUS = {
SOAR_TO_CYBEREASON_STATUS = {
'Unread': "UNREAD",
'To Review': "TODO",
'Not Relevant': "FP",
Expand All @@ -31,3 +31,5 @@

MALOP_HISTORICAL_DAYS_KEY = "malop_historical_days asset configuration parameter"
MALWARE_HISTORICAL_DAYS_KEY = "malware_historical_days asset configuration parameter"

DEFAULT_REQUEST_TIMEOUT = 60 # in seconds
17 changes: 9 additions & 8 deletions cybereason_poller.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -368,14 +368,15 @@ def _ingest_malop(self, connector, config, malop_id, malop_data):

return phantom.APP_SUCCESS if success else phantom.APP_ERROR

def _does_container_exist_for_malop_malware(self, connector, malop_id):
def _does_container_exist_for_malop_malware(self, connector, source_data_identifier):
url = '{0}rest/container?_filter_source_data_identifier="{1}"&_filter_asset={2}'.format(
connector.get_phantom_base_url(),
malop_id, connector.get_asset_id()
source_data_identifier, connector.get_asset_id()
)
existing_container_id = False

try:
r = requests.get(url, verify=False)
r = requests.get(url, verify=False, timeout=DEFAULT_REQUEST_TIMEOUT) # nosemgrep
resp_json = r.json()
except Exception as e:
err = connector._get_error_message_from_exception(e)
Expand All @@ -402,7 +403,7 @@ def _update_container_for_malop_malware(self, connector, config, existing_contai
update_json = container.copy()
del update_json["artifacts"]
url = '{0}rest/container/{1}'.format(connector.get_phantom_base_url(), existing_container_id)
r = requests.post(url, json=update_json, verify=False)
r = requests.post(url, json=update_json, verify=False, timeout=DEFAULT_REQUEST_TIMEOUT) # nosemgrep
resp_json = r.json()

for artifact in container["artifacts"]:
Expand Down Expand Up @@ -435,7 +436,7 @@ def _get_artifact(self, connector, config, source_data_identifier, container_id)
url = '{0}rest/artifact?_filter_source_data_identifier="{1}"&_filter_container_id={2}&sort=id&order=desc'.format(
connector.get_phantom_base_url(), source_data_identifier, container_id)
try:
r = requests.get(url, verify=False)
r = requests.get(url, verify=False, timeout=DEFAULT_REQUEST_TIMEOUT) # nosemgrep
resp_json = r.json()
except Exception as e:
err = connector._get_error_message_from_exception(e)
Expand Down Expand Up @@ -762,7 +763,7 @@ def _get_malware_with_offset(self, connector, malware_millisec_since_last_poll,
def _ingest_malware(self, connector, config, malware):
success = phantom.APP_ERROR
container = self._get_container_dict_for_malware(connector, config, malware)
existing_container_id = self._does_container_exist_for_malop_malware(connector, malware["guid"])
existing_container_id = self._does_container_exist_for_malop_malware(connector, "{}_{}".format(malware["guid"], malware["timestamp"]))
if not existing_container_id:
# Container does not exist. Go ahead and save it
connector.debug_print("Saving container for Malware with id {}".format(malware["guid"]))
Expand All @@ -782,7 +783,7 @@ def _get_container_dict_for_malware(self, connector, config, malware):
)
container_json["data"] = malware
container_json["description"] = malware["name"]
container_json["source_data_identifier"] = malware["guid"]
container_json["source_data_identifier"] = "{}_{}".format(malware["guid"], malware["timestamp"])
container_json["label"] = config.get("ingest", {}).get("container_label")
status_map = self._get_status_map_malware()
container_json["status"] = status_map.get(malware["status"], "New")
Expand Down Expand Up @@ -889,7 +890,7 @@ def _get_cef_type_map(self):
}

# Converts timestamps from Cybereason API
# (e.g. string "1585270873770") to Phantom/ISO 8601 format (e.g. 2020-03-27T01:01:13.770Z)
# (e.g. string "1585270873770") to SOAR/ISO 8601 format (e.g. 2020-03-27T01:01:13.770Z)
def _phtimestamp_from_crtimestamp(self, cybereason_timestamp):
timestamp = datetime.datetime.fromtimestamp(int(cybereason_timestamp) / 1000.0) # Timestamp is in epoch-milliseconds
return timestamp.isoformat()[:-3] + "Z" # Remove the microsecond accuracy, add "Z" for UTC timezone
1 change: 0 additions & 1 deletion cybereason_query_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import traceback

# Phantom App imports
import phantom.app as phantom
from phantom.action_result import ActionResult

Expand Down
4 changes: 3 additions & 1 deletion cybereason_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import requests

from cybereason_consts import DEFAULT_REQUEST_TIMEOUT


class CybereasonSession:

Expand All @@ -26,7 +28,7 @@ def __init__(self, connector):
}
try:
url = "{0}/login.html".format(connector._base_url)
res = self.session.post(url, data=post_body, verify=connector._verify_server_cert)
res = self.session.post(url, data=post_body, verify=connector._verify_server_cert, timeout=DEFAULT_REQUEST_TIMEOUT)
if self.session.cookies.get_dict().get("JSESSIONID") is None:
connector.save_progress("Error when logging in to the the Cybereason console: No session cookie returned")
connector.save_progress("Status code: {}".format(res.status_code))
Expand Down
Loading

0 comments on commit df25050

Please sign in to comment.