@panguard-ai/panguard-trap 0.1.0
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/dist/cli/index.d.ts +45 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +298 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/intel/index.d.ts +38 -0
- package/dist/intel/index.d.ts.map +1 -0
- package/dist/intel/index.js +157 -0
- package/dist/intel/index.js.map +1 -0
- package/dist/profiler/attacker-profiler.d.ts +68 -0
- package/dist/profiler/attacker-profiler.d.ts.map +1 -0
- package/dist/profiler/attacker-profiler.js +316 -0
- package/dist/profiler/attacker-profiler.js.map +1 -0
- package/dist/profiler/index.d.ts +8 -0
- package/dist/profiler/index.d.ts.map +1 -0
- package/dist/profiler/index.js +8 -0
- package/dist/profiler/index.js.map +1 -0
- package/dist/services/base-service.d.ts +61 -0
- package/dist/services/base-service.d.ts.map +1 -0
- package/dist/services/base-service.js +190 -0
- package/dist/services/base-service.js.map +1 -0
- package/dist/services/generic-trap.d.ts +22 -0
- package/dist/services/generic-trap.d.ts.map +1 -0
- package/dist/services/generic-trap.js +439 -0
- package/dist/services/generic-trap.js.map +1 -0
- package/dist/services/http-trap.d.ts +36 -0
- package/dist/services/http-trap.d.ts.map +1 -0
- package/dist/services/http-trap.js +218 -0
- package/dist/services/http-trap.js.map +1 -0
- package/dist/services/index.d.ts +26 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +52 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/mysql-trap.d.ts +22 -0
- package/dist/services/mysql-trap.d.ts.map +1 -0
- package/dist/services/mysql-trap.js +374 -0
- package/dist/services/mysql-trap.js.map +1 -0
- package/dist/services/rdp-trap.d.ts +21 -0
- package/dist/services/rdp-trap.d.ts.map +1 -0
- package/dist/services/rdp-trap.js +299 -0
- package/dist/services/rdp-trap.js.map +1 -0
- package/dist/services/redis-trap.d.ts +21 -0
- package/dist/services/redis-trap.d.ts.map +1 -0
- package/dist/services/redis-trap.js +321 -0
- package/dist/services/redis-trap.js.map +1 -0
- package/dist/services/smb-trap.d.ts +21 -0
- package/dist/services/smb-trap.d.ts.map +1 -0
- package/dist/services/smb-trap.js +358 -0
- package/dist/services/smb-trap.js.map +1 -0
- package/dist/services/ssh-trap.d.ts +43 -0
- package/dist/services/ssh-trap.d.ts.map +1 -0
- package/dist/services/ssh-trap.js +397 -0
- package/dist/services/ssh-trap.js.map +1 -0
- package/dist/threat-cloud-uploader.d.ts +48 -0
- package/dist/threat-cloud-uploader.d.ts.map +1 -0
- package/dist/threat-cloud-uploader.js +125 -0
- package/dist/threat-cloud-uploader.js.map +1 -0
- package/dist/trap-engine.d.ts +80 -0
- package/dist/trap-engine.d.ts.map +1 -0
- package/dist/trap-engine.js +279 -0
- package/dist/trap-engine.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +89 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH Honeypot Service
|
|
3
|
+
* SSH 蜜罐服務
|
|
4
|
+
*
|
|
5
|
+
* Simulates an SSH server to capture brute force attacks,
|
|
6
|
+
* credential stuffing, and post-auth commands.
|
|
7
|
+
* 模擬 SSH 伺服器以捕獲暴力破解、撞庫攻擊及登入後指令。
|
|
8
|
+
*
|
|
9
|
+
* MITRE ATT&CK Coverage:
|
|
10
|
+
* - T1110: Brute Force
|
|
11
|
+
* - T1078: Valid Accounts
|
|
12
|
+
* - T1059: Command and Scripting Interpreter
|
|
13
|
+
*
|
|
14
|
+
* @module @panguard-ai/panguard-trap/services/ssh-trap
|
|
15
|
+
*/
|
|
16
|
+
import { createLogger } from '@panguard-ai/core';
|
|
17
|
+
import { BaseTrapService } from './base-service.js';
|
|
18
|
+
const logger = createLogger('panguard-trap:service:ssh');
|
|
19
|
+
/** Known suspicious commands / 已知可疑指令 */
|
|
20
|
+
const SUSPICIOUS_COMMANDS = [
|
|
21
|
+
{ pattern: /wget|curl.*http/i, technique: 'T1105', description: 'Ingress Tool Transfer' },
|
|
22
|
+
{
|
|
23
|
+
pattern: /chmod\s+[+]?[0-7]*x|chmod\s+777/i,
|
|
24
|
+
technique: 'T1222',
|
|
25
|
+
description: 'File and Directory Permissions Modification',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pattern: /\/etc\/shadow|\/etc\/passwd/i,
|
|
29
|
+
technique: 'T1003',
|
|
30
|
+
description: 'OS Credential Dumping',
|
|
31
|
+
},
|
|
32
|
+
{ pattern: /crontab|\/etc\/cron/i, technique: 'T1053', description: 'Scheduled Task/Job' },
|
|
33
|
+
{
|
|
34
|
+
pattern: /base64\s+-d|echo.*\|.*sh/i,
|
|
35
|
+
technique: 'T1140',
|
|
36
|
+
description: 'Deobfuscate/Decode Files or Information',
|
|
37
|
+
},
|
|
38
|
+
{ pattern: /nc\s+-|ncat|netcat/i, technique: 'T1571', description: 'Non-Standard Port' },
|
|
39
|
+
{ pattern: /iptables.*-D|ufw\s+disable/i, technique: 'T1562', description: 'Impair Defenses' },
|
|
40
|
+
{
|
|
41
|
+
pattern: /rm\s+-rf\s+\/|dd\s+if=\/dev\/zero/i,
|
|
42
|
+
technique: 'T1485',
|
|
43
|
+
description: 'Data Destruction',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
pattern: /xmrig|minerd|crypto/i,
|
|
47
|
+
technique: 'T1496',
|
|
48
|
+
description: 'Resource Hijacking (Cryptomining)',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
pattern: /ssh-keygen|authorized_keys/i,
|
|
52
|
+
technique: 'T1098',
|
|
53
|
+
description: 'Account Manipulation',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pattern: /id\s*$|whoami|uname\s+-a/i,
|
|
57
|
+
technique: 'T1082',
|
|
58
|
+
description: 'System Information Discovery',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pattern: /ifconfig|ip\s+addr/i,
|
|
62
|
+
technique: 'T1016',
|
|
63
|
+
description: 'System Network Configuration Discovery',
|
|
64
|
+
},
|
|
65
|
+
{ pattern: /cat\s+\/proc|ps\s+aux/i, technique: 'T1057', description: 'Process Discovery' },
|
|
66
|
+
];
|
|
67
|
+
/** Fake filesystem responses / 假檔案系統回應 */
|
|
68
|
+
const FAKE_RESPONSES = {
|
|
69
|
+
id: 'uid=1000(admin) gid=1000(admin) groups=1000(admin),27(sudo)',
|
|
70
|
+
whoami: 'admin',
|
|
71
|
+
'uname -a': 'Linux web-server-01 5.15.0-91-generic #101-Ubuntu SMP x86_64 GNU/Linux',
|
|
72
|
+
pwd: '/home/admin',
|
|
73
|
+
ls: 'Desktop Documents Downloads backup.tar.gz deploy.sh',
|
|
74
|
+
'ls -la': 'total 48\ndrwxr-xr-x 6 admin admin 4096 Jan 15 10:30 .\ndrwxr-xr-x 3 root root 4096 Dec 1 2024 ..\n-rw-r--r-- 1 admin admin 220 Dec 1 2024 .bash_logout\n-rw-r--r-- 1 admin admin 3771 Dec 1 2024 .bashrc\ndrwxr-xr-x 2 admin admin 4096 Jan 15 10:30 Desktop\ndrwxr-xr-x 2 admin admin 4096 Jan 10 08:15 Documents\n-rw-r--r-- 1 admin admin 8192 Jan 14 16:45 backup.tar.gz\n-rwxr-xr-x 1 admin admin 512 Jan 12 09:00 deploy.sh',
|
|
75
|
+
'cat /etc/passwd': 'root:x:0:0:root:/root:/bin/bash\nadmin:x:1000:1000:Admin:/home/admin:/bin/bash\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin',
|
|
76
|
+
ifconfig: 'eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500\n inet 10.0.1.100 netmask 255.255.255.0 broadcast 10.0.1.255',
|
|
77
|
+
hostname: 'web-server-01',
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* SSH Honeypot service
|
|
81
|
+
* SSH 蜜罐服務
|
|
82
|
+
*/
|
|
83
|
+
export class SSHTrapService extends BaseTrapService {
|
|
84
|
+
server = null;
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
ssh2Server = null;
|
|
87
|
+
sshBanner;
|
|
88
|
+
usingSsh2 = false;
|
|
89
|
+
constructor(config) {
|
|
90
|
+
super({ ...config, type: 'ssh' });
|
|
91
|
+
this.sshBanner = config.banner ?? 'SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6';
|
|
92
|
+
}
|
|
93
|
+
async doStart() {
|
|
94
|
+
// Try ssh2 module first for real SSH-2.0 protocol support
|
|
95
|
+
if (await this.tryStartSsh2()) {
|
|
96
|
+
this.usingSsh2 = true;
|
|
97
|
+
logger.info('SSH honeypot using ssh2 module (full SSH-2.0 protocol)');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Fallback to basic TCP
|
|
101
|
+
logger.info('SSH honeypot using TCP fallback (ssh2 not available)');
|
|
102
|
+
const net = await import('node:net');
|
|
103
|
+
this.server = net.createServer((socket) => {
|
|
104
|
+
this.handleConnection(socket);
|
|
105
|
+
});
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
this.server.listen(this.config.port, () => {
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
this.server.on('error', reject);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Try to start with ssh2 module for proper SSH-2.0 handshake.
|
|
115
|
+
* Returns true if successful, false if ssh2 is not installed.
|
|
116
|
+
*/
|
|
117
|
+
async tryStartSsh2() {
|
|
118
|
+
try {
|
|
119
|
+
// Dynamic import wrapped to avoid TypeScript module resolution errors
|
|
120
|
+
// ssh2 is an optional dependency - may not be installed
|
|
121
|
+
const moduleName = 'ssh2';
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
|
+
const ssh2Module = (await import(/* webpackIgnore: true */ moduleName));
|
|
124
|
+
const { generateKeyPairSync } = await import('node:crypto');
|
|
125
|
+
// Generate RSA host key
|
|
126
|
+
const { privateKey } = generateKeyPairSync('rsa', {
|
|
127
|
+
modulusLength: 2048,
|
|
128
|
+
privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
|
129
|
+
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
|
130
|
+
});
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
|
+
const ServerClass = ssh2Module.Server ?? ssh2Module.default?.Server;
|
|
133
|
+
if (!ServerClass)
|
|
134
|
+
return false;
|
|
135
|
+
this.ssh2Server = new ServerClass({
|
|
136
|
+
hostKeys: [privateKey],
|
|
137
|
+
banner: this.sshBanner.replace('SSH-2.0-', ''),
|
|
138
|
+
},
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
+
(client) => {
|
|
141
|
+
this.handleSsh2Client(client);
|
|
142
|
+
});
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
this.ssh2Server.listen(this.config.port, () => {
|
|
145
|
+
resolve(true);
|
|
146
|
+
});
|
|
147
|
+
this.ssh2Server.on('error', () => {
|
|
148
|
+
resolve(false);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Handle a real SSH-2.0 client connection via ssh2 module.
|
|
158
|
+
*/
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
160
|
+
handleSsh2Client(client) {
|
|
161
|
+
const remoteIP = client._sock?.remoteAddress ?? 'unknown';
|
|
162
|
+
const remotePort = client._sock?.remotePort ?? 0;
|
|
163
|
+
const session = this.createSession(remoteIP, remotePort);
|
|
164
|
+
this.addMitreTechnique(session.sessionId, 'T1110');
|
|
165
|
+
let authAttempts = 0;
|
|
166
|
+
const maxAuthBeforeGrant = 3;
|
|
167
|
+
client.on('authentication', (ctx) => {
|
|
168
|
+
authAttempts++;
|
|
169
|
+
const username = ctx.username ?? '';
|
|
170
|
+
const password = ctx.password ?? '';
|
|
171
|
+
if (ctx.method === 'password' && password) {
|
|
172
|
+
const shouldGrant = authAttempts >= maxAuthBeforeGrant;
|
|
173
|
+
this.recordCredential(session.sessionId, username, password, shouldGrant);
|
|
174
|
+
if (shouldGrant) {
|
|
175
|
+
this.addMitreTechnique(session.sessionId, 'T1078');
|
|
176
|
+
ctx.accept();
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
ctx.reject();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (ctx.method === 'none') {
|
|
183
|
+
ctx.reject();
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
ctx.reject();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
client.on('ready', () => {
|
|
190
|
+
logger.info(`SSH2 client authenticated: ${session.sessionId}`);
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
192
|
+
client.on('session', (accept) => {
|
|
193
|
+
const sshSession = accept();
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
195
|
+
sshSession.on('shell', (accept) => {
|
|
196
|
+
const stream = accept();
|
|
197
|
+
stream.write('admin@web-server-01:~$ ');
|
|
198
|
+
let inputBuffer = '';
|
|
199
|
+
stream.on('data', (data) => {
|
|
200
|
+
const char = data.toString();
|
|
201
|
+
// Echo back
|
|
202
|
+
stream.write(char);
|
|
203
|
+
if (char === '\r' || char === '\n') {
|
|
204
|
+
const command = inputBuffer.trim();
|
|
205
|
+
inputBuffer = '';
|
|
206
|
+
if (!command) {
|
|
207
|
+
stream.write('\r\nadmin@web-server-01:~$ ');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (command === 'exit' || command === 'quit' || command === 'logout') {
|
|
211
|
+
stream.write('\r\n');
|
|
212
|
+
stream.close();
|
|
213
|
+
client.end();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.recordCommand(session.sessionId, command);
|
|
217
|
+
this.addMitreTechnique(session.sessionId, 'T1059');
|
|
218
|
+
// Check for suspicious commands
|
|
219
|
+
for (const sus of SUSPICIOUS_COMMANDS) {
|
|
220
|
+
if (sus.pattern.test(command)) {
|
|
221
|
+
this.addMitreTechnique(session.sessionId, sus.technique);
|
|
222
|
+
this.recordEvent(session.sessionId, 'exploit_attempt', command, {
|
|
223
|
+
technique: sus.technique,
|
|
224
|
+
description: sus.description,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const response = this.getFakeResponse(command);
|
|
229
|
+
stream.write(`\r\n${response}\r\nadmin@web-server-01:~$ `);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
inputBuffer += char;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
sshSession.on('exec',
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
|
+
(accept, _reject, info) => {
|
|
239
|
+
const stream = accept();
|
|
240
|
+
const command = info.command;
|
|
241
|
+
this.recordCommand(session.sessionId, command);
|
|
242
|
+
this.addMitreTechnique(session.sessionId, 'T1059');
|
|
243
|
+
for (const sus of SUSPICIOUS_COMMANDS) {
|
|
244
|
+
if (sus.pattern.test(command)) {
|
|
245
|
+
this.addMitreTechnique(session.sessionId, sus.technique);
|
|
246
|
+
this.recordEvent(session.sessionId, 'exploit_attempt', command, {
|
|
247
|
+
technique: sus.technique,
|
|
248
|
+
description: sus.description,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const response = this.getFakeResponse(command);
|
|
253
|
+
stream.write(response + '\r\n');
|
|
254
|
+
stream.exit(0);
|
|
255
|
+
stream.close();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
client.on('close', () => {
|
|
260
|
+
this.endSession(session.sessionId);
|
|
261
|
+
});
|
|
262
|
+
client.on('error', (err) => {
|
|
263
|
+
logger.debug(`SSH2 client error: ${err.message}`);
|
|
264
|
+
this.endSession(session.sessionId);
|
|
265
|
+
});
|
|
266
|
+
// Timeout
|
|
267
|
+
const timeout = this.config.sessionTimeoutMs ?? 30_000;
|
|
268
|
+
setTimeout(() => {
|
|
269
|
+
try {
|
|
270
|
+
client.end();
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
/* ignore */
|
|
274
|
+
}
|
|
275
|
+
}, timeout);
|
|
276
|
+
}
|
|
277
|
+
async doStop() {
|
|
278
|
+
return new Promise((resolve) => {
|
|
279
|
+
if (this.ssh2Server) {
|
|
280
|
+
this.ssh2Server.close(() => resolve());
|
|
281
|
+
this.ssh2Server = null;
|
|
282
|
+
}
|
|
283
|
+
else if (this.server) {
|
|
284
|
+
this.server.close(() => resolve());
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
resolve();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
handleConnection(socket) {
|
|
292
|
+
const remoteIP = socket.remoteAddress ?? 'unknown';
|
|
293
|
+
const remotePort = socket.remotePort ?? 0;
|
|
294
|
+
const session = this.createSession(remoteIP, remotePort);
|
|
295
|
+
// Add MITRE technique for initial access
|
|
296
|
+
this.addMitreTechnique(session.sessionId, 'T1110');
|
|
297
|
+
// Send SSH banner
|
|
298
|
+
socket.write(`${this.sshBanner}\r\n`);
|
|
299
|
+
let authAttempts = 0;
|
|
300
|
+
let authenticated = false;
|
|
301
|
+
let inputBuffer = '';
|
|
302
|
+
// Set timeout
|
|
303
|
+
const timeout = this.config.sessionTimeoutMs ?? 30_000;
|
|
304
|
+
socket.setTimeout(timeout);
|
|
305
|
+
socket.on('data', (data) => {
|
|
306
|
+
const input = data.toString().trim();
|
|
307
|
+
inputBuffer += input;
|
|
308
|
+
if (!authenticated) {
|
|
309
|
+
// Simulate SSH auth protocol (simplified)
|
|
310
|
+
authAttempts++;
|
|
311
|
+
// Parse username:password from input
|
|
312
|
+
const parts = inputBuffer.split('\n').filter(Boolean);
|
|
313
|
+
for (const part of parts) {
|
|
314
|
+
const trimmed = part.trim();
|
|
315
|
+
if (trimmed.includes(':')) {
|
|
316
|
+
const [username, password] = trimmed.split(':');
|
|
317
|
+
if (username && password) {
|
|
318
|
+
const shouldGrant = this.config.maxConnections
|
|
319
|
+
? authAttempts >= (this.config.responseDelayMs ? 3 : 5)
|
|
320
|
+
: false;
|
|
321
|
+
this.recordCredential(session.sessionId, username, password, shouldGrant);
|
|
322
|
+
if (shouldGrant) {
|
|
323
|
+
authenticated = true;
|
|
324
|
+
this.addMitreTechnique(session.sessionId, 'T1078');
|
|
325
|
+
socket.write('admin@web-server-01:~$ ');
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
socket.write('Permission denied, please try again.\r\n');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
inputBuffer = '';
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
// Post-auth: record commands
|
|
337
|
+
const lines = input.split('\n').filter(Boolean);
|
|
338
|
+
for (const line of lines) {
|
|
339
|
+
const command = line.trim();
|
|
340
|
+
if (!command)
|
|
341
|
+
continue;
|
|
342
|
+
if (command === 'exit' || command === 'quit' || command === 'logout') {
|
|
343
|
+
socket.end();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this.recordCommand(session.sessionId, command);
|
|
347
|
+
this.addMitreTechnique(session.sessionId, 'T1059');
|
|
348
|
+
// Check for suspicious commands
|
|
349
|
+
for (const sus of SUSPICIOUS_COMMANDS) {
|
|
350
|
+
if (sus.pattern.test(command)) {
|
|
351
|
+
this.addMitreTechnique(session.sessionId, sus.technique);
|
|
352
|
+
this.recordEvent(session.sessionId, 'exploit_attempt', command, {
|
|
353
|
+
technique: sus.technique,
|
|
354
|
+
description: sus.description,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Send fake response
|
|
359
|
+
const response = this.getFakeResponse(command);
|
|
360
|
+
socket.write(`${response}\r\nadmin@web-server-01:~$ `);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
socket.on('timeout', () => {
|
|
365
|
+
logger.info(`SSH session timeout: ${session.sessionId} / SSH 連線逾時`);
|
|
366
|
+
socket.end();
|
|
367
|
+
});
|
|
368
|
+
socket.on('close', () => {
|
|
369
|
+
this.endSession(session.sessionId);
|
|
370
|
+
});
|
|
371
|
+
socket.on('error', (err) => {
|
|
372
|
+
logger.debug(`SSH socket error: ${err.message}`);
|
|
373
|
+
this.endSession(session.sessionId);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/** Get fake response for a command / 取得指令的假回應 */
|
|
377
|
+
getFakeResponse(command) {
|
|
378
|
+
// Check exact matches first
|
|
379
|
+
const exactMatch = FAKE_RESPONSES[command];
|
|
380
|
+
if (exactMatch)
|
|
381
|
+
return exactMatch;
|
|
382
|
+
// Check partial matches
|
|
383
|
+
for (const [cmd, response] of Object.entries(FAKE_RESPONSES)) {
|
|
384
|
+
if (command.startsWith(cmd))
|
|
385
|
+
return response;
|
|
386
|
+
}
|
|
387
|
+
// Generic responses
|
|
388
|
+
if (command.startsWith('cd '))
|
|
389
|
+
return '';
|
|
390
|
+
if (command.startsWith('echo '))
|
|
391
|
+
return command.slice(5);
|
|
392
|
+
if (command.startsWith('cat '))
|
|
393
|
+
return `cat: ${command.slice(4)}: No such file or directory`;
|
|
394
|
+
return `bash: ${command.split(' ')[0]}: command not found`;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=ssh-trap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh-trap.js","sourceRoot":"","sources":["../../src/services/ssh-trap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threat Cloud Uploader for PanguardTrap
|
|
3
|
+
* PanguardTrap 的 Threat Cloud 上傳器
|
|
4
|
+
*
|
|
5
|
+
* Batches and uploads TrapIntelligence reports to the Threat Cloud server.
|
|
6
|
+
* Supports offline queueing and automatic retry.
|
|
7
|
+
*
|
|
8
|
+
* 批次上傳 TrapIntelligence 報告至 Threat Cloud 伺服器。
|
|
9
|
+
* 支援離線排隊和自動重試。
|
|
10
|
+
*
|
|
11
|
+
* @module @panguard-ai/panguard-trap/threat-cloud-uploader
|
|
12
|
+
*/
|
|
13
|
+
import type { TrapIntelligence } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Uploads TrapIntelligence to the Threat Cloud server in batches.
|
|
16
|
+
* 批次上傳蜜罐情報至 Threat Cloud。
|
|
17
|
+
*/
|
|
18
|
+
export declare class ThreatCloudUploader {
|
|
19
|
+
private readonly endpoint;
|
|
20
|
+
private buffer;
|
|
21
|
+
private flushTimer;
|
|
22
|
+
/** Max items before immediate flush / 觸發立即上傳的最大數量 */
|
|
23
|
+
private static readonly BATCH_SIZE;
|
|
24
|
+
/** Max buffer size to prevent memory growth / 防止記憶體膨脹的上限 */
|
|
25
|
+
private static readonly MAX_BUFFER;
|
|
26
|
+
/** Periodic flush interval (ms) / 定期上傳間隔 */
|
|
27
|
+
private static readonly FLUSH_INTERVAL;
|
|
28
|
+
constructor(endpoint: string);
|
|
29
|
+
/**
|
|
30
|
+
* Queue a TrapIntelligence report for upload.
|
|
31
|
+
* 將蜜罐情報加入上傳佇列。
|
|
32
|
+
*/
|
|
33
|
+
enqueue(intel: TrapIntelligence): void;
|
|
34
|
+
/**
|
|
35
|
+
* Flush all buffered intel to the server.
|
|
36
|
+
* 上傳所有緩衝中的情報。
|
|
37
|
+
*/
|
|
38
|
+
flush(): Promise<number>;
|
|
39
|
+
/**
|
|
40
|
+
* Stop the periodic flush timer.
|
|
41
|
+
* 停止定期上傳計時器。
|
|
42
|
+
*/
|
|
43
|
+
stop(): void;
|
|
44
|
+
/** Get pending buffer size / 取得待上傳數量 */
|
|
45
|
+
getPendingCount(): number;
|
|
46
|
+
private toPayload;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=threat-cloud-uploader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threat-cloud-uploader.d.ts","sourceRoot":"","sources":["../src/threat-cloud-uploader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAkBnD;;;GAGG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,UAAU,CAA+C;IAEjE,qDAAqD;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAM;IACxC,4DAA4D;IAC5D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAO;IACzC,4CAA4C;IAC5C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAU;gBAEpC,QAAQ,EAAE,MAAM;IAW5B;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAetC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAuC9B;;;OAGG;IACH,IAAI,IAAI,IAAI;IAOZ,wCAAwC;IACxC,eAAe,IAAI,MAAM;IAQzB,OAAO,CAAC,SAAS;CAiBlB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threat Cloud Uploader for PanguardTrap
|
|
3
|
+
* PanguardTrap 的 Threat Cloud 上傳器
|
|
4
|
+
*
|
|
5
|
+
* Batches and uploads TrapIntelligence reports to the Threat Cloud server.
|
|
6
|
+
* Supports offline queueing and automatic retry.
|
|
7
|
+
*
|
|
8
|
+
* 批次上傳 TrapIntelligence 報告至 Threat Cloud 伺服器。
|
|
9
|
+
* 支援離線排隊和自動重試。
|
|
10
|
+
*
|
|
11
|
+
* @module @panguard-ai/panguard-trap/threat-cloud-uploader
|
|
12
|
+
*/
|
|
13
|
+
import { createLogger } from '@panguard-ai/core';
|
|
14
|
+
const logger = createLogger('panguard-trap:cloud-uploader');
|
|
15
|
+
/**
|
|
16
|
+
* Uploads TrapIntelligence to the Threat Cloud server in batches.
|
|
17
|
+
* 批次上傳蜜罐情報至 Threat Cloud。
|
|
18
|
+
*/
|
|
19
|
+
export class ThreatCloudUploader {
|
|
20
|
+
endpoint;
|
|
21
|
+
buffer = [];
|
|
22
|
+
flushTimer = null;
|
|
23
|
+
/** Max items before immediate flush / 觸發立即上傳的最大數量 */
|
|
24
|
+
static BATCH_SIZE = 20;
|
|
25
|
+
/** Max buffer size to prevent memory growth / 防止記憶體膨脹的上限 */
|
|
26
|
+
static MAX_BUFFER = 500;
|
|
27
|
+
/** Periodic flush interval (ms) / 定期上傳間隔 */
|
|
28
|
+
static FLUSH_INTERVAL = 30_000;
|
|
29
|
+
constructor(endpoint) {
|
|
30
|
+
this.endpoint = endpoint.replace(/\/+$/, '');
|
|
31
|
+
this.flushTimer = setInterval(() => {
|
|
32
|
+
void this.flush();
|
|
33
|
+
}, ThreatCloudUploader.FLUSH_INTERVAL);
|
|
34
|
+
// Allow Node process to exit if this is the only timer
|
|
35
|
+
if (this.flushTimer.unref)
|
|
36
|
+
this.flushTimer.unref();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Queue a TrapIntelligence report for upload.
|
|
40
|
+
* 將蜜罐情報加入上傳佇列。
|
|
41
|
+
*/
|
|
42
|
+
enqueue(intel) {
|
|
43
|
+
const payload = this.toPayload(intel);
|
|
44
|
+
this.buffer.push(payload);
|
|
45
|
+
// Cap buffer
|
|
46
|
+
if (this.buffer.length > ThreatCloudUploader.MAX_BUFFER) {
|
|
47
|
+
this.buffer = this.buffer.slice(-ThreatCloudUploader.MAX_BUFFER);
|
|
48
|
+
}
|
|
49
|
+
// Flush immediately if batch size reached
|
|
50
|
+
if (this.buffer.length >= ThreatCloudUploader.BATCH_SIZE) {
|
|
51
|
+
void this.flush();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Flush all buffered intel to the server.
|
|
56
|
+
* 上傳所有緩衝中的情報。
|
|
57
|
+
*/
|
|
58
|
+
async flush() {
|
|
59
|
+
if (this.buffer.length === 0)
|
|
60
|
+
return 0;
|
|
61
|
+
const batch = [...this.buffer];
|
|
62
|
+
this.buffer = [];
|
|
63
|
+
try {
|
|
64
|
+
const url = `${this.endpoint}/api/trap-intel`;
|
|
65
|
+
const payload = JSON.stringify(batch);
|
|
66
|
+
const res = await fetch(url, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
body: payload,
|
|
70
|
+
signal: AbortSignal.timeout(10_000),
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
|
74
|
+
}
|
|
75
|
+
logger.info(`Uploaded ${batch.length} trap intel reports to Threat Cloud / ` +
|
|
76
|
+
`已上傳 ${batch.length} 筆蜜罐情報`);
|
|
77
|
+
return batch.length;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
81
|
+
logger.error(`Trap intel upload failed: ${msg} / 上傳失敗: ${msg}`);
|
|
82
|
+
// Re-buffer failed batch (at front so ordering is preserved)
|
|
83
|
+
this.buffer.unshift(...batch);
|
|
84
|
+
if (this.buffer.length > ThreatCloudUploader.MAX_BUFFER) {
|
|
85
|
+
this.buffer = this.buffer.slice(-ThreatCloudUploader.MAX_BUFFER);
|
|
86
|
+
}
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Stop the periodic flush timer.
|
|
92
|
+
* 停止定期上傳計時器。
|
|
93
|
+
*/
|
|
94
|
+
stop() {
|
|
95
|
+
if (this.flushTimer) {
|
|
96
|
+
clearInterval(this.flushTimer);
|
|
97
|
+
this.flushTimer = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Get pending buffer size / 取得待上傳數量 */
|
|
101
|
+
getPendingCount() {
|
|
102
|
+
return this.buffer.length;
|
|
103
|
+
}
|
|
104
|
+
// -------------------------------------------------------------------------
|
|
105
|
+
// Internal
|
|
106
|
+
// -------------------------------------------------------------------------
|
|
107
|
+
toPayload(intel) {
|
|
108
|
+
return {
|
|
109
|
+
timestamp: intel.timestamp.toISOString(),
|
|
110
|
+
serviceType: intel.serviceType,
|
|
111
|
+
sourceIP: intel.sourceIP,
|
|
112
|
+
attackType: intel.attackType,
|
|
113
|
+
mitreTechniques: [...intel.mitreTechniques],
|
|
114
|
+
skillLevel: intel.skillLevel,
|
|
115
|
+
intent: intel.intent,
|
|
116
|
+
tools: [...intel.tools],
|
|
117
|
+
topCredentials: intel.topCredentials.map((c) => ({
|
|
118
|
+
username: c.username,
|
|
119
|
+
count: c.count,
|
|
120
|
+
})),
|
|
121
|
+
region: intel.region,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=threat-cloud-uploader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threat-cloud-uploader.js","sourceRoot":"","sources":["../src/threat-cloud-uploader.ts"],"names":[],"mappings":"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"}
|