Skip to content

Commit d5e30a6

Browse files
Merge pull request #986 from weslambert/velociraptor_update_responder
Update Velociraptor Responder
2 parents d982c6e + 7fccf97 commit d5e30a6

File tree

2 files changed

+28
-134
lines changed

2 files changed

+28
-134
lines changed
Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"name": "Velociraptor_Flow",
3-
"version": "0.1",
4-
"author": "Wes Lambert",
2+
"name": "Velociraptor",
3+
"version": "0.2",
4+
"author": "Wes Lambert, @therealwlambert",
55
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
66
"license": "AGPL-V3",
7-
"description": "Run Velociraptor flow",
7+
"description": "Run Velociraptor artifact collection",
88
"dataTypeList": ["thehive:case_artifact"],
99
"command": "Velociraptor/velociraptor_flow.py",
1010
"baseConfig": "Velociraptor",
@@ -26,25 +26,12 @@
2626
"defaultValue": ""
2727
},
2828
{
29-
"name": "upload_flow_results",
30-
"description": "Upload the results of a flow as an observable",
31-
"type": "boolean",
29+
"name": "query_max_duration",
30+
"description": "Max query duration (in seconds)",
31+
"type": "number",
3232
"multi": false,
33-
"required": true
34-
},
35-
{
36-
"name": "thehive_url",
37-
"description": "URL pointing to your TheHive installation, e.g. 'http://127.0.0.1:9000'",
38-
"type": "string",
39-
"multi": false,
40-
"required": true
41-
},
42-
{
43-
"name": "thehive_apikey",
44-
"description": "TheHive API key (used to add the downloaded file back to the alert/case)",
45-
"type": "string",
46-
"multi": false,
47-
"required": true
33+
"required": false,
34+
"defaultValue": "600"
4835
}
4936
]
5037
}

responders/Velociraptor/velociraptor_flow.py

Lines changed: 19 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ def __init__(self):
1919
self.configpath = self.get_param('config.velociraptor_client_config', None, "File path missing!")
2020
self.config = yaml.load(open(self.configpath).read(), Loader=yaml.FullLoader)
2121
self.artifact = self.get_param('config.velociraptor_artifact', None, 'Artifact missing!')
22-
self.upload_flow_results = self.get_param('config.upload_flow_results', None, 'Upload decision missing!')
22+
self.artifact_args = self.get_param('config.velociraptor_artifact_args', None)
2323
self.observable_type = self.get_param('data.dataType', None, "Data type is empty")
2424
self.observable = self.get_param('data.data', None, 'Data missing!')
25-
self.thehive_url = self.get_param('config.thehive_url', None, "TheHive URL missing!")
26-
self.thehive_apikey = self.get_param('config.thehive_apikey', None, "TheHive API key missing!")
27-
25+
self.max_wait = self.get_param('config.query_max_duration', 600)
26+
self.query = self.get_param('config.velociraptor_query', None)
27+
2828
def run(self):
2929
Responder.run(self)
3030
case_id = self.get_param('data._parent')
@@ -48,9 +48,10 @@ def run(self):
4848
self.report({'message': "Not a valid data type!" })
4949
return
5050

51-
# Send initial request
51+
# Query to get client ID
5252
client_request = api_pb2.VQLCollectorArgs(
53-
max_wait=1,
53+
# Setting static max_wait here, because it should not take as long as other queries
54+
max_wait=60,
5455
Query=[api_pb2.VQLRequest(
5556
Name="TheHive-ClientQuery",
5657
VQL=client_query,
@@ -64,121 +65,27 @@ def run(self):
6465
except:
6566
self.report({'message': 'Could not find a suitable client.'})
6667
pass
68+
69+
# Free-form query
70+
freeform_query = self.query
71+
72+
# Artifact query
73+
artifact_query = "LET collection <= collect_client(client_id='"+ client_id +"',artifacts=['" + self.artifact + "'], spec=dict()) LET collection_completed <= SELECT * FROM watch_monitoring(artifact='System.Flow.Completion') WHERE FlowId = collection.flow_id LIMIT 1 SELECT * FROM source(client_id=collection.request.client_id, flow_id=collection.flow_id, artifact=collection_completed.Flow.artifacts_with_results[0])"
6774

68-
# Define initial query
69-
init_query = "SELECT collect_client(client_id='"+ client_id +"',artifacts=['" + self.artifact + "']) FROM scope()"
75+
7076

71-
# Send initial request
7277
request = api_pb2.VQLCollectorArgs(
73-
max_wait=1,
78+
max_wait=self.max_wait,
7479
Query=[api_pb2.VQLRequest(
7580
Name="TheHive-Query",
76-
VQL=init_query,
81+
VQL=artifact_query,
7782
)])
7883

7984
for response in stub.Query(request):
8085
try:
81-
init_results = json.loads(response.Response)
82-
flow=list(init_results[0].values())[0]
83-
84-
flow_id = str(flow['flow_id'])
85-
# Define second query
86-
flow_query = "SELECT * from flows(client_id='" + str(flow['request']['client_id']) + "', flow_id='" + flow_id + "')"
87-
88-
state=0
89-
90-
# Check to see if the flow has completed
91-
while (state != 2):
92-
93-
followup_request = api_pb2.VQLCollectorArgs(
94-
max_wait=10,
95-
Query=[api_pb2.VQLRequest(
96-
Name="TheHive-QueryForFlow",
97-
VQL=flow_query,
98-
)])
99-
100-
for followup_response in stub.Query(followup_request):
101-
try:
102-
flow_results = json.loads(followup_response.Response)
103-
except:
104-
pass
105-
state = flow_results[0]['state']
106-
global artifact_results
107-
artifact_results = flow_results[0]['artifacts_with_results']
108-
self.report({'message': state })
109-
if state == 2:
110-
time.sleep(5)
111-
break
112-
113-
# Grab the source from the artifact
114-
source_results=[]
115-
for artifact in artifact_results:
116-
source_query="SELECT * from source(client_id='"+ str(flow['request']['client_id']) + "', flow_id='" + flow_id + "', artifact='" + artifact + "')"
117-
source_request = api_pb2.VQLCollectorArgs(
118-
max_wait=10,
119-
Query=[api_pb2.VQLRequest(
120-
Name="TheHive-SourceQuery",
121-
VQL=source_query,
122-
)])
123-
for source_response in stub.Query(source_request):
124-
try:
125-
source_result = json.loads(source_response.Response)
126-
source_results += source_result
127-
except:
128-
pass
129-
self.report({'message': source_results })
130-
131-
if self.upload_flow_results is True:
132-
# Create flow download
133-
vfs_query = "SELECT create_flow_download(client_id='"+ str(flow['request']['client_id']) + "', flow_id='" + str(flow['flow_id']) + "', wait='true') as VFSPath from scope()"
134-
vfs_request = api_pb2.VQLCollectorArgs(
135-
max_wait=10,
136-
Query=[api_pb2.VQLRequest(
137-
Name="TheHive-VFSQuery",
138-
VQL=vfs_query,
139-
)])
140-
for vfs_response in stub.Query(vfs_request):
141-
try:
142-
vfs_result = json.loads(vfs_response.Response)[0]['VFSPath']
143-
except:
144-
pass
145-
# "Artifact" plugin.
146-
offset = 0
147-
148-
file_request = api_pb2.VFSFileBuffer(
149-
vfs_path=vfs_result,
150-
length=10000,
151-
offset=offset,
152-
)
153-
154-
res = stub.VFSGetBuffer(file_request)
155-
156-
if len(res.data) == 0:
157-
break
158-
159-
160-
161-
f = open("/tmp/" + self.artifact + "_" + client_id + "_" + flow_id + "_" + case_id + ".zip",'wb')
162-
f.write(res.data)
163-
offset+=len(res.data)
164-
165-
#Upload file to TheHive
166-
api = TheHiveApi(self.thehive_url, self.thehive_apikey, cert=False)
167-
168-
description = "Velociraptor flow for artifact" + self.artifact + "for client " + client_id + " via flow " + flow_id + "."
169-
filepath = '/tmp/' + self.artifact + "_" + client_id + "_" + flow_id + "_" + case_id + ".zip"
170-
file_observable = CaseObservable(dataType='file',
171-
data=[filepath],
172-
tlp=self.get_param('data.tlp'),
173-
ioc=True,
174-
tags=['src:Velociraptor', client_id ],
175-
message=description
176-
)
177-
178-
response = api.create_case_observable(case_id, file_observable)
179-
if response.status_code != 201:
180-
self.error({'message': str(response.status_code) + " " + response.text})
181-
os.remove(filepath)
86+
query_results = json.loads(response.Response)
87+
#flow=list(init_results[0].values())[0]
88+
self.report({'message': query_results })
18289
except:
18390
pass
18491
def operations(self, raw):

0 commit comments

Comments
 (0)