IRIS.Sync.Asset
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)
Hints:
- If it fails to add the client to IRIS, it will assign the
IRIS-ERRORlabel to it. A successful run afterwards will remove it. - It is recommended to add the parameters with ‘Iris’ prefix to the Server Metadata 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:
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
}
)