Shodan Search Analyzer

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})();
Shodan Search Analyzer | Robomonkey