From e71e6e04311d90f8d78e3a41dbd072f740cbdbd7 Mon Sep 17 00:00:00 2001 From: Marco Lancini <info@marcolancini.it> Date: Fri, 18 May 2018 17:27:34 +0100 Subject: [PATCH] Add code --- README.md | 2 +- goscan/Makefile | 41 +++ goscan/core/cli/completer.go | 198 ++++++++++++++ goscan/core/cli/executor.go | 143 +++++++++++ goscan/core/model/model.go | 106 ++++++++ goscan/core/scan/enumerate.go | 470 ++++++++++++++++++++++++++++++++++ goscan/core/scan/enumscan.go | 38 +++ goscan/core/scan/nmap.go | 126 +++++++++ goscan/core/scan/portscan.go | 109 ++++++++ goscan/core/scan/sweep.go | 100 ++++++++ goscan/core/utils/logger.go | 42 +++ goscan/core/utils/net.go | 30 +++ goscan/core/utils/utils.go | 167 ++++++++++++ goscan/main.go | 68 +++++ 14 files changed, 1639 insertions(+), 1 deletion(-) create mode 100644 goscan/Makefile create mode 100644 goscan/core/cli/completer.go create mode 100644 goscan/core/cli/executor.go create mode 100644 goscan/core/model/model.go create mode 100644 goscan/core/scan/enumerate.go create mode 100644 goscan/core/scan/enumscan.go create mode 100644 goscan/core/scan/nmap.go create mode 100644 goscan/core/scan/portscan.go create mode 100644 goscan/core/scan/sweep.go create mode 100644 goscan/core/utils/logger.go create mode 100644 goscan/core/utils/net.go create mode 100644 goscan/core/utils/utils.go create mode 100644 goscan/main.go diff --git a/README.md b/README.md index 6ab2697..51e30d0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ It can be used to perform host discovery, port scanning, and service enumeration in situations where being stealthy is not a priority, and time is limited (think at CTFs, OSCP, exams, etc.). -<a href="https://asciinema.org/a/4ebtOAiDKmM1X89yCIpFQjqSQ" target="_blank"><img src="https://asciinema.org/a/4ebtOAiDKmM1X89yCIpFQjqSQ.png"/></a> +[](https://asciinema.org/a/4ebtOAiDKmM1X89yCIpFQjqSQ) diff --git a/goscan/Makefile b/goscan/Makefile new file mode 100644 index 0000000..9586355 --- /dev/null +++ b/goscan/Makefile @@ -0,0 +1,41 @@ +NAME := goscan +VERSION := 1.0 +AUTHOR := Marco Lancini [@LanciniMarco] +LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.author=$(AUTHOR)' + +.DEFAULT_GOAL := help + +.PHONY: setup +setup: ## Setup for required tools. + go get github.com/golang/lint/golint + go get golang.org/x/tools/cmd/goimports + go get -u github.com/golang/dep/cmd/dep + dep ensure + +.PHONY: fmt +fmt: ## Formatting source codes. + go fmt ./... + +.PHONY: lint +lint: ## Run golint and go vet. + @golint ./core/... + @go vet ./core/... + +.PHONY: build +build: main.go ## Build a binary. + go build -ldflags "$(LDFLAGS)" + +.PHONY: cross +cross: main.go ## Build binaries for cross platform. + mkdir -p pkg + @for os in "darwin" "linux"; do \ + for arch in "amd64" "386"; do \ + GOOS=$${os} GOARCH=$${arch} make build; \ + zip pkg/goscan_$(VERSION)_$${os}_$${arch}.zip goscan; \ + done; \ + done + +.PHONY: help +help: ## Show help text + @echo "Commands:" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/goscan/core/cli/completer.go b/goscan/core/cli/completer.go new file mode 100644 index 0000000..3dfe3a0 --- /dev/null +++ b/goscan/core/cli/completer.go @@ -0,0 +1,198 @@ +package cli + +import ( + "fmt" + "github.com/c-bata/go-prompt" + "goscan/core/utils" + "io/ioutil" + "path/filepath" + "strings" +) + +func excludeOptions(args []string) []string { + ret := make([]string, 0, len(args)) + for i := range args { + if !strings.HasPrefix(args[i], "-") { + ret = append(ret, args[i]) + } + } + return ret +} + +func Completer(d prompt.Document) []prompt.Suggest { + if d.TextBeforeCursor() == "" { + return []prompt.Suggest{} + } + args := strings.Split(d.TextBeforeCursor(), " ") + + // If PIPE is in text before the cursor, returns empty suggestions. + for i := range args { + if args[i] == "|" { + return []prompt.Suggest{} + } + } + return argumentsCompleter(d, excludeOptions(args)) +} + +var commands = []prompt.Suggest{ + {Text: "set_target", Description: "Set target CIDR."}, + {Text: "set_output_folder", Description: "Set the output folder."}, + {Text: "show", Description: "Show results."}, + {Text: "sweep", Description: "Perform an ARP/ping sweep."}, + {Text: "portscan", Description: "Perform a port scan."}, + {Text: "enumerate", Description: "Perform enumeration of detected services."}, + {Text: "help", Description: "Show help"}, + {Text: "exit", Description: "Exit this program"}, +} + +func argumentsCompleter(d prompt.Document, args []string) []prompt.Suggest { + if len(args) <= 1 { + return prompt.FilterHasPrefix(commands, args[0], true) + } + + first := args[0] + switch first { + case "set_target": + if len(args) == 2 { + return prompt.FilterContains(getTargetSuggestions(), args[1], true) + } + case "set_output_folder": + return fileCompleter(d) + case "show": + second := args[1] + if len(args) == 2 { + subcommands := []prompt.Suggest{ + {Text: "hosts", Description: "Show live hosts"}, + } + return prompt.FilterHasPrefix(subcommands, second, true) + } + case "sweep": + second := args[1] + if len(args) == 2 { + subcommands := []prompt.Suggest{ + {Text: "ALL", Description: "Perform ARP scan and PING sweep"}, + {Text: "ARP", Description: "Perform ARP scan"}, + {Text: "PING", Description: "Perform PING sweep"}, + } + return prompt.FilterHasPrefix(subcommands, second, true) + } + if len(args) == 3 { + return prompt.FilterContains(getTargetSuggestions(), args[2], true) + } + case "portscan": + second := args[1] + if len(args) == 2 { + subcommands := []prompt.Suggest{ + {Text: "TCP-FULL", Description: "Perform FULL TCP scan"}, + {Text: "TCP-STANDARD", Description: "Perform TCP scan (top 200)"}, + {Text: "TCP-VULN-SCAN", Description: "Perform TCP VULN scan (vulscan.nse)"}, + {Text: "UDP-STANDARD", Description: "Perform UDP scan (common ports)"}, + } + return prompt.FilterHasPrefix(subcommands, second, true) + } + if len(args) == 3 { + return prompt.FilterContains(getHostSuggestions(), args[2], true) + } + case "enumerate": + if len(args) == 2 { + second := args[1] + subcommands := []prompt.Suggest{ + {Text: "ALL", Description: "Automatically identify open services and enumerate them"}, + {Text: "DNS", Description: "Enumerate DNS"}, + {Text: "FINGER", Description: "Enumerate FINGER"}, + {Text: "FTP", Description: "Enumerate FTP"}, + {Text: "HTTP", Description: "Enumerate HTTP"}, + {Text: "RDP", Description: "Enumerate RDP"}, + {Text: "SMB", Description: "Enumerate SMB"}, + {Text: "SMTP", Description: "Enumerate SMTP"}, + {Text: "SNMP", Description: "Enumerate SNMP"}, + {Text: "SQL", Description: "Enumerate SQL"}, + {Text: "SSH", Description: "Enumerate SSH"}, + } + return prompt.FilterHasPrefix(subcommands, second, true) + } + if len(args) == 3 { + third := args[2] + subcommands := []prompt.Suggest{ + {Text: "POLITE", Description: "Avoid bruteforcing"}, + {Text: "BRUTEFORCE", Description: "Include bruteforce scripts"}, + } + return prompt.FilterHasPrefix(subcommands, third, true) + } + if len(args) == 4 { + return prompt.FilterContains(getHostSuggestions(), args[3], true) + } + default: + return []prompt.Suggest{} + } + + return []prompt.Suggest{} +} + +func getHostSuggestions() []prompt.Suggest { + s := []prompt.Suggest{} + s = append(s, prompt.Suggest{ + Text: "ALL", + Description: "Scan all live hosts", + }) + + for _, h := range utils.Config.Hosts { + s = append(s, prompt.Suggest{ + Text: h.Address, + Description: fmt.Sprintf("Scan only host: %s", h.Address), + }) + } + + return s +} + +func getTargetSuggestions() []prompt.Suggest { + // Default suggestions + var target string = utils.Const_example_target_cidr + var desc string = utils.Const_example_target_desc + + // Detect if a target has already been selected + if utils.Config.Target != "" { + target = fmt.Sprintf("%s", utils.Config.Target) + desc = "Currently selected target" + } + + // Build suggestion + s := make([]prompt.Suggest, 1, 5) + s[0] = prompt.Suggest{ + Text: target, + Description: desc, + } + + // Parse address from network interface + localInterfaces := utils.ParseLocalIP() + for eth, ip := range localInterfaces { + s = append(s, prompt.Suggest{ + Text: utils.ParseCIDR(ip), + Description: fmt.Sprintf("Subnet from interface: %s", eth), + }) + + } + + return s +} + +func fileCompleter(d prompt.Document) []prompt.Suggest { + path := d.GetWordBeforeCursor() + if strings.HasPrefix(path, "./") { + path = path[2:] + } + dir := filepath.Dir(path) + files, err := ioutil.ReadDir(dir) + if err != nil { + return []prompt.Suggest{} + } + suggests := make([]prompt.Suggest, 0, len(files)) + for _, f := range files { + if !f.IsDir() { + continue + } + suggests = append(suggests, prompt.Suggest{Text: filepath.Join(dir, f.Name())}) + } + return prompt.FilterHasPrefix(suggests, path, false) +} diff --git a/goscan/core/cli/executor.go b/goscan/core/cli/executor.go new file mode 100644 index 0000000..cc8068f --- /dev/null +++ b/goscan/core/cli/executor.go @@ -0,0 +1,143 @@ +package cli + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "goscan/core/scan" + "goscan/core/utils" + "os" +) + +func Executor(s string) { + // Parse cmd + cmd, args := utils.ParseCmd(s) + + // Execute commands + switch cmd { + + case "set_target": + cmdSetTarget(args) + case "set_output_folder": + cmdSetOutputFolder(args) + case "show": + cmdShow(args) + case "sweep": + cmdSweep(args) + case "portscan": + cmdPortscan(args) + case "enumerate": + cmdEnumerate(args) + case "help": + cmdHelp() + case "exit", "quit": + os.Exit(0) + return + + case "": + default: + return + } + + // Start checking for running scans + go scan.ReportStatusNmap() + go scan.ReportStatusEnum() +} + +func cmdHelp() { + utils.Config.Log.LogInfo("GoScan automates the scanning and enumeration steps of a penetration test") + utils.Config.Log.LogInfo("Available commands:") + + data := [][]string{ + []string{"Set output folder", "set_output_folder <PATH>"}, + []string{"Ping Sweep", "sweep <TYPE> <TARGET>"}, + []string{"Port Scan", "portscan <TYPE> <TARGET>"}, + []string{"Service Enumeration", "enumerate <TYPE> <POLITE/AGGRESSIVE> <TARGET>"}, + []string{"Show live hosts", "show hosts"}, + } + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Command", "Syntax"}) + table.SetAlignment(3) + table.SetAutoWrapText(false) + table.AppendBulk(data) + table.Render() +} + +func cmdShow(args []string) { + what, _ := utils.ParseNextArg(args) + switch what { + case "hosts": + utils.ShowHosts() + } +} + +func cmdSetTarget(args []string) bool { + if len(args) != 1 { + utils.Config.Log.LogError("Invalid command provided") + return false + } + ip, _ := utils.ParseNextArg(args) + cidr := utils.ParseCIDR(ip) + if cidr == "" { + utils.Config.Log.LogError("Invalid CIDR provided") + return false + } + utils.Config.Log.LogInfo(fmt.Sprintf("Selected target: %s", cidr)) + utils.Config.Target = cidr + + return true +} + +func cmdSetOutputFolder(args []string) { + if len(args) != 1 { + utils.Config.Log.LogError("Invalid command provided") + return + } + folder, _ := utils.ParseNextArg(args) + utils.Config.Outfolder = folder + utils.EnsureDir(utils.Config.Outfolder) +} + +func cmdSweep(args []string) { + if len(args) != 2 { + utils.Config.Log.LogError("Invalid command provided") + return + } + // Get type of scan + kind, args := utils.ParseNextArg(args) + // Get target and update global config + target, _ := utils.ParseNextArg(args) + check := cmdSetTarget(args) + if check == false { + return + } + // Perform ping sweep + scan.ScanSweep(target, kind) +} + +func cmdPortscan(args []string) { + if len(args) != 2 { + utils.Config.Log.LogError("Invalid command provided") + return + } + // Get type of scan + kind, args := utils.ParseNextArg(args) + // Get target host + target, _ := utils.ParseNextArg(args) + // Perform port scan + scan.ScanPort(target, kind) +} + +func cmdEnumerate(args []string) { + if len(args) != 3 { + utils.Config.Log.LogError("Invalid command provided") + return + } + // Get type of scan + kind, args := utils.ParseNextArg(args) + // Get politeness + polite, args := utils.ParseNextArg(args) + // Get target host + target, _ := utils.ParseNextArg(args) + // Perform port scan + scan.ScanEnumerate(target, polite, kind) +} diff --git a/goscan/core/model/model.go b/goscan/core/model/model.go new file mode 100644 index 0000000..e6304cc --- /dev/null +++ b/goscan/core/model/model.go @@ -0,0 +1,106 @@ +package model + +import ( + "fmt" +) + +// --------------------------------------------------------------------------------------- +// SCAN STRUCTURE +// --------------------------------------------------------------------------------------- +type Scan struct { + Name string + Target string + Status int + Outfolder string + Outfile string + Cmd string + Result []byte +} + +func (s *Scan) String() string { + return fmt.Sprintf("Target: %s [%d]", s.Target, s.Status) +} + +// --------------------------------------------------------------------------------------- +// ENUMERATE STRUCTURE +// --------------------------------------------------------------------------------------- +type Enumeration struct { + Target *Host + Outfolder string + Kind string + Status int + Result []byte + Polite string +} + +func (e *Enumeration) String() string { + return fmt.Sprintf("Enumeration [%s]: %s [%d]", e.Kind, e.Target.Address, e.Status) +} + +// --------------------------------------------------------------------------------------- +// SERVICE +// --------------------------------------------------------------------------------------- +type Service struct { + Name string + Version string + Product string + OsType string +} + +func (s *Service) String() string { + return fmt.Sprintf("%s%s - %s", s.Name, s.Version, s.Product) +} + +// --------------------------------------------------------------------------------------- +// PORT +// --------------------------------------------------------------------------------------- +type Port struct { + Number int + Protocol string + Status string + Service Service +} + +func (p *Port) String() string { + return fmt.Sprintf("%5d/%s %-8s: %s", p.Number, p.Protocol, p.Status, p.Service) +} + +// Returns true if 2 Ports are the same +func (p *Port) Equal(other Port) bool { + return (p.Number == other.Number) && (p.Protocol == other.Protocol) +} + +// --------------------------------------------------------------------------------------- +// HOST +// --------------------------------------------------------------------------------------- +type Host struct { + Address string + Status string + OS string + Ports []Port +} + +func (h *Host) String() string { + out := fmt.Sprintf("%s - %s", h.Status, h.Address) + if h.OS != "" { + out = fmt.Sprintf("%s [%s] ", out, h.OS) + } + for i := 0; i < len(h.Ports); i++ { + out = fmt.Sprintf("%s [%s]", out, h.Ports[i].String()) + } + return out +} + +// Add port to the list only if it's new +func (h *Host) AddPort(newPort Port) { + + existing := false + for _, p := range h.Ports { + if p.Equal(newPort) { + existing = true + } + } + if existing == false { + h.Ports = append(h.Ports, newPort) + } +} diff --git a/goscan/core/scan/enumerate.go b/goscan/core/scan/enumerate.go new file mode 100644 index 0000000..2c6c9dc --- /dev/null +++ b/goscan/core/scan/enumerate.go @@ -0,0 +1,470 @@ +package scan + +import ( + "fmt" + "goscan/core/model" + "goscan/core/utils" + "os/exec" + "path/filepath" + "strings" + "time" +) + +// --------------------------------------------------------------------------------------- +// ENUMERATION +// --------------------------------------------------------------------------------------- +type EnumScan model.Enumeration + +func NewEnumScan(target *model.Host, kind, polite string) *EnumScan { + // Create a Scan + s := &EnumScan{ + Target: target, + Kind: kind, + Polite: polite, + Status: not_started, + } + return s +} + +func (s *EnumScan) preScan() { + s.Status = in_progress +} +func (s *EnumScan) postScan() { + s.Status = finished +} + +func (s *EnumScan) makeOutputPath(folder, file string) string { + resFolder := filepath.Join(utils.Config.Outfolder, utils.CleanPath(s.Target.Address), folder) + resFile := filepath.Join(resFolder, file) + utils.EnsureDir(resFolder) + return resFile +} + +func (s *EnumScan) runCmd(cmd string) ([]byte, error) { + utils.Config.Log.LogDebug(fmt.Sprintf("Running: %s", cmd)) + res, err := exec.Command("sh", "-c", cmd).Output() + if err != nil { + utils.Config.Log.LogError(fmt.Sprintf("Failed Enumeration: %s", err)) + s.Status = failed + } + return res, err +} + +func (s *EnumScan) Run() { + // Pre-scan checks + s.preScan() + + // Dispatch scan + switch s.Kind { + case "DNS": + s.EnumDNS() + case "FINGER": + s.EnumFINGER() + case "FTP": + s.EnumFTP() + case "HTTP": + s.EnumHTTP() + case "RDP": + s.EnumRDP() + case "SMB": + s.EnumSMB() + case "SMTP": + s.EnumSMTP() + case "SNMP": + s.EnumSNMP() + case "SQL": + s.EnumSQL() + case "SSH": + s.EnumSSH() + case "ALL": + s.EnumDNS() + s.EnumFINGER() + s.EnumFTP() + s.EnumHTTP() + s.EnumRDP() + s.EnumSMB() + s.EnumSMTP() + s.EnumSNMP() + s.EnumSQL() + s.EnumSSH() + } + + // Post-scan checks + s.postScan() + +} + +// --------------------------------------------------------------------------------------- +// SCANNERS +// --------------------------------------------------------------------------------------- +func (s *EnumScan) EnumDNS() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "dns") { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + name := "dns_nmap" + nmapArgs := fmt.Sprintf("-sV -Pn -sU -p53,%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "FTP", name, nmapArgs) + nmap.RunNmap() + + // POLITE + } + } + } +} + +func (s *EnumScan) EnumFINGER() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + if s.Target.Ports[i].Number == 79 { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, "finger")) + + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + name := fmt.Sprintf("finger_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV -Pn --script=finger -p%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "FINGER", name, nmapArgs) + nmap.RunNmap() + + // ----------------------------------------------------------------------- + // FINGER-USER-ENUM + // ----------------------------------------------------------------------- + output := s.makeOutputPath("FINGER", "finger_user-enum") + cmd := fmt.Sprintf("finger-user-enum.pl -U %s -t %s > %s", utils.WORDLIST_FINGER_USER, s.Target.Address, output) + s.runCmd(cmd) + } + } + } +} + +func (s *EnumScan) EnumFTP() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "ftp") { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + name := fmt.Sprintf("ftp_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV -Pn --script=ftp-anon,ftp-bounce,ftp-libopie,ftp-proftpd-backdoor,ftp-vsftpd-backdoor,ftp-vuln-cve2010-4221 -p%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "FTP", name, nmapArgs) + nmap.RunNmap() + + // ----------------------------------------------------------------------- + // FTP-USER-ENUM + // ----------------------------------------------------------------------- + output := s.makeOutputPath("FTP", "ftp_user-enum") + cmd := fmt.Sprintf("ftp-user-enum.pl -U %s -t %s > %s", utils.WORDLIST_FTP_USER, s.Target.Address, output) + s.runCmd(cmd) + + // ----------------------------------------------------------------------- + // HYDRA + // ----------------------------------------------------------------------- + // If not polite scan + if s.Polite != "POLITE" { + // Build command + output := s.makeOutputPath("FTP", "ftp_hydra") + cmd := fmt.Sprintf("hydra -L %s -P %s -f -o %s -u %s -s %d ftp", + utils.WORDLIST_HYDRA_FTP_USER, utils.WORDLIST_HYDRA_FTP_PWD, + output, + s.Target.Address, s.Target.Ports[i].Number, + ) + // Run command + s.runCmd(cmd) + } + } + } + } +} + +func (s *EnumScan) EnumHTTP() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "http") || strings.Contains(strings.ToLower(service), "https") || strings.Contains(strings.ToLower(service), "ssl/http") { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + name := fmt.Sprintf("http_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV -Pn --script=http-vhosts,http-userdir-enum,http-apache-negotiation,http-backup-finder,http-config-backup,http-default-accounts,http-methods,http-method-tamper,http-passwd,http-sitemap-generator,http-auth-finder,http-auth,http-fileupload-exploiter,http-put,http-sql-injection,http-stored-xss,http-xssed,http-php-version,http-unsafe-output-escaping,http-phpmyadmin-dir-traversal,http-ntlm-info,http-phpself-xss,http-open-redirect,http-iis-webdav-vuln,http-form-fuzzer,http-vuln-cve2009-3960,http-vuln-cve2010-0738,http-vuln-cve2010-2861,http-vuln-cve2011-3368,http-vuln-cve2012-1823,http-vuln-cve2013-0156,http-robots.txt,http-wordpress-brute,http-wordpress-enum --script-args http-put.url='/uploads/rootme.php',http-put.file='/root/www/php-reverse.php',basepath='/' -p%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "HTTP", name, nmapArgs) + nmap.RunNmap() + + // ----------------------------------------------------------------------- + // NIKTO + // ----------------------------------------------------------------------- + // Build command + output := s.makeOutputPath("HTTP", fmt.Sprintf("http_nikto_%d", s.Target.Ports[i].Number)) + cmd := fmt.Sprintf("nikto -host %s -p %d > %s", s.Target.Address, s.Target.Ports[i].Number, output) + // Run command + s.runCmd(cmd) + + // ----------------------------------------------------------------------- + // DIRB + // ----------------------------------------------------------------------- + // Build command + output = s.makeOutputPath("HTTP", fmt.Sprintf("http_dirb_%d", s.Target.Ports[i].Number)) + protocol := "https" + if strings.Contains(strings.ToLower(service), "http") { + protocol = "http" + } + cmd = fmt.Sprintf("dirb %s://%s:%d -o %s -S -r", protocol, s.Target.Address, s.Target.Ports[i].Number, output) + // Run command + s.runCmd(cmd) + + // If not polite scan + if s.Polite != "POLITE" { + // ------------------------------------------------------------------- + // SQLMAP + // ------------------------------------------------------------------- + // Build command + output := s.makeOutputPath("HTTP", fmt.Sprintf("http_sqlmap_%d", s.Target.Ports[i].Number)) + protocol := "https" + if strings.Contains(strings.ToLower(service), "http") { + protocol = "http" + } + cmd := fmt.Sprintf("sqlmap -u %s://%s:%d --crawl=1 > %s", protocol, s.Target.Address, s.Target.Ports[i].Number, output) + // Run command + s.runCmd(cmd) + + // ------------------------------------------------------------------- + // FIMAP + // ------------------------------------------------------------------- + // Build command + output = s.makeOutputPath("HTTP", fmt.Sprintf("http_fimap_%d", s.Target.Ports[i].Number)) + protocol = "https" + if strings.Contains(strings.ToLower(service), "http") { + protocol = "http" + } + cmd = fmt.Sprintf("fimap -u \"%s://%s:%d\" > %s", protocol, s.Target.Address, s.Target.Ports[i].Number, output) + // Run command + s.runCmd(cmd) + } + } + } + } +} + +func (s *EnumScan) EnumRDP() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "ms-wbt-server") { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + name := fmt.Sprintf("rdp_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV --script=rdp-vuln-ms12-020 -p%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "RDP", name, nmapArgs) + nmap.RunNmap() + } + } + } +} + +func (s *EnumScan) EnumSMB() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "smb") || strings.Contains(strings.ToLower(service), "microsoft-ds") || strings.Contains(strings.ToLower(service), "netbios") { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + + // POLITE + } + } + } +} + +func (s *EnumScan) EnumSMTP() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "smtp") { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + name := fmt.Sprintf("smtp_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV -Pn --script=smtp-enum-users,smtp-vuln* --script-args='smtp-vuln-cve2010-4344.exploit' -p25,465,587,%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "SMTP", name, nmapArgs) + nmap.RunNmap() + + // ----------------------------------------------------------------------- + // SMTP-USER-ENUM + // ----------------------------------------------------------------------- + output := s.makeOutputPath("SMTP", "smtp_user-enum") + cmd := fmt.Sprintf("smtp-user-enum -M VRFY -U %s -t %s > %s", utils.WORDLIST_SMTP, s.Target.Address, output) + s.runCmd(cmd) + + // POLITE + } + } + } +} + +func (s *EnumScan) EnumSNMP() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "snmp") { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + name := fmt.Sprintf("snmp_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV -Pn --script=snmp-netstat,snmp-processes,snmp-win32-services,snmp-win32-shares,snmp-win32-software,snmp-win32-users -p161,162,%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "SNMP", name, nmapArgs) + nmap.RunNmap() + + // ----------------------------------------------------------------------- + // SNMPCHECK + // ----------------------------------------------------------------------- + output := s.makeOutputPath("SNMP", "snmp_snmpcheck") + cmd := fmt.Sprintf("snmpcheck %s > %s", s.Target.Address, output) + s.runCmd(cmd) + + // POLITE + } + } + } +} + +func (s *EnumScan) EnumSSH() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "ssh") { + // Start Enumerating + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + // If not polite scan + if s.Polite != "POLITE" { + // ------------------------------------------------------------------- + // HYDRA + // ------------------------------------------------------------------- + // Build command + output := s.makeOutputPath("SSH", "ssh_hydra") + cmd := fmt.Sprintf("hydra -L %s -P %s -f -o %s -u %s -s %d ssh", + utils.WORDLIST_HYDRA_SSH_USER, utils.WORDLIST_HYDRA_SSH_PWD, + output, + s.Target.Address, s.Target.Ports[i].Number, + ) + // Run command + s.runCmd(cmd) + } + } + } + } +} + +func (s *EnumScan) EnumSQL() { + for i := 0; i < len(s.Target.Ports); i++ { + // Enumerate only if port is open + if s.Target.Ports[i].Status == "open" { + // Dispatch the correct scanner + service := s.Target.Ports[i].Service.Name + if strings.Contains(strings.ToLower(service), "ms-sql") { + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + name := fmt.Sprintf("sql_mssql_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV -Pn --script=ms-sql-info,ms-sql-config,ms-sql-dump-hashes,ms-sql-brute,ms-sql-dac,ms-sql-empty-password,ms-sql-hasdbaccess,ms-sql-query,ms-sql-tables,ms-sql-xp-cmdshell --script-args mssql.instance-port=%d,mssql.username=sa,mssql.password=sa,ms-sql-query.query='SELECT * FROM master..syslogins' -p%d", s.Target.Ports[i].Number, s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "SQL", name, nmapArgs) + nmap.RunNmap() + + } else if strings.Contains(strings.ToLower(service), "mysql") { + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + name := fmt.Sprintf("sql_mysql_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV -Pn --script=mysql-brute,mysql-databases,mysql-empty-password,mysql-enum,mysql-info,mysql-users,mysql-variables,mysql-vuln-cve2012-2122 -p%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "SQL", name, nmapArgs) + nmap.RunNmap() + + } else if strings.Contains(strings.ToLower(service), "oracle") { + // ----------------------------------------------------------------------- + // NMAP + // ----------------------------------------------------------------------- + utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service)) + name := fmt.Sprintf("sql_oracle_nmap_%d", s.Target.Ports[i].Number) + nmapArgs := fmt.Sprintf("-sV -Pn --script=oracle-brute,oracle-enum-users,oracle-sid-brute --script-args oracle-brute.sid=ORCL -p%d", s.Target.Ports[i].Number) + nmap := NewScan(name, s.Target.Address, "SQL", name, nmapArgs) + nmap.RunNmap() + + } + } + } +} + +// --------------------------------------------------------------------------------------- +// SCAN MANAGEMENT +// --------------------------------------------------------------------------------------- +func ReportStatusEnum() { + ticker := time.Tick(notificationDelay) + for { + <-ticker + + if len(EnumList) != 0 { + i := 0 + for _, scan := range EnumList { + switch { + case scan.Status == null: + break + case scan.Status == failed: + utils.Config.Log.LogError(fmt.Sprintf("Enumeration failed on host: %s", scan.Target.Address)) + case scan.Status == in_progress: + utils.Config.Log.LogInfo(fmt.Sprintf("Enumeration in progress on host (%s):\t%s", scan.Kind, scan.Target.Address)) + // Update in place, remove finished scans + EnumList[i] = scan + i++ + case scan.Status == finished: + utils.Config.Log.LogNotify(fmt.Sprintf("Enumeration finished on host (%s):\t%s", scan.Kind, scan.Target.Address)) + utils.Config.Log.LogNotify(fmt.Sprintf("Output has been saved at (%s):\t%s", scan.Kind, utils.Config.Outfolder)) + } + } + // Update in place, remove finished scans + EnumList = EnumList[:i] + } + } +} diff --git a/goscan/core/scan/enumscan.go b/goscan/core/scan/enumscan.go new file mode 100644 index 0000000..16d876c --- /dev/null +++ b/goscan/core/scan/enumscan.go @@ -0,0 +1,38 @@ +package scan + +import ( + "goscan/core/model" + "goscan/core/utils" +) + +// --------------------------------------------------------------------------------------- +// CONSTANTS +// --------------------------------------------------------------------------------------- +var EnumList = []*EnumScan{} + +// --------------------------------------------------------------------------------------- +// SCAN LAUNCHER +// --------------------------------------------------------------------------------------- +func ScanEnumerate(target string, polite string, kind string) { + for i := 0; i < len(utils.Config.Hosts); i++ { + // Scan only if: + // - target is ALL + // - or if host is the selected one + if target == "ALL" || target == utils.Config.Hosts[i].Address { + go workerEnum(&utils.Config.Hosts[i], kind, polite) + } + } +} + +// --------------------------------------------------------------------------------------- +// WORKER +// --------------------------------------------------------------------------------------- +func workerEnum(h *model.Host, kind string, polite string) { + // Instantiate new EnumScan + s := NewEnumScan(h, kind, polite) + EnumList = append(EnumList, s) + + // Run the scan + s.Run() + +} diff --git a/goscan/core/scan/nmap.go b/goscan/core/scan/nmap.go new file mode 100644 index 0000000..5c8bbec --- /dev/null +++ b/goscan/core/scan/nmap.go @@ -0,0 +1,126 @@ +package scan + +import ( + "fmt" + go_nmap "github.com/lair-framework/go-nmap" + "goscan/core/model" + "goscan/core/utils" + "io/ioutil" + "os/exec" + "path/filepath" + "time" +) + +// --------------------------------------------------------------------------------------- +// CONSTANTS +// --------------------------------------------------------------------------------------- +const ( + null = iota + not_started + in_progress + failed + done + finished +) + +// --------------------------------------------------------------------------------------- +// NMAP INTERACTION +// --------------------------------------------------------------------------------------- +type NmapScan model.Scan + +// Constructor for NmapScan +func NewScan(name, target, folder, file, nmapArgs string) *NmapScan { + // Create a Scan + s := &NmapScan{ + Name: name, + Target: target, + Status: not_started, + } + // Construct output path and create if it doesn't exist + s.Outfolder = filepath.Join(utils.Config.Outfolder, utils.CleanPath(target), folder) + s.Outfile = filepath.Join(s.Outfolder, file) + utils.EnsureDir(s.Outfolder) + // Construct command + s.Cmd = s.constructCmd(nmapArgs) + + return s +} + +func (s *NmapScan) preScan() { + s.Status = in_progress +} +func (s *NmapScan) postScan() { + s.Status = finished +} + +func (s *NmapScan) constructCmd(args string) string { + return fmt.Sprintf("nmap %s %s -oA %s", args, s.Target, s.Outfile) +} + +// Run nmap scan +func (s *NmapScan) RunNmap() { + // Pre-scan checks + s.preScan() + + // Run nmap + utils.Config.Log.LogDebug(fmt.Sprintf("Nmap command: %s", s.Cmd)) + res, err := exec.Command("sh", "-c", s.Cmd).Output() + if err != nil { + utils.Config.Log.LogError(fmt.Sprintf("Failed to nmap destination %s (%s): %s", s.Target, s.Name, err)) + s.Status = failed + } + s.Result = res + + // Post-scan checks + s.postScan() + +} + +// Parse nmap XML output file +func (s *NmapScan) ParseOutput() *go_nmap.NmapRun { + sweepXML := fmt.Sprintf("%s.xml", s.Outfile) + dat, err := ioutil.ReadFile(sweepXML) + if err != nil { + utils.Config.Log.LogError(fmt.Sprintf("Error while opening output file: %s", sweepXML)) + return nil + } + + res, err := go_nmap.Parse(dat) + if err != nil { + utils.Config.Log.LogError("Error while parsing nmap output") + return nil + } + return res +} + +// --------------------------------------------------------------------------------------- +// SCAN MANAGEMENT +// --------------------------------------------------------------------------------------- +func ReportStatusNmap() { + ticker := time.Tick(notificationDelay) + for { + <-ticker + + if len(ScansList) != 0 { + i := 0 + for _, scan := range ScansList { + switch { + case scan.Status == null: + break + case scan.Status == failed: + utils.Config.Log.LogError(fmt.Sprintf("Nmap failed on host: %s", scan.Target)) + case scan.Status == in_progress: + utils.Config.Log.LogInfo(fmt.Sprintf("Nmap work in progress on host (%s):\t%s", scan.Name, scan.Target)) + // Update in place, remove finished scans + ScansList[i] = scan + i++ + case scan.Status == finished: + utils.Config.Log.LogNotify(fmt.Sprintf("Nmap finished on host (%s):\t%s", scan.Name, scan.Target)) + utils.Config.Log.LogNotify(fmt.Sprintf("Output has been saved at (%s):\t%s", scan.Name, utils.Config.Outfolder)) + } + } + // Update in place, remove finished scans + ScansList = ScansList[:i] + } + } +} diff --git a/goscan/core/scan/portscan.go b/goscan/core/scan/portscan.go new file mode 100644 index 0000000..9db16e5 --- /dev/null +++ b/goscan/core/scan/portscan.go @@ -0,0 +1,109 @@ +package scan + +import ( + "goscan/core/model" + "goscan/core/utils" + "sync" + "time" +) + +// --------------------------------------------------------------------------------------- +// CONSTANTS +// --------------------------------------------------------------------------------------- +var mutex sync.Mutex +var notificationDelay time.Duration = time.Duration(utils.Const_notification_delay_unit) * time.Second +var ScansList = []*NmapScan{} + +// --------------------------------------------------------------------------------------- +// DISPATCHER +// --------------------------------------------------------------------------------------- +func ScanPort(target string, kind string) { + folder := "portscan" + + // Dispatch scan + switch kind { + case "TCP-FULL": + utils.Config.Log.LogInfo("Starting full TCP port scan") + file, nmapArgs := "tcp_full", utils.Const_NMAP_TCP_FULL + execScan(file, target, folder, file, nmapArgs) + case "TCP-STANDARD": + utils.Config.Log.LogInfo("Starting top 200 TCP port scan") + file, nmapArgs := "tcp_standard", utils.Const_NMAP_TCP_STANDARD + execScan(file, target, folder, file, nmapArgs) + case "TCP-VULN-SCAN": + utils.Config.Log.LogInfo("Starting TCP vuln scan") + file, nmapArgs := "tcp_vuln", utils.Const_NMAP_TCP_VULN + execScan(file, target, folder, file, nmapArgs) + case "UDP-STANDARD": + utils.Config.Log.LogInfo("Starting UDP port scan (common ports)") + file, nmapArgs := "udp_standard", utils.Const_NMAP_UDP_STANDARD + execScan(file, target, folder, file, nmapArgs) + default: + utils.Config.Log.LogError("Invalid type of scan") + return + } +} + +// --------------------------------------------------------------------------------------- +// SCAN LAUNCHER +// --------------------------------------------------------------------------------------- +func execScan(name, target, folder, file, nmapArgs string) { + for i := 0; i < len(utils.Config.Hosts); i++ { + // Scan only if: + // - target is ALL + // - or if host is the selected one + if target == "ALL" || target == utils.Config.Hosts[i].Address { + go worker(name, &utils.Config.Hosts[i], folder, file, nmapArgs) + } + } +} + +// --------------------------------------------------------------------------------------- +// WORKER +// --------------------------------------------------------------------------------------- +func worker(name string, h *model.Host, folder string, file string, nmapArgs string) { + // Instantiate new NmapScan + s := NewScan(name, h.Address, folder, file, nmapArgs) + ScansList = append(ScansList, s) + + // Run the scan + s.RunNmap() + + // Parse nmap's output + res := s.ParseOutput() + + // Extract ports and services + for _, record := range res.Hosts { + // Extract OS + if len(record.Os.OsMatches) != 0 && record.Os.OsMatches[0].Name != "" { + mutex.Lock() + h.OS = record.Os.OsMatches[0].Name + mutex.Unlock() + } + // Patse ports + for _, port := range record.Ports { + var tService model.Service + var tPort model.Port + // Extract service + if port.Service.Name != "" { + tService = model.Service{ + Name: port.Service.Name, + Version: port.Service.Version, + Product: port.Service.Product, + OsType: port.Service.OsType, + } + } + // Extract port + tPort = model.Port{ + Number: port.PortId, + Protocol: port.Protocol, + Status: port.State.State, + Service: tService, + } + // If new port, add it to host + mutex.Lock() + h.AddPort(tPort) + mutex.Unlock() + } + } +} diff --git a/goscan/core/scan/sweep.go b/goscan/core/scan/sweep.go new file mode 100644 index 0000000..a7828e2 --- /dev/null +++ b/goscan/core/scan/sweep.go @@ -0,0 +1,100 @@ +package scan + +import ( + "fmt" + "goscan/core/model" + "goscan/core/utils" + "os/exec" + "path/filepath" + "strings" +) + +// --------------------------------------------------------------------------------------- +// DISPATCHER +// --------------------------------------------------------------------------------------- +func ScanSweep(target string, kind string) { + // Dispatch scan + switch kind { + case "PING": + pingSweep(target) + case "ARP": + arpScan(target) + case "ALL": + arpScan(target) + pingSweep(target) + default: + utils.Config.Log.LogError("Invalid type of scan") + return + } + // Print hosts on screen + utils.ShowHosts() +} + +// --------------------------------------------------------------------------------------- +// SCANS +// --------------------------------------------------------------------------------------- +func pingSweep(target string) { + // Create a new Scan and run it + utils.Config.Log.LogInfo("Starting ping sweep...") + name, folder, file, nmapArgs := "pingsweep", "sweep", "ping", "-n -sn" + s := NewScan(name, target, folder, file, nmapArgs) + s.RunNmap() + + // Parse nmap's output + res := s.ParseOutput() + + // Identify live hosts + for _, host := range res.Hosts { + status := host.Status.State + if status == "up" { + addr := host.Addresses[0].Addr + temp := model.Host{ + Address: addr, + Status: status, + } + AddHost(temp) + } + } + utils.Config.Log.LogInfo("Ping sweep completed!") +} + +func arpScan(target string) { + // Directly run netdiscover + utils.Config.Log.LogInfo("Starting ARP scan...") + outfile := filepath.Join(utils.Config.Outfolder, utils.CleanPath(target), "sweep/netdiscover") + cmd := fmt.Sprintf("sudo netdiscover -r %s -P > %s && cat %s | grep -E '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}' | cut -d ' ' -f 2 | sort -u > %s", target, outfile, outfile, outfile) + + // Execute the command + utils.Config.Log.LogDebug(fmt.Sprintf("Netdiscover command: %s", cmd)) + out, err := exec.Command("sh", "-c", cmd).Output() + if err != nil { + utils.Config.Log.LogError("Failed to run netdiscover") + return + } + + // Identify live hosts + for _, line := range strings.Split(strings.TrimSuffix(string(out[:]), "\n"), "\n") { + if line != "" { + temp := model.Host{ + Address: line, + Status: "up", + } + AddHost(temp) + } + } + + utils.Config.Log.LogInfo("ARP scan completed!") +} + +// Add port to the list only if it's new +func AddHost(newHost model.Host) { + existing := false + for _, h := range utils.Config.Hosts { + if h.Address == newHost.Address { + existing = true + } + } + if existing == false { + utils.Config.Hosts = append(utils.Config.Hosts, newHost) + } +} diff --git a/goscan/core/utils/logger.go b/goscan/core/utils/logger.go new file mode 100644 index 0000000..bdd9cfb --- /dev/null +++ b/goscan/core/utils/logger.go @@ -0,0 +1,42 @@ +package utils + +import ( + "fmt" + "github.com/fatih/color" +) + +// --------------------------------------------------------------------------------------- +// LOGGER +// --------------------------------------------------------------------------------------- +type Logger struct{} + +func InitLogger() *Logger { + return &Logger{} +} + +func (l *Logger) LogDebug(message string) { + highlight := color.New(color.FgWhite).SprintFunc() + reset := color.New(color.FgWhite).SprintFunc() + fmt.Println(highlight("[-]"), reset(message)) +} + +func (l *Logger) LogInfo(message string) { + highlight := color.New(color.FgBlue).SprintFunc() + reset := color.New(color.FgWhite).SprintFunc() + fmt.Println(highlight("[*]"), reset(message)) +} + +func (l *Logger) LogNotify(message string) { + highlight := color.New(color.FgGreen).SprintFunc() + fmt.Println(highlight("[+]"), highlight(message)) +} + +func (l *Logger) LogWarning(message string) { + highlight := color.New(color.FgYellow).SprintFunc() + fmt.Println(highlight("[?]"), highlight(message)) +} + +func (l *Logger) LogError(message string) { + highlight := color.New(color.FgRed).SprintFunc() + fmt.Println(highlight("[!]"), highlight(message)) +} diff --git a/goscan/core/utils/net.go b/goscan/core/utils/net.go new file mode 100644 index 0000000..18c76fa --- /dev/null +++ b/goscan/core/utils/net.go @@ -0,0 +1,30 @@ +package utils + +import ( + "net" +) + +// Parse a string and returns the corresponding CIDR +func ParseCIDR(s string) string { + _, ipv4Net, err := net.ParseCIDR(s) + if err != nil { + return "" + } + return ipv4Net.String() +} + +// Returns all the addresses of the local network interfaces +func ParseLocalIP() map[string]string { + // Returns a Map of interface:subnet + res := make(map[string]string) + + ifaces, _ := net.Interfaces() + for _, i := range ifaces { + addrs, _ := i.Addrs() + for _, addr := range addrs { + res[i.Name] = addr.String() + break + } + } + return res +} diff --git a/goscan/core/utils/utils.go b/goscan/core/utils/utils.go new file mode 100644 index 0000000..07ada1d --- /dev/null +++ b/goscan/core/utils/utils.go @@ -0,0 +1,167 @@ +package utils + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "goscan/core/model" + "os" + "os/user" + "path/filepath" + "strings" +) + +// --------------------------------------------------------------------------------------- +// CONSTANTS +// --------------------------------------------------------------------------------------- +var Config config + +var Const_notification_delay_unit = 5 +var Const_example_target_cidr = "127.0.0.1/32" +var Const_example_target_desc = "Target CIDR or /32 for single target" + +// NMAP COMMANDS +var Const_UDP_PORTS = "19,53,69,79,111,123,135,137,138,161,177,445,500,514,520,1434,1900,5353" +var Const_NMAP_TCP_FULL = "-Pn -sT -sC -A -T4 -p-" +var Const_NMAP_TCP_STANDARD = "-Pn -sS -A -T4 --top-ports 200" +var Const_NMAP_TCP_VULN = "-Pn -sT -sV -p- --script=vulscan/vulscan.nse" +var Const_NMAP_UDP_STANDARD = fmt.Sprintf("-Pn -sU -sC -A -T4 -p%s", Const_UDP_PORTS) + +// WORDLISTS +var WORDLIST_FUZZ_NAMELIST = "/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/names/namelist.txt" +var WORDLIST_MSF_PWDS = "/usr/share/wordlists/metasploit/unix_passwords.txt" +var WORDLIST_FINGER_USER = WORDLIST_FUZZ_NAMELIST +var WORDLIST_FTP_USER = WORDLIST_FUZZ_NAMELIST +var WORDLIST_SMTP = WORDLIST_FUZZ_NAMELIST +var WORDLIST_SNMP = "/usr/share/doc/onesixtyone/dict.txt" +var WORDLIST_HYDRA_SSH_USER = WORDLIST_FUZZ_NAMELIST +var WORDLIST_HYDRA_SSH_PWD = WORDLIST_MSF_PWDS +var WORDLIST_HYDRA_FTP_USER = WORDLIST_FUZZ_NAMELIST +var WORDLIST_HYDRA_FTP_PWD = WORDLIST_MSF_PWDS + +// --------------------------------------------------------------------------------------- +// CONFIG +// --------------------------------------------------------------------------------------- +type config struct { + Outfolder string + Target string + Log *Logger + Hosts []model.Host +} + +// Initialize global config (db, logger, etc.) +// From now on it will be accessible as utils.Config +func InitConfig() { + Config = config{} + // Initialize logger + Config.Log = InitLogger() + // Setup Outfolder + usr, _ := user.Current() + Config.Outfolder = filepath.Join(usr.HomeDir, "goscan") + EnsureDir(Config.Outfolder) +} + +// --------------------------------------------------------------------------------------- +// MANAGE COMMANDS +// --------------------------------------------------------------------------------------- +// Tokenize the command line +func ParseCmd(s string) (string, []string) { + // Remove trailing spaces + s = strings.TrimSpace(s) + if len(s) == 0 { + return "", make([]string, 0) + } + // Tokenize the string + tokens := strings.Fields(s) + // Get the command (1st word), and args + cmd, args := tokens[0], tokens[1:] + return cmd, args +} + +// Extract the next argument from command line +func ParseNextArg(args []string) (string, []string) { + if len(args) < 2 { + return args[0], make([]string, 0) + } + return args[0], args[1:] +} + +// --------------------------------------------------------------------------------------- +// MANAGE FILES +// --------------------------------------------------------------------------------------- +// Ensures the program is run as root +func CheckSudo() { + if os.Geteuid() != 0 { + Config.Log.LogError("This program need to have root permission to execute nmap for now.") + os.Exit(1) + } +} + +// Ensure the directory exists, or creeates it otherwise +func EnsureDir(dir string) { + // Create a directory if doesn't exist + if _, err := os.Stat(dir); os.IsNotExist(err) { + os.MkdirAll(dir, os.ModePerm) + Config.Log.LogDebug(fmt.Sprintf("Created directory: %s", dir)) + } +} + +// Replace slashes with underscores, when the string is used in a path +func CleanPath(s string) string { + return strings.Replace(s, "/", "_", -1) + +} + +// Given a path and a list of strings, it writes them to file +func WriteArrayToFile(path string, s []string) { + Config.Log.LogDebug(fmt.Sprintf("Writing output to file: %s", path)) + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + Config.Log.LogError("Cannot create file") + } + defer f.Close() + + sep := "\n" + for _, line := range s { + if _, err = f.WriteString(line + sep); err != nil { + Config.Log.LogError(fmt.Sprintf("Error while writing to file: %s", err)) + } + } + +} + +// --------------------------------------------------------------------------------------- +// HOSTS +// --------------------------------------------------------------------------------------- +func ShowHosts() { + if len(Config.Hosts) == 0 { + Config.Log.LogError("No hosts are up!") + return + } + + Config.Log.LogNotify("The following hosts are up:") + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Status", "Address", "OS", "Ports"}) + table.SetRowLine(true) + table.SetAlignment(3) + table.SetAutoWrapText(false) + + for i := 0; i < len(Config.Hosts); i++ { + rStatus := Config.Hosts[i].Status + rAddress := Config.Hosts[i].Address + rOS := Config.Hosts[i].OS + + if len(Config.Hosts[i].Ports) == 0 { + v := []string{rStatus, rAddress, rOS, ""} + table.Append(v) + } else { + rPorts := "" + for _, s := range Config.Hosts[i].Ports { + rPorts = fmt.Sprintf("%s* %s\n", rPorts, s.String()) + } + v := []string{rStatus, rAddress, rOS, rPorts} + table.Append(v) + } + } + + table.Render() +} diff --git a/goscan/main.go b/goscan/main.go new file mode 100644 index 0000000..4904f84 --- /dev/null +++ b/goscan/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "github.com/c-bata/go-prompt" + "github.com/fatih/color" + "goscan/core/cli" + "goscan/core/utils" + "strings" +) + +var ( + author string + version string +) + +// --------------------------------------------------------------------------------------- +// INIT +// --------------------------------------------------------------------------------------- +func showBanner() { + name := fmt.Sprintf("goscan (v.%s)", version) + banner := ` +_________ ___________________________ __ +__ ____/_______ ___/_ ____/__ |__ | / / +_ / __ _ __ \____ \_ / __ /| |_ |/ / +/ /_/ / / /_/ /___/ // /___ _ ___ | /| / +\____/ \____//____/ \____/ /_/ |_/_/ |_/ + + ` + + // Shell width + all_lines := strings.Split(banner, "\n") + w := len(all_lines[1]) + + // Print Centered + fmt.Println(banner) + color.Green(fmt.Sprintf("%[1]*s", -w, fmt.Sprintf("%[1]*s", (w+len(name))/2, name))) + color.Blue(fmt.Sprintf("%[1]*s", -w, fmt.Sprintf("%[1]*s", (w+len(author))/2, author))) + fmt.Println() +} + +func initCore() { + // Check sudo + utils.CheckSudo() + // Show banner + showBanner() + // Initialize global config (db, logger, etc.) + // From now on it will be accessible as utils.Config + utils.InitConfig() +} + +// --------------------------------------------------------------------------------------- +// MAIN +// --------------------------------------------------------------------------------------- +func main() { + // Setup core + initCore() + + // Start CLI + p := prompt.New( + cli.Executor, + cli.Completer, + prompt.OptionTitle("goscan: Interactive Network Scanner"), + prompt.OptionPrefix("[goscan] > "), + prompt.OptionInputTextColor(prompt.White), + ) + p.Run() +}