Analyze Shodan search results with AI-powered insights, statistics, and data extraction
Size
19.2 KB
Version
1.0.1
Created
Jan 2, 2026
Updated
about 1 month ago
1// ==UserScript==
2// @name Shodan Search Analyzer
3// @description Analyze Shodan search results with AI-powered insights, statistics, and data extraction
4// @version 1.0.1
5// @match https://*.shodan.io/*
6// @icon https://www.shodan.io/static/img/favicon-60c1b1cd.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Shodan Search Analyzer initialized');
12
13 // Utility function to debounce
14 function debounce(func, wait) {
15 let timeout;
16 return function executedFunction(...args) {
17 const later = () => {
18 clearTimeout(timeout);
19 func(...args);
20 };
21 clearTimeout(timeout);
22 timeout = setTimeout(later, wait);
23 };
24 }
25
26 // Add custom styles
27 function addStyles() {
28 const styles = `
29 .shodan-analyzer-panel {
30 position: fixed;
31 top: 80px;
32 right: 20px;
33 width: 380px;
34 max-height: 80vh;
35 background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
36 border: 2px solid #0f3460;
37 border-radius: 12px;
38 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
39 z-index: 10000;
40 overflow: hidden;
41 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
42 }
43
44 .shodan-analyzer-header {
45 background: linear-gradient(135deg, #0f3460 0%, #16213e 100%);
46 padding: 16px 20px;
47 border-bottom: 2px solid #e94560;
48 display: flex;
49 justify-content: space-between;
50 align-items: center;
51 cursor: move;
52 }
53
54 .shodan-analyzer-title {
55 color: #ffffff;
56 font-size: 16px;
57 font-weight: 600;
58 margin: 0;
59 display: flex;
60 align-items: center;
61 gap: 8px;
62 }
63
64 .shodan-analyzer-title::before {
65 content: '🔍';
66 font-size: 20px;
67 }
68
69 .shodan-analyzer-close {
70 background: transparent;
71 border: none;
72 color: #e94560;
73 font-size: 24px;
74 cursor: pointer;
75 padding: 0;
76 width: 28px;
77 height: 28px;
78 display: flex;
79 align-items: center;
80 justify-content: center;
81 border-radius: 4px;
82 transition: all 0.2s;
83 }
84
85 .shodan-analyzer-close:hover {
86 background: rgba(233, 69, 96, 0.2);
87 transform: scale(1.1);
88 }
89
90 .shodan-analyzer-content {
91 padding: 20px;
92 max-height: calc(80vh - 70px);
93 overflow-y: auto;
94 color: #e0e0e0;
95 }
96
97 .shodan-analyzer-content::-webkit-scrollbar {
98 width: 8px;
99 }
100
101 .shodan-analyzer-content::-webkit-scrollbar-track {
102 background: #1a1a2e;
103 }
104
105 .shodan-analyzer-content::-webkit-scrollbar-thumb {
106 background: #0f3460;
107 border-radius: 4px;
108 }
109
110 .shodan-analyzer-content::-webkit-scrollbar-thumb:hover {
111 background: #e94560;
112 }
113
114 .shodan-analyzer-section {
115 margin-bottom: 20px;
116 background: rgba(15, 52, 96, 0.3);
117 padding: 16px;
118 border-radius: 8px;
119 border-left: 4px solid #e94560;
120 }
121
122 .shodan-analyzer-section-title {
123 color: #e94560;
124 font-size: 14px;
125 font-weight: 600;
126 margin: 0 0 12px 0;
127 text-transform: uppercase;
128 letter-spacing: 0.5px;
129 }
130
131 .shodan-stat-item {
132 display: flex;
133 justify-content: space-between;
134 padding: 8px 0;
135 border-bottom: 1px solid rgba(233, 69, 96, 0.2);
136 }
137
138 .shodan-stat-item:last-child {
139 border-bottom: none;
140 }
141
142 .shodan-stat-label {
143 color: #a0a0a0;
144 font-size: 13px;
145 }
146
147 .shodan-stat-value {
148 color: #ffffff;
149 font-weight: 600;
150 font-size: 13px;
151 }
152
153 .shodan-analyze-btn {
154 background: linear-gradient(135deg, #e94560 0%, #d63447 100%);
155 color: white;
156 border: none;
157 padding: 12px 24px;
158 border-radius: 6px;
159 cursor: pointer;
160 font-size: 14px;
161 font-weight: 600;
162 width: 100%;
163 transition: all 0.3s;
164 box-shadow: 0 4px 12px rgba(233, 69, 96, 0.3);
165 }
166
167 .shodan-analyze-btn:hover {
168 transform: translateY(-2px);
169 box-shadow: 0 6px 16px rgba(233, 69, 96, 0.4);
170 }
171
172 .shodan-analyze-btn:disabled {
173 background: #555;
174 cursor: not-allowed;
175 transform: none;
176 }
177
178 .shodan-loading {
179 text-align: center;
180 padding: 20px;
181 color: #e94560;
182 }
183
184 .shodan-loading::after {
185 content: '...';
186 animation: dots 1.5s steps(4, end) infinite;
187 }
188
189 @keyframes dots {
190 0%, 20% { content: '.'; }
191 40% { content: '..'; }
192 60%, 100% { content: '...'; }
193 }
194
195 .shodan-insight {
196 background: rgba(233, 69, 96, 0.1);
197 padding: 12px;
198 border-radius: 6px;
199 margin-top: 8px;
200 font-size: 13px;
201 line-height: 1.6;
202 color: #e0e0e0;
203 }
204
205 .shodan-toggle-btn {
206 position: fixed;
207 top: 80px;
208 right: 20px;
209 background: linear-gradient(135deg, #e94560 0%, #d63447 100%);
210 color: white;
211 border: none;
212 padding: 12px 20px;
213 border-radius: 8px;
214 cursor: pointer;
215 font-size: 14px;
216 font-weight: 600;
217 z-index: 9999;
218 box-shadow: 0 4px 12px rgba(233, 69, 96, 0.4);
219 transition: all 0.3s;
220 }
221
222 .shodan-toggle-btn:hover {
223 transform: translateY(-2px);
224 box-shadow: 0 6px 16px rgba(233, 69, 96, 0.5);
225 }
226
227 .shodan-tag {
228 display: inline-block;
229 background: rgba(233, 69, 96, 0.2);
230 color: #e94560;
231 padding: 4px 10px;
232 border-radius: 4px;
233 font-size: 12px;
234 margin: 4px 4px 4px 0;
235 border: 1px solid rgba(233, 69, 96, 0.3);
236 }
237 `;
238 TM_addStyle(styles);
239 }
240
241 // Extract search results data
242 function extractSearchResults() {
243 const results = [];
244 const resultElements = document.querySelectorAll('.search-result');
245
246 resultElements.forEach(element => {
247 try {
248 const ipElement = element.querySelector('.ip');
249 const portElement = element.querySelector('.port');
250 const orgElement = element.querySelector('.details-organization');
251 const locationElement = element.querySelector('.details-location');
252 const bannerElement = element.querySelector('.banner');
253
254 const result = {
255 ip: ipElement ? ipElement.textContent.trim() : 'N/A',
256 port: portElement ? portElement.textContent.trim() : 'N/A',
257 organization: orgElement ? orgElement.textContent.trim() : 'N/A',
258 location: locationElement ? locationElement.textContent.trim() : 'N/A',
259 banner: bannerElement ? bannerElement.textContent.trim().substring(0, 200) : 'N/A'
260 };
261
262 results.push(result);
263 } catch (error) {
264 console.error('Error extracting result:', error);
265 }
266 });
267
268 return results;
269 }
270
271 // Calculate statistics
272 function calculateStatistics(results) {
273 const stats = {
274 totalResults: results.length,
275 uniqueIPs: new Set(results.map(r => r.ip)).size,
276 uniquePorts: new Set(results.map(r => r.port)).size,
277 uniqueOrgs: new Set(results.map(r => r.organization)).size,
278 topPorts: {},
279 topOrgs: {},
280 topLocations: {}
281 };
282
283 // Count occurrences
284 results.forEach(result => {
285 stats.topPorts[result.port] = (stats.topPorts[result.port] || 0) + 1;
286 stats.topOrgs[result.organization] = (stats.topOrgs[result.organization] || 0) + 1;
287 stats.topLocations[result.location] = (stats.topLocations[result.location] || 0) + 1;
288 });
289
290 // Sort and get top 5
291 stats.topPorts = Object.entries(stats.topPorts)
292 .sort((a, b) => b[1] - a[1])
293 .slice(0, 5);
294 stats.topOrgs = Object.entries(stats.topOrgs)
295 .sort((a, b) => b[1] - a[1])
296 .slice(0, 5);
297 stats.topLocations = Object.entries(stats.topLocations)
298 .sort((a, b) => b[1] - a[1])
299 .slice(0, 5);
300
301 return stats;
302 }
303
304 // Create analyzer panel
305 function createAnalyzerPanel() {
306 const panel = document.createElement('div');
307 panel.className = 'shodan-analyzer-panel';
308 panel.id = 'shodan-analyzer-panel';
309 panel.innerHTML = `
310 <div class="shodan-analyzer-header">
311 <h3 class="shodan-analyzer-title">Search Analyzer</h3>
312 <button class="shodan-analyzer-close" id="shodan-close-panel">×</button>
313 </div>
314 <div class="shodan-analyzer-content" id="shodan-analyzer-content">
315 <button class="shodan-analyze-btn" id="shodan-analyze-btn">Analyze Results</button>
316 </div>
317 `;
318
319 document.body.appendChild(panel);
320
321 // Make panel draggable
322 makeDraggable(panel);
323
324 // Add event listeners
325 document.getElementById('shodan-close-panel').addEventListener('click', () => {
326 panel.style.display = 'none';
327 showToggleButton();
328 });
329
330 document.getElementById('shodan-analyze-btn').addEventListener('click', analyzeResults);
331
332 return panel;
333 }
334
335 // Create toggle button
336 function createToggleButton() {
337 const button = document.createElement('button');
338 button.className = 'shodan-toggle-btn';
339 button.id = 'shodan-toggle-btn';
340 button.textContent = '🔍 Analyze';
341 button.style.display = 'none';
342
343 button.addEventListener('click', () => {
344 const panel = document.getElementById('shodan-analyzer-panel');
345 if (panel) {
346 panel.style.display = 'block';
347 button.style.display = 'none';
348 }
349 });
350
351 document.body.appendChild(button);
352 return button;
353 }
354
355 function showToggleButton() {
356 const button = document.getElementById('shodan-toggle-btn');
357 if (button) {
358 button.style.display = 'block';
359 }
360 }
361
362 // Make panel draggable
363 function makeDraggable(element) {
364 const header = element.querySelector('.shodan-analyzer-header');
365 let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
366
367 header.onmousedown = dragMouseDown;
368
369 function dragMouseDown(e) {
370 e.preventDefault();
371 pos3 = e.clientX;
372 pos4 = e.clientY;
373 document.onmouseup = closeDragElement;
374 document.onmousemove = elementDrag;
375 }
376
377 function elementDrag(e) {
378 e.preventDefault();
379 pos1 = pos3 - e.clientX;
380 pos2 = pos4 - e.clientY;
381 pos3 = e.clientX;
382 pos4 = e.clientY;
383 element.style.top = (element.offsetTop - pos2) + "px";
384 element.style.right = "auto";
385 element.style.left = (element.offsetLeft - pos1) + "px";
386 }
387
388 function closeDragElement() {
389 document.onmouseup = null;
390 document.onmousemove = null;
391 }
392 }
393
394 // Analyze results with AI
395 async function analyzeResults() {
396 const content = document.getElementById('shodan-analyzer-content');
397 const button = document.getElementById('shodan-analyze-btn');
398
399 button.disabled = true;
400 button.textContent = 'Analyzing...';
401
402 content.innerHTML = '<div class="shodan-loading">Analyzing search results</div>';
403
404 try {
405 // Extract results
406 const results = extractSearchResults();
407
408 if (results.length === 0) {
409 content.innerHTML = `
410 <div class="shodan-analyzer-section">
411 <p style="text-align: center; color: #a0a0a0;">No search results found on this page. Try performing a search first.</p>
412 </div>
413 <button class="shodan-analyze-btn" id="shodan-analyze-btn">Analyze Results</button>
414 `;
415 document.getElementById('shodan-analyze-btn').addEventListener('click', analyzeResults);
416 return;
417 }
418
419 // Calculate statistics
420 const stats = calculateStatistics(results);
421
422 // Get current search query
423 const searchQuery = document.querySelector('input[name="query"]')?.value || 'Unknown';
424
425 // Prepare data for AI analysis
426 const analysisPrompt = `Analyze these Shodan search results for query "${searchQuery}":
427
428Total Results: ${stats.totalResults}
429Unique IPs: ${stats.uniqueIPs}
430Unique Ports: ${stats.uniquePorts}
431
432Top Ports: ${stats.topPorts.map(([port, count]) => `${port} (${count})`).join(', ')}
433Top Organizations: ${stats.topOrgs.slice(0, 3).map(([org, count]) => `${org} (${count})`).join(', ')}
434Top Locations: ${stats.topLocations.slice(0, 3).map(([loc, count]) => `${loc} (${count})`).join(', ')}
435
436Sample Results:
437${results.slice(0, 3).map(r => `IP: ${r.ip}, Port: ${r.port}, Org: ${r.organization}, Location: ${r.location}`).join('\n')}
438
439Provide a security analysis including:
4401. What these results indicate
4412. Common vulnerabilities or risks
4423. Security recommendations
4434. Notable patterns or concerns`;
444
445 console.log('Sending analysis request to AI...');
446
447 // Get AI insights
448 const aiInsights = await RM.aiCall(analysisPrompt);
449
450 console.log('AI analysis received');
451
452 // Display results
453 displayAnalysisResults(stats, aiInsights, searchQuery);
454
455 } catch (error) {
456 console.error('Analysis error:', error);
457 content.innerHTML = `
458 <div class="shodan-analyzer-section">
459 <p style="color: #e94560;">Error during analysis: ${error.message}</p>
460 <p style="color: #a0a0a0; font-size: 12px; margin-top: 8px;">Please try again or check the console for details.</p>
461 </div>
462 <button class="shodan-analyze-btn" id="shodan-analyze-btn">Retry Analysis</button>
463 `;
464 document.getElementById('shodan-analyze-btn').addEventListener('click', analyzeResults);
465 }
466 }
467
468 // Display analysis results
469 function displayAnalysisResults(stats, aiInsights, searchQuery) {
470 const content = document.getElementById('shodan-analyzer-content');
471
472 let html = `
473 <div class="shodan-analyzer-section">
474 <h4 class="shodan-analyzer-section-title">📊 Statistics</h4>
475 <div class="shodan-stat-item">
476 <span class="shodan-stat-label">Total Results</span>
477 <span class="shodan-stat-value">${stats.totalResults}</span>
478 </div>
479 <div class="shodan-stat-item">
480 <span class="shodan-stat-label">Unique IPs</span>
481 <span class="shodan-stat-value">${stats.uniqueIPs}</span>
482 </div>
483 <div class="shodan-stat-item">
484 <span class="shodan-stat-label">Unique Ports</span>
485 <span class="shodan-stat-value">${stats.uniquePorts}</span>
486 </div>
487 <div class="shodan-stat-item">
488 <span class="shodan-stat-label">Organizations</span>
489 <span class="shodan-stat-value">${stats.uniqueOrgs}</span>
490 </div>
491 </div>
492 `;
493
494 if (stats.topPorts.length > 0) {
495 html += `
496 <div class="shodan-analyzer-section">
497 <h4 class="shodan-analyzer-section-title">🔌 Top Ports</h4>
498 ${stats.topPorts.map(([port, count]) => `
499 <div class="shodan-stat-item">
500 <span class="shodan-stat-label">Port ${port}</span>
501 <span class="shodan-stat-value">${count} hosts</span>
502 </div>
503 `).join('')}
504 </div>
505 `;
506 }
507
508 if (stats.topLocations.length > 0) {
509 html += `
510 <div class="shodan-analyzer-section">
511 <h4 class="shodan-analyzer-section-title">🌍 Top Locations</h4>
512 ${stats.topLocations.slice(0, 5).map(([loc, count]) => `
513 <span class="shodan-tag">${loc} (${count})</span>
514 `).join('')}
515 </div>
516 `;
517 }
518
519 if (aiInsights) {
520 html += `
521 <div class="shodan-analyzer-section">
522 <h4 class="shodan-analyzer-section-title">🤖 AI Security Analysis</h4>
523 <div class="shodan-insight">${aiInsights.replace(/\n/g, '<br>')}</div>
524 </div>
525 `;
526 }
527
528 html += `
529 <button class="shodan-analyze-btn" id="shodan-analyze-btn">Refresh Analysis</button>
530 `;
531
532 content.innerHTML = html;
533 document.getElementById('shodan-analyze-btn').addEventListener('click', analyzeResults);
534 }
535
536 // Initialize
537 function init() {
538 console.log('Initializing Shodan Search Analyzer...');
539
540 // Add styles
541 addStyles();
542
543 // Wait for page to be ready
544 if (document.readyState === 'loading') {
545 document.addEventListener('DOMContentLoaded', () => {
546 createAnalyzerPanel();
547 createToggleButton();
548 });
549 } else {
550 createAnalyzerPanel();
551 createToggleButton();
552 }
553
554 console.log('Shodan Search Analyzer ready!');
555 }
556
557 // Start the extension
558 init();
559})();