Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/scan/builtin/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "github.com/dropalldatabases/sif/internal/modules"
// Register registers all Go-based built-in scans as modules.
// Allows complex Go scans to participate in the module system
func Register() {
modules.Register(&ShodanModule{})
modules.Register(&FrameworksModule{})
modules.Register(&NucleiModule{})
modules.Register(&WhoisModule{})
Expand Down
157 changes: 157 additions & 0 deletions internal/scan/builtin/shodan_module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (Celeste Hickenlooper), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/

package builtin

import (
"context"
"fmt"
"github.com/dropalldatabases/sif/internal/modules"
"github.com/dropalldatabases/sif/internal/scan"
"strings"
)

type ShodanModule struct{}

func (m *ShodanModule) Info() modules.Info {
return modules.Info{
ID: "shodan-lookup",
Name: "Shodan Host Intelligence",
Author: "sif",
Severity: "info",
Description: "Queries Shodan API for host information, open ports, and vulnerabilities (requires SHODAN_API_KEY)",
Tags: []string{"recon", "osint", "shodan", "vulns"},
}
}

func (m *ShodanModule) Type() modules.ModuleType {
return modules.TypeScript
}

func (m *ShodanModule) Execute(ctx context.Context, target string, opts modules.Options) (*modules.Result, error) {
// Call existing legacy scan.Shodan function
shodanResult, err := scan.Shodan(target, opts.Timeout, opts.LogDir)

if err != nil {
return nil, err
}

result := &modules.Result{
ModuleID: m.Info().ID,
Target: target,
Findings: []modules.Finding{},
}

// If nothing returned, return empty result
if shodanResult == nil || shodanResult.IP == "" {
return result, nil
}

// Create main finding
evidence := fmt.Sprintf("Shodan data found for %s", shodanResult.IP)

severity := "info"
if len(shodanResult.Vulns) > 0 {
severity = "high"
evidence = fmt.Sprintf("Host %s has %d known vulnerabilities", shodanResult.IP, len(shodanResult.Vulns))
}

finding := modules.Finding{
URL: target,
Severity: severity,
Evidence: evidence,
Extracted: map[string]string{
"ip": shodanResult.IP,
},
}

// Add hostnames
if len(shodanResult.Hostnames) > 0 {
finding.Extracted["hostnames"] = strings.Join(shodanResult.Hostnames, ", ")
}

// Add organization info
if shodanResult.Organization != "" {
finding.Extracted["organization"] = shodanResult.Organization
}

// Add ISP info
if shodanResult.ISP != "" {
finding.Extracted["isp"] = shodanResult.ISP
}

// Add ASN
if shodanResult.ASN != "" {
finding.Extracted["asn"] = shodanResult.ASN
}

// Add location
if shodanResult.Country != "" {
location := shodanResult.Country
if shodanResult.City != "" {
location = shodanResult.City + ", " + shodanResult.Country
}

finding.Extracted["location"] = location
}

// Add OS
if shodanResult.OS != "" {
finding.Extracted["os"] = shodanResult.OS
}

// Add open ports
if len(shodanResult.Ports) > 0 {
portStrs := make([]string, len(shodanResult.Ports))
for i, port := range shodanResult.Ports {
portStrs[i] = fmt.Sprintf("%d", port)
}

finding.Extracted["open_ports"] = strings.Join(portStrs, ", ")
finding.Extracted["port_count"] = fmt.Sprintf("%d", len(shodanResult.Ports))
}

// Add vulnerabilities
if len(shodanResult.Vulns) > 0 {
finding.Extracted["vulnerabilities"] = strings.Join(shodanResult.Vulns, ", ")
finding.Extracted["vuln_count"] = fmt.Sprintf("%d", len(shodanResult.Vulns))
}

// Add last update
if shodanResult.LastUpdate != "" {
finding.Extracted["last_update"] = shodanResult.LastUpdate
}

// Add service count
if len(shodanResult.Services) > 0 {
finding.Extracted["service_count"] = fmt.Sprintf("%d", len(shodanResult.Services))

// Add service details
serviceDetails := make([]string, 0, len(shodanResult.Services))
for _, service := range shodanResult.Services {
detail := fmt.Sprintf("%d/%s", service.Port, service.Protocol)
if service.Product != "" {
detail += " " + service.Product

if service.Version != "" {
detail += " " + service.Version
}
}
serviceDetails = append(serviceDetails, detail)
}
finding.Extracted["services"] = strings.Join(serviceDetails, "; ")
}

result.Findings = append(result.Findings, finding)

return result, nil
}