honeyweb-core 1.0.2 → 2.0.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/ai/gemini.js +52 -0
- package/ai/index.js +49 -0
- package/ai/prompt-templates.js +64 -0
- package/config/defaults.js +79 -0
- package/config/index.js +83 -0
- package/config/schema.js +72 -0
- package/detection/behavioral.js +186 -0
- package/detection/bot-detector.js +140 -0
- package/detection/index.js +136 -0
- package/detection/patterns.js +83 -0
- package/detection/rate-limiter.js +109 -0
- package/detection/whitelist.js +116 -0
- package/index.js +57 -200
- package/middleware/blocklist-checker.js +34 -0
- package/middleware/dashboard.js +176 -0
- package/middleware/index.js +72 -0
- package/middleware/request-analyzer.js +111 -0
- package/middleware/trap-injector.js +52 -0
- package/package.json +27 -18
- package/storage/index.js +25 -0
- package/storage/json-store.js +206 -0
- package/storage/memory-store.js +128 -0
- package/utils/cache.js +106 -0
- package/utils/dns-verify.js +95 -0
- package/utils/event-emitter.js +65 -0
- package/utils/logger.js +88 -0
- package/blocked-ips.json +0 -0
- package/index V1.js +0 -85
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// honeyweb-core/middleware/trap-injector.js
|
|
2
|
+
// Trap injection middleware (extracted from main index.js)
|
|
3
|
+
|
|
4
|
+
const cheerio = require('cheerio');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create trap injector middleware
|
|
8
|
+
* @param {Object} config - Trap configuration
|
|
9
|
+
* @returns {Function} - Middleware function
|
|
10
|
+
*/
|
|
11
|
+
function createTrapInjector(config) {
|
|
12
|
+
const trapPaths = config.traps.paths;
|
|
13
|
+
|
|
14
|
+
return function injectTraps(req, res, next) {
|
|
15
|
+
// Only inject if trap injection is enabled
|
|
16
|
+
if (!config.traps.injection.enabled) {
|
|
17
|
+
return next();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Intercept res.send to inject traps
|
|
21
|
+
const originalSend = res.send;
|
|
22
|
+
|
|
23
|
+
res.send = function (body) {
|
|
24
|
+
// Only inject if the content is HTML
|
|
25
|
+
if (typeof body === 'string' && (body.includes('<html') || body.includes('<body'))) {
|
|
26
|
+
try {
|
|
27
|
+
const $ = cheerio.load(body);
|
|
28
|
+
|
|
29
|
+
// Pick a random trap
|
|
30
|
+
const trapUrl = trapPaths[Math.floor(Math.random() * trapPaths.length)];
|
|
31
|
+
|
|
32
|
+
// Create invisible link (Hidden by CSS)
|
|
33
|
+
const trapLink = `<a href="${trapUrl}" style="opacity:0; position:absolute; z-index:-999; left:-9999px;">Admin Panel</a>`;
|
|
34
|
+
|
|
35
|
+
// Inject into body
|
|
36
|
+
$('body').append(trapLink);
|
|
37
|
+
|
|
38
|
+
body = $.html();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// If cheerio fails, just send original body
|
|
41
|
+
console.error('[TrapInjector] Failed to inject trap:', err.message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
originalSend.call(this, body);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
next();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = createTrapInjector;
|
package/package.json
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "honeyweb-core",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "honeyweb-core",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Production-ready honeypot middleware with behavioral analysis, bot fingerprinting, and AI threat intelligence",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"verify": "node verify-implementation.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"honeypot",
|
|
12
|
+
"security",
|
|
13
|
+
"middleware",
|
|
14
|
+
"bot-detection",
|
|
15
|
+
"web-security",
|
|
16
|
+
"express",
|
|
17
|
+
"ai-security"
|
|
18
|
+
],
|
|
19
|
+
"author": "Team P2BG5",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@google/generative-ai": "^0.24.1",
|
|
24
|
+
"cheerio": "^1.2.0",
|
|
25
|
+
"fs-extra": "^11.3.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/storage/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// honeyweb-core/storage/index.js
|
|
2
|
+
// Storage factory
|
|
3
|
+
|
|
4
|
+
const JsonStore = require('./json-store');
|
|
5
|
+
const MemoryStore = require('./memory-store');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create storage instance based on configuration
|
|
9
|
+
* @param {Object} config - Storage configuration
|
|
10
|
+
* @returns {JsonStore|MemoryStore}
|
|
11
|
+
*/
|
|
12
|
+
function createStorage(config) {
|
|
13
|
+
const type = config.type || 'json';
|
|
14
|
+
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'json':
|
|
17
|
+
return new JsonStore(config);
|
|
18
|
+
case 'memory':
|
|
19
|
+
return new MemoryStore(config);
|
|
20
|
+
default:
|
|
21
|
+
throw new Error(`[HoneyWeb] Unknown storage type: ${type}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { createStorage, JsonStore, MemoryStore };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// honeyweb-core/storage/json-store.js
|
|
2
|
+
// Async JSON storage with in-memory cache
|
|
3
|
+
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const LRUCache = require('../utils/cache');
|
|
7
|
+
|
|
8
|
+
class JsonStore {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.filePath = path.resolve(config.path || './blocked-ips.json');
|
|
11
|
+
this.banDuration = config.banDuration || 86400000; // 24 hours
|
|
12
|
+
this.autoCleanup = config.autoCleanup !== false;
|
|
13
|
+
this.cache = config.cache ? new LRUCache(1000, 60000) : null; // 1 minute cache
|
|
14
|
+
|
|
15
|
+
// Initialize file
|
|
16
|
+
this._initFile();
|
|
17
|
+
|
|
18
|
+
// Start auto-cleanup if enabled
|
|
19
|
+
if (this.autoCleanup) {
|
|
20
|
+
this.cleanupInterval = setInterval(() => {
|
|
21
|
+
this._cleanupExpired();
|
|
22
|
+
}, 300000); // Clean up every 5 minutes
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async _initFile() {
|
|
27
|
+
try {
|
|
28
|
+
if (!await fs.pathExists(this.filePath)) {
|
|
29
|
+
await fs.writeJson(this.filePath, []);
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error('[JsonStore] Failed to initialize file:', err);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get all banned IPs
|
|
38
|
+
* @returns {Promise<Array>} - Array of banned IP objects
|
|
39
|
+
*/
|
|
40
|
+
async getAll() {
|
|
41
|
+
// Check cache first
|
|
42
|
+
if (this.cache && this.cache.has('all')) {
|
|
43
|
+
return this.cache.get('all');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const data = await fs.readJson(this.filePath);
|
|
48
|
+
|
|
49
|
+
// Cache the result
|
|
50
|
+
if (this.cache) {
|
|
51
|
+
this.cache.set('all', data);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return data;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error('[JsonStore] Error reading file:', err);
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if IP is banned
|
|
63
|
+
* @param {string} ip
|
|
64
|
+
* @returns {Promise<boolean>}
|
|
65
|
+
*/
|
|
66
|
+
async isBanned(ip) {
|
|
67
|
+
const bannedIPs = await this.getAll();
|
|
68
|
+
|
|
69
|
+
// Support both simple array format and object format
|
|
70
|
+
if (bannedIPs.length > 0 && typeof bannedIPs[0] === 'string') {
|
|
71
|
+
return bannedIPs.includes(ip);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Object format with expiry
|
|
75
|
+
const entry = bannedIPs.find(entry => entry.ip === ip);
|
|
76
|
+
if (!entry) return false;
|
|
77
|
+
|
|
78
|
+
// Check if expired
|
|
79
|
+
if (entry.expiry && Date.now() > entry.expiry) {
|
|
80
|
+
await this.unban(ip);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Ban an IP
|
|
89
|
+
* @param {string} ip
|
|
90
|
+
* @param {string} reason
|
|
91
|
+
* @returns {Promise<void>}
|
|
92
|
+
*/
|
|
93
|
+
async ban(ip, reason = 'Suspicious activity') {
|
|
94
|
+
const bannedIPs = await this.getAll();
|
|
95
|
+
|
|
96
|
+
// Check if already banned
|
|
97
|
+
const existingIndex = bannedIPs.findIndex(entry =>
|
|
98
|
+
typeof entry === 'string' ? entry === ip : entry.ip === ip
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (existingIndex !== -1) {
|
|
102
|
+
return; // Already banned
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add new ban with expiry
|
|
106
|
+
const banEntry = {
|
|
107
|
+
ip,
|
|
108
|
+
reason,
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
expiry: Date.now() + this.banDuration
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
bannedIPs.push(banEntry);
|
|
114
|
+
|
|
115
|
+
await this._save(bannedIPs);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Unban an IP
|
|
120
|
+
* @param {string} ip
|
|
121
|
+
* @returns {Promise<void>}
|
|
122
|
+
*/
|
|
123
|
+
async unban(ip) {
|
|
124
|
+
const bannedIPs = await this.getAll();
|
|
125
|
+
|
|
126
|
+
const filtered = bannedIPs.filter(entry =>
|
|
127
|
+
typeof entry === 'string' ? entry !== ip : entry.ip !== ip
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
await this._save(filtered);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Save data to file and invalidate cache
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
137
|
+
async _save(data) {
|
|
138
|
+
try {
|
|
139
|
+
await fs.writeJson(this.filePath, data, { spaces: 2 });
|
|
140
|
+
|
|
141
|
+
// Invalidate cache
|
|
142
|
+
if (this.cache) {
|
|
143
|
+
this.cache.delete('all');
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.error('[JsonStore] Error writing file:', err);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Clean up expired bans
|
|
152
|
+
* @private
|
|
153
|
+
*/
|
|
154
|
+
async _cleanupExpired() {
|
|
155
|
+
const bannedIPs = await this.getAll();
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
|
|
158
|
+
const active = bannedIPs.filter(entry => {
|
|
159
|
+
if (typeof entry === 'string') return true; // Keep simple format
|
|
160
|
+
return !entry.expiry || now <= entry.expiry;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (active.length !== bannedIPs.length) {
|
|
164
|
+
await this._save(active);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get statistics
|
|
170
|
+
* @returns {Promise<Object>}
|
|
171
|
+
*/
|
|
172
|
+
async getStats() {
|
|
173
|
+
const bannedIPs = await this.getAll();
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
|
|
176
|
+
let active = 0;
|
|
177
|
+
let expired = 0;
|
|
178
|
+
|
|
179
|
+
bannedIPs.forEach(entry => {
|
|
180
|
+
if (typeof entry === 'string') {
|
|
181
|
+
active++;
|
|
182
|
+
} else if (!entry.expiry || now <= entry.expiry) {
|
|
183
|
+
active++;
|
|
184
|
+
} else {
|
|
185
|
+
expired++;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
total: bannedIPs.length,
|
|
191
|
+
active,
|
|
192
|
+
expired
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Cleanup and stop intervals
|
|
198
|
+
*/
|
|
199
|
+
destroy() {
|
|
200
|
+
if (this.cleanupInterval) {
|
|
201
|
+
clearInterval(this.cleanupInterval);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = JsonStore;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// honeyweb-core/storage/memory-store.js
|
|
2
|
+
// In-memory storage for testing
|
|
3
|
+
|
|
4
|
+
class MemoryStore {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.bannedIPs = new Map(); // ip -> { reason, timestamp, expiry }
|
|
7
|
+
this.banDuration = config.banDuration || 86400000; // 24 hours
|
|
8
|
+
this.autoCleanup = config.autoCleanup !== false;
|
|
9
|
+
|
|
10
|
+
// Start auto-cleanup if enabled
|
|
11
|
+
if (this.autoCleanup) {
|
|
12
|
+
this.cleanupInterval = setInterval(() => {
|
|
13
|
+
this._cleanupExpired();
|
|
14
|
+
}, 60000); // Clean up every minute
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get all banned IPs
|
|
20
|
+
* @returns {Promise<Array>}
|
|
21
|
+
*/
|
|
22
|
+
async getAll() {
|
|
23
|
+
const result = [];
|
|
24
|
+
for (const [ip, data] of this.bannedIPs.entries()) {
|
|
25
|
+
result.push({
|
|
26
|
+
ip,
|
|
27
|
+
...data
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if IP is banned
|
|
35
|
+
* @param {string} ip
|
|
36
|
+
* @returns {Promise<boolean>}
|
|
37
|
+
*/
|
|
38
|
+
async isBanned(ip) {
|
|
39
|
+
const entry = this.bannedIPs.get(ip);
|
|
40
|
+
if (!entry) return false;
|
|
41
|
+
|
|
42
|
+
// Check if expired
|
|
43
|
+
if (entry.expiry && Date.now() > entry.expiry) {
|
|
44
|
+
this.bannedIPs.delete(ip);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Ban an IP
|
|
53
|
+
* @param {string} ip
|
|
54
|
+
* @param {string} reason
|
|
55
|
+
* @returns {Promise<void>}
|
|
56
|
+
*/
|
|
57
|
+
async ban(ip, reason = 'Suspicious activity') {
|
|
58
|
+
this.bannedIPs.set(ip, {
|
|
59
|
+
reason,
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
expiry: Date.now() + this.banDuration
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Unban an IP
|
|
67
|
+
* @param {string} ip
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
async unban(ip) {
|
|
71
|
+
this.bannedIPs.delete(ip);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Clean up expired bans
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
_cleanupExpired() {
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
for (const [ip, entry] of this.bannedIPs.entries()) {
|
|
81
|
+
if (entry.expiry && now > entry.expiry) {
|
|
82
|
+
this.bannedIPs.delete(ip);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get statistics
|
|
89
|
+
* @returns {Promise<Object>}
|
|
90
|
+
*/
|
|
91
|
+
async getStats() {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
let active = 0;
|
|
94
|
+
let expired = 0;
|
|
95
|
+
|
|
96
|
+
for (const entry of this.bannedIPs.values()) {
|
|
97
|
+
if (!entry.expiry || now <= entry.expiry) {
|
|
98
|
+
active++;
|
|
99
|
+
} else {
|
|
100
|
+
expired++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
total: this.bannedIPs.size,
|
|
106
|
+
active,
|
|
107
|
+
expired
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Clear all bans
|
|
113
|
+
*/
|
|
114
|
+
clear() {
|
|
115
|
+
this.bannedIPs.clear();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Cleanup and stop intervals
|
|
120
|
+
*/
|
|
121
|
+
destroy() {
|
|
122
|
+
if (this.cleanupInterval) {
|
|
123
|
+
clearInterval(this.cleanupInterval);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = MemoryStore;
|
package/utils/cache.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// honeyweb-core/utils/cache.js
|
|
2
|
+
// LRU cache implementation for DNS verification and blocklist
|
|
3
|
+
|
|
4
|
+
class LRUCache {
|
|
5
|
+
constructor(maxSize = 1000, ttl = 3600000) {
|
|
6
|
+
this.maxSize = maxSize;
|
|
7
|
+
this.ttl = ttl; // Time to live in milliseconds
|
|
8
|
+
this.cache = new Map();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get value from cache
|
|
13
|
+
* @param {string} key
|
|
14
|
+
* @returns {*} - Value or undefined if not found/expired
|
|
15
|
+
*/
|
|
16
|
+
get(key) {
|
|
17
|
+
const item = this.cache.get(key);
|
|
18
|
+
|
|
19
|
+
if (!item) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check if expired
|
|
24
|
+
if (Date.now() > item.expiry) {
|
|
25
|
+
this.cache.delete(key);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Move to end (most recently used)
|
|
30
|
+
this.cache.delete(key);
|
|
31
|
+
this.cache.set(key, item);
|
|
32
|
+
|
|
33
|
+
return item.value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set value in cache
|
|
38
|
+
* @param {string} key
|
|
39
|
+
* @param {*} value
|
|
40
|
+
* @param {number} customTTL - Optional custom TTL for this entry
|
|
41
|
+
*/
|
|
42
|
+
set(key, value, customTTL) {
|
|
43
|
+
// Remove if already exists
|
|
44
|
+
if (this.cache.has(key)) {
|
|
45
|
+
this.cache.delete(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Evict oldest if at capacity
|
|
49
|
+
if (this.cache.size >= this.maxSize) {
|
|
50
|
+
const firstKey = this.cache.keys().next().value;
|
|
51
|
+
this.cache.delete(firstKey);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ttl = customTTL || this.ttl;
|
|
55
|
+
this.cache.set(key, {
|
|
56
|
+
value,
|
|
57
|
+
expiry: Date.now() + ttl
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if key exists and is not expired
|
|
63
|
+
* @param {string} key
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
has(key) {
|
|
67
|
+
return this.get(key) !== undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Delete key from cache
|
|
72
|
+
* @param {string} key
|
|
73
|
+
*/
|
|
74
|
+
delete(key) {
|
|
75
|
+
this.cache.delete(key);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Clear all entries
|
|
80
|
+
*/
|
|
81
|
+
clear() {
|
|
82
|
+
this.cache.clear();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get cache size
|
|
87
|
+
* @returns {number}
|
|
88
|
+
*/
|
|
89
|
+
size() {
|
|
90
|
+
return this.cache.size;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Clean up expired entries
|
|
95
|
+
*/
|
|
96
|
+
cleanup() {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
for (const [key, item] of this.cache.entries()) {
|
|
99
|
+
if (now > item.expiry) {
|
|
100
|
+
this.cache.delete(key);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = LRUCache;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// honeyweb-core/utils/dns-verify.js
|
|
2
|
+
// DNS verification utilities for bot whitelist
|
|
3
|
+
|
|
4
|
+
const dns = require('dns').promises;
|
|
5
|
+
const LRUCache = require('./cache');
|
|
6
|
+
|
|
7
|
+
class DNSVerifier {
|
|
8
|
+
constructor(cacheTTL = 86400000) {
|
|
9
|
+
// Cache DNS results for 24 hours by default
|
|
10
|
+
this.cache = new LRUCache(500, cacheTTL);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Verify if IP belongs to claimed bot domain
|
|
15
|
+
* @param {string} ip - IP address to verify
|
|
16
|
+
* @param {string} expectedDomain - Expected domain pattern (e.g., 'googlebot.com')
|
|
17
|
+
* @returns {Promise<Object>} - { verified: boolean, hostname: string, error: string }
|
|
18
|
+
*/
|
|
19
|
+
async verify(ip, expectedDomain) {
|
|
20
|
+
// Check cache first
|
|
21
|
+
const cacheKey = `${ip}:${expectedDomain}`;
|
|
22
|
+
if (this.cache.has(cacheKey)) {
|
|
23
|
+
return this.cache.get(cacheKey);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Step 1: Reverse DNS lookup (IP -> hostname)
|
|
28
|
+
const hostnames = await dns.reverse(ip);
|
|
29
|
+
|
|
30
|
+
if (!hostnames || hostnames.length === 0) {
|
|
31
|
+
const result = {
|
|
32
|
+
verified: false,
|
|
33
|
+
hostname: null,
|
|
34
|
+
error: 'No reverse DNS record found'
|
|
35
|
+
};
|
|
36
|
+
this.cache.set(cacheKey, result);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const hostname = hostnames[0];
|
|
41
|
+
|
|
42
|
+
// Step 2: Check if hostname matches expected domain
|
|
43
|
+
if (!hostname.endsWith(expectedDomain)) {
|
|
44
|
+
const result = {
|
|
45
|
+
verified: false,
|
|
46
|
+
hostname,
|
|
47
|
+
error: `Hostname ${hostname} does not match expected domain ${expectedDomain}`
|
|
48
|
+
};
|
|
49
|
+
this.cache.set(cacheKey, result);
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Step 3: Forward DNS lookup (hostname -> IP) to verify
|
|
54
|
+
const addresses = await dns.resolve4(hostname);
|
|
55
|
+
|
|
56
|
+
if (!addresses.includes(ip)) {
|
|
57
|
+
const result = {
|
|
58
|
+
verified: false,
|
|
59
|
+
hostname,
|
|
60
|
+
error: 'Forward DNS lookup does not match original IP'
|
|
61
|
+
};
|
|
62
|
+
this.cache.set(cacheKey, result);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Verification successful
|
|
67
|
+
const result = {
|
|
68
|
+
verified: true,
|
|
69
|
+
hostname,
|
|
70
|
+
error: null
|
|
71
|
+
};
|
|
72
|
+
this.cache.set(cacheKey, result);
|
|
73
|
+
return result;
|
|
74
|
+
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const result = {
|
|
77
|
+
verified: false,
|
|
78
|
+
hostname: null,
|
|
79
|
+
error: err.message
|
|
80
|
+
};
|
|
81
|
+
// Cache failures for shorter time (5 minutes)
|
|
82
|
+
this.cache.set(cacheKey, result, 300000);
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Clear DNS cache
|
|
89
|
+
*/
|
|
90
|
+
clearCache() {
|
|
91
|
+
this.cache.clear();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = DNSVerifier;
|