Synchronizes client information from Velociraptor to DFIR-IRIS .
Parses available information from clients such as network interfaces, IP addresses, asset type and applied labels.
Once it has been added, the asset ID from DFIR-IRIS will be added as client metadata and IRIS
will be added as label.
If this artifact is applied on a client that has the asset ID set in its metadata, it won’t be readded but rather updated: Labels and the compromised status will by synchronized.
Tested with Dfir-Iris API v2.0.4 (IRIS v2.4.7)
IRIS-ERROR
label to it. A successful run afterwards will remove it.SELECT server_set_metadata(IrisURL="https://dfir-iris.local:4433",
IrisKey="This-is-an_API_KEY",
IrisCaseId="1",
IrisRootCA='''-----BEGIN CERTIFICATE-----
<...>
-----END CERTIFICATE-----'''),server_metadata() FROM scope()
to add just a few systems and have the results of the operation as JSON:
SELECT client_id,{SELECT * FROM Artifact.Exchange.IRIS.Sync.Asset(clientId=client_id,isCompromised="Y")} FROM clients(search="label:compromised")
Example to add many systems in a performant way and have the results in well-structured columns.
SELECT * FROM foreach(row={SELECT * FROM clients(search="label:suspicious")},query={SELECT * FROM Artifact.Exchange.IRIS.Sync.Asset(clientId=client_id,isCompromised="UNK")},async=true)
ATTENTION: ALWAYS USE ASYNC=FALSE IF CLIENTS ARE PRESENT IN THE TABLE MULTIPLE TIMES! OTHERWISE THESE ASSETS MIGHT BE DUPLICATED IN IRIS!!!
name: IRIS.Sync.Asset
author: Stephan Mikiss @stephmikiss (SEC Defence @SEC Consult) | Updated 2024-08 - [10root Cyber Security] (https://10root.com)
description: |
Synchronizes client information from Velociraptor to [DFIR-IRIS](https://dfir-iris.org/).
Parses available information from clients such as network interfaces, IP addresses, asset type and applied labels.
Once it has been added, the asset ID from DFIR-IRIS will be added as client metadata and `IRIS` will be added as label.
If this artifact is applied on a client that has the asset ID set in its metadata, it won't be readded but rather
updated: Labels and the compromised status will by synchronized.
*Tested with Dfir-Iris API v2.0.4 (IRIS v2.4.7)*
#### Hints:
- If it fails to add the client to IRIS, it will assign the `IRIS-ERROR` label to it. A successful run afterwards will remove it.
- It is **recommended** to add the parameters with 'Iris' prefix to the <a href="#/host/server">Server Metadata</a> to ease the usage of the artifact. The metadata can alternatively be set from a notebook using VQL similar to this example:
```
SELECT server_set_metadata(IrisURL="https://dfir-iris.local:4433",
IrisKey="This-is-an_API_KEY",
IrisCaseId="1",
IrisRootCA='''-----BEGIN CERTIFICATE-----
<...>
-----END CERTIFICATE-----'''),server_metadata() FROM scope()
```
- You can define the compromise status of a system when creating and when updating the information. **However, if an asset is categorized as *compromised*, you cannot change the status using this artifact.** This is a safety measure to mitigate a potential high impact error. Beside that, you can freely change the status between *No*, *Unknown* and *To be determined*.
- The true power of this artifact lies in the ability to quickly add many clients to DFIR-IRIS. As it is usually not needed to add all clients that are enrolled in Velociraptor to IRIS but rather an excerpt of important, suspicious, or compromised systems, you will *most likely use this artifact from within a notebook*.
#### Example:
to add just a few systems and have the results of the operation as JSON:
```VQL
SELECT client_id,{SELECT * FROM Artifact.Exchange.IRIS.Sync.Asset(clientId=client_id,isCompromised="Y")} FROM clients(search="label:compromised")
```
**Example** to add many systems in a performant way and have the results in well-structured columns.
```VQL
SELECT * FROM foreach(row={SELECT * FROM clients(search="label:suspicious")},query={SELECT * FROM Artifact.Exchange.IRIS.Sync.Asset(clientId=client_id,isCompromised="UNK")},async=true)
```
**ATTENTION: ALWAYS USE ASYNC=FALSE IF CLIENTS ARE PRESENT IN THE TABLE MULTIPLE TIMES! OTHERWISE THESE ASSETS MIGHT BE DUPLICATED IN IRIS!!!**
type: SERVER
parameters:
- name: clientId
description: Client Id of the client that should be synced to DFIR-IRIS
- name: isCompromised
default: TBD
description: Specify whether this asset should be marked as compromised in IRIS using "Y" (compromised), "N" (not compromised), "TBD" (to be determined), or "UNK" (unknown).
type: choices
choices:
- Y
- N
- TBD
- UNK
- name: labelIgnoreListRegex
default: "IRIS|^Workstation$|^Server$|^Domain Controller$|^Linux$"
description: Labels that should be ignored and not added to IRIS
- name: IrisURL
type: server_metadata
description: URL of DFIR-IRIS. Preferred method is to use the server metadata
- name: IrisKey
type: server_metadata
description: API Key of DFIR-IRIS. Preferred method is to use the server metadata
- name: IrisCaseId
type: server_metadata
description: Case ID of the current case. Preferred method is to use the server metadata
- name: IrisRootCA
type: server_metadata
description: RootCA of DFIR-IRIS for self-signed or internal certificates of DFIR-IRIS. Preferred over completely skipping SSL verification.
- name: DisableSSLVerify
type: bool
default: false
description: Disable TLS verification for HTTPS request to DFIR-IRIS.
sources:
- query: |
LET AssetType = SELECT * FROM switch(
a = {SELECT {
SELECT if(condition = `Computer Info`.DomainRole =~ "Workstation",
then = 9,
else = if(condition= `Computer Info`.DomainRole =~ "Server",
then = 10,
else = if(condition= `Computer Info`.DomainRole =~ "Domain Controller",
then = 11
)))
FROM flow_results(client_id=clientId,flow_id=last_interrogate_flow_id,artifact="Generic.Client.Info/WindowsInfo")
} as AssetTypeId
FROM clients(client_id=clientId)
WHERE os_info.system =~ "windows"
},
b = {SELECT 3 as AssetTypeId
FROM clients(client_id=clientId)
WHERE os_info.system =~ "linux"})
LET resolveIPs =
SELECT
{SELECT `Network Info` FROM flow_results(client_id=client_id,flow_id=last_interrogate_flow_id,artifact="Generic.Client.Info/WindowsInfo")} as NetworkInfo
FROM clients(client_id=clientId)
LET primaryIP =
SELECT parse_string_with_regex(string=if(condition=NetworkInfo[0],then=NetworkInfo[0].IPAddresses,else=NetworkInfo.IPAddresses),regex="(?P<IP>[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})").IP as PrimaryIPv4Address
FROM resolveIPs
LET networkInterfaces =
SELECT parse_string_with_regex(string=NetworkInfo.Caption,regex="^\\[.*\\] (?P<IF>.*)").IF as NetworkInterface,
NetworkInfo.IPAddresses as IPAddresses, NetworkInfo.MACAddress as MACAddress
FROM flatten(query=resolveIPs)
LET NetInfo =
SELECT format(format="%v (%v): %v",args=[NetworkInterface,MACAddress,IPAddresses]) AS fmt FROM networkInterfaces
LET networkInterfacesDescription =
SELECT join(array=NetInfo.fmt, sep="\n") as NetworkInfo
FROM NetInfo
LET labelToTags =
join(array=filter(list=labels, condition="x=>NOT x =~ labelIgnoreListRegex"),sep=",")
LET metadata_preparation =
SELECT client_metadata(client_id=clientId) as metadata FROM scope() WHERE metadata.IRIS_AssetId
LET assetId =
SELECT metadata_preparation.metadata[0].IRIS_AssetId as assetId FROM scope()
LET addMetadata(assetIdValue) =
SELECT client_set_metadata(client_id=clientId,metadata=client_metadata(client_id=clientId) + dict(IRIS_AssetId=assetIdValue)), client_metadata(client_id=clientId)
FROM scope()
LET assetProperties =
serialize(item=dict(
asset_name=format(format="%v",args=[os_info.hostname]),
asset_type_id=AssetType.AssetTypeId[0],
analysis_status_id=1,
asset_compromise_status_id=if(condition=isCompromised=~"^Y$",then=1,else=if(condition=isCompromised=~"^N$",then=2,else=if(condition=isCompromised=~"^UNK$",then=3,else=0))),
asset_domain=format(format="%v",args=[join(array=slice(list=split(sep_string=".",string=os_info.fqdn),start=1,end=-1),sep=".")]),
asset_ip=format(format="%v",args=[primaryIP.PrimaryIPv4Address]),
asset_tags=if(condition=labelToTags,then=format(format="Velo,%v",args=[labelToTags]),else="Velo"),
asset_description=format(format="Velo ClientId: %v\nVelo Agent First seen: %v\nAsset added to IRIS by Velo: %v\nNetwork Info:\n%v", args=[client_id,timestamp(epoch=first_seen_at),timestamp(epoch=now()),networkInterfacesDescription.NetworkInfo[0]])
), format="json" )
LET apiRequestIrisAdd =
SELECT *, if(condition=parse_json(data=Content).data.asset_id,
then={ SELECT addMetadata(assetIdValue=format(format="%v",
args=parse_json(data=Content).data.asset_id)),
label(client_id=clientId,op="set",labels="IRIS"),
label(client_id=clientId,op="remove",labels="IRIS-ERROR")
FROM scope()},
else={ SELECT label(client_id=clientId,op="set",labels="IRIS-ERROR")
FROM scope()}) as applyLabels
FROM http_client(
data=assetProperties,
headers=dict(
`Content-Type`="application/json", `Authorization`=format(format="Bearer %v",
args=[IrisKey])),
skip_verify=DisableSSLVerify,
root_ca=IrisRootCA,
method="POST",
url=format(format="%v/case/assets/add?cid=%v", args=[IrisURL,IrisCaseId]))
LET apiRequestIrisGet(assetId) =
SELECT parse_json(data=Content),
parse_json(data=Content).data.asset_name as asset_name,
parse_json(data=Content).data.asset_type_id as asset_type_id,
parse_json(data=Content).data.analysis_status_id as analysis_status_id,
parse_json(data=Content).data.asset_tags as asset_tags,
parse_json(data=Content).data.linked_ioc as linked_ioc,
parse_json(data=Content).data.custom_attributes as custom_attributes,
parse_json(data=Content).data.asset_compromise_status_id as asset_compromise_status_id
FROM http_client(
headers=dict(
`Content-Type`="application/json", `Authorization`=format(format="Bearer %v",
args=[IrisKey])),
skip_verify=DisableSSLVerify,
root_ca=IrisRootCA,
method="GET",
url=format(format="%v/case/assets/%v?cid=%v", args=[IrisURL,assetId,IrisCaseId]))
LET assetPropertiesUpdate = serialize(
item=dict(
asset_name=currentAsset.asset_name[0],
asset_type_id=currentAsset.asset_type_id[0],
analysis_status_id=currentAsset.analysis_status_id[0],
asset_compromise_status_id=if(
condition=isCompromised =~ "^Y$",
then=1,
else=if(
condition=currentAsset.asset_compromise_status_id[0] = 1,
then=1,
else=if(
condition=isCompromised =~ "^N$",
then=2,
else=if(
condition=isCompromised =~ "^UNK$",
then=3,
else=if(
condition=isCompromised =~ "^TBD$",
then=0,
else=currentAsset.asset_compromise_status_id[0]))))),
asset_tags=if(
condition=labelToTags,
then=format(format="Velo,%v", args=[labelToTags]),
else="Velo")),
format="json")
LET apiRequestIrisUpdate(currentAsset,assetId) =
SELECT *,
if(condition= Response=200,
then={ SELECT addMetadata(assetIdValue=format(format="%v",args=parse_json(data=Content).data.asset_id)),
label(client_id=clientId,op="set",labels="IRIS"),
label(client_id=clientId,op="remove",labels="IRIS-ERROR")
FROM scope()},
else={ SELECT label(client_id=clientId,op="set",labels="IRIS-ERROR") FROM scope()}) as applyLabels
FROM http_client(data=assetPropertiesUpdate,
headers=dict(
`Content-Type`="application/json", `Authorization`=format(format="Bearer %v",
args=[IrisKey])),
skip_verify=DisableSSLVerify,
root_ca=IrisRootCA,
method="POST",
url=format(format="%v/case/assets/update/%v?cid=%v",
args=[IrisURL,assetId,IrisCaseId]))
LET addAsset =
SELECT { SELECT * FROM apiRequestIrisAdd } as apiRequestIrisAdd FROM clients(client_id=clientId)
LET updateAsset =
SELECT { SELECT * FROM apiRequestIrisUpdate(currentAsset=apiRequestIrisGet(assetId=client_metadata(client_id=clientId).IRIS_AssetId),assetId=client_metadata(client_id=clientId).IRIS_AssetId) } as apiRequestIrisUpdate
FROM clients(client_id=clientId)
SELECT * FROM if(condition= metadata_preparation,
then={ SELECT
"Already Added -> Update labels and compromise status in IRIS" AS Action,
if(
condition=apiRequestIrisUpdate.Response = 200,
then="SUCCESS",
else="ERROR") AS Result,
parse_json(
data=apiRequestIrisUpdate.Content).data AS AssetProperties,
apiRequestIrisUpdate.applyLabels[0].`addMetadata(assetIdValue=format(format="%v", args=parse_json(data=Content).data.asset_id))`[0].`client_metadata(client_id=clientId)`.IRIS_AssetId AS IRIS_AssetId,
apiRequestIrisUpdate AS _rawEvent
FROM updateAsset
},
else={ SELECT
"Needs to be added" AS Action,
if(
condition=apiRequestIrisAdd.Response = 200,
then="SUCCESS",
else="ERROR") AS Result,
parse_json(
data=apiRequestIrisAdd.Content).data AS AssetProperties,
apiRequestIrisAdd.applyLabels[0].`addMetadata(assetIdValue=format(format="%v", args=parse_json(data=Content).data.asset_id))`[0].`client_metadata(client_id=clientId)`.IRIS_AssetId AS IRIS_AssetId,
apiRequestIrisAdd AS _rawEvent
FROM addAsset
}
)