Microsoft Sentinel Analytic Rules
cloudbrothers.infoAzure Sentinel RepoToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

Excessive share permissions

Back
Idaba0b08c-aace-40c5-a21d-39153023dcaa
RulenameExcessive share permissions
DescriptionThe query searches for event 5143, which is triggered when a share is created or changed and includes de share permissions.

First it checks to see if this is a whitelisted share for the system (e.g. domaincontroller netlogon, printserver print$ etc.).

The share permissions are then checked against ‘allow’ rule (A) for a number of well known overly permissive groups, like all users, guests, authenticated users etc.

If these are found, an alert is raised so the share creation may be audited.

Note: this rule only checks for changed permissions, to prevent repeat alerts if for example a comment is changed, but the permissions are not altered.
SeverityMedium
TacticsCollection
Discovery
TechniquesT1039
T1135
Required data connectorsSecurityEvents
WindowsSecurityEvents
KindScheduled
Query frequency1h
Query period1h
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/FalconFriday/Analytic Rules/ExcessiveSharePermissions.yaml
Version1.0.1
Arm templateaba0b08c-aace-40c5-a21d-39153023dcaa.json
Deploy To Azure
let timeframe=1h;
let system_roles = datatable(role:string, system:string)                  // Link roles to systems.
  ["DC","dc1.corp.local",
  "DC","dc2.corp.local",
  "PRINT","printer.corp.local"
  ];
let share_roles = datatable(role:string, share:string)                    // Link roles to shares.
  ["DC", @"\\*\sysvol",
  "DC",@"\\*\netlogon",
  "PRINT",@"\\*\print$"];
let allowed_system_shares = system_roles                                  // Link systems to shares.
  | join kind=inner share_roles on role
  | extend system = tolower(system), share = tolower(share)
  | project-away role
  | summarize allowed_shares = make_set(share) by system;
let monitored_principals=datatable(identifier:string, Group_Name:string)  // Define a data-table with groups to monitor.
  ["AN", "Anonymous Logon",            // We accept the 'alias' for these well-known SIDS.
  "AU", "Authenticated Users",
  "BG","Built-in guests",
  "BU","Built-in users",
  "DG","Domain guests",
  "DU","Domain users",
  "WD","Everyone",
  "IU","Interactively Logged-on users",
  "LG","Local Guest",
  "NU","Network logon users",
  "513", "Domain Users",                                                  // Support matching on the last part of a SID.
  "514", "Domain Guests",
  "545", "Builtin Users",
  "546", "Builtin Guests",
  "S-1-5-7", "Anonymous Logon"                                            // For the global SIDS, we accept them as-is.
  ];
SecurityEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 5143
| extend EventXML = parse_xml(EventData)
| extend OldSD = tostring(EventXML["EventData"]["Data"][13]["#text"])     // Grab the previous Security Descriptor.
| extend NewSD = tostring(EventXML["EventData"]["Data"][14]["#text"])     // Grab the new Security Descriptor.
| project-away EventXML
| where tostring(OldSD) !~ tostring(NewSD)                                // Don't bother with unchanged permissions.
| extend system = tolower(Computer), share=tolower(ShareName)             // Normalize system and share name for matching with whitelist.
| join kind=leftouter allowed_system_shares on system                     // Retrieve the allowed shares per system.
| where not(set_has_element(allowed_shares, share))                       // Check if the current share is an allowed share.
| project-away system, share, allowed_shares                              // Get rid of temporary fields.
| extend DACLS = extract_all(@"(D:(?:\((?:[\w\-]*;){5}(?:[\w\-]*)\))*)", tostring(NewSD)) // Grab all instances of D:(DACL), in case there are multiple sets.
| project-away OldSD, NewSD                                               // Get rid of data we no longer need.
| mv-expand DACLS to typeof(string)                                       // In case there are any duplicate/subsequent D: entries (e.g., D:<dacls>S:<sacls>D:<dacls>) split them out to individual D: sets.
| extend DACLS = substring(DACLS,2)                                       // Strip the leading D:.
| extend DACLS = split(DACLS, ")")                                        // Split the sets of DACLS ()() to an array of individual DACLS (). This removes the trailing ) character.
| mv-expand DACLS to typeof(string)                                       // Duplicate the records in such a way that only 1 DACL per record exist. We will aggregate them back later.
| extend DACLS = substring(DACLS, 1)                                      // Also remove the leading ( character.
| where not(isempty(DACLS)) and DACLS startswith "A;"                     // Remove any empty or non-allow DACLs.
| extend allowed_principal = tostring(split(DACLS,";",5)[0])              // Grab the SID what is affected by this DACL.
| extend allowed_principal = iff(not(allowed_principal startswith "S-" and string_size(allowed_principal) > 15), allowed_principal, split(allowed_principal,"-",countof(allowed_principal,"-"))[0]) // This line takes only the last part (e.g., 513) of a long SID, so you can refer to groups/users without needing to supply the full SID above.
| join kind=inner monitored_principals on $left.allowed_principal == $right.identifier // Join the found groups to the table of groups to be monitored above. Adds the more readable 'group_name).
| project-away allowed_principal, identifier, DACLS
| summarize Authorized_Public_Principals = make_set(Group_Name), take_any(*) by TimeGenerated, SourceComputerId, EventData // Summarize the fields back, making a set of the various group_name values for this record.
| project-away Group_Name
// Begin client-specific filter.
// End client-specific filter.
relevantTechniques:
- T1039
- T1135
name: Excessive share permissions
requiredDataConnectors:
- dataTypes:
  - SecurityEvent
  connectorId: SecurityEvents
- dataTypes:
  - SecurityEvent
  connectorId: WindowsSecurityEvents
entityMappings:
- fieldMappings:
  - identifier: FullName
    columnName: Computer
  entityType: Host
triggerThreshold: 0
id: aba0b08c-aace-40c5-a21d-39153023dcaa
tactics:
- Collection
- Discovery
version: 1.0.1
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/FalconFriday/Analytic Rules/ExcessiveSharePermissions.yaml
queryPeriod: 1h
kind: Scheduled
queryFrequency: 1h
severity: Medium
status: Available
description: |
  The query searches for event 5143, which is triggered when a share is created or changed and includes de share permissions.
  First it checks to see if this is a whitelisted share for the system (e.g. domaincontroller netlogon, printserver print$ etc.).
  The share permissions are then checked against 'allow' rule (A) for a number of well known overly permissive groups, like all users, guests, authenticated users etc.
  If these are found, an alert is raised so the share creation may be audited.
  Note: this rule only checks for changed permissions, to prevent repeat alerts if for example a comment is changed, but the permissions are not altered.  
query: |
  let timeframe=1h;
  let system_roles = datatable(role:string, system:string)                  // Link roles to systems.
    ["DC","dc1.corp.local",
    "DC","dc2.corp.local",
    "PRINT","printer.corp.local"
    ];
  let share_roles = datatable(role:string, share:string)                    // Link roles to shares.
    ["DC", @"\\*\sysvol",
    "DC",@"\\*\netlogon",
    "PRINT",@"\\*\print$"];
  let allowed_system_shares = system_roles                                  // Link systems to shares.
    | join kind=inner share_roles on role
    | extend system = tolower(system), share = tolower(share)
    | project-away role
    | summarize allowed_shares = make_set(share) by system;
  let monitored_principals=datatable(identifier:string, Group_Name:string)  // Define a data-table with groups to monitor.
    ["AN", "Anonymous Logon",            // We accept the 'alias' for these well-known SIDS.
    "AU", "Authenticated Users",
    "BG","Built-in guests",
    "BU","Built-in users",
    "DG","Domain guests",
    "DU","Domain users",
    "WD","Everyone",
    "IU","Interactively Logged-on users",
    "LG","Local Guest",
    "NU","Network logon users",
    "513", "Domain Users",                                                  // Support matching on the last part of a SID.
    "514", "Domain Guests",
    "545", "Builtin Users",
    "546", "Builtin Guests",
    "S-1-5-7", "Anonymous Logon"                                            // For the global SIDS, we accept them as-is.
    ];
  SecurityEvent
  | where TimeGenerated >= ago(timeframe)
  | where EventID == 5143
  | extend EventXML = parse_xml(EventData)
  | extend OldSD = tostring(EventXML["EventData"]["Data"][13]["#text"])     // Grab the previous Security Descriptor.
  | extend NewSD = tostring(EventXML["EventData"]["Data"][14]["#text"])     // Grab the new Security Descriptor.
  | project-away EventXML
  | where tostring(OldSD) !~ tostring(NewSD)                                // Don't bother with unchanged permissions.
  | extend system = tolower(Computer), share=tolower(ShareName)             // Normalize system and share name for matching with whitelist.
  | join kind=leftouter allowed_system_shares on system                     // Retrieve the allowed shares per system.
  | where not(set_has_element(allowed_shares, share))                       // Check if the current share is an allowed share.
  | project-away system, share, allowed_shares                              // Get rid of temporary fields.
  | extend DACLS = extract_all(@"(D:(?:\((?:[\w\-]*;){5}(?:[\w\-]*)\))*)", tostring(NewSD)) // Grab all instances of D:(DACL), in case there are multiple sets.
  | project-away OldSD, NewSD                                               // Get rid of data we no longer need.
  | mv-expand DACLS to typeof(string)                                       // In case there are any duplicate/subsequent D: entries (e.g., D:<dacls>S:<sacls>D:<dacls>) split them out to individual D: sets.
  | extend DACLS = substring(DACLS,2)                                       // Strip the leading D:.
  | extend DACLS = split(DACLS, ")")                                        // Split the sets of DACLS ()() to an array of individual DACLS (). This removes the trailing ) character.
  | mv-expand DACLS to typeof(string)                                       // Duplicate the records in such a way that only 1 DACL per record exist. We will aggregate them back later.
  | extend DACLS = substring(DACLS, 1)                                      // Also remove the leading ( character.
  | where not(isempty(DACLS)) and DACLS startswith "A;"                     // Remove any empty or non-allow DACLs.
  | extend allowed_principal = tostring(split(DACLS,";",5)[0])              // Grab the SID what is affected by this DACL.
  | extend allowed_principal = iff(not(allowed_principal startswith "S-" and string_size(allowed_principal) > 15), allowed_principal, split(allowed_principal,"-",countof(allowed_principal,"-"))[0]) // This line takes only the last part (e.g., 513) of a long SID, so you can refer to groups/users without needing to supply the full SID above.
  | join kind=inner monitored_principals on $left.allowed_principal == $right.identifier // Join the found groups to the table of groups to be monitored above. Adds the more readable 'group_name).
  | project-away allowed_principal, identifier, DACLS
  | summarize Authorized_Public_Principals = make_set(Group_Name), take_any(*) by TimeGenerated, SourceComputerId, EventData // Summarize the fields back, making a set of the various group_name values for this record.
  | project-away Group_Name
  // Begin client-specific filter.
  // End client-specific filter.  
triggerOperator: gt
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2024-01-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/aba0b08c-aace-40c5-a21d-39153023dcaa')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/aba0b08c-aace-40c5-a21d-39153023dcaa')]",
      "properties": {
        "alertRuleTemplateName": "aba0b08c-aace-40c5-a21d-39153023dcaa",
        "customDetails": null,
        "description": "The query searches for event 5143, which is triggered when a share is created or changed and includes de share permissions.\nFirst it checks to see if this is a whitelisted share for the system (e.g. domaincontroller netlogon, printserver print$ etc.).\nThe share permissions are then checked against 'allow' rule (A) for a number of well known overly permissive groups, like all users, guests, authenticated users etc.\nIf these are found, an alert is raised so the share creation may be audited.\nNote: this rule only checks for changed permissions, to prevent repeat alerts if for example a comment is changed, but the permissions are not altered.\n",
        "displayName": "Excessive share permissions",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Host",
            "fieldMappings": [
              {
                "columnName": "Computer",
                "identifier": "FullName"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/FalconFriday/Analytic Rules/ExcessiveSharePermissions.yaml",
        "query": "let timeframe=1h;\nlet system_roles = datatable(role:string, system:string)                  // Link roles to systems.\n  [\"DC\",\"dc1.corp.local\",\n  \"DC\",\"dc2.corp.local\",\n  \"PRINT\",\"printer.corp.local\"\n  ];\nlet share_roles = datatable(role:string, share:string)                    // Link roles to shares.\n  [\"DC\", @\"\\\\*\\sysvol\",\n  \"DC\",@\"\\\\*\\netlogon\",\n  \"PRINT\",@\"\\\\*\\print$\"];\nlet allowed_system_shares = system_roles                                  // Link systems to shares.\n  | join kind=inner share_roles on role\n  | extend system = tolower(system), share = tolower(share)\n  | project-away role\n  | summarize allowed_shares = make_set(share) by system;\nlet monitored_principals=datatable(identifier:string, Group_Name:string)  // Define a data-table with groups to monitor.\n  [\"AN\", \"Anonymous Logon\",            // We accept the 'alias' for these well-known SIDS.\n  \"AU\", \"Authenticated Users\",\n  \"BG\",\"Built-in guests\",\n  \"BU\",\"Built-in users\",\n  \"DG\",\"Domain guests\",\n  \"DU\",\"Domain users\",\n  \"WD\",\"Everyone\",\n  \"IU\",\"Interactively Logged-on users\",\n  \"LG\",\"Local Guest\",\n  \"NU\",\"Network logon users\",\n  \"513\", \"Domain Users\",                                                  // Support matching on the last part of a SID.\n  \"514\", \"Domain Guests\",\n  \"545\", \"Builtin Users\",\n  \"546\", \"Builtin Guests\",\n  \"S-1-5-7\", \"Anonymous Logon\"                                            // For the global SIDS, we accept them as-is.\n  ];\nSecurityEvent\n| where TimeGenerated >= ago(timeframe)\n| where EventID == 5143\n| extend EventXML = parse_xml(EventData)\n| extend OldSD = tostring(EventXML[\"EventData\"][\"Data\"][13][\"#text\"])     // Grab the previous Security Descriptor.\n| extend NewSD = tostring(EventXML[\"EventData\"][\"Data\"][14][\"#text\"])     // Grab the new Security Descriptor.\n| project-away EventXML\n| where tostring(OldSD) !~ tostring(NewSD)                                // Don't bother with unchanged permissions.\n| extend system = tolower(Computer), share=tolower(ShareName)             // Normalize system and share name for matching with whitelist.\n| join kind=leftouter allowed_system_shares on system                     // Retrieve the allowed shares per system.\n| where not(set_has_element(allowed_shares, share))                       // Check if the current share is an allowed share.\n| project-away system, share, allowed_shares                              // Get rid of temporary fields.\n| extend DACLS = extract_all(@\"(D:(?:\\((?:[\\w\\-]*;){5}(?:[\\w\\-]*)\\))*)\", tostring(NewSD)) // Grab all instances of D:(DACL), in case there are multiple sets.\n| project-away OldSD, NewSD                                               // Get rid of data we no longer need.\n| mv-expand DACLS to typeof(string)                                       // In case there are any duplicate/subsequent D: entries (e.g., D:<dacls>S:<sacls>D:<dacls>) split them out to individual D: sets.\n| extend DACLS = substring(DACLS,2)                                       // Strip the leading D:.\n| extend DACLS = split(DACLS, \")\")                                        // Split the sets of DACLS ()() to an array of individual DACLS (). This removes the trailing ) character.\n| mv-expand DACLS to typeof(string)                                       // Duplicate the records in such a way that only 1 DACL per record exist. We will aggregate them back later.\n| extend DACLS = substring(DACLS, 1)                                      // Also remove the leading ( character.\n| where not(isempty(DACLS)) and DACLS startswith \"A;\"                     // Remove any empty or non-allow DACLs.\n| extend allowed_principal = tostring(split(DACLS,\";\",5)[0])              // Grab the SID what is affected by this DACL.\n| extend allowed_principal = iff(not(allowed_principal startswith \"S-\" and string_size(allowed_principal) > 15), allowed_principal, split(allowed_principal,\"-\",countof(allowed_principal,\"-\"))[0]) // This line takes only the last part (e.g., 513) of a long SID, so you can refer to groups/users without needing to supply the full SID above.\n| join kind=inner monitored_principals on $left.allowed_principal == $right.identifier // Join the found groups to the table of groups to be monitored above. Adds the more readable 'group_name).\n| project-away allowed_principal, identifier, DACLS\n| summarize Authorized_Public_Principals = make_set(Group_Name), take_any(*) by TimeGenerated, SourceComputerId, EventData // Summarize the fields back, making a set of the various group_name values for this record.\n| project-away Group_Name\n// Begin client-specific filter.\n// End client-specific filter.\n",
        "queryFrequency": "PT1H",
        "queryPeriod": "PT1H",
        "severity": "Medium",
        "status": "Available",
        "subTechniques": [],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "Collection",
          "Discovery"
        ],
        "techniques": [
          "T1039",
          "T1135"
        ],
        "templateVersion": "1.0.1",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}