@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.
Files changed (69) hide show
  1. package/dist/cli/index.d.ts +45 -0
  2. package/dist/cli/index.d.ts.map +1 -0
  3. package/dist/cli/index.js +298 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/index.d.ts +23 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +26 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/intel/index.d.ts +38 -0
  10. package/dist/intel/index.d.ts.map +1 -0
  11. package/dist/intel/index.js +157 -0
  12. package/dist/intel/index.js.map +1 -0
  13. package/dist/profiler/attacker-profiler.d.ts +68 -0
  14. package/dist/profiler/attacker-profiler.d.ts.map +1 -0
  15. package/dist/profiler/attacker-profiler.js +316 -0
  16. package/dist/profiler/attacker-profiler.js.map +1 -0
  17. package/dist/profiler/index.d.ts +8 -0
  18. package/dist/profiler/index.d.ts.map +1 -0
  19. package/dist/profiler/index.js +8 -0
  20. package/dist/profiler/index.js.map +1 -0
  21. package/dist/services/base-service.d.ts +61 -0
  22. package/dist/services/base-service.d.ts.map +1 -0
  23. package/dist/services/base-service.js +190 -0
  24. package/dist/services/base-service.js.map +1 -0
  25. package/dist/services/generic-trap.d.ts +22 -0
  26. package/dist/services/generic-trap.d.ts.map +1 -0
  27. package/dist/services/generic-trap.js +439 -0
  28. package/dist/services/generic-trap.js.map +1 -0
  29. package/dist/services/http-trap.d.ts +36 -0
  30. package/dist/services/http-trap.d.ts.map +1 -0
  31. package/dist/services/http-trap.js +218 -0
  32. package/dist/services/http-trap.js.map +1 -0
  33. package/dist/services/index.d.ts +26 -0
  34. package/dist/services/index.d.ts.map +1 -0
  35. package/dist/services/index.js +52 -0
  36. package/dist/services/index.js.map +1 -0
  37. package/dist/services/mysql-trap.d.ts +22 -0
  38. package/dist/services/mysql-trap.d.ts.map +1 -0
  39. package/dist/services/mysql-trap.js +374 -0
  40. package/dist/services/mysql-trap.js.map +1 -0
  41. package/dist/services/rdp-trap.d.ts +21 -0
  42. package/dist/services/rdp-trap.d.ts.map +1 -0
  43. package/dist/services/rdp-trap.js +299 -0
  44. package/dist/services/rdp-trap.js.map +1 -0
  45. package/dist/services/redis-trap.d.ts +21 -0
  46. package/dist/services/redis-trap.d.ts.map +1 -0
  47. package/dist/services/redis-trap.js +321 -0
  48. package/dist/services/redis-trap.js.map +1 -0
  49. package/dist/services/smb-trap.d.ts +21 -0
  50. package/dist/services/smb-trap.d.ts.map +1 -0
  51. package/dist/services/smb-trap.js +358 -0
  52. package/dist/services/smb-trap.js.map +1 -0
  53. package/dist/services/ssh-trap.d.ts +43 -0
  54. package/dist/services/ssh-trap.d.ts.map +1 -0
  55. package/dist/services/ssh-trap.js +397 -0
  56. package/dist/services/ssh-trap.js.map +1 -0
  57. package/dist/threat-cloud-uploader.d.ts +48 -0
  58. package/dist/threat-cloud-uploader.d.ts.map +1 -0
  59. package/dist/threat-cloud-uploader.js +125 -0
  60. package/dist/threat-cloud-uploader.js.map +1 -0
  61. package/dist/trap-engine.d.ts +80 -0
  62. package/dist/trap-engine.d.ts.map +1 -0
  63. package/dist/trap-engine.js +279 -0
  64. package/dist/trap-engine.js.map +1 -0
  65. package/dist/types.d.ts +229 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +89 -0
  68. package/dist/types.js.map +1 -0
  69. package/package.json +37 -0
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Base trap service implementation
3
+ * 蜜罐基底服務實作
4
+ *
5
+ * Provides common functionality for all trap services:
6
+ * - TCP server lifecycle
7
+ * - Session management
8
+ * - Event recording
9
+ * 提供所有蜜罐服務的共用功能:
10
+ * - TCP 伺服器生命週期
11
+ * - 連線管理
12
+ * - 事件記錄
13
+ *
14
+ * @module @panguard-ai/panguard-trap/services/base-service
15
+ */
16
+ import { createLogger } from '@panguard-ai/core';
17
+ const logger = createLogger('panguard-trap:service:base');
18
+ let sessionCounter = 0;
19
+ /** Generate unique session ID / 產生唯一連線 ID */
20
+ function generateSessionId(serviceType) {
21
+ sessionCounter += 1;
22
+ const ts = Date.now().toString(36);
23
+ const cnt = sessionCounter.toString(36).padStart(4, '0');
24
+ return `${serviceType}-${ts}-${cnt}`;
25
+ }
26
+ /**
27
+ * Abstract base class for all trap services
28
+ * 所有蜜罐服務的抽象基底類別
29
+ */
30
+ export class BaseTrapService {
31
+ serviceType;
32
+ _status = 'stopped';
33
+ config;
34
+ activeSessions = new Map();
35
+ completedSessionCount = 0;
36
+ sessionHandlers = [];
37
+ constructor(config) {
38
+ this.serviceType = config.type;
39
+ this.config = config;
40
+ }
41
+ get status() {
42
+ return this._status;
43
+ }
44
+ setStatus(status) {
45
+ this._status = status;
46
+ }
47
+ /**
48
+ * Start the service
49
+ * 啟動服務
50
+ */
51
+ async start() {
52
+ if (this._status === 'running')
53
+ return;
54
+ this._status = 'starting';
55
+ try {
56
+ await this.doStart();
57
+ this._status = 'running';
58
+ logger.info(`${this.serviceType} trap started on port ${this.config.port} / ${this.serviceType} 蜜罐已啟動於埠 ${this.config.port}`);
59
+ }
60
+ catch (err) {
61
+ this._status = 'error';
62
+ const msg = err instanceof Error ? err.message : String(err);
63
+ logger.error(`${this.serviceType} trap start failed: ${msg} / 啟動失敗: ${msg}`);
64
+ throw err;
65
+ }
66
+ }
67
+ /**
68
+ * Stop the service
69
+ * 停止服務
70
+ */
71
+ async stop() {
72
+ if (this._status === 'stopped')
73
+ return;
74
+ try {
75
+ // Close all active sessions
76
+ for (const [sessionId, session] of this.activeSessions) {
77
+ this.endSession(sessionId, session);
78
+ }
79
+ await this.doStop();
80
+ this._status = 'stopped';
81
+ logger.info(`${this.serviceType} trap stopped / ${this.serviceType} 蜜罐已停止`);
82
+ }
83
+ catch (err) {
84
+ this._status = 'error';
85
+ throw err;
86
+ }
87
+ }
88
+ getActiveSessions() {
89
+ return Array.from(this.activeSessions.values());
90
+ }
91
+ getTotalSessionCount() {
92
+ return this.completedSessionCount + this.activeSessions.size;
93
+ }
94
+ onSession(handler) {
95
+ this.sessionHandlers.push(handler);
96
+ }
97
+ // -------------------------------------------------------------------------
98
+ // Session Management (for subclasses)
99
+ // 連線管理(供子類別使用)
100
+ // -------------------------------------------------------------------------
101
+ /** Create a new session / 建立新連線 */
102
+ createSession(sourceIP, sourcePort) {
103
+ const session = {
104
+ sessionId: generateSessionId(this.serviceType),
105
+ serviceType: this.serviceType,
106
+ sourceIP,
107
+ sourcePort,
108
+ startTime: new Date(),
109
+ events: [],
110
+ credentials: [],
111
+ commands: [],
112
+ mitreTechniques: [],
113
+ };
114
+ this.activeSessions.set(session.sessionId, session);
115
+ this.recordEvent(session.sessionId, 'connection', `${sourceIP}:${sourcePort}`);
116
+ logger.info(`New ${this.serviceType} session from ${sourceIP}:${sourcePort} (${session.sessionId}) / 新連線`);
117
+ return session;
118
+ }
119
+ /** End a session / 結束連線 */
120
+ endSession(sessionId, session) {
121
+ const s = session ?? this.activeSessions.get(sessionId);
122
+ if (!s)
123
+ return undefined;
124
+ s.endTime = new Date();
125
+ s.durationMs = s.endTime.getTime() - s.startTime.getTime();
126
+ this.recordEvent(sessionId, 'disconnection', `duration=${s.durationMs}ms`);
127
+ this.activeSessions.delete(sessionId);
128
+ this.completedSessionCount += 1;
129
+ // Notify session handlers
130
+ for (const handler of this.sessionHandlers) {
131
+ try {
132
+ handler(s);
133
+ }
134
+ catch {
135
+ // ignore handler errors
136
+ }
137
+ }
138
+ logger.info(`Session ended: ${sessionId} (${s.durationMs}ms, ${s.events.length} events) / 連線結束`);
139
+ return s;
140
+ }
141
+ /** Record an event in a session / 在連線中記錄事件 */
142
+ recordEvent(sessionId, type, data, details) {
143
+ const session = this.activeSessions.get(sessionId);
144
+ if (!session)
145
+ return undefined;
146
+ const event = {
147
+ timestamp: new Date(),
148
+ type,
149
+ data,
150
+ details,
151
+ };
152
+ session.events.push(event);
153
+ return event;
154
+ }
155
+ /** Record a credential attempt / 記錄認證嘗試 */
156
+ recordCredential(sessionId, username, password, grantedAccess) {
157
+ const session = this.activeSessions.get(sessionId);
158
+ if (!session)
159
+ return;
160
+ const attempt = {
161
+ timestamp: new Date(),
162
+ username,
163
+ password,
164
+ grantedAccess,
165
+ };
166
+ session.credentials.push(attempt);
167
+ this.recordEvent(sessionId, 'authentication_attempt', `${username}:***`, {
168
+ username,
169
+ grantedAccess,
170
+ });
171
+ }
172
+ /** Record a command input / 記錄指令輸入 */
173
+ recordCommand(sessionId, command) {
174
+ const session = this.activeSessions.get(sessionId);
175
+ if (!session)
176
+ return;
177
+ session.commands.push(command);
178
+ this.recordEvent(sessionId, 'command_input', command);
179
+ }
180
+ /** Add MITRE technique to session / 新增 MITRE 技術到連線 */
181
+ addMitreTechnique(sessionId, technique) {
182
+ const session = this.activeSessions.get(sessionId);
183
+ if (!session)
184
+ return;
185
+ if (!session.mitreTechniques.includes(technique)) {
186
+ session.mitreTechniques.push(technique);
187
+ }
188
+ }
189
+ }
190
+ //# sourceMappingURL=base-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-service.js","sourceRoot":"","sources":["../../src/services/base-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Generic TCP Honeypot Service
3
+ * 通用 TCP 蜜罐服務
4
+ *
5
+ * Handles text-based protocols (FTP, Telnet) with enhanced command
6
+ * simulation, MITRE ATT&CK detection, and realistic interaction.
7
+ *
8
+ * Binary protocols (MySQL, Redis, SMB, RDP) have dedicated service files.
9
+ *
10
+ * @module @panguard-ai/panguard-trap/services/generic-trap
11
+ */
12
+ import type { TrapServiceConfig } from '../types.js';
13
+ import { BaseTrapService } from './base-service.js';
14
+ export declare class GenericTrapService extends BaseTrapService {
15
+ private server;
16
+ private readonly handler;
17
+ constructor(config: TrapServiceConfig);
18
+ protected doStart(): Promise<void>;
19
+ protected doStop(): Promise<void>;
20
+ private handleConnection;
21
+ }
22
+ //# sourceMappingURL=generic-trap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generic-trap.d.ts","sourceRoot":"","sources":["../../src/services/generic-trap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA2ZpD,qBAAa,kBAAmB,SAAQ,eAAe;IACrD,OAAO,CAAC,MAAM,CAAmE;IACjF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;gBAE9B,MAAM,EAAE,iBAAiB;cASrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;cAUxB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAUvC,OAAO,CAAC,gBAAgB;CAyFzB"}
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Generic TCP Honeypot Service
3
+ * 通用 TCP 蜜罐服務
4
+ *
5
+ * Handles text-based protocols (FTP, Telnet) with enhanced command
6
+ * simulation, MITRE ATT&CK detection, and realistic interaction.
7
+ *
8
+ * Binary protocols (MySQL, Redis, SMB, RDP) have dedicated service files.
9
+ *
10
+ * @module @panguard-ai/panguard-trap/services/generic-trap
11
+ */
12
+ import { createLogger } from '@panguard-ai/core';
13
+ import { BaseTrapService } from './base-service.js';
14
+ const logger = createLogger('panguard-trap:service:generic');
15
+ // ---------------------------------------------------------------------------
16
+ // Telnet IAC negotiation bytes (sent before banner)
17
+ // ---------------------------------------------------------------------------
18
+ const TELNET_IAC = Buffer.from([
19
+ 0xff,
20
+ 0xfb,
21
+ 0x01, // IAC WILL ECHO
22
+ 0xff,
23
+ 0xfb,
24
+ 0x03, // IAC WILL SUPPRESS-GO-AHEAD
25
+ 0xff,
26
+ 0xfd,
27
+ 0x18, // IAC DO TERMINAL-TYPE
28
+ 0xff,
29
+ 0xfd,
30
+ 0x1f, // IAC DO NAWS (window size)
31
+ ]);
32
+ // ---------------------------------------------------------------------------
33
+ // FTP MITRE patterns
34
+ // ---------------------------------------------------------------------------
35
+ const FTP_MITRE_PATTERNS = [
36
+ [/STOR\s+.*\.(php|jsp|asp|sh|py|pl|cgi)/i, 'T1505.003'], // Web shell upload
37
+ [/STOR\s+.*\.(exe|dll|bat|ps1|vbs|msi)/i, 'T1105'], // Ingress tool transfer
38
+ [/RETR\s+.*(passwd|shadow|\.ssh|\.env|\.htaccess|\.git)/i, 'T1005'], // Data from local system
39
+ [/SITE\s+EXEC/i, 'T1059'], // Command execution
40
+ [/SITE\s+CHMOD\s+777/i, 'T1222'], // File permission modification
41
+ [/MKD\s+\.\./i, 'T1083'], // Directory traversal
42
+ [/CWD\s+\.\./i, 'T1083'],
43
+ [/DELE\s+/i, 'T1485'], // Data destruction
44
+ [/RMD\s+/i, 'T1485'],
45
+ ];
46
+ // ---------------------------------------------------------------------------
47
+ // Telnet MITRE patterns (post-auth shell commands)
48
+ // ---------------------------------------------------------------------------
49
+ const TELNET_MITRE_PATTERNS = [
50
+ [/wget\s|curl\s/i, 'T1105'], // Ingress tool transfer
51
+ [/chmod\s+777/i, 'T1222'], // File permission modification
52
+ [/\/etc\/(shadow|passwd)/i, 'T1003'], // OS credential dumping
53
+ [/crontab/i, 'T1053'], // Scheduled task
54
+ [/base64\s+-d|echo.*\|\s*(sh|bash)/i, 'T1140'], // Deobfuscation
55
+ [/\bnc\b|\bnetcat\b/i, 'T1571'], // Non-standard port
56
+ [/iptables\s+-D/i, 'T1562'], // Impair defenses
57
+ [/rm\s+-rf\s+\//i, 'T1485'], // Data destruction
58
+ [/xmrig|minerd|cryptonight/i, 'T1496'], // Resource hijacking
59
+ [/ssh-keygen|authorized_keys/i, 'T1098'], // Account manipulation
60
+ [/\buname\b|\bwhoami\b|\bid\b/i, 'T1082'], // System information discovery
61
+ [/ifconfig|ip\s+addr/i, 'T1016'], // System network configuration
62
+ [/\bps\b\s+(aux|ef)/i, 'T1057'], // Process discovery
63
+ [/cat\s+\/etc\/passwd/i, 'T1087'], // Account discovery
64
+ [/find\s+.*-perm/i, 'T1083'], // File & directory discovery
65
+ ];
66
+ // ---------------------------------------------------------------------------
67
+ // Fake shell responses for Telnet post-auth
68
+ // ---------------------------------------------------------------------------
69
+ const FAKE_SHELL_RESPONSES = {
70
+ id: 'uid=1000(admin) gid=1000(admin) groups=1000(admin),27(sudo)',
71
+ whoami: 'admin',
72
+ 'uname -a': 'Linux server 5.15.0-91-generic #101-Ubuntu SMP Tue Nov 14 13:30:08 UTC 2023 x86_64 GNU/Linux',
73
+ uname: 'Linux',
74
+ hostname: 'prod-web-01',
75
+ uptime: ' 14:32:01 up 42 days, 3:17, 1 user, load average: 0.08, 0.03, 0.01',
76
+ pwd: '/home/admin',
77
+ ls: 'backup.tar.gz deploy.sh logs www',
78
+ 'ls -la': 'total 32\ndrwxr-xr-x 4 admin admin 4096 Jan 15 10:00 .\ndrwxr-xr-x 3 root root 4096 Jan 1 00:00 ..\n-rw------- 1 admin admin 512 Jan 15 10:30 .bash_history\n-rw-r--r-- 1 admin admin 8192 Jan 14 12:00 backup.tar.gz\n-rwxr-xr-x 1 admin admin 512 Jan 12 08:00 deploy.sh\ndrwxr-xr-x 2 admin admin 4096 Jan 15 10:00 logs\ndrwxr-xr-x 5 www-data www-data 4096 Jan 10 14:00 www',
79
+ 'cat /etc/passwd': 'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nadmin:x:1000:1000:admin:/home/admin:/bin/bash\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nmysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/false',
80
+ ifconfig: 'eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500\n inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.255',
81
+ 'ps aux': 'USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\nroot 1 0.0 0.1 169024 11264 ? Ss Jan01 0:05 /sbin/init\nadmin 1234 0.0 0.0 8072 4096 pts/0 Ss 14:32 0:00 -bash\nwww-data 5678 0.0 0.5 256000 40960 ? S Jan10 1:23 nginx: worker',
82
+ w: ' 14:32:01 up 42 days, 3:17, 1 user, load average: 0.08, 0.03, 0.01\nUSER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT\nadmin pts/0 10.0.2.2 14:32 0.00s 0.01s 0.00s w',
83
+ 'df -h': 'Filesystem Size Used Avail Use% Mounted on\n/dev/sda1 50G 12G 35G 26% /\ntmpfs 2.0G 0 2.0G 0% /dev/shm',
84
+ 'free -m': ' total used free shared buff/cache available\nMem: 3951 1024 512 128 2415 2600\nSwap: 2048 0 2048',
85
+ 'netstat -tlnp': 'Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program\ntcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 845/sshd\ntcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 5678/nginx\ntcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN 1122/mysqld',
86
+ 'cat /etc/os-release': 'NAME="Ubuntu"\nVERSION="22.04.3 LTS (Jammy Jellyfish)"\nID=ubuntu\nVERSION_ID="22.04"',
87
+ };
88
+ // ---------------------------------------------------------------------------
89
+ // FTP Protocol Handler (enhanced)
90
+ // ---------------------------------------------------------------------------
91
+ const ftpHandler = {
92
+ initialTechnique: 'T1110',
93
+ getBanner(config) {
94
+ const banner = config.banner ?? '220 ProFTPD 1.3.8 Server (Panguard) [::ffff:0.0.0.0]\r\n';
95
+ return Buffer.from(banner, 'utf-8');
96
+ },
97
+ handleInput(input, state) {
98
+ const parts = input.trim().split(' ');
99
+ const command = parts[0]?.toUpperCase() ?? '';
100
+ const arg = parts.slice(1).join(' ');
101
+ // MITRE detection
102
+ const mitre = [];
103
+ for (const [pattern, technique] of FTP_MITRE_PATTERNS) {
104
+ if (pattern.test(input)) {
105
+ mitre.push(technique);
106
+ }
107
+ }
108
+ // Pre-auth commands
109
+ if (command === 'USER') {
110
+ state.username = arg;
111
+ return { response: `331 Password required for ${arg}\r\n` };
112
+ }
113
+ if (command === 'PASS') {
114
+ state.authAttempts++;
115
+ if (state.authAttempts >= 3) {
116
+ state.authenticated = true;
117
+ return {
118
+ response: `230 User ${state.username ?? 'anonymous'} logged in\r\n`,
119
+ mitre: ['T1078'], // Valid accounts
120
+ };
121
+ }
122
+ return { response: '530 Login incorrect.\r\n' };
123
+ }
124
+ if (command === 'QUIT' || command === 'EXIT') {
125
+ return { response: null };
126
+ }
127
+ if (command === 'FEAT') {
128
+ return { response: '211-Features:\r\n PASV\r\n UTF8\r\n SIZE\r\n MDTM\r\n211 End\r\n' };
129
+ }
130
+ if (command === 'AUTH' && arg.toUpperCase() === 'TLS') {
131
+ return { response: '534 TLS not available\r\n' };
132
+ }
133
+ if (!state.authenticated) {
134
+ return { response: '530 Please login with USER and PASS.\r\n' };
135
+ }
136
+ // Post-auth commands
137
+ switch (command) {
138
+ case 'LIST':
139
+ case 'NLST':
140
+ case 'LS':
141
+ return {
142
+ response: '150 Opening ASCII mode data connection for file list\r\n' +
143
+ '-rw-r--r-- 1 admin admin 8192 Jan 14 12:00 backup.tar.gz\r\n' +
144
+ '-rwxr-xr-x 1 admin admin 512 Jan 12 08:00 deploy.sh\r\n' +
145
+ 'drwxr-xr-x 2 admin admin 4096 Jan 15 10:00 logs\r\n' +
146
+ 'drwxr-xr-x 5 www-data www-data 4096 Jan 10 14:00 www\r\n' +
147
+ '-rw-r--r-- 1 admin admin 16384 Jan 13 09:00 database.sql\r\n' +
148
+ '-rw------- 1 admin admin 256 Jan 11 07:00 .env\r\n' +
149
+ '226 Transfer complete\r\n',
150
+ };
151
+ case 'PWD':
152
+ return { response: `257 "${state.cwd}" is current directory\r\n` };
153
+ case 'CWD':
154
+ if (arg.includes('..')) {
155
+ const parent = state.cwd.split('/').slice(0, -1).join('/') || '/';
156
+ state.cwd = parent;
157
+ }
158
+ else if (arg.startsWith('/')) {
159
+ state.cwd = arg;
160
+ }
161
+ else {
162
+ state.cwd = `${state.cwd}/${arg}`.replace(/\/+/g, '/');
163
+ }
164
+ return { response: `250 CWD command successful. "${state.cwd}"\r\n`, mitre };
165
+ case 'TYPE':
166
+ return { response: `200 Type set to ${arg === 'A' ? 'A' : 'I'}\r\n` };
167
+ case 'PASV':
168
+ state.pasv = true;
169
+ // Fake PASV response (address doesn't matter - honeypot)
170
+ return { response: '227 Entering Passive Mode (10,0,2,15,156,64)\r\n' };
171
+ case 'PORT':
172
+ return { response: '200 PORT command successful\r\n' };
173
+ case 'SYST':
174
+ return { response: '215 UNIX Type: L8\r\n' };
175
+ case 'SIZE':
176
+ return { response: `213 ${Math.floor(Math.random() * 50000)}\r\n` };
177
+ case 'MDTM':
178
+ return { response: '213 20250115120000\r\n' };
179
+ case 'RETR':
180
+ return {
181
+ response: '150 Opening BINARY mode data connection\r\n550 Permission denied\r\n',
182
+ mitre,
183
+ eventType: 'file_download',
184
+ };
185
+ case 'STOR':
186
+ return {
187
+ response: '150 Opening BINARY mode data connection\r\n226 Transfer complete\r\n',
188
+ mitre,
189
+ eventType: 'file_upload',
190
+ };
191
+ case 'DELE':
192
+ return { response: `250 DELE command successful. "${arg}"\r\n`, mitre };
193
+ case 'MKD':
194
+ return { response: `257 "${arg}" directory created\r\n`, mitre };
195
+ case 'RMD':
196
+ return { response: `250 RMD command successful. "${arg}"\r\n`, mitre };
197
+ case 'SITE':
198
+ return { response: '200 SITE command ok\r\n', mitre };
199
+ case 'STAT':
200
+ return {
201
+ response: '211-Status of ProFTPD 1.3.8\r\n' +
202
+ ` Connected to ${state.username ?? 'anonymous'}\r\n` +
203
+ ` Working directory: ${state.cwd}\r\n` +
204
+ '211 End of status\r\n',
205
+ };
206
+ case 'HELP':
207
+ return {
208
+ response: '214-The following commands are recognized:\r\n' +
209
+ ' USER PASS QUIT LIST PWD CWD TYPE PASV PORT SYST SIZE MDTM RETR STOR DELE MKD RMD STAT HELP FEAT NLST SITE\r\n' +
210
+ '214 Help OK.\r\n',
211
+ };
212
+ case 'NOOP':
213
+ return { response: '200 NOOP ok\r\n' };
214
+ default:
215
+ return { response: `500 '${command}': command not understood\r\n` };
216
+ }
217
+ },
218
+ };
219
+ // ---------------------------------------------------------------------------
220
+ // Telnet Protocol Handler (enhanced with IAC and shell simulation)
221
+ // ---------------------------------------------------------------------------
222
+ const telnetHandler = {
223
+ initialTechnique: 'T1110',
224
+ getBanner(config) {
225
+ const os = config.banner ?? 'Ubuntu 22.04 LTS';
226
+ const textBanner = Buffer.from(`\r\n${os}\r\nlogin: `, 'utf-8');
227
+ return Buffer.concat([TELNET_IAC, textBanner]);
228
+ },
229
+ handleInput(input, state) {
230
+ // Strip any IAC sequences from client input
231
+ const trimmed = input.replace(/\xff[\xfb\xfc\xfd\xfe]./g, '').trim();
232
+ if (!trimmed)
233
+ return { response: '' };
234
+ // Pre-auth: username
235
+ if (!state.username) {
236
+ state.username = trimmed;
237
+ return { response: 'Password: ' };
238
+ }
239
+ // Pre-auth: password
240
+ if (!state.authenticated) {
241
+ state.authAttempts++;
242
+ if (state.authAttempts >= 3) {
243
+ state.authenticated = true;
244
+ return {
245
+ response: `\r\nWelcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-91-generic x86_64)\r\n\r\n` +
246
+ ` * Documentation: https://help.ubuntu.com\r\n` +
247
+ ` * Management: https://landscape.canonical.com\r\n` +
248
+ `\r\nLast login: Mon Jan 15 10:30:00 2025 from 10.0.2.2\r\n` +
249
+ `${state.username}@prod-web-01:~$ `,
250
+ mitre: ['T1078'], // Valid accounts
251
+ };
252
+ }
253
+ state.username = undefined;
254
+ return { response: '\r\nLogin incorrect\r\nlogin: ' };
255
+ }
256
+ // Post-auth: shell simulation
257
+ if (trimmed === 'exit' || trimmed === 'quit' || trimmed === 'logout') {
258
+ return { response: null };
259
+ }
260
+ // MITRE detection
261
+ const mitre = [];
262
+ for (const [pattern, technique] of TELNET_MITRE_PATTERNS) {
263
+ if (pattern.test(trimmed)) {
264
+ mitre.push(technique);
265
+ }
266
+ }
267
+ // Check fake shell responses
268
+ const exactResponse = FAKE_SHELL_RESPONSES[trimmed];
269
+ if (exactResponse) {
270
+ return {
271
+ response: `${exactResponse}\r\n${state.username}@prod-web-01:${state.cwd}$ `,
272
+ mitre: mitre.length > 0 ? mitre : undefined,
273
+ };
274
+ }
275
+ // Pattern-based responses
276
+ const cmd = trimmed.split(' ')[0] ?? '';
277
+ if (cmd === 'cd') {
278
+ const dir = trimmed.split(' ')[1] ?? '/home/admin';
279
+ if (dir === '~' || dir === '') {
280
+ state.cwd = '~';
281
+ }
282
+ else if (dir === '..') {
283
+ state.cwd = state.cwd === '~' ? '/' : '~';
284
+ }
285
+ else {
286
+ state.cwd = dir;
287
+ }
288
+ return {
289
+ response: `${state.username}@prod-web-01:${state.cwd}$ `,
290
+ mitre: mitre.length > 0 ? mitre : undefined,
291
+ };
292
+ }
293
+ if (cmd === 'echo') {
294
+ const output = trimmed.slice(5);
295
+ return {
296
+ response: `${output}\r\n${state.username}@prod-web-01:${state.cwd}$ `,
297
+ mitre: mitre.length > 0 ? mitre : undefined,
298
+ };
299
+ }
300
+ if (cmd === 'cat') {
301
+ const file = trimmed.split(' ')[1] ?? '';
302
+ const known = FAKE_SHELL_RESPONSES[`cat ${file}`];
303
+ if (known) {
304
+ return {
305
+ response: `${known}\r\n${state.username}@prod-web-01:${state.cwd}$ `,
306
+ mitre: mitre.length > 0 ? mitre : undefined,
307
+ };
308
+ }
309
+ return {
310
+ response: `cat: ${file}: No such file or directory\r\n${state.username}@prod-web-01:${state.cwd}$ `,
311
+ mitre: mitre.length > 0 ? mitre : undefined,
312
+ };
313
+ }
314
+ if (cmd === 'wget' || cmd === 'curl') {
315
+ return {
316
+ response: `--2025-01-15 14:32:05--\r\nConnecting... failed: Connection refused.\r\n${state.username}@prod-web-01:${state.cwd}$ `,
317
+ mitre: ['T1105', ...mitre],
318
+ eventType: 'exploit_attempt',
319
+ };
320
+ }
321
+ // Generic "command not found" for unknown commands
322
+ return {
323
+ response: `bash: ${cmd}: command not found\r\n${state.username}@prod-web-01:${state.cwd}$ `,
324
+ mitre: mitre.length > 0 ? mitre : undefined,
325
+ };
326
+ },
327
+ };
328
+ // ---------------------------------------------------------------------------
329
+ // Handler Registry (FTP + Telnet only; other protocols have dedicated files)
330
+ // ---------------------------------------------------------------------------
331
+ const PROTOCOL_HANDLERS = {
332
+ ftp: ftpHandler,
333
+ telnet: telnetHandler,
334
+ };
335
+ // ---------------------------------------------------------------------------
336
+ // Generic Trap Service
337
+ // ---------------------------------------------------------------------------
338
+ export class GenericTrapService extends BaseTrapService {
339
+ server = null;
340
+ handler;
341
+ constructor(config) {
342
+ super(config);
343
+ const handler = PROTOCOL_HANDLERS[config.type];
344
+ if (!handler) {
345
+ throw new Error(`No protocol handler for service type: ${config.type}`);
346
+ }
347
+ this.handler = handler;
348
+ }
349
+ async doStart() {
350
+ const net = await import('node:net');
351
+ this.server = net.createServer((socket) => this.handleConnection(socket));
352
+ return new Promise((resolve, reject) => {
353
+ this.server.listen(this.config.port, () => resolve());
354
+ this.server.on('error', reject);
355
+ });
356
+ }
357
+ async doStop() {
358
+ return new Promise((resolve) => {
359
+ if (this.server) {
360
+ this.server.close(() => resolve());
361
+ }
362
+ else {
363
+ resolve();
364
+ }
365
+ });
366
+ }
367
+ handleConnection(socket) {
368
+ const remoteIP = socket.remoteAddress ?? 'unknown';
369
+ const remotePort = socket.remotePort ?? 0;
370
+ const session = this.createSession(remoteIP, remotePort);
371
+ this.addMitreTechnique(session.sessionId, this.handler.initialTechnique);
372
+ const state = {
373
+ authenticated: false,
374
+ authAttempts: 0,
375
+ cwd: '/home/admin',
376
+ pasv: false,
377
+ };
378
+ // Send banner (may include binary IAC bytes for Telnet)
379
+ const banner = this.handler.getBanner(this.config);
380
+ if (banner.length > 0) {
381
+ socket.write(banner);
382
+ }
383
+ const timeout = this.config.sessionTimeoutMs ?? 30_000;
384
+ socket.setTimeout(timeout);
385
+ socket.on('data', (data) => {
386
+ const input = data.toString('utf-8');
387
+ const lines = input.split('\n').filter(Boolean);
388
+ for (const line of lines) {
389
+ const trimmed = line.trim();
390
+ if (!trimmed)
391
+ continue;
392
+ // Record credential attempts
393
+ if (!state.authenticated && state.username && !trimmed.includes(':')) {
394
+ this.recordCredential(session.sessionId, state.username, trimmed, state.authAttempts >= 2);
395
+ }
396
+ // Record as command if authenticated
397
+ if (state.authenticated) {
398
+ this.recordCommand(session.sessionId, trimmed);
399
+ }
400
+ const result = this.handler.handleInput(trimmed, state);
401
+ // Add MITRE techniques
402
+ if (result.mitre) {
403
+ for (const t of result.mitre) {
404
+ this.addMitreTechnique(session.sessionId, t);
405
+ }
406
+ }
407
+ // Record exploit events
408
+ if (result.eventType) {
409
+ this.recordEvent(session.sessionId, result.eventType, trimmed, {
410
+ mitre: result.mitre,
411
+ });
412
+ }
413
+ if (result.response === null) {
414
+ socket.end();
415
+ return;
416
+ }
417
+ const delay = this.config.responseDelayMs ?? 100;
418
+ setTimeout(() => {
419
+ try {
420
+ socket.write(result.response);
421
+ }
422
+ catch {
423
+ // socket closed
424
+ }
425
+ }, delay);
426
+ }
427
+ });
428
+ socket.on('timeout', () => {
429
+ logger.info(`${this.serviceType} session timeout: ${session.sessionId}`);
430
+ socket.end();
431
+ });
432
+ socket.on('close', () => this.endSession(session.sessionId));
433
+ socket.on('error', (err) => {
434
+ logger.debug(`${this.serviceType} socket error: ${err.message}`);
435
+ this.endSession(session.sessionId);
436
+ });
437
+ }
438
+ }
439
+ //# sourceMappingURL=generic-trap.js.map