cloudflare/Azure-Sentinel
Publicmirrored fromhttps://github.com/cloudflare/Azure-Sentinel
Hunting Queries/SigninLogs/AnomalousUserAppSigninLocationIncreaseDetail.txt
38lines · modecode
| 1 | // Name: anomalous sign-in location by user account and authenticating application - with sign-in details |
| 2 | // |
| 3 | // Id: 7f6e8f14-62fa-4ce6-a490-c07f1d9888ba |
| 4 | // |
| 5 | // Description: This query over Azure Active Directory sign-in considers all user sign-ins for each Azure Active |
| 6 | // Directory application and picks out the most anomalous change in location profile for a user within an |
| 7 | // individual application. The intent is to hunt for user account compromise, possibly via a specific application |
| 8 | // vector. |
| 9 | // This variation of the query joins the results back onto the original sign-in data to allow review of the |
| 10 | // location set with each identified user in tabular form. |
| 11 | // |
| 12 | // DataSource: #SigninLogs |
| 13 | // |
| 14 | // Tactics: #InitialAccess |
| 15 | // |
| 16 | let timeRange=ago(14d); |
| 17 | SigninLogs |
| 18 | // Forces Log Analytics to recognize that the query should be run over full time range |
| 19 | | where TimeGenerated >= timeRange |
| 20 | | extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/", tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";") |
| 21 | | project TimeGenerated, AppDisplayName , UserPrincipalName, locationString |
| 22 | // Create time series |
| 23 | | make-series dLocationCount = dcount(locationString) on TimeGenerated in range(timeRange,now(), 1d) |
| 24 | by UserPrincipalName, AppDisplayName |
| 25 | // Compute best fit line for each entry |
| 26 | | extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dLocationCount) |
| 27 | // Chart the 3 most interesting lines |
| 28 | // A 0-value slope corresponds to an account being completely stable over time for a given Azure Active Directory application |
| 29 | | top 3 by Slope desc |
| 30 | // Extract the set of locations for each top user: |
| 31 | | join kind=inner (SigninLogs |
| 32 | | where TimeGenerated >= timeRange |
| 33 | | extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/", tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")// , tostring(LocationDetails["geoCoordinates"])) |
| 34 | | summarize locationList = makeset(locationString), threeDayWindowLocationCount=dcount(locationString) by AppDisplayName , UserPrincipalName, timerange=bin(TimeGenerated, 3d)) on AppDisplayName, UserPrincipalName |
| 35 | | order by UserPrincipalName, timerange asc |
| 36 | | project timerange, AppDisplayName , UserPrincipalName, threeDayWindowLocationCount, locationList |
| 37 | | order by AppDisplayName, UserPrincipalName, timerange asc |
| 38 | | extend AccountCustomEntity = UserPrincipalName |