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])
relevantTechniques:
- T1199
name: External Upstream Source Added to Azure DevOps Feed
requiredDataConnectors: []
entityMappings:
- fieldMappings:
  - identifier: FullName
    columnName: ActorUPN
  - identifier: Name
    columnName: AccountName
  - identifier: UPNSuffix
    columnName: AccountUPNSuffix
  entityType: Account
- fieldMappings:
  - identifier: Address
    columnName: IpAddress
  entityType: IP
triggerThreshold: 0
id: adc32a33-1cd6-46f5-8801-e3ed8337885f
tactics:
- InitialAccess
version: 1.0.3
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/ExternalUpstreamSourceAddedtoAzureDevOpsFeed.yaml
queryPeriod: 1d
kind: Scheduled
queryFrequency: 1d
severity: Medium
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.'  
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])  
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/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",
        "subTechniques": [],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "InitialAccess"
        ],
        "techniques": [
          "T1199"
        ],
        "templateVersion": "1.0.3",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}