diff --git a/responders/Velociraptor/requirements.txt b/responders/Velociraptor/requirements.txt new file mode 100755 index 000000000..1f00236fc --- /dev/null +++ b/responders/Velociraptor/requirements.txt @@ -0,0 +1,4 @@ +cortexutils +cryptography +grpcio-tools +pyvelociraptor diff --git a/responders/Velociraptor/velociraptor_flow.json b/responders/Velociraptor/velociraptor_flow.json new file mode 100755 index 000000000..66923a6fd --- /dev/null +++ b/responders/Velociraptor/velociraptor_flow.json @@ -0,0 +1,30 @@ +{ + "name": "Velociraptor_Flow", + "version": "0.1", + "author": "Wes Lambert", + "url": "https://github.com/TheHive-Project/Cortex-Analyzers", + "license": "AGPL-V3", + "description": "Run Velociraptor flow", + "dataTypeList": ["thehive:case_artifact"], + "command": "Velociraptor/velociraptor_flow.py", + "baseConfig": "Velociraptor", + "configurationItems": [ + { + "name": "velociraptor_client_config", + "description": "Path to API client config file", + "type": "string", + "multi": false, + "required": true, + "defaultValue": "" + }, + { + "name": "velociraptor_artifact", + "description": "Artifact to collect", + "type": "string", + "multi": false, + "required": true, + "defaultValue": "" + } + ] +} + diff --git a/responders/Velociraptor/velociraptor_flow.py b/responders/Velociraptor/velociraptor_flow.py new file mode 100755 index 000000000..29acc1a87 --- /dev/null +++ b/responders/Velociraptor/velociraptor_flow.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +from cortexutils.responder import Responder +import json +import grpc +import re +import time +import yaml +import pyvelociraptor +from pyvelociraptor import api_pb2 +from pyvelociraptor import api_pb2_grpc + +class Velociraptor(Responder): + def __init__(self): + Responder.__init__(self) + self.configpath = self.get_param('config.velociraptor_client_config', None, "File path missing!") + self.config = yaml.load(open(self.configpath).read(), Loader=yaml.FullLoader) + self.artifact = self.get_param('config.velociraptor_artifact', None, 'Artifact missing!') + self.observable_type = self.get_param('data.dataType', None, "Data type is empty") + self.observable = self.get_param('data.data', None, 'Data missing!') + + def run(self): + Responder.run(self) + creds = grpc.ssl_channel_credentials( + root_certificates=self.config["ca_certificate"].encode("utf8"), + private_key=self.config["client_private_key"].encode("utf8"), + certificate_chain=self.config["client_cert"].encode("utf8") + ) + + options = (('grpc.ssl_target_name_override', "VelociraptorServer",),) + + with grpc.secure_channel(self.config["api_connection_string"], + creds, options) as channel: + stub = api_pb2_grpc.APIStub(channel) + + if self.observable_type == "ip": + client_query = "select client_id from clients() where last_ip =~ '"+ self.observable + "'" + elif re.search(r'fqdn|other', self.observable_type): + client_query = "select client_id from clients(search='host:" + self.observable + "')" + else: + self.report({'message': "Not a valid data type!" }) + return + + # Send initial request + client_request = api_pb2.VQLCollectorArgs( + max_wait=1, + Query=[api_pb2.VQLRequest( + Name="TheHive-ClientQuery", + VQL=client_query, + )]) + + for client_response in stub.Query(client_request): + try: + client_results = json.loads(client_response.Response) + global client_id + client_id = client_results[0]['client_id'] + except: + self.report({'message': 'Could not find a suitable client.'}) + pass + + # Define initial query + init_query = "SELECT collect_client(client_id='"+ client_id +"',artifacts=['" + self.artifact + "']) FROM scope()" + + # Send initial request + request = api_pb2.VQLCollectorArgs( + max_wait=1, + Query=[api_pb2.VQLRequest( + Name="TheHive-Query", + VQL=init_query, + )]) + + for response in stub.Query(request): + try: + init_results = json.loads(response.Response) + flow=list(init_results[0].values())[0] + self.report({'message': init_results }) + + # Define second query + flow_query = "SELECT * from flows(client_id='" + str(flow['request']['client_id']) + "', flow_id='" + str(flow['flow_id']) + "')" + + state=0 + + # Check to see if the flow has completed + while (state == 0): + + followup_request = api_pb2.VQLCollectorArgs( + max_wait=1, + Query=[api_pb2.VQLRequest( + Name="TheHive-QueryForFlow", + VQL=flow_query, + )]) + + for followup_response in stub.Query(followup_request): + try: + flow_results = json.loads(followup_response.Response) + except: + pass + state = flow_results[0]['state'] + if state == 1: + time.sleep(5) + break + + # Grab the source from the artifact + source_query="SELECT * from source(client_id='"+ str(flow['request']['client_id']) + "', flow_id='" + str(flow['flow_id']) + "', artifact='" + self.artifact + "')" + source_request = api_pb2.VQLCollectorArgs( + max_wait=1, + Query=[api_pb2.VQLRequest( + Name="TheHive-SourceQuery", + VQL=source_query, + )]) + source_results=[] + for source_response in stub.Query(source_request): + try: + source_result = json.loads(source_response.Response) + source_results += source_result + self.report({'message': source_results }) + except: + pass + except: + pass + + def operations(self, raw): + global client_id + return [self.build_operation('AddTagToArtifact', tag=client_id)] + return [self.build_operation('AddTagToCase', tag=client_id)] + +if __name__ == '__main__': + Velociraptor().run()