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

External Upstream Source Added to Azure DevOps Feed

Back
Idadc32a33-1cd6-46f5-8801-e3ed8337885f
RulenameExternal Upstream Source Added to Azure DevOps Feed
DescriptionThe detection looks for new external sources added to an Azure DevOps feed. An allow list can be customized to explicitly allow known good sources.

An attacker could look to add a malicious feed in order to inject malicious packages into a build pipeline.
SeverityMedium
TacticsInitialAccess
TechniquesT1199
KindScheduled
Query frequency1d
Query period1d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/ExternalUpstreamSourceAddedtoAzureDevOpsFeed.yaml
Version1.0.3
Arm templateadc32a33-1cd6-46f5-8801-e3ed8337885f.json
Deploy To Azure
// Add any known allowed sources and source locations to the filter below (the NuGet Gallery has been added here as an example).
let allowed_sources = dynamic(["NuGet Gallery"]);
let allowed_locations = dynamic(["https://api.nuget.org/v3/index.json"]);
AzureDevOpsAuditing
// Look for feeds created or modified at either the organization or project level
| where OperationName matches regex "Artifacts.Feed.(Org|Project).Modify"
| where Details has "UpstreamSources, added"
| extend FeedName = tostring(Data.FeedName)
| extend FeedId = tostring(Data.FeedId)
| extend UpstreamsAdded = Data.UpstreamsAdded
// As multiple feeds may be added expand these out
| mv-expand UpstreamsAdded
// Only focus on external feeds
| where UpstreamsAdded.UpstreamSourceType !~ "internal"
| extend SourceLocation = tostring(UpstreamsAdded.Location)
| extend SourceName = tostring(UpstreamsAdded.Name)
// Exclude sources and locations in the allow list
| where SourceLocation !in (allowed_locations) and SourceName !in (allowed_sources)
| extend SourceProtocol = tostring(UpstreamsAdded.Protocol)
| extend SourceStatus = tostring(UpstreamsAdded.Status)
| project-reorder TimeGenerated, OperationName, ScopeDisplayName, ProjectName, FeedName, SourceName, SourceLocation, SourceProtocol, ActorUPN, UserAgent, IpAddress
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
status: Available
description: |
  'The detection looks for new external sources added to an Azure DevOps feed. An allow list can be customized to explicitly allow known good sources. 
  An attacker could look to add a malicious feed in order to inject malicious packages into a build pipeline.'  
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/ExternalUpstreamSourceAddedtoAzureDevOpsFeed.yaml
version: 1.0.3
triggerThreshold: 0
kind: Scheduled
queryPeriod: 1d
entityMappings:
- entityType: Account
  fieldMappings:
  - columnName: ActorUPN
    identifier: FullName
  - columnName: AccountName
    identifier: Name
  - columnName: AccountUPNSuffix
    identifier: UPNSuffix
- entityType: IP
  fieldMappings:
  - columnName: IpAddress
    identifier: Address
requiredDataConnectors: []
triggerOperator: gt
id: adc32a33-1cd6-46f5-8801-e3ed8337885f
name: External Upstream Source Added to Azure DevOps Feed
query: |
  // Add any known allowed sources and source locations to the filter below (the NuGet Gallery has been added here as an example).
  let allowed_sources = dynamic(["NuGet Gallery"]);
  let allowed_locations = dynamic(["https://api.nuget.org/v3/index.json"]);
  AzureDevOpsAuditing
  // Look for feeds created or modified at either the organization or project level
  | where OperationName matches regex "Artifacts.Feed.(Org|Project).Modify"
  | where Details has "UpstreamSources, added"
  | extend FeedName = tostring(Data.FeedName)
  | extend FeedId = tostring(Data.FeedId)
  | extend UpstreamsAdded = Data.UpstreamsAdded
  // As multiple feeds may be added expand these out
  | mv-expand UpstreamsAdded
  // Only focus on external feeds
  | where UpstreamsAdded.UpstreamSourceType !~ "internal"
  | extend SourceLocation = tostring(UpstreamsAdded.Location)
  | extend SourceName = tostring(UpstreamsAdded.Name)
  // Exclude sources and locations in the allow list
  | where SourceLocation !in (allowed_locations) and SourceName !in (allowed_sources)
  | extend SourceProtocol = tostring(UpstreamsAdded.Protocol)
  | extend SourceStatus = tostring(UpstreamsAdded.Status)
  | project-reorder TimeGenerated, OperationName, ScopeDisplayName, ProjectName, FeedName, SourceName, SourceLocation, SourceProtocol, ActorUPN, UserAgent, IpAddress
  | extend timestamp = TimeGenerated
  | extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])  
tactics:
- InitialAccess
queryFrequency: 1d
relevantTechniques:
- T1199
severity: Medium
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2023-02-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/adc32a33-1cd6-46f5-8801-e3ed8337885f')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/adc32a33-1cd6-46f5-8801-e3ed8337885f')]",
      "properties": {
        "alertRuleTemplateName": "adc32a33-1cd6-46f5-8801-e3ed8337885f",
        "customDetails": null,
        "description": "'The detection looks for new external sources added to an Azure DevOps feed. An allow list can be customized to explicitly allow known good sources. \nAn attacker could look to add a malicious feed in order to inject malicious packages into a build pipeline.'\n",
        "displayName": "External Upstream Source Added to Azure DevOps Feed",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "ActorUPN",
                "identifier": "FullName"
              },
              {
                "columnName": "AccountName",
                "identifier": "Name"
              },
              {
                "columnName": "AccountUPNSuffix",
                "identifier": "UPNSuffix"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "IpAddress",
                "identifier": "Address"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/ExternalUpstreamSourceAddedtoAzureDevOpsFeed.yaml",
        "query": "// Add any known allowed sources and source locations to the filter below (the NuGet Gallery has been added here as an example).\nlet allowed_sources = dynamic([\"NuGet Gallery\"]);\nlet allowed_locations = dynamic([\"https://api.nuget.org/v3/index.json\"]);\nAzureDevOpsAuditing\n// Look for feeds created or modified at either the organization or project level\n| where OperationName matches regex \"Artifacts.Feed.(Org|Project).Modify\"\n| where Details has \"UpstreamSources, added\"\n| extend FeedName = tostring(Data.FeedName)\n| extend FeedId = tostring(Data.FeedId)\n| extend UpstreamsAdded = Data.UpstreamsAdded\n// As multiple feeds may be added expand these out\n| mv-expand UpstreamsAdded\n// Only focus on external feeds\n| where UpstreamsAdded.UpstreamSourceType !~ \"internal\"\n| extend SourceLocation = tostring(UpstreamsAdded.Location)\n| extend SourceName = tostring(UpstreamsAdded.Name)\n// Exclude sources and locations in the allow list\n| where SourceLocation !in (allowed_locations) and SourceName !in (allowed_sources)\n| extend SourceProtocol = tostring(UpstreamsAdded.Protocol)\n| extend SourceStatus = tostring(UpstreamsAdded.Status)\n| project-reorder TimeGenerated, OperationName, ScopeDisplayName, ProjectName, FeedName, SourceName, SourceLocation, SourceProtocol, ActorUPN, UserAgent, IpAddress\n| extend timestamp = TimeGenerated\n| extend AccountName = tostring(split(ActorUPN, \"@\")[0]), AccountUPNSuffix = tostring(split(ActorUPN, \"@\")[1])\n",
        "queryFrequency": "P1D",
        "queryPeriod": "P1D",
        "severity": "Medium",
        "status": "Available",
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "InitialAccess"
        ],
        "techniques": [
          "T1199"
        ],
        "templateVersion": "1.0.3",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}