diff --git a/modules/syn_scan/banner_grabbing.go b/modules/syn_scan/banner_grabbing.go new file mode 100644 index 00000000..db96343b --- /dev/null +++ b/modules/syn_scan/banner_grabbing.go @@ -0,0 +1,41 @@ +package syn_scan + +import ( + "github.com/bettercap/bettercap/network" + + "github.com/evilsocket/islazy/async" +) + +type bannerGrabberFn func(mod *SynScanner, ip string, port int) string + +type grabberJob struct { + Host *network.Endpoint + Port *OpenPort +} + +var tcpBannerGrabbers = map[int]bannerGrabberFn{ + 80: httpGrabber, + 8080: httpGrabber, + 443: httpGrabber, + 8443: httpGrabber, +} + +func (mod *SynScanner) bannerGrabber(arg async.Job) { + job := arg.(grabberJob) + if job.Port.Proto != "tcp" { + return + } + + ip := job.Host.IpAddress + port := job.Port.Port + fn, found := tcpBannerGrabbers[port] + if !found { + fn = tcpGrabber + } + + mod.Debug("grabbing banner for %s:%d", ip, port) + job.Port.Banner = fn(mod, ip, port) + if job.Port.Banner != "" { + mod.Info("found banner for %s:%d -> %s", ip, port, job.Port.Banner) + } +} diff --git a/modules/syn_scan/http_grabber.go b/modules/syn_scan/http_grabber.go new file mode 100644 index 00000000..5eaa1291 --- /dev/null +++ b/modules/syn_scan/http_grabber.go @@ -0,0 +1,89 @@ +package syn_scan + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "golang.org/x/net/html" + "net/http" + "strings" + "time" +) + +func isTitleElement(n *html.Node) bool { + return n.Type == html.ElementNode && strings.ToLower(n.Data) == "title" +} + +func searchForTitle(n *html.Node) string { + if isTitleElement(n) { + return n.FirstChild.Data + } + + for c := n.FirstChild; c != nil; c = c.NextSibling { + if result := searchForTitle(c); result != "" { + return result + } + } + + return "" +} + +func httpGrabber(mod *SynScanner, ip string, port int) string { + schema := "http" + timeout := time.Duration(10 * time.Second) + client := &http.Client{ + Timeout: timeout, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return nil + }, + } + + if port == 443 || port == 8443 { + schema = "https" + client = &http.Client{ + Timeout: timeout, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return nil + }, + }, + }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return nil + }, + } + } + + url := fmt.Sprintf("%s://%s:%d/", schema, ip, port) + resp, err := client.Get(url) + if err != nil { + mod.Debug("error while grabbing banner from %s: %v", url, err) + return "" + } + defer resp.Body.Close() + + fallback := "" + for name, values := range resp.Header { + for _, value := range values { + header := strings.ToLower(name) + if len(value) > len(fallback) && (header == "x-powered-by" || header == "server") { + mod.Debug("found header %s for %s:%d -> %s", header, ip, port, value) + fallback = value + } + } + } + + doc, err := html.Parse(resp.Body) + if err != nil { + mod.Debug("error while reading and parsing response from %s: %v", url, err) + return fallback + } + + if title := searchForTitle(doc); title != "" { + return title + } + + return fallback +} diff --git a/modules/syn_scan/syn_scan.go b/modules/syn_scan/syn_scan.go index 95e683b5..4bf447f3 100644 --- a/modules/syn_scan/syn_scan.go +++ b/modules/syn_scan/syn_scan.go @@ -13,6 +13,7 @@ import ( "github.com/malfunkt/iprange" + "github.com/evilsocket/islazy/async" "github.com/evilsocket/islazy/str" ) @@ -35,6 +36,7 @@ type SynScanner struct { progressEvery time.Duration stats synScannerStats waitGroup *sync.WaitGroup + bannerQueue *async.WorkQueue } func NewSynScanner(s *session.Session) *SynScanner { @@ -45,6 +47,7 @@ func NewSynScanner(s *session.Session) *SynScanner { progressEvery: time.Duration(1) * time.Second, } + mod.bannerQueue = async.NewQueue(4, mod.bannerGrabber) mod.State.Store("scanning", &mod.addresses) mod.State.Store("progress", 0.0) @@ -257,7 +260,7 @@ func (mod *SynScanner) synScan() error { mod.Debug("sent %d bytes of SYN packet to %s for port %d", len(raw), address.String(), dstPort) } - time.Sleep(time.Duration(10) * time.Millisecond) + time.Sleep(time.Duration(25) * time.Millisecond) } } }) diff --git a/modules/syn_scan/syn_scan_reader.go b/modules/syn_scan/syn_scan_reader.go index 5ab97640..a5a00706 100644 --- a/modules/syn_scan/syn_scan_reader.go +++ b/modules/syn_scan/syn_scan_reader.go @@ -8,6 +8,8 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" + + "github.com/evilsocket/islazy/async" ) type OpenPort struct { @@ -60,13 +62,17 @@ func (mod *SynScanner) onPacket(pkt gopacket.Packet) { } if host != nil { - ports := host.Meta.GetOr("ports", map[int]OpenPort{}).(map[int]OpenPort) + ports := host.Meta.GetOr("ports", map[int]*OpenPort{}).(map[int]*OpenPort) if _, found := ports[port]; !found { - ports[port] = OpenPort{ + openPort := &OpenPort{ Proto: "tcp", Port: port, Service: network.GetServiceByPort(port, "tcp"), } + + ports[port] = openPort + + mod.bannerQueue.Add(async.Job(grabberJob{host, openPort})) } host.Meta.Set("ports", ports) diff --git a/modules/syn_scan/tcp_grabber.go b/modules/syn_scan/tcp_grabber.go new file mode 100644 index 00000000..965e80aa --- /dev/null +++ b/modules/syn_scan/tcp_grabber.go @@ -0,0 +1,18 @@ +package syn_scan + +import ( + "bufio" + "fmt" + "net" + "strings" +) + +func tcpGrabber(mod *SynScanner, ip string, port int) string { + if conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, port)); err == nil { + defer conn.Close() + msg, _ := bufio.NewReader(conn).ReadString('\n') + return strings.Trim(msg, "\r\n\t ") + } + + return "" +}