Merge branch 'weslambert-feature/Velociraptor-Responder' into develop

This commit is contained in:
Jérôme Leonard 2020-08-12 09:46:18 +00:00
commit bfc502902e
4 changed files with 170 additions and 0 deletions

View File

@ -0,0 +1,9 @@
### Velociraptor
This responder can be used to run a flow for a Velociraptor artifact. This could include gathering data, or performing initial response, as the artifact (or artifact "pack") could encompass any number of actions. The responder can be run on an observable type of `ip`, `fqdn`, or `other`, and will look for a matching client via the Velociraptor server. If a client match is found for the last seen IP, or the hostname, the responder will kick off the flow, the results will be returned, and the client ID will be added as a tag to the case and the observable.
#### Requirements
The following options are required in the Velociraptor Responder configuration:
- `velociraptor_client_config`: The path to the Velociraptor API client config.
(See the following for generating an API client config: https://www.velocidex.com/docs/user-interface/api/, and ensure the appropriate ACLs are granted to the API user).
- `velociraptor_artifact`: The name artifact you which to collect (as you would see it in the Velociraptor GUI)

View File

@ -0,0 +1,4 @@
cortexutils
cryptography
grpcio-tools
pyvelociraptor

View File

@ -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": ""
}
]
}

View File

@ -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()