From 5188b6b65e10f87376bc9a1e62793dfa870bc1bc Mon Sep 17 00:00:00 2001 From: Le0nsec Date: Sat, 4 Dec 2021 00:55:49 +0800 Subject: [PATCH] :tada: Initial commit --- .gitignore | 2 + Dockerfile | 7 + README.md | 72 ++++++++++ docker-compose.yml | 7 + src/build.sh | 53 ++++++++ src/common.go | 276 ++++++++++++++++++++++++++++++++++++++ src/go.mod | 3 + src/httpbanner.go | 144 ++++++++++++++++++++ src/ping.go | 89 +++++++++++++ src/portscan.go | 323 +++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 976 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100755 src/build.sh create mode 100644 src/common.go create mode 100644 src/go.mod create mode 100644 src/httpbanner.go create mode 100644 src/ping.go create mode 100644 src/portscan.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9e4cf6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +src/release +.DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..900c7e2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM golang:1.16-buster + +RUN mkdir /src +WORKDIR /src +ENV GOPROXY https://goproxy.io + +ENTRYPOINT ["/src/build.sh"] diff --git a/README.md b/README.md index 9d2aa76..671652c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,74 @@ # portscan + A compact, cross-platform scanner that scans ports and recognizes fingerprints. + +## Usage: + +``` +Usage of ./portscan: + -H headers + request headers. exmaple: -H User-Agent:xx -H Referer:xx + -O filepath + save details open ports filepath + -f file + load external file, ip:port are read by line + -h host + scan host. format: 127.0.0.1 | 192.168.1.1/24 | 192.168.1.1-5 + -o filepath + save open ip:port per line filepath + -p port + scan port. format: 1-65535 | 21,22,25 | 8080 (default "7,11,13,15,17,19,21,22,23,25,26,37,38,43,49,51,53,67,70,79,80,81,82,83,84,85,86,88,89,102,104,110,111,113,119,121,135,138,139,143,175,179,199,211,264,311,389,443,444,445,465,500,502,503,505,512,515,548,554,564,587,631,636,646,666,771,777,789,800,801,873,880,902,992,993,995,1000,1022,1023,1024,1025,1026,1027,1080,1099,1177,1194,1200,1201,1234,1241,1248,1260,1290,1311,1344,1400,1433,1471,1494,1505,1515,1521,1588,1720,1723,1741,1777,1863,1883,1911,1935,1962,1967,1991,2000,2001,2002,2020,2022,2030,2049,2080,2082,2083,2086,2087,2096,2121,2181,2222,2223,2252,2323,2332,2375,2376,2379,2401,2404,2424,2455,2480,2501,2601,2628,3000,3128,3260,3288,3299,3306,3307,3310,3333,3388,3389,3390,3460,3541,3542,3689,3690,3749,3780,4000,4022,4040,4063,4064,4369,4443,4444,4505,4506,4567,4664,4712,4730,4782,4786,4840,4848,4880,4911,4949,5000,5001,5002,5006,5007,5009,5050,5084,5222,5269,5357,5400,5432,5555,5560,5577,5601,5631,5672,5678,5800,5801,5900,5901,5902,5903,5938,5984,5985,5986,6000,6001,6068,6379,6488,6560,6565,6581,6588,6590,6664,6665,6666,6667,6668,6669,6998,7000,7001,7005,7014,7071,7077,7080,7288,7401,7443,7474,7493,7537,7547,7548,7634,7657,7777,7779,7911,8000,8001,8008,8009,8010,8020,8025,8030,8040,8060,8069,8080,8081,8082,8086,8087,8088,8089,8090,8098,8099,8112,8123,8125,8126,8139,8161,8200,8291,8333,8334,8377,8378,8443,8500,8545,8554,8649,8686,8800,8834,8880,8883,8888,8889,8983,9000,9001,9002,9003,9009,9010,9042,9051,9080,9090,9100,9151,9191,9200,9295,9333,9418,9443,9527,9530,9595,9653,9700,9711,9869,9944,9981,9999,10000,10001,10162,10243,10333,11001,11211,11300,11310,12300,12345,13579,14000,14147,14265,16010,16030,16992,16993,17000,18001,18081,18245,18246,19999,20000,20547,22105,22222,23023,23424,25000,25105,25565,27015,27017,28017,32400,33338,33890,37215,37777,41795,42873,45554,49151,49152,49153,49154,49155,50000,50050,50070,50100,51106,52869,55442,55553,60001,60010,60030,61613,61616,62078,64738") + -path urlpath + request urlpath. example: /admin (default "/") + -ping + ping before scanning + -redirect + follow 30x redirect + -t threads + scan max threads (default 200) + -timeout timeout + connection timeout millisecond (default 4000) + -v show verbose +``` + +- 直接扫描 + + ```bash + $ ./portscan -h 192.168.43.97/24 -p 1-10000 + ``` + +- 先ping存活主机再对存活主机进行扫描(需要root权限) + + ```bash + $ sudo ./portscan -h 192.168.43.97/16 -ping + ``` + +- `-f` 从文件导入要扫描的ip,一行一个 + +- `-o` 输出端口扫描结果到文件,格式为`ip:port`每行 + +- `-O` 输出详细banner结果到文件 + + 格式如: + ``` + 10.22.33.4:22 open SSH-2.0-OpenSSH_7.5 + 10.22.33.6:22 open SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3 + 10.22.33.11:22 open SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3 + 10.22.33.4:80 open HTTP/1.1 200 OK pfSense - Login nginx text/html; charset=UTF-8 + 10.22.33.4:53 open + 10.22.33.11:80 open HTTP/1.1 200 OK nginx/1.14.0 (Ubuntu) text/html + 10.22.33.11:443 open HTTP/1.1 302 Found nginx text/html; charset=utf-8 + 10.22.33.26:22 open SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3 + 10.22.33.29:22 open SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3 + ``` + +- `-t` 指定最大线程数 + +- `-timeout` 指定连接超时 + +## Docker + +1. `docker-compose up -d` + +2. 二进制文件编译完成后在`./src/release/`目录下 + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..31e846d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +version: "2" +services: + portscan: + build: . + image: leonsec/portscan + volumes: + - "./src:/src" diff --git a/src/build.sh b/src/build.sh new file mode 100755 index 0000000..f75a22c --- /dev/null +++ b/src/build.sh @@ -0,0 +1,53 @@ +#!/bin/bash +version=1.0 +#if [ $# -eq 0 ] +#then +# echo "Please input version, like \"./release.sh 0.60\"" +# exit +#fi +rm -f release/portscan_*$version.tgz +echo "Build ReleaseFile for version $version" + +#export GOPATH=`pwd` + +echo "build linux_amd64" +export GOOS=linux GOARCH=amd64 +go build -ldflags="-w -s" +tar zcvf portscan_linux_amd64_$version.tgz portscan +rm -f portscan portscan.exe + +echo "build linux_386" +export GOOS=linux GOARCH=386 +go build -ldflags="-w -s" +tar zcvf portscan_linux_386_$version.tgz portscan +rm -f portscan portscan.exe + +echo "build mac_x64" +export GOOS=darwin GOARCH=amd64 +go build -ldflags="-w -s" +tar zcvf portscan_mac_amd64_$version.tgz portscan +rm -f portscan portscan.exe + +echo "build mac_arm64" +export GOOS=darwin GOARCH=arm64 +go build -ldflags="-w -s" +tar zcvf portscan_mac_arm64_$version.tgz portscan +rm -f portscan portscan.exe + +echo "build win32" +export GOOS=windows GOARCH=386 +go build -ldflags="-w -s" +tar zcvf portscan_win32_$version.tgz portscan.exe +rm -f portscan portscan.exe + +echo "build win64" +export GOOS=windows GOARCH=amd64 +go build -ldflags="-w -s" +tar zcvf portscan_win64_$version.tgz portscan.exe +rm -f portscan portscan.exe + +echo "Build Over" + +mkdir release +mv *.tgz release +ls -l release/portscan_*$version.tgz \ No newline at end of file diff --git a/src/common.go b/src/common.go new file mode 100644 index 0000000..fa4241a --- /dev/null +++ b/src/common.go @@ -0,0 +1,276 @@ +package main + +import ( + "bufio" + "fmt" + "math" + "net" + "os" + "regexp" + "sort" + "strconv" + "strings" +) + +type IPList struct { + A int + B []int + C []int + D []int +} + +var cidrIPList IPList + +func ParsePort(portString string) ([]int, error) { + + var portList []int + + pair := strings.Split(portString, ",") + for _, item := range pair { + if strings.Contains(item, "-") { + portRange := strings.Split(item, "-") + if len(portRange) != 2 { + return portList, fmt.Errorf("%s is invalid port range", portString) + } + start, _ := strconv.Atoi(portRange[0]) + end, _ := strconv.Atoi(portRange[1]) + for i := start; i <= end; i++ { + portList = append(portList, i) + } + } else { + item, _ := strconv.Atoi(item) + portList = append(portList, item) + } + } + + sort.Ints(portList) + return portList, nil +} + +func ParseIP(ipString string) ([]string, error) { + ipList := []string{} + + pair := strings.Split(ipString, ",") + for _, item := range pair { + + if net.ParseIP(item) != nil { + ipList = append(ipList, item) + } else if ip, network, err := net.ParseCIDR(item); err == nil { + n, _ := network.Mask.Size() + ipSub := strings.Split(ip.Mask(network.Mask).String(), ".") + cidrIPList.A, _ = strconv.Atoi(ipSub[0]) + + if n >= 24 { + a, _ := strconv.Atoi(ipSub[1]) + cidrIPList.B = append(cidrIPList.B, a) + a, _ = strconv.Atoi(ipSub[2]) + cidrIPList.C = append(cidrIPList.C, a) + for i := 1; i < IPRange(n, 32); i++ { + cidrIPList.D = append(cidrIPList.D, i) + } + } else if n >= 16 && n < 24 { + a, _ := strconv.Atoi(ipSub[1]) + cidrIPList.B = append(cidrIPList.B, a) + for i := 0; i < IPRange(n, 24); i++ { + cidrIPList.C = append(cidrIPList.C, i) + } + for i := 1; i < 256; i++ { + cidrIPList.D = append(cidrIPList.D, i) + } + } else if n >= 8 && n < 16 { + for i := 0; i < IPRange(n, 16); i++ { + cidrIPList.B = append(cidrIPList.B, i) + } + for i := 0; i < 256; i++ { + cidrIPList.C = append(cidrIPList.C, i) + } + for i := 1; i < 256; i++ { + cidrIPList.D = append(cidrIPList.D, i) + } + } else { + return ipList, fmt.Errorf("%s is not supported", item) + } + return ipList, fmt.Errorf("CIDR") + } else if strings.Contains(item, "-") { + splitIP := strings.SplitN(item, "-", 2) + ip := net.ParseIP(splitIP[0]) + endIP := net.ParseIP(splitIP[1]) + if endIP != nil { + if !isStartingIPLower(ip, endIP) { + return ipList, fmt.Errorf("%s is greater than %s", ip.String(), endIP.String()) + } + ipList = append(ipList, ip.String()) + for !ip.Equal(endIP) { + increaseIP(ip) + ipList = append(ipList, ip.String()) + } + } else { + ipOct := strings.SplitN(ip.String(), ".", 4) + endIP := net.ParseIP(ipOct[0] + "." + ipOct[1] + "." + ipOct[2] + "." + splitIP[1]) + if endIP != nil { + if !isStartingIPLower(ip, endIP) { + return ipList, fmt.Errorf("%s is greater than %s", ip.String(), endIP.String()) + } + ipList = append(ipList, ip.String()) + for !ip.Equal(endIP) { + increaseIP(ip) + ipList = append(ipList, ip.String()) + } + } else { + return ipList, fmt.Errorf("%s is not an IP Address or CIDR Network", item) + } + } + } else { + return ipList, fmt.Errorf("%s is not an IP Address or CIDR Network", item) + } + } + return ipList, nil +} + +/* +// LinesToIPList processes a list of IP addresses or networks in CIDR format. +// Returning a list of all possible IP addresses. +func LinesToIPList(lines []string) ([]string, error) { + ipList := []string{} + for _, line := range lines { + _ipList, err := ParseIP(line) + if err != nil { + return _ipList, fmt.Errorf("%s is not an IP Address", line) + } + for _, line := range _ipList { + ipList = append(ipList, line) + } + } + return ipList, nil +} + +func center(s string, width int) string { + n := width - len(s) + if n <= 0 { + return s + } + half := n / 2 + if n%2 != 0 && width%2 != 0 { + half = half + 1 + } + return strings.Repeat(" ", half) + s + strings.Repeat(" ", (n-half)) +} + +func rjust(s string, width int) string { + n := width - len(s) + if n <= 0 { + return s + } + return strings.Repeat(" ", n) + s +} +*/ + +func ljust(s string, width int) string { + n := width - len(s) + if n <= 0 { + return s + } + return s + strings.Repeat(" ", n) +} + +/* +func isValidIPV4(ip string) bool { + b := net.ParseIP(ip) + if b.To4() == nil { + return false + } + return true +} +*/ + +// increases an IP by a single address. +func increaseIP(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +} + +func isStartingIPLower(start, end net.IP) bool { + if len(start) != len(end) { + return false + } + for i := range start { + if start[i] > end[i] { + return false + } + } + return true +} + +// ReadFileLines returns all the lines in a file. +func ReadFileLines(path string) ([]string, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + lines := []string{} + scanner := bufio.NewScanner(file) + for scanner.Scan() { + if scanner.Text() == "" { + continue + } + lines = append(lines, scanner.Text()) + } + return lines, scanner.Err() +} + +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s\n", err.Error()) + os.Exit(1) + } +} + +func IPRange(n int, m int) int { + return int(math.Pow(2, float64(m-n))) +} + +func removeDuplicatesAndEmpty(a []string) (ret []string) { + a_len := len(a) + for i := 0; i < a_len; i++ { + if (i > 0 && a[i-1] == a[i]) || len(a[i]) == 0 { + continue + } + ret = append(ret, a[i]) + } + return +} + +func find(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} + +func ipListSort(list []string) []string { + var sortList1, sortList2, sortList3 []string + for _, i := range list { + re, _ := regexp.Compile(`(\d+)`) + rep := re.ReplaceAllString(i, "00$1") + sortList1 = append(sortList1, rep) + } + for _, i := range sortList1 { + re, _ := regexp.Compile(`0*(\d{3})`) + rep := re.ReplaceAllString(i, "$1") + sortList2 = append(sortList2, rep) + } + sort.Strings(sortList2) + for _, i := range sortList2 { + re, _ := regexp.Compile(`0*(\d+)`) + rep := re.ReplaceAllString(i, "$1") + sortList3 = append(sortList3, rep) + } + return sortList3 +} diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..8382cbe --- /dev/null +++ b/src/go.mod @@ -0,0 +1,3 @@ +module portscan + +go 1.16 diff --git a/src/httpbanner.go b/src/httpbanner.go new file mode 100644 index 0000000..8cf43ca --- /dev/null +++ b/src/httpbanner.go @@ -0,0 +1,144 @@ +package main + +var headers = map[string]string{ + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", +} + +type arrayFlags []string + +type HttpInfo struct { + StatusCode int `json:"status_code"` + Url string `json:"url"` + Title string `json:"title"` + Server string `json:"server"` + Length string `json:"length"` + Type string `json:"type"` +} + +/* +func bannerScan(list []string) { + // prepare request headers + for _, line := range reqHeaders { + pair := strings.SplitN(line, ":", 2) + if len(pair) == 2 { + k, v := pair[0], strings.Trim(pair[1], " ") + if strings.ToLower(k) == "host" { + reqHost = v + } + headers[k] = v + } + } + + fmt.Printf("headers:\n") + for k, v := range headers { + fmt.Printf(" %s: %s\n", k, v) + } + fmt.Printf("\nNumber of scans: %d\n", len(list)) + + for _, line := range list { + ch <- true + wg.Add(1) + + pair := strings.SplitN(line, ":", 2) + host := pair[0] + port, _ := strconv.Atoi(pair[1]) + url := fmt.Sprintf("http://%s:%d%s", host, port, path) + if port == 443 { + url = fmt.Sprintf("https://%s%s", host, path) + } + go fetch(url) + } + wg.Wait() + + if outputJSONFile != "" { + saveResult(result) + } +} + +func fetch(url string) { + + defer func() { + <-ch + wg.Done() + }() + tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + client := &http.Client{ + Timeout: time.Duration(timeout) * time.Second, + Transport: tr, + } + if !redirect { + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + } + req, err := http.NewRequest("GET", url, nil) + if err != nil { + //fmt.Println(err) + return + } + req.Host = reqHost + for k, v := range headers { + req.Header.Add(k, v) + } + + resp, err := client.Do(req) + if err != nil { + //fmt.Println("http.Get:", err.Error()) + return + } + defer resp.Body.Close() + + info := &HttpInfo{} + info.Url = url + info.StatusCode = resp.StatusCode + + // 获取标题 + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + // fmt.Println("ioutil.ReadAll", err.Error()) + return + } + respBody := string(body) + r := regexp.MustCompile(`(?i)\s?(.*?)\s?`) + m := r.FindStringSubmatch(respBody) + if len(m) == 2 { + info.Title = m[1] + } + + // 获取响应头Server字段 + info.Server = resp.Header.Get("Server") + info.Length = resp.Header.Get("Content-Length") + + pair := strings.SplitN(resp.Header.Get("Content-Type"), ";", 2) + if len(pair) == 2 { + info.Type = pair[0] + } + + result = append(result, *info) + fmt.Printf("%-5d %-6s %-16s %-50s %-60s %s\n", info.StatusCode, info.Length, info.Type, info.Url, info.Server, info.Title) +} + +func saveResult([]HttpInfo) { + output, err := json.MarshalIndent(result, "", " ") + if err != nil { + fmt.Println(err) + } + + f, err := os.OpenFile(outputJSONFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) + if err != nil { + fmt.Println(err) + } + defer f.Close() + + f.WriteString(string(output)) + +} +*/ +func (i *arrayFlags) String() string { + return "my string representation" +} + +func (i *arrayFlags) Set(value string) error { + *i = append(*i, value) + return nil +} diff --git a/src/ping.go b/src/ping.go new file mode 100644 index 0000000..70c0b62 --- /dev/null +++ b/src/ping.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "net" + "os" + "time" +) + +func pingList(list []string) []string { + openHostList = []string{} + fmt.Println("\nhost discovering...") + for _, host := range list { + //ch <- true + //wg.Add(1) + fmt.Printf("%s is scanning\n", host) + ping(host) + } + //wg.Wait() + return openHostList +} + +func ping(host string) { + conn, err := net.Dial("ip:icmp", host) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + defer conn.Close() + + var msg [512]byte + msg[0] = 8 + msg[1] = 0 + msg[2] = 0 + msg[3] = 0 + msg[4] = 0 + msg[5] = 13 + msg[6] = 0 + msg[7] = 37 + msg[8] = 99 + len := 9 + check := checkSum(msg[0:len]) + msg[2] = byte(check >> 8) + msg[3] = byte(check & 0xff) + //fmt.Println(msg[0:len]) + for i := 0; i < 2; i++ { + _, err = conn.Write(msg[0:len]) + if err != nil { + //fmt.Println(err.Error()) + continue + } + + conn.SetReadDeadline((time.Now().Add(time.Millisecond * 400))) + _, err := conn.Read(msg[0:]) + if err != nil { + //fmt.Println(err.Error()) + continue + } + + //fmt.Println(msg[0 : 20+len]) + //fmt.Println("Got response") + if msg[20+5] == 13 && msg[20+7] == 37 && msg[20+8] == 99 { + fmt.Printf("%s open\n", ljust(host, 21)) + openHostList = append(openHostList, host) + //<-ch + //wg.Done() + return + } + } + //<-ch + //wg.Done() +} + +func checkSum(msg []byte) uint16 { + sum := 0 + + len := len(msg) + for i := 0; i < len-1; i += 2 { + sum += int(msg[i])*256 + int(msg[i+1]) + } + if len%2 == 1 { + sum += int(msg[len-1]) * 256 + } + + sum = (sum >> 16) + (sum & 0xffff) + sum += (sum >> 16) + var answer uint16 = uint16(^sum) + return answer +} diff --git a/src/portscan.go b/src/portscan.go new file mode 100644 index 0000000..454d1db --- /dev/null +++ b/src/portscan.go @@ -0,0 +1,323 @@ +package main + +import ( + "crypto/tls" + "flag" + "fmt" + "net" + "net/http" + "os" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +var ( + wg sync.WaitGroup + ch chan bool + host string + port string + loadFile string + timeout int + verbose bool + outputFile string + outputDetailFile string + goroutineNum int + reqHost string + reqHeaders arrayFlags + redirect bool + path string + isPing bool + f *os.File + f_detail *os.File + openList []string + openHostList []string + //banner bool + //outputJSONFile string + //result []HttpInfo +) + +func main() { + defaultPorts := "7,11,13,15,17,19,21,22,23,25,26,37,38,43,49,51,53,67,70,79,80,81,82,83,84,85,86,88,89,102,104,110,111,113,119,121,135,138,139,143,175,179,199,211,264,311,389,443,444,445,465,500,502,503,505,512,515,548,554,564,587,631,636,646,666,771,777,789,800,801,873,880,902,992,993,995,1000,1022,1023,1024,1025,1026,1027,1080,1099,1177,1194,1200,1201,1234,1241,1248,1260,1290,1311,1344,1400,1433,1471,1494,1505,1515,1521,1588,1720,1723,1741,1777,1863,1883,1911,1935,1962,1967,1991,2000,2001,2002,2020,2022,2030,2049,2080,2082,2083,2086,2087,2096,2121,2181,2222,2223,2252,2323,2332,2375,2376,2379,2401,2404,2424,2455,2480,2501,2601,2628,3000,3128,3260,3288,3299,3306,3307,3310,3333,3388,3389,3390,3460,3541,3542,3689,3690,3749,3780,4000,4022,4040,4063,4064,4369,4443,4444,4505,4506,4567,4664,4712,4730,4782,4786,4840,4848,4880,4911,4949,5000,5001,5002,5006,5007,5009,5050,5084,5222,5269,5357,5400,5432,5555,5560,5577,5601,5631,5672,5678,5800,5801,5900,5901,5902,5903,5938,5984,5985,5986,6000,6001,6068,6379,6488,6560,6565,6581,6588,6590,6664,6665,6666,6667,6668,6669,6998,7000,7001,7005,7014,7071,7077,7080,7288,7401,7443,7474,7493,7537,7547,7548,7634,7657,7777,7779,7911,8000,8001,8008,8009,8010,8020,8025,8030,8040,8060,8069,8080,8081,8082,8086,8087,8088,8089,8090,8098,8099,8112,8123,8125,8126,8139,8161,8200,8291,8333,8334,8377,8378,8443,8500,8545,8554,8649,8686,8800,8834,8880,8883,8888,8889,8983,9000,9001,9002,9003,9009,9010,9042,9051,9080,9090,9100,9151,9191,9200,9295,9333,9418,9443,9527,9530,9595,9653,9700,9711,9869,9944,9981,9999,10000,10001,10162,10243,10333,11001,11211,11300,11310,12300,12345,13579,14000,14147,14265,16010,16030,16992,16993,17000,18001,18081,18245,18246,19999,20000,20547,22105,22222,23023,23424,25000,25105,25565,27015,27017,28017,32400,33338,33890,37215,37777,41795,42873,45554,49151,49152,49153,49154,49155,50000,50050,50070,50100,51106,52869,55442,55553,60001,60010,60030,61613,61616,62078,64738" + + flag.StringVar(&host, "h", "", "scan `host`. format: 127.0.0.1 | 192.168.1.1/24 | 192.168.1.1-5") + flag.StringVar(&port, "p", defaultPorts, "scan `port`. format: 1-65535 | 21,22,25 | 8080") + flag.BoolVar(&isPing, "ping", false, "ping before scanning") + flag.StringVar(&loadFile, "f", "", "load external `file`, ip:port are read by line") + flag.IntVar(&timeout, "timeout", 4000, "connection `timeout` millisecond") + flag.StringVar(&outputFile, "o", "", "save open ip:port per line `filepath`") + flag.StringVar(&outputDetailFile, "O", "", "save details open ports `filepath`") + flag.StringVar(&path, "path", "/", "request `urlpath`. example: /admin") + flag.BoolVar(&redirect, "redirect", false, "follow 30x redirect") + flag.Var(&reqHeaders, "H", "request `headers`. exmaple: -H User-Agent:xx -H Referer:xx") + flag.IntVar(&goroutineNum, "t", 200, "scan max `threads`") + flag.BoolVar(&verbose, "v", false, "show verbose") + //flag.BoolVar(&banner, "banner", false, "whether to get the banner of the active port") + //flag.StringVar(&outputJSONFile, "oj", "", "save banner json `filepath`, it required the -banner parameter") + flag.Parse() + + var ( + scanList, ipList []string + portList []int + ) + //限制goroutine数量 + ch = make(chan bool, goroutineNum) + + //host = "172.16.3.250" + //port = "8080" + + if (host == "" && loadFile == "") || (host != "" && loadFile != "") { + flag.Usage() + os.Exit(0) + } + /* + if outputJSONFile != "" && !banner { + fmt.Println("outputJSONFile required the -banner parameter") + os.Exit(1) + } + */ + + if loadFile != "" { + lines, err := ReadFileLines(loadFile) + checkError(err) + + if port != "" { + portList, _ = ParsePort(port) + for _, line := range lines { + line = strings.Trim(line, " ") + if strings.Contains(line, ":") { + hostPort := strings.Split(line, ":") + line = hostPort[0] + } + ipList = append(ipList, line) + } + run(ipList, portList, 1) + } else { + for _, line := range lines { + line = strings.Trim(line, " ") + if strings.Contains(line, ":") { + scanList = append(scanList, line) + } else { + fmt.Println("loadfile contents format error") + os.Exit(1) + } + } + run(scanList, portList, 0) + } + } else { + portList, err := ParsePort(port) + checkError(err) + ipList, err = ParseIP(host) + // 利用error传CIDR判断为CIDR模式 + if err != nil && err.Error() == "CIDR" { + a := cidrIPList.A + for _, b := range cidrIPList.B { + for _, c := range cidrIPList.C { + s := []string{} + for _, d := range cidrIPList.D { + s = append(s, fmt.Sprintf("%d.%d.%d.%d", a, b, c, d)) + } + run(s, portList, 1) + } + } + } else { + checkError(err) + run(ipList, portList, 1) + } + } + + fmt.Println("portscan finish...") + /* + if banner { + fmt.Println("\nbanner scanning...") + bannerScan(openList) + fmt.Println("bannerscan finish...") + } + */ +} + +func run(ipList []string, portList []int, mode int) { + if outputFile != "" { + var err error + f, err = os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) + checkError(err) + defer f.Close() + } + if outputDetailFile != "" { + var err error + f_detail, err = os.OpenFile(outputDetailFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) + checkError(err) + defer f_detail.Close() + } + if mode == 0 { + hostList := []string{} + + if isPing { + for _, line := range ipList { + hostList = append(hostList, strings.SplitN(line, ":", 2)[0]) + } + hostList = removeDuplicatesAndEmpty(hostList) + hostList = pingList(hostList) + if len(hostList) == 0 { + fmt.Println("no active host") + return + } + openScanList := []string{} + for _, line := range ipList { + host := strings.SplitN(line, ":", 2)[0] + _, exist := find(hostList, host) + if exist { + openScanList = append(openScanList, line) + } + } + ipList = ipListSort(openScanList) + } + fmt.Println("\nscanning...") + fmt.Printf("Number of scans: %d\n", len(ipList)) + for _, line := range ipList { + ch <- true + wg.Add(1) + + pair := strings.SplitN(line, ":", 2) + host := pair[0] + port, _ := strconv.Atoi(pair[1]) + go scan(host, port) + } + wg.Wait() + } + + if mode == 1 { + if isPing { + ipList = pingList(ipList) + ipList = ipListSort(ipList) + fmt.Println(ipList) + if len(ipList) == 0 { + fmt.Println("no active host") + return + } + } + fmt.Println("\nscanning...") + if len(portList) > 10 { + port = strconv.Itoa(portList[0]) + "," + strconv.Itoa(portList[1]) + "," + strconv.Itoa(portList[2]) + "..." + strconv.Itoa(portList[len(portList)-1]) + } + if len(ipList) == 1 { + fmt.Printf("host: %s\n", ipList[0]) + fmt.Printf("port: %s\n", port) + } else { + fmt.Printf("host: %s - %s\n", ipList[0], ipList[len(ipList)-1]) + fmt.Printf("port: %s\n", port) + } + + for _, host := range ipList { + for _, port := range portList { + ch <- true + wg.Add(1) + go scan(host, port) + } + } + wg.Wait() + } +} + +func scan(host string, port int) { + open, str := isOpen(host, port) + if open { + ip := fmt.Sprintf("%s:%d", host, port) + fmt.Printf("%s open %s\n", ljust(ip, 21), str) + f.WriteString(fmt.Sprintf("%s:%d\r\n", host, port)) + f_detail.WriteString(fmt.Sprintf("%s open %s\n", ljust(ip, 21), str)) + openList = append(openList, ip) + } + <-ch + wg.Done() +} + +func isOpen(host string, port int) (bool, string) { + var msg [128]byte + var str string + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Duration(timeout)*time.Millisecond) + if err != nil { + //fmt.Println(err) + return false, str + } + conn.SetReadDeadline((time.Now().Add(time.Millisecond * time.Duration(timeout)))) + _, err = conn.Read(msg[0:]) + if err != nil { + //fmt.Println(err.Error()) + //fmt.Println(msg) + isHttpService, str := fetchHttpDetails(host, port) + if isHttpService { + return true, str + } + return true, str + } + conn.Close() + str = strings.Replace(string(msg[0:]), "\n", "", -1) + + return true, str +} + +func fetchHttpDetails(host string, port int) (bool, string) { + var str string + for _, line := range reqHeaders { + pair := strings.SplitN(line, ":", 2) + if len(pair) == 2 { + k, v := pair[0], strings.Trim(pair[1], " ") + if strings.ToLower(k) == "host" { + reqHost = v + } + headers[k] = v + } + } + url := fmt.Sprintf("http://%s:%d%s", host, port, path) + if port == 443 { + url = fmt.Sprintf("https://%s%s", host, path) + } + tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + client := &http.Client{ + Timeout: time.Duration(timeout) * time.Millisecond, + Transport: tr, + } + if !redirect { + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + } + req, err := http.NewRequest("GET", url, nil) + if err != nil { + //fmt.Println(err) + return false, str + } + req.Host = reqHost + for k, v := range headers { + req.Header.Add(k, v) + } + + resp, err := client.Do(req) + if err != nil { + //fmt.Println(err.Error()) + return false, str + } + defer resp.Body.Close() + + info := &HttpInfo{} + info.Type = resp.Header.Get("Content-Type") + info.Server = resp.Header.Get("Server") + + var body [1024]byte + _, err = resp.Body.Read(body[0:]) + if err != nil { + str = fmt.Sprintf("%s %s %s %s", resp.Proto, resp.Status, info.Server, info.Type) + //fmt.Println(err.Error()) + return true, str + } + respBody := string(body[0:]) + r := regexp.MustCompile(`(?i)\s?(.*?)\s?`).FindStringSubmatch(respBody) + if len(r) == 2 { + info.Title = r[1] + } + str = fmt.Sprintf("%s %s %s %s %s", resp.Proto, resp.Status, info.Title, info.Server, info.Type) + return true, str +}