Nikolay Klender

Anomaly Detection with ESA

Discussion created by Nikolay Klender on Jun 23, 2016
Latest reply on Jul 4, 2016 by Nikolay Klender

Great Esper capabilities which is heart of ESA allows to create complex rules. In this article I describe how to build a profile of some activity and fire an alert when there is deviations from usual activity. Lets take domain administrators accounts or other critical accounts as an example. Most of times such accounts are used from particular sources therefore  we can build a profile which will contains: user_src, ip_src, count of logins. After some time profile of one of accounts will looks like this:

X axis - is a ip_src (I replaced it with numbers), Y axis - number of login attempts.

ip_src with number 6 and 7 are looks like ok but 1,2,3 needs to be investigated. Next we can calculate probability of every source simply divide count of logins from particular ip_src by total login attempts. Next formula will estimate probability (in %) that particular source is NON usual for this user:


The result of this estimation will looks like this.

So we can say that if calculated value will be grater than defined threshold (say 97%) this is abnormal and must be investigated. Hope you understood idea

To implement this approach in ESA we well create two rules (modules in terms of Esper).

1. ProfileBuilder rule:


module profileBuilder;

@RSAPersist /*to save and restore during rebooting*/

create window (name string,key string, value string, cnt long, date long);


create constant variable long profileDurationHoursMultiplier = 24L;

create constant variable long profileDuration = 30L;

create constant variable long profileRewiewIntervalHour = 12;


@Name('ClearProfileTimer') /* delete old records */

on pattern [every timer:interval(profileRewiewIntervalHour hour)]

delete from profile

where Math.round(current_timestamp/1000/60/60/profileDurationHoursMultiplier)-date>=profileDuration;


We created named window "profile" which will hold:

  • date will be used to implement sliding behavior (like partitioning in databases)
  • name - is a name of profile . So one window we can use in several other profile rules.
  • key, value in our example will hold username and ip_src
  • cnt - is a count of values for particular key per date partition


There are several constants which defines how wide in time our profile window will be (measures in hours).

So in my example profileDurationHoursMultiplier*profileDuration = 24 hours * 30 = 720 hours = 30 days

profileRewiewIntervalHour defines how often old records (based on date field) will be reviewed and deleted. You can change this constants according your needs.

2. AdminsProfile rule


module domainAdmin;

uses profileBuilder;


create constant variable string profileName = 'domainAdmins';

create constant variable int minScore = 95;

create constant variable string[] adminList= {'superadmin1','superadmin2'};

create constant variable string[] whiteListIP={'',''};


/*using afreemaker variable which will be injected to Esper rule during deployment stage*/

<#assign myFilter="device_type in ('msacs','winevent_nic') AND reference_id in ('4624','4625','4768','4776')

AND adminList.anyOf(v=>v=user_src.toLowerCase()) and ip_src IS NOT NULL

AND NOT whiteListIP.anyOf(v=>v=ip_src.toLowerCase()) " >


@Name('Fill domain admins profile')

on Event(${myFilter}) e

merge profile p

where p.key =e.user_src.toLowerCase() and p.value=e.ip_src and = Math.round(current_timestamp/1000/60/60/profileDurationHoursMultiplier )

when matched

then update set cnt = cnt+1L

when not matched

then insert select profileName as name, ip_src as value, user_src.toLowerCase() as key, 1L as cnt,Math.round(current_timestamp/1000/60/60/profileDurationHoursMultiplier)  as date;


create  expression pCount { x =>

(select case sum(cnt)

when NULL then 1L

else sum(cnt) end

from profile p where = profileName AND (x.user_src).toLowerCase() = p.key and x.ip_src = p.value)

} ;


create  expression pTotal { x =>

(select case sum(cnt)

when NULL then 1L

else sum(cnt) end

from profile p where = profileName AND p.key=(x.user_src).toLowerCase()) } ;


create expression pScore{ x=>

  Math.round(100 - 100*pCount(x)/pTotal(x))





select e.ip_src as ip_src,e.user_src as user_src, pCount(e) as count,

pTotal(e) as total,

pScore(e) as score

from Event( ${myFilter} ).std:lastevent() e

where pScore(e)>=minScore

group by e.user_src,e.ip_src

output first every 1 hour;


At the beginning we define some constants

  • profileName - name of our profile
  • minScore - probability (in %) that particular source is NON usual for this user
  • adminList - array of critical accounts which must be under control
  • whiteListIP - array of allowed ip addresses

we also use freemaker variable myFilter which will be injected to esper statements by freemaker engine.

Next we fill our profile with upsert operation which is implemented with merge instruction. If username and ip_src is already in profile for this date then increase counter by 1 or simply create new record.


pCount,pTotal, pScore is expressions which will be used in the last rule for calculating count of login attempts from particular IP for a user, total number of logins attempts for user and score - the probability that ip is non usual for this user.

The latest rule will calculate probability and compare it with constant we defined at the beginning.

Example of alert looks like this:

So features of my approach are:

  • one profile window can be used to hold statistics for several different activities, for example to track domain admins sources, to track vpn usage from non usual cities for particular user, or any others
  • profile is sliding and automatically recalculated
  • profile is automatically saved and restored during ESA rebooting


Note: rule can generate false positives especially during firs days of working. To modify while list of IP rule must be changed and redeployed.


I hope that RSA team will create some sort of interface to create on-demand queries with such functionality it will be possible to mannualy select, delete, update values in named windows to adjust profile by hands.