Skip to content

Commit

Permalink
fix Alert bulk operations & change .all_events to get all events for …
Browse files Browse the repository at this point in the history
…a process across all segments
  • Loading branch information
jgarman committed Jan 12, 2017
1 parent 78da76a commit 5a11ed3
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 17 deletions.
7 changes: 7 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ If you're more a Cb Protection fellow, then you're in luck as well::
Major Features
--------------

- **Enhanced Live Response API**
The new cbapi now provides a robust interface to the Cb Response Live Response capability.
Easily create Live Response sessions, initiate commands on remote hosts, and pull down data as
necessary to make your Incident Response process much more efficient and automated.

- **Consistent API for both Cb Response and Protection platforms**
We now support Cb Response and Protection users in the same API layer. Even better,
the object model is the same for both; if you know one API you can easily transition to the other. cbapi
Expand All @@ -84,6 +89,8 @@ Major Features
productivity and lowers the bar to entry.

- **Python 3 and Python 2 compatible**
Use all the new features and modules available in Python 3 with cbapi. This module is compatible with Python
versions 2.6.6 and above, 2.7.x, 3.4.x, and 3.5.x.

- **Better support for multiple Cb servers**
cbapi now introduces the concept of Credential Profiles; named collections of URL, API keys, and optional proxy
Expand Down
4 changes: 2 additions & 2 deletions examples/response/alert_bulk_resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import sys
from cbapi.response.models import Alert
from cbapi.errors import ApiError
from cbapi.example_helpers import build_cli_parser, get_cb_response_object
import time

Expand All @@ -20,9 +19,10 @@ def main():
alert_query = alert_query.where(args.query)

alert_count = len(alert_query)
print("Resolving {0:d} alerts...".format(len(alert_query)))

if alert_count > 0:
print("Resolving {0:d} alerts...".format(len(alert_query)))

alert_query.change_status("Resolved")

print("Waiting for alert changes to take effect...")
Expand Down
93 changes: 78 additions & 15 deletions src/cbapi/response/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,24 +151,25 @@ def site(self):


class AlertQuery(Query):
def _format_query(self):
qt = (("cb.urlver", "1"), ("q", self._query))
return "&".join(("{0}={1}".format(k, urllib.parse.quote(v)) for k, v in qt))
def _bulk_update(self, payload):
# Using IDs for Alerts, since queries don't quite work as planned, and an "empty" query doesn't work.
alert_ids = [a["unique_id"] for a in self._search()]
payload["alert_ids"] = alert_ids

def set_ignored(self, ignored_flag=True):
search_query = self._format_query()
payload = {"updates": {"is_ignored": ignored_flag}, "query": search_query}
self._cb.post_object("/api/v1/alerts", payload)
return None

def set_ignored(self, ignored_flag=True):
payload = {"updates": {"is_ignored": ignored_flag, "requested_status": "False Positive"}}
return self._bulk_update(payload)

def assign(self, target):
search_query = self._format_query()
payload = {"query": search_query, "assigned_to": target}
self._cb.post_object("/api/v1/alerts", payload)
payload = {"assigned_to": target, "requested_status": "In Progress"}
return self._bulk_update(payload)

def change_status(self, new_status):
search_query = self._format_query()
payload = {"query": search_query, "requested_stats": new_status}
self._cb.post_object("/api/v1/alerts", payload)
payload = {"requested_status": new_status}
return self._bulk_update(payload)


class Alert(MutableBaseModel):
Expand Down Expand Up @@ -1968,13 +1969,75 @@ def children(self):
yield self._event_parser.parse_childproc(i, raw_childproc)
i += 1

@property
def all_events_segment(self):
"""
Returns a list of all events associated with this process segment, sorted by timestamp
:return: list of CbEvent objects
"""
segment_events = list(self.modloads) + list(self.netconns) + list(self.filemods) + \
list(self.children) + list(self.regmods) + list(self.crossprocs)
segment_events.sort()
return segment_events

@property
def all_events(self):
"""
Returns a list of all events associated with this process, sorted by timestamp
Returns a list of all events associated with this process across all segments, sorted by timestamp
:return: list of CbEvent objects
"""
return sorted(list(self.modloads) + list(self.netconns) + list(self.filemods) + \
list(self.children) + list(self.regmods) + list(self.crossprocs))

all_events = []

# first let's find the list of segments
proclist = self._cb.select(Process).where("process_id:{0}".format(self.id))[::]
proclist.sort(key=lambda x: x.segment)
for proc in proclist:
all_events += proc.all_events_segment

return all_events

@property
def depth(self):
"""
Returns the depth of this process from the "root" system process
:return: integer representing the depth of the process (0 is the root system process). To prevent infinite
recursion, a maximum depth of 500 processes is enforced.
"""

depth = 0
MAX_DEPTH = 500

proc = self
visited = []

while depth < MAX_DEPTH:
try:
proc = proc.parent
except ObjectNotFoundError:
break
else:
current_proc_id = proc.id
depth += 1

if current_proc_id in visited:
raise ApiError("Process cycle detected at depth {0}".format(depth))
else:
visited.append(current_proc_id)

return depth

@property
def threat_intel_hits(self):
try:
hits = self._cb.get_object("/api/v1/process/{0}/{1}/threat_intel_hits".format(self.id, self.segment))
return hits
except ServerError as e:
raise ApiError("Sharing IOCs not set up in Cb server. See {}/#/share for more information."
.format(self._cb.credentials.url))

@property
def tamper_events(self):
Expand Down

0 comments on commit 5a11ed3

Please sign in to comment.