detect_anomalous_new_entity_fl()
Gilt für: ✅Microsoft Fabric✅Azure Data Explorer✅Azure Monitor✅Microsoft Sentinel
Erkennen der Darstellung von anomalielen neuen Entitäten in zeitstempelten Daten.
Bei der Funktion detect_anomalous_new_entity_fl()
handelt es sich um eine UDF (benutzerdefinierte Funktion), die das Erscheinungsbild anomales neuer Entitäten ( z. B. IP-Adressen oder Benutzer ) in Zeitstempeldaten wie Datenverkehrsprotokollen erkennt. Im Kontext der Cybersicherheit können solche Ereignisse verdächtig sein und auf einen potenziellen Angriff oder eine Kompromittierung hinweisen.
Das Anomaliemodell basiert auf einer Poisson-Verteilung, die die Anzahl der neuen Entitäten darstellt, die pro Zeitcontainer (z. B. Tag) für jeden Bereich angezeigt werden. Der Poisson-Verteilungsparameter wird basierend auf der Häufigkeit des Auftretens neuer Entitäten in der Ausbildungsphase geschätzt, wobei der hinzugefügte Verfallsfaktor die Tatsache widerspiegelt, dass die jüngsten Erscheinungen wichtiger sind als alte. Daher berechnen wir die Wahrscheinlichkeit, dass eine neue Entität im definierten Erkennungszeitraum pro Bereich auftritt , z. B. ein Abonnement oder ein Konto. Die Modellausgabe wird durch mehrere optionale Parameter gesteuert, z. B. minimaler Schwellenwert für Anomalien, Verfallsratesparameter und andere.
Die direkte Ausgabe des Modells ist eine Anomaliebewertung basierend auf der Umkehrung der geschätzten Wahrscheinlichkeit, auf eine neue Entität zu stoßen. Die Punktzahl ist monoton im Bereich von [0, 1], wobei 1 etwas Anomalie darstellt. Zusätzlich zur Anomaliebewertung gibt es ein binäres Flag für erkannte Anomalien (gesteuert durch einen minimalen Schwellenwertparameter) und andere erläuternde Felder.
Syntax
detect_anomalous_new_entity_fl(
entityColumnName, scopeColumnName, timeColumnName, startTraining, startDetection, endDetection, [maxEntitiesThresh], [minTrainingDaysThresh], [decayParam], [anomalyScoreThresh] )
Erfahren Sie mehr über Syntaxkonventionen.
Parameter
Name | Type | Erforderlich | Beschreibung |
---|---|---|---|
entityColumnName | string |
✔️ | Der Name der Eingabetabellenspalte, die die Namen oder IDs der Entitäten enthält, für die ein Anomaliemodell berechnet wird. |
scopeColumnName | string |
✔️ | Der Name der Eingabetabellenspalte, die die Partition oder den Bereich enthält, sodass für jeden Bereich ein anderes Anomaliemodell erstellt wird. |
timeColumnName | string |
✔️ | Der Name der Eingabetabellenspalte, die die Zeitstempel enthält, die zum Definieren der Schulungs- und Erkennungszeiträume verwendet werden. |
startTraining | datetime |
✔️ | Der Beginn des Schulungszeitraums für das Anomaliemodell. Sein Ende wird durch den Anfang des Erkennungszeitraums definiert. |
startDetection | datetime |
✔️ | Der Anfang des Erkennungszeitraums für Anomalieerkennung. |
endDetection | datetime |
✔️ | Das Ende des Erkennungszeitraums für Anomalieerkennung. |
maxEntitiesThresh | int |
Die maximale Anzahl vorhandener Entitäten im Bereich zum Berechnen von Anomalien. Wenn die Anzahl der Entitäten über dem Schwellenwert liegt, wird der Bereich als zu laut betrachtet, und Anomalien werden nicht berechnet. Der Standardwert lautet 60. | |
minTrainingDaysThresh | int |
Die mindeste Anzahl von Tagen im Schulungszeitraum, für die ein Bereich zum Berechnen von Anomalien vorhanden ist. Wenn er unter dem Schwellenwert liegt, wird der Bereich als zu neu und unbekannt betrachtet, sodass Anomalien nicht berechnet werden. Der Standardwert ist 14. | |
decayParam | real |
Der Verfallsratesparameter für das Anomaliemodell, eine Zahl im Bereich (0,1). Niedrigere Werte bedeuten einen schnelleren Verfall, sodass späteren Erscheinungen im Trainingszeitraum mehr Bedeutung eingeräumt wird. Ein Wert von 1 bedeutet keinen Verfall, sodass ein einfacher Mittelwert für poisson-Verteilungsparameterschätzung verwendet wird. Der Standardwert ist 0,95. | |
AnomalyScoreThresh | real |
Der Mindestwert der Anomaliebewertung, für die eine Anomalie erkannt wird, eine Zahl im Bereich [0, 1]. Höhere Werte bedeuten, dass nur signifikantere Fälle als anomalien eingestuft werden, sodass weniger Anomalien erkannt werden (höhere Genauigkeit, niedrigerer Rückruf). Der Standardwert ist 0,9. |
Funktionsdefinition
Sie können die Funktion definieren, indem Sie den Code entweder als abfragedefinierte Funktion einbetten oder wie folgt als gespeicherte Funktion in Ihrer Datenbank erstellen:
Definieren Sie die Funktion mithilfe der folgenden Let-Anweisung. Es sind keine Berechtigungen erforderlich.
Wichtig
Eine Let-Anweisung kann nicht alleine ausgeführt werden. Auf sie muss eine tabellarische Ausdrucksanweisung folgen. Informationen zum Ausführen eines funktionierenden Beispiels detect_anomalous_new_entity_fl()
finden Sie unter Beispiel.
let detect_anomalous_new_entity_fl = (T:(*), entityColumnName:string, scopeColumnName:string
, timeColumnName:string, startTraining:datetime, startDetection:datetime, endDetection:datetime
, maxEntitiesThresh:int = 60, minTrainingDaysThresh:int = 14, decayParam:real = 0.95, anomalyScoreThresh:real = 0.9)
{
//pre-process the input data by adding standard column names and dividing to datasets
let timePeriodBinSize = 'day'; // we assume a reasonable bin for time is day, so the probability model is built per that bin size
let processedData = (
T
| extend scope = column_ifexists(scopeColumnName, '')
| extend entity = column_ifexists(entityColumnName, '')
| extend sliceTime = todatetime(column_ifexists(timeColumnName, ''))
| where isnotempty(scope) and isnotempty(entity) and isnotempty(sliceTime)
| extend dataSet = case((sliceTime >= startTraining and sliceTime < startDetection), 'trainSet'
, sliceTime >= startDetection and sliceTime <= endDetection, 'detectSet'
, 'other')
| where dataSet in ('trainSet', 'detectSet')
);
// summarize the data by scope and entity. this will be used to create a distribution of entity appearances based on first seen data
let entityData = (
processedData
| summarize countRowsEntity = count(), firstSeenEntity = min(sliceTime), lastSeenEntity = max(sliceTime), firstSeenSet = arg_min(sliceTime, dataSet)
by scope, entity
| extend firstSeenSet = dataSet
| project-away dataSet
);
// aggregate entity data per scope and get the number of entities appearing over time
let aggregatedCandidateScopeData = (
entityData
| summarize countRowsScope = sum(countRowsEntity), countEntitiesScope = dcount(entity), countEntitiesScopeInTrain = dcountif(entity, firstSeenSet == 'trainSet')
, firstSeenScope = min(firstSeenEntity), lastSeenScope = max(lastSeenEntity), hasNewEntities = iff(dcountif(entity,firstSeenSet == 'detectSet') > 0, 1, 0)
by scope
| extend slicesInTrainingScope = datetime_diff(timePeriodBinSize, startDetection, firstSeenScope)
| where countEntitiesScopeInTrain <= maxEntitiesThresh and slicesInTrainingScope >= minTrainingDaysThresh and lastSeenScope >= startDetection and hasNewEntities == 1
);
let modelData = (
entityData
| join kind = inner (aggregatedCandidateScopeData) on scope
| where firstSeenSet == 'trainSet'
| summarize countAddedEntities = dcount(entity), firstSeenScope = min(firstSeenScope), slicesInTrainingScope = max(slicesInTrainingScope), countEntitiesScope = max(countEntitiesScope)
by scope, firstSeenSet, firstSeenEntity
| extend diffInDays = datetime_diff(timePeriodBinSize, startDetection, firstSeenEntity)
// adding exponentially decaying weights to counts
| extend decayingWeight = pow(base = decayParam, exponent = diffInDays)
| extend decayingValue = countAddedEntities * decayingWeight
| summarize newEntityProbability = round(1 - exp(-1.0 * sum(decayingValue)/max(diffInDays)), 4)
, countKnownEntities = sum(countAddedEntities), lastNewEntityTimestamp = max(firstSeenEntity), slicesOnScope = max(slicesInTrainingScope)///for explainability
by scope, firstSeenSet
// anomaly score is based on probability to get no new entities, calculated using Poisson distribution (P(X=0) = exp(-avg)) with added decay on average
| extend newEntityAnomalyScore = round(1 - newEntityProbability, 4)
| extend isAnomalousNewEntity = iff(newEntityAnomalyScore >= anomalyScoreThresh, 1, 0)
);
let resultsData = (
processedData
| where dataSet == 'detectSet'
| join kind = inner (modelData) on scope
| project-away scope1
| where isAnomalousNewEntity == 1
| summarize arg_min(sliceTime, *) by scope, entity
| extend anomalyType = strcat('newEntity_', entityColumnName), anomalyExplainability = strcat('The ', entityColumnName, ' ', entity, ' wasn\'t seen on ', scopeColumnName, ' ', scope, ' during the last ', slicesOnScope, ' ', timePeriodBinSize, 's. Previously, ', countKnownEntities
, ' entities were seen, the last one of them appearing at ', format_datetime(lastNewEntityTimestamp, 'yyyy-MM-dd HH:mm'), '.')
| join kind = leftouter (entityData | where firstSeenSet == 'trainSet' | extend entityFirstSeens = strcat(entity, ' : ', format_datetime(firstSeenEntity, 'yyyy-MM-dd HH:mm')) | sort by scope, firstSeenEntity asc | summarize anomalyState = make_list(entityFirstSeens) by scope) on scope
| project-away scope1
);
resultsData
};
// Write your query to use the function here.
Beispiel
Im folgenden Beispiel wird der Aufrufoperator verwendet, um die Funktion auszuführen.
Um eine abfragedefinierte Funktion zu verwenden, rufen Sie sie nach der definition der eingebetteten Funktion auf.
let detect_anomalous_new_entity_fl = (T:(*), entityColumnName:string, scopeColumnName:string
, timeColumnName:string, startTraining:datetime, startDetection:datetime, endDetection:datetime
, maxEntitiesThresh:int = 60, minTrainingDaysThresh:int = 14, decayParam:real = 0.95, anomalyScoreThresh:real = 0.9)
{
//pre-process the input data by adding standard column names and dividing to datasets
let timePeriodBinSize = 'day'; // we assume a reasonable bin for time is day, so the probability model is built per that bin size
let processedData = (
T
| extend scope = column_ifexists(scopeColumnName, '')
| extend entity = column_ifexists(entityColumnName, '')
| extend sliceTime = todatetime(column_ifexists(timeColumnName, ''))
| where isnotempty(scope) and isnotempty(entity) and isnotempty(sliceTime)
| extend dataSet = case((sliceTime >= startTraining and sliceTime < startDetection), 'trainSet'
, sliceTime >= startDetection and sliceTime <= endDetection, 'detectSet'
, 'other')
| where dataSet in ('trainSet', 'detectSet')
);
// summarize the data by scope and entity. this will be used to create a distribution of entity appearances based on first seen data
let entityData = (
processedData
| summarize countRowsEntity = count(), firstSeenEntity = min(sliceTime), lastSeenEntity = max(sliceTime), firstSeenSet = arg_min(sliceTime, dataSet)
by scope, entity
| extend firstSeenSet = dataSet
| project-away dataSet
);
// aggregate entity data per scope and get the number of entities appearing over time
let aggregatedCandidateScopeData = (
entityData
| summarize countRowsScope = sum(countRowsEntity), countEntitiesScope = dcount(entity), countEntitiesScopeInTrain = dcountif(entity, firstSeenSet == 'trainSet')
, firstSeenScope = min(firstSeenEntity), lastSeenScope = max(lastSeenEntity), hasNewEntities = iff(dcountif(entity,firstSeenSet == 'detectSet') > 0, 1, 0)
by scope
| extend slicesInTrainingScope = datetime_diff(timePeriodBinSize, startDetection, firstSeenScope)
| where countEntitiesScopeInTrain <= maxEntitiesThresh and slicesInTrainingScope >= minTrainingDaysThresh and lastSeenScope >= startDetection and hasNewEntities == 1
);
let modelData = (
entityData
| join kind = inner (aggregatedCandidateScopeData) on scope
| where firstSeenSet == 'trainSet'
| summarize countAddedEntities = dcount(entity), firstSeenScope = min(firstSeenScope), slicesInTrainingScope = max(slicesInTrainingScope), countEntitiesScope = max(countEntitiesScope)
by scope, firstSeenSet, firstSeenEntity
| extend diffInDays = datetime_diff(timePeriodBinSize, startDetection, firstSeenEntity)
// adding exponentially decaying weights to counts
| extend decayingWeight = pow(base = decayParam, exponent = diffInDays)
| extend decayingValue = countAddedEntities * decayingWeight
| summarize newEntityProbability = round(1 - exp(-1.0 * sum(decayingValue)/max(diffInDays)), 4)
, countKnownEntities = sum(countAddedEntities), lastNewEntityTimestamp = max(firstSeenEntity), slicesOnScope = max(slicesInTrainingScope)///for explainability
by scope, firstSeenSet
// anomaly score is based on probability to get no new entities, calculated using Poisson distribution (P(X=0) = exp(-avg)) with added decay on average
| extend newEntityAnomalyScore = round(1 - newEntityProbability, 4)
| extend isAnomalousNewEntity = iff(newEntityAnomalyScore >= anomalyScoreThresh, 1, 0)
);
let resultsData = (
processedData
| where dataSet == 'detectSet'
| join kind = inner (modelData) on scope
| project-away scope1
| where isAnomalousNewEntity == 1
| summarize arg_min(sliceTime, *) by scope, entity
| extend anomalyType = strcat('newEntity_', entityColumnName), anomalyExplainability = strcat('The ', entityColumnName, ' ', entity, ' wasn\'t seen on ', scopeColumnName, ' ', scope, ' during the last ', slicesOnScope, ' ', timePeriodBinSize, 's. Previously, ', countKnownEntities
, ' entities were seen, the last one of them appearing at ', format_datetime(lastNewEntityTimestamp, 'yyyy-MM-dd HH:mm'), '.')
| join kind = leftouter (entityData | where firstSeenSet == 'trainSet' | extend entityFirstSeens = strcat(entity, ' : ', format_datetime(firstSeenEntity, 'yyyy-MM-dd HH:mm')) | sort by scope, firstSeenEntity asc | summarize anomalyState = make_list(entityFirstSeens) by scope) on scope
| project-away scope1
);
resultsData
};
// synthetic data generation
let detectPeriodStart = datetime(2022-04-30 05:00:00.0000000);
let trainPeriodStart = datetime(2022-03-01 05:00);
let names = pack_array("Admin", "Dev1", "Dev2", "IT-support");
let countNames = array_length(names);
let testData = range t from 1 to 24*60 step 1
| extend timeSlice = trainPeriodStart + 1h * t
| extend countEvents = round(2*rand() + iff((t/24)%7>=5, 10.0, 15.0) - (((t%24)/10)*((t%24)/10)), 2) * 100 // generate a series with weekly seasonality
| extend userName = tostring(names[toint(rand(countNames))])
| extend deviceId = hash_md5(rand())
| extend accountName = iff(((rand() < 0.2) and (timeSlice < detectPeriodStart)), 'testEnvironment', 'prodEnvironment')
| extend userName = iff(timeSlice == detectPeriodStart, 'H4ck3r', userName)
| extend deviceId = iff(timeSlice == detectPeriodStart, 'abcdefghijklmnoprtuvwxyz012345678', deviceId)
| sort by timeSlice desc
;
testData
| invoke detect_anomalous_new_entity_fl(entityColumnName = 'userName' //principalName for positive, deviceId for negative
, scopeColumnName = 'accountName'
, timeColumnName = 'timeSlice'
, startTraining = trainPeriodStart
, startDetection = detectPeriodStart
, endDetection = detectPeriodStart
)
Output
scope | Entität | sliceTime | t | timeSlice | countEvents | userName | deviceId | . | dataSet | firstSeenSet | newEntityProbability | countKnownEntities | lastNewEntityTimestamp | slicesOnScope | newEntityAnomalyScore | isAnomalousNewEntity | anomalyType | Anomalieexplainability | AnomalyState |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prodEnvironment | H4ck3r | 2022-04-30 05:00:00.0000000 | 1440 | 2022-04-30 05:00:00.0000000 | 1,687 | H4ck3r | abcdefghijklmnoprtuvwxyz012345678 | prodEnvironment | detectSet | trainSet | 0.0031 | 4 | 2022-03-01 09:00:00.0000000 | 60 | 0.9969 | 1 | newEntity_userName | Der userName H4ck3r wurde während der letzten 60 Tage nicht auf accountName prodEnvironment gesehen. Zuvor wurden vier Entitäten gesehen, die letzte von ihnen erschien 2022-03-01 09:00. | ["IT-support : 2022-03-01 07:00", "Admin : 2022-03-01 08:00", "Dev2 : 2022-03-01 09:00", "Dev1 : 2022-03-01 14:00"] |
Die Ausgabe der Ausführung der Funktion ist die erste gesehene Zeile im Testdatensatz für jede Entität pro Bereich, gefiltert nach neuen Entitäten (d. h., sie wurden während des Schulungszeitraums nicht angezeigt), die als Anomalie gekennzeichnet wurden (d. h., dass die Entitätsanomalzahl über anomalyScoreThresh lag). Einige weitere Felder werden zur Übersichtlichkeit hinzugefügt:
dataSet
: Aktuelles Dataset (ist immerdetectSet
).firstSeenSet
: Dataset, in dem der Bereich zuerst gesehen wurde (sollte "trainSet" sein).newEntityProbability
: Wahrscheinlichkeit, dass eine neue Entität anhand der Poisson-Modellschätzung angezeigt wird.countKnownEntities
: vorhandene Entitäten im Bereich.lastNewEntityTimestamp
: Das letzte Mal, wenn eine neue Entität vor dem Anomalien gesehen wurde.slicesOnScope
: Anzahl der Segmente pro Bereich.newEntityAnomalyScore
: Anomaliebewertung war die neue Entität im Bereich [0, 1], höhere Werte bedeuten mehr Anomalie.isAnomalousNewEntity
: binäre Kennzeichnung für anomale neue EntitätenanomalyType
: zeigt den Typ der Anomalie an (hilfreich beim Ausführen mehrerer Anomalieerkennungslogiken).anomalyExplainability
: textual wrapper for generated anomaly and its explanation.anomalyState
: Bag of existing entities on scope with their first seen times.
Wenn Sie diese Funktion für ein Benutzerkonto mit Standardparametern ausführen, erhalten Sie einen zuvor nicht angezeigten und anomalen Benutzer ('H4ck3r') mit hoher Anomaliebewertung von 0,9969, was bedeutet, dass dies unerwartet ist (aufgrund einer geringen Anzahl vorhandener Benutzer im Schulungszeitraum).
Wenn wir die Funktion mit Standardparametern auf deviceId als Entität ausführen, wird aufgrund einer großen Anzahl vorhandener Geräte, die sie erwartet, keine Anomalie angezeigt. Wenn wir jedoch den Parameter anomalyScoreThresh auf 0,0001 senken und den Parameter auf "maxEntitiesThresh" auf 10000 erhöhen, verringern wir die Genauigkeit effektiv zugunsten des Rückrufs und erkennen eine Anomalie (mit einer niedrigen Anomaliebewertung) auf dem Gerät "abcdefghijklmnoprtuvwxyz012345678".
Die Ausgabe zeigt die anomalienen Entitäten zusammen mit Erklärungsfeldern im standardisierten Format. Diese Felder sind nützlich für die Untersuchung der Anomalien und für die Ausführung der Anomalienentitätserkennung für mehrere Entitäten oder für die gemeinsame Ausführung anderer Algorithmen.
Die vorgeschlagene Verwendung im Cybersicherheitskontext führt die Funktion für sinnvolle Entitäten aus , z. B. Benutzernamen oder IP-Adressen – pro aussagekräftigen Bereichen , z. B. Abonnement für Konten. Eine erkannte anomale neue Entität bedeutet, dass ihre Darstellung im Bereich nicht erwartet wird und möglicherweise verdächtig ist.