Adds Velociraptor rows as timeline entries to DFIR-IRIS .
Links the assets and IOCs as specified in the parameters. Additionally, if the client does not yet exist in Iris, this artifact will leverage the IRIS.Sync.Asset artifact to add the asset to Iris first and link it in the event.
Tested with Dfir-Iris API v2.0.4 (IRIS v2.4.7)
timestamp
function.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()
LET ClientId <= '''C.daa3bab35a125058'''
LET FlowId <= '''F.CPTTPTRO63LF6'''
LET ArtifactName <= '''Windows.Timeline.MFT'''
-- This is the query that should return the events you want to add to Iris.
-- You might want to add a WHERE clause to filter out unwanted events or
-- select only specific fields. In this example we limit it to 10 records.
LET eventsToAdd = SELECT * FROM source(artifact=ArtifactName)
LIMIT 10
SELECT * FROM foreach(
row={
SELECT to_dict(item=_value) AS event,
serialize(format="json", item=_value) AS raw_event
FROM items(item={ SELECT * FROM eventsToAdd })
},
query={
SELECT *
FROM Artifact.Exchange.IRIS.Timeline.Add(
AdditionalAssetId="1,2,3",
AddToGraph=true,
AddToSummary=false,
IocId="8,9,10",
Category="pers",
clientId=ClientId,
Description=format(
format="Malicious file dropped to the system to establish persistence.\nFile path: %v\nActivity: %v",
args=[event.path, event.message]),
RawEvent=raw_event,
DisableSSLVerify=true,
FlowId=FlowId,
Timestamp=event.event_time,
Title="Persistence established via Autostart Location")
},
async=false)
ATTENTION: ALWAYS USE ASYNC=FALSE OTHERWISE ANY ASSETS THAT NEED TO BE CREATED MIGHT BE DUPLICATED!!!
name: IRIS.Timeline.Add
author: Stephan Mikiss @stephmikiss (SEC Defence @SEC Consult) | Updated 2024-08 - [10root Cyber Security] (https://10root.com)
description: |
Adds Velociraptor rows as timeline entries to [DFIR-IRIS](https://dfir-iris.org/).
Links the assets and IOCs as specified in the parameters. Additionally, if the client does not yet exist in Iris, this artifact will leverage the **IRIS.Sync.Asset** artifact to add the asset to Iris first and link it in the event.
*Tested with Dfir-Iris API v2.0.4 (IRIS v2.4.7)*
#### Notes:
- The following parameters are *mandatory*:
1. **Timestamp**: This specifies the name of the field in the source containing the event timestamp. For this artifact to parse it correctly the field should contain a parsed timestamp object. If you are using this artifact from a global notebook then the field is probably already parsed. If not then you should ensure that it is parsed in your source using the `timestamp` function.
2. **Title**: This specifies the name of the field in the source containing the event title which will be used on the Iris timeline.
#### Hints:
- 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()
```
- The true power of this artifact lies in the ability to quickly add many entries to DFIR-IRIS. You will *most likely use this artifact from within a notebook*.
- There is a basic mechanism established to stop duplicates from being added. An event is compared to existing entries based on asset name, flow id, timestamp and the description. You can add multiple events happening at the same time for the same asset originating from the same flow as long as the description varies, e.g. by including dynamic details of the activity that differentiates between the events at the same time like a process name.
#### Notebook usage example:
```VQL
LET ClientId <= '''C.daa3bab35a125058'''
LET FlowId <= '''F.CPTTPTRO63LF6'''
LET ArtifactName <= '''Windows.Timeline.MFT'''
-- This is the query that should return the events you want to add to Iris.
-- You might want to add a WHERE clause to filter out unwanted events or
-- select only specific fields. In this example we limit it to 10 records.
LET eventsToAdd = SELECT * FROM source(artifact=ArtifactName)
LIMIT 10
SELECT * FROM foreach(
row={
SELECT to_dict(item=_value) AS event,
serialize(format="json", item=_value) AS raw_event
FROM items(item={ SELECT * FROM eventsToAdd })
},
query={
SELECT *
FROM Artifact.Exchange.IRIS.Timeline.Add(
AdditionalAssetId="1,2,3",
AddToGraph=true,
AddToSummary=false,
IocId="8,9,10",
Category="pers",
clientId=ClientId,
Description=format(
format="Malicious file dropped to the system to establish persistence.\nFile path: %v\nActivity: %v",
args=[event.path, event.message]),
RawEvent=raw_event,
DisableSSLVerify=true,
FlowId=FlowId,
Timestamp=event.event_time,
Title="Persistence established via Autostart Location")
},
async=false)
```
**ATTENTION: ALWAYS USE ASYNC=FALSE OTHERWISE ANY ASSETS THAT NEED TO BE CREATED MIGHT BE DUPLICATED!!!**
# Can be CLIENT, CLIENT_EVENT, SERVER, SERVER_EVENT
type: SERVER
parameters:
- name: clientId
description: Client Id of the client that should be synced to DFIR-IRIS
- name: AdditionalAssetId
description: Comma seperated list of IRIS AssetIds of additional assets beside the client to link in this event.
- name: IocId
description: Comma seperated list of IRIS IocIds to link IOCs in this event.
- name: Timestamp
description: Timestamp of the event as a time.Time object. This can be a field in the source data containing a timestamp object.
- name: Title
description: Title of the event.
- name: FlowId
description: FlowId or HuntId of the event source. This is needed to allow detection of duplicates!
- name: Tags
description: List of comma seperated tags to be added to the event.
- name: Description
description: Description of the event. Very important to actually understand what this entry is all about :)
- name: AddToSummary
description: Add it to timeline summary?
type: bool
- name: AddToGraph
description: Add it to attack graph?
type: bool
- name: Category
description: "Category of the action, mostly MITRE Enterprise Tactics. Allowed options are abbreviations and their MITRE ID: tbd,legit,rem,ini,exec,pers,priv,def,creds,disc,lat,coll,c2,exfil,imp"
type: choices
choices:
- tbd
- legit
- rem
- ini
- exec
- pers
- priv
- def
- creds
- disc
- lat
- coll
- c2
- exfil
- imp
- name: Color
description: Specify the color for this event in Iris. Green by default for obvious reasons.
type: choices
choices:
- green
- white
- blue
- lightblue
- purple
- red
- orange
- name: RawEvent
description: Add the raw event, message or the entire row as additional information.
- 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 metadata_preparation = SELECT client_metadata(client_id=clientId) as metadata FROM scope() WHERE metadata.IRIS_AssetId
LET syncAsset = SELECT * FROM Artifact.Exchange.IRIS.Sync.Asset(clientId=clientId,IrisURL=IrisURL,IrisCaseId=IrisCaseId,IrisKey=IrisKey,IrisRootCA=IrisRootCA,DisableSSLVerify=DisableSSLVerify)
LET eventAsset1 = if(condition=metadata_preparation,then=array(a=metadata_preparation.metadata[0].IRIS_AssetId),else=if(condition=syncAsset.Result[0]="SUCCESS",then=array(a=metadata_preparation.metadata[0].IRIS_AssetId),else=[]))
LET eventAsset2 = if(condition=AdditionalAssetId,then=split(string=AdditionalAssetId,sep=",|;"),else=[])
LET eventCategory = if(condition=Category=~"^legit",then=2,
else= if(condition=Category=~"^rem",then=3,
else= if(condition=Category=~"^ini|^ta0001$",then=4,
else= if(condition=Category=~"^exec|^ta0002$",then=5,
else= if(condition=Category=~"^pers|^ta0003$",then=6,
else= if(condition=Category=~"^priv|^ta0004$",then=7,
else= if(condition=Category=~"^def|^ta0005$",then=8,
else= if(condition=Category=~"^cred|^ta0006$",then=9,
else= if(condition=Category=~"^disc|^ta0007$",then=10,
else= if(condition=Category=~"^lat|^ta0008$",then=11,
else= if(condition=Category=~"^coll|^ta0009$",then=12,
else= if(condition=Category=~"^c2|^com|^ta0011$",then=13,
else= if(condition=Category=~"^exf|^ta0010$",then=14,
else= if(condition=Category=~"^imp|^ta0040$",then=15,
else= 1))))))))))))))
LET eventColor = if(condition=Color =~ "^white",then="#fff",
else = if(condition=Color =~ "^blue",then="#1572E899",
else = if(condition=Color =~ "^purple",then="#6861CE99",
else = if(condition=Color =~ "^lightblue",then="#48ABF799",
else = if(condition=Color =~ "^red",then="#F2596199",
else = if(condition=Color =~ "^orange",then="#FFAD4699",
else = "#31CE3699"))))))
LET eventDate = format(format="%d-%02d-%02dT%02d:%02d:%02d.%03.f", args=[
Timestamp.Year, Timestamp.Month, Timestamp.Day,
Timestamp.Hour, Timestamp.Minute, Timestamp.Second,
Timestamp.Nanosecond / 1000000
])
LET eventProperties = serialize(
item=dict(
event_title=Title,
event_source=if(condition=FlowId,then=format(format="Velo: %v",args=[FlowId]),else="Velo"),
event_assets=if(condition=eventAsset1 OR eventAsset2,then=eventAsset1 + eventAsset2,else=[]),
event_iocs=if(condition=IocId,then=split(string=IocId,sep=",|;"),else=[]),
event_tags=if(condition=Tags,then=format(format="Velo,%v",args=[Tags]),else="Velo"),
event_category_id=eventCategory,
event_in_summary=AddToSummary,
event_in_graph=AddToGraph,
event_color=eventColor,
event_date=eventDate,
event_tz="+00:00",
event_content=Description,
event_raw=RawEvent
)
,format="json"
)
LET apiRequestIrisAddEvent =
SELECT *
FROM http_client(
data=eventProperties,
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/timeline/events/add?cid=%v", args=[IrisURL,IrisCaseId]))
LET resolveHostname = SELECT os_info.hostname as hostname from clients(client_id=clientId)
LET filterParams = dict(cid=IrisCaseId,q=format(format='{"asset":["%v"],"source":["%v"],"startDate":["%v"],"endDate":["%v"]}',args=[resolveHostname.hostname[0],FlowId,eventDate,eventDate]))
LET checkExistingEntries =
SELECT * FROM flatten(query={ SELECT parse_json(data=Content).data.timeline as Timeline
FROM http_client(
headers=dict(`Content-Type`="application/json", `Authorization`=format(format="Bearer %v", args=[IrisKey])),
method="GET",
root_ca=IrisRootCA,
skip_verify=DisableSSLVerify,
params=filterParams,
url=format(format="%v/case/timeline/advanced-filter", args=[IrisURL])) GROUP BY Timeline })
WHERE base64encode(string=Timeline.event_content) = base64encode(string=Description)
SELECT * FROM if(condition=checkExistingEntries,
then={SELECT "Already Added -> Skipping the event. Check for existing entries manually!" as Action FROM scope()},
else={SELECT "Needs to be added" as Action, if(condition= Response=200,then="SUCCESS",else="ERROR") as Result,
parse_json(data=eventProperties) AS _RequestData, parse_json(data=Content).data as _ResponseData
FROM apiRequestIrisAddEvent})