@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,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
|