Tag Archive: Microsoft Defender XDR

New security KQL functions focused around graph

Microsoft has published a set of new security functions (user-defined) in KQL. These are focused on the graph mindset, meaning relationships between nodes (objects). A previous blogpost explains graph semantics a bit more:

Graph semantics in Kusto – SEC-LABS R&D

The new functions released has many goodies, such as calculating blast radius, exposure perimeter and much more

User-defined functions can be saved as functions in Defender XDR or executed within the query itself.

Function NameDescription
detect_anomalous_new_entity_fl()Detect the appearance of anomalous new entities in timestamped data.
detect_anomalous_spike_fl()Detect the appearance of anomalous spikes in numeric variables in timestamped data.
graph_blast_radius_fl()Calculate the Blast Radius (list and score) of source nodes over path or edge data.
graph_exposure_perimeter_fl()Calculate the Exposure Perimeter (list and score) of target nodes over path or edge data.
graph_node_centrality_fl()Calculate various metrics of node centrality (such as degree and betweenness) over graph data (edge and nodes).
graph_path_discovery_fl()Discover valid paths between relevant endpoints (sources and targets) over graph data (edge and nodes).

https://learn.microsoft.com/en-us/kusto/functions-library/functions-library?view=azure-data-explorer#cybersecurity-functions

Example query (with defined table as input) from learn

let edges = datatable (SourceNodeName:string, EdgeName:string, EdgeType:string, TargetNodeName:string, Region:string)[						
    'vm-work-1',            'e1',           'can use',	            'webapp-prd', 	          'US',
    'vm-custom',        	'e2',           'can use',	            'webapp-prd', 	          'US',
    'webapp-prd',           'e3',           'can access',	        'vm-custom', 	          'US',
    'webapp-prd',       	'e4',           'can access',	        'test-machine', 	      'US',
    'vm-custom',        	'e5',           'can access',	        'server-0126', 	          'US',
    'vm-custom',        	'e6',	        'can access',	        'hub_router', 	          'US',
    'webapp-prd',       	'e7',	        'can access',	        'hub_router', 	          'US',
    'test-machine',       	'e8',	        'can access',	        'vm-custom',              'US',
    'test-machine',        	'e9',	        'can access',	        'hub_router', 	          'US',
    'hub_router',           'e10',	        'routes traffic to',	'remote_DT', 	          'US',
    'vm-work-1',            'e11',	        'can access',	        'storage_main_backup', 	  'US',
    'hub_router',           'e12',	        'routes traffic to',	'vm-work-2', 	          'US',
    'vm-work-2',        	'e13',          'can access',	        'backup_prc', 	          'US',
    'remote_DT',            'e14',	        'can access',	        'backup_prc', 	          'US',
    'backup_prc',           'e15',	        'moves data to',        'storage_main_backup', 	  'US',
    'backup_prc',           'e16',	        'moves data to',        'storage_DevBox', 	      'US',
    'device_A1',            'e17',	        'is connected to',      'sevice_B2', 	          'EU',
    'sevice_B2',            'e18',	        'is connected to',      'device_A1', 	          'EU'
];
let nodes = datatable (NodeName:string, NodeType:string, NodeEnvironment:string, Region:string) [
        'vm-work-1',                'Virtual Machine',      'Production',       'US',
        'vm-custom',                'Virtual Machine',      'Production',       'US',
        'webapp-prd',               'Application',          'None',             'US',
        'test-machine',             'Virtual Machine',      'Test',             'US',
        'hub_router',               'Traffic Router',       'None',             'US',
        'vm-work-2',                'Virtual Machine',      'Production',       'US',
        'remote_DT',                'Virtual Machine',      'Production',       'US',
        'backup_prc',               'Service',              'Production',       'US',
        'server-0126',              'Server',               'Production',       'US',
        'storage_main_backup',      'Cloud Storage',        'Production',       'US',
        'storage_DevBox',           'Cloud Storage',        'Test',             'US',
        'device_A1',                'Device',               'Backend',          'EU',
        'device_B2',                'Device',               'Backend',          'EU'
];
let nodesEnriched = (
    nodes
    | extend IsValidStart = (NodeType == 'Virtual Machine'),             IsValidEnd = (NodeType == 'Cloud Storage')              // option 1
    //| extend IsValidStart = (NodeName in('vm-work-1', 'vm-work-2')),     IsValidEnd = (NodeName in('storage_main_backup'))       // option 2
    //| extend IsValidStart = (NodeEnvironment == 'Test'),                 IsValidEnd = (NodeEnvironment == 'Production')          // option 3
);
let graph_path_discovery_fl = (   edgesTableName:string, nodesTableName:string, scopeColumnName:string
								, isValidPathStartColumnName:string, isValidPathEndColumnName:string
								, nodeIdColumnName:string, edgeIdColumnName:string, sourceIdColumnName:string, targetIdColumnName:string
								, minPathLength:long = 1, maxPathLength:long = 8, resultCountLimit:long = 100000) 
{
let edges = (
    table(edgesTableName)
    | extend sourceId           = column_ifexists(sourceIdColumnName, '')
    | extend targetId           = column_ifexists(targetIdColumnName, '')
    | extend edgeId             = column_ifexists(edgeIdColumnName, '')
    | extend scope              = column_ifexists(scopeColumnName, '')
    );
let nodes = (
    table(nodesTableName)
    | extend nodeId             = column_ifexists(nodeIdColumnName, '')
    | extend isValidPathStart   = column_ifexists(isValidPathStartColumnName, '')
    | extend isValidPathEnd     = column_ifexists(isValidPathEndColumnName, '')
    | extend scope              = column_ifexists(scopeColumnName, '')
);
let paths = (
    edges
    // Build graph object partitioned by scope, so that no connections are allowed between scopes.
    // In case no scopes are relevant, partitioning should be removed for better performance.
    | make-graph sourceId --> targetId with nodes on nodeId partitioned-by scope (
    // Look for existing paths between source nodes and target nodes with less than predefined number of hops
    // Current configurations looks for directed paths without any cycles; this can be changed if needed
      graph-match cycles = none (s)-[e*minPathLength..maxPathLength]->(t)
        // Filter only by paths with that connect valid endpoints
        where ((s.isValidPathStart) and (t.isValidPathEnd))
        project   sourceId                  = s.nodeId
                , isSourceValidPathStart    = s.isValidPathStart
                , targetId                  = t.nodeId
                , isTargetValidPathEnd      = t.isValidPathEnd
                , scope                     = s.scope
                , edgeIds                   = e.edgeId
                , edgeAllTargetIds          = e.targetId
    | limit resultCountLimit
    )
    | extend  pathLength                    = array_length(edgeIds)
            , pathId                        = hash_md5(strcat(sourceId, strcat(edgeIds), targetId))
            , pathAllNodeIds                = array_concat(pack_array(sourceId), edgeAllTargetIds)
    | project-away edgeAllTargetIds
    | mv-apply with_itemindex = SortIndex nodesInPath = pathAllNodeIds to typeof(string), edgesInPath = edgeIds to typeof(string) on (
        extend step = strcat(
              iff(isnotempty(nodesInPath), strcat('(', nodesInPath, ')'), '')
            , iff(isnotempty(edgesInPath), strcat('-[',  edgesInPath, ']->'), ''))
       | summarize fullPath = array_strcat(make_list(step), '')
    )
);
paths
};
graph_path_discovery_fl(edgesTableName          = 'edges'
                , nodesTableName                = 'nodesEnriched'
                , scopeColumnName               = 'Region'
                , nodeIdColumnName              = 'NodeName'
                , edgeIdColumnName              = 'EdgeName'
                , sourceIdColumnName            = 'SourceNodeName'
                , targetIdColumnName            = 'TargetNodeName'
                , isValidPathStartColumnName    = 'IsValidStart'
                , isValidPathEndColumnName      = 'IsValidEnd'
)

Happy Hunting!

Assigning severity to incidents and other features are now GA

The speed of how new useful functionalities in Microsoft Defender XDR, previously Microsoft 365 Defender, are being developed is very high. From this perspective it is super important to send feedback, not only things that may not work as you expected or if there is an error, but also new feature requests.

Some new features which was released in GA in February is within the incident management space.

Change incident severity

When a incident is being generated, the severity is based on the alert with highest severity. If the severity is wrong, you can change it by opening the manage incident which will open the incident pane.

Assign incident to a group

Instead of only assign the incident to a specific individual (who might be on a leave), it is now possible to assign the incident to a group by opening the manage incident which will open the incident pane.

Go hunt directly from attack story

When selecting an item in the attack story, you will get an option for “Go Hunt” which will give you the options to choose between All activities, Related alerts and See all available queries

When selecting a query, you will have the response in the same window. The positive thing with this is that you don’t have to move away from the incident view. If you want to continue the hunting you have the option to “Open in advanced hunting”

Happy Hunting!

Microsoft Defender XDR Deceptions Feature

Last year Microsoft announced a deception capability in Microsoft Defender for Endpoint. The idea with the deception is that adversaries access a Decoys or Lure which will trigger an incident for the response team to act on.

In Settings > Endpoints > Advanced features

Enable Deception

To create Deception rules

In Settings > Endpoints > Deception rules

It is possible to scope this specific deception rule to Devices with a specific tag

The system will automatically generate Alias or Hostnames which can be edited to better fit your organization

Lures can be autogenerated or use custom lures (file size up to 10MB)

A Lure can be of any filetype except PE files (exe and dll)
It is recommended that the lure contains information of decoys.

Happy Hunting!