From b889677dcbf95cc6d45d8cd5159d5c1194be4b6e Mon Sep 17 00:00:00 2001 From: cckuailong <346813862@qq.com> Date: Mon, 14 Aug 2023 17:04:29 +0800 Subject: [PATCH] v0.2.2 - The -i option supports IP range scanning, such as 1.2.3.4/24 - The -p option supports custom scan ports, such as 80,8000-8009 - The -V option outputs all scan information, disabled by default, only outputting results - The -F option help you to filter the result with http status code - Fixed a bug where the progress bar would still be displayed when no parameters were given - Added some informative output --- .github/workflows/release.yml | 2 +- README.md | 21 ++++++- README_zh.md | 21 ++++++- core/hostscan.go | 84 +++++++++++++++++--------- core/task.go | 12 +++- core/type.go | 107 +++++++++++++++++++++++++++++++++- main.go | 8 ++- utils/http.go | 29 +++++++++ vars/flag.go | 5 +- vars/var.go | 2 +- 10 files changed, 253 insertions(+), 38 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7afbcdf..2d416ea 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: - version: latest + version: 1.18.0 args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 9c84132..0227044 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,9 @@ hostscan --help Usage of hostscan: -D string Hosts in file to test + -F string + Filter result with List of Response Status Code. + Example: 200,201,302 -I string Nginx Ip in file to test -O string @@ -51,10 +54,16 @@ Usage of hostscan: -T int Thread for Http connection. (default 3) -U Open to send random UserAgent to avoid bot detection. + -V Output All scan Info. + Default is false, only output the result with title. -d string Host to test -i string - Nginx IP + Nginx IP. + Example: 1.1.1.1 or 1.2.3.4/24 + -p string + Port List of Nginx IP. If the flag is set, hostscan will ignore the port in origin IP input. + Example: 80,8080,8000-8009 -t int Timeout for Http connection. (default 5) -v Show hostscan version @@ -136,6 +145,16 @@ server { Simple Nginx Web Page. +## ChangeLog + +v0.2.2 +- The -i option supports IP range scanning, such as 1.2.3.4/24 +- The -p option supports custom scan ports, such as 80,8000-8009 +- The -V option outputs all scan information, disabled by default, only outputting results +- The -F option help you to filter the result with http status code +- Fixed a bug where the progress bar would still be displayed when no parameters were given +- Added some informative output + ## References [Fofapro's Hosts_scan](https://github.com/fofapro/Hosts_scan) diff --git a/README_zh.md b/README_zh.md index 4d3fccb..bf74d4e 100644 --- a/README_zh.md +++ b/README_zh.md @@ -48,6 +48,9 @@ hostscan --help Usage of hostscan: -D string Hosts in file to test + -F string + Filter result with List of Response Status Code. + Example: 200,201,302 -I string Nginx Ip in file to test -O string @@ -55,10 +58,16 @@ Usage of hostscan: -T int Thread for Http connection. (default 3) -U Open to send random UserAgent to avoid bot detection. + -V Output All scan Info. + Default is false, only output the result with title. -d string Host to test -i string - Nginx IP + Nginx IP. + Example: 1.1.1.1 or 1.2.3.4/24 + -p string + Port List of Nginx IP. If the flag is set, hostscan will ignore the port in origin IP input. + Example: 80,8080,8000-8009 -t int Timeout for Http connection. (default 5) -v Show hostscan version @@ -140,6 +149,16 @@ server { 简单的nginx初始页面 +## ChangeLog + +v0.2.2 +- -i选项支持IP段扫描,1.2.3.4/24 +- -p选项支持自定义扫描端口,如 80,8000-8009 +- -V选项输出所有扫描信息,默认关闭,只输出结果 +- -F选项用于根据http响应状态码筛选结果 +- 修复无参数时,进度条也会显示的bug +- 增加一些提示性输出 + ## 参考链接 [Fofapro 的 Hosts_scan](https://github.com/fofapro/Hosts_scan) diff --git a/core/hostscan.go b/core/hostscan.go index 8c5d885..f632c30 100644 --- a/core/hostscan.go +++ b/core/hostscan.go @@ -14,12 +14,12 @@ import ( ) -func calcTaskTotal(taskType string) int{ +func calcTaskTotal(taskType string, SingleIpCnt int) int{ var err error var ipCnt, hostCnt, schemeCnt int schemeCnt = len(vars.Schemes) if taskType == "ip_host" { - ipCnt = 1 + ipCnt = SingleIpCnt hostCnt = 1 }else if taskType == "ipfile_host" { ipCnt, err = utils.LineCounter(*vars.IpFile) @@ -29,7 +29,7 @@ func calcTaskTotal(taskType string) int{ } hostCnt = 1 }else if taskType == "ip_hostfile" { - ipCnt = 1 + ipCnt = SingleIpCnt hostCnt, err = utils.LineCounter(*vars.HostFile) if err != nil{ elog.Error(fmt.Sprintf("Get Lines Count[%s]: %v", *vars.HostFile, err)) @@ -50,12 +50,23 @@ func calcTaskTotal(taskType string) int{ return 0 } - return ipCnt * hostCnt * schemeCnt + totalTask := ipCnt * hostCnt * schemeCnt + + elog.Info(fmt.Sprintf("Total Task: %d || Ip: %d, Host: %d, Scheme:%d", totalTask, ipCnt, hostCnt, schemeCnt)) + + return totalTask } func Scan(taskType string) error{ wg := sync.WaitGroup{} - totalTask := calcTaskTotal(taskType) + ip_list := []string{} + if strings.Contains(*vars.Ip, "/"){ + ip_list = HandleIpRange(*vars.Ip) + }else{ + ip_list = append(ip_list, *vars.Ip) + } + + totalTask := calcTaskTotal(taskType, len(ip_list)) if totalTask == 0{ elog.Error(fmt.Sprintf("Get Lines Count: 0")) @@ -86,14 +97,20 @@ func Scan(taskType string) error{ } if taskType == "ip_host" { - for _, scheme := range vars.Schemes { - task := Task{ - Uri: fmt.Sprintf("%s://%s", scheme, *vars.Ip), - Host: *vars.Host, + for _,ip := range ip_list{ + for _, scheme := range vars.Schemes { + handled_set := HandleCustomPorts(*vars.Host, ip) + for _,item := range handled_set{ + task := Task{ + Uri: fmt.Sprintf("%s://%s", scheme, item.IP), + Host: item.Host, + } + // 生产者,不断地往taskChan channel发送数据,直到channel阻塞 + taskChan <- task + } } - // 生产者,不断地往taskChan channel发送数据,直到channel阻塞 - taskChan <- task } + }else if taskType == "ipfile_host" { ip_f, err := os.Open(*vars.IpFile) defer ip_f.Close() @@ -113,12 +130,15 @@ func Scan(taskType string) error{ } for _, scheme := range vars.Schemes { - task := Task{ - Uri: fmt.Sprintf("%s://%s", scheme, ip), - Host: *vars.Host, + handled_set := HandleCustomPorts(*vars.Host, ip) + for _,item := range handled_set{ + task := Task{ + Uri: fmt.Sprintf("%s://%s", scheme, item.IP), + Host: item.Host, + } + // 生产者,不断地往taskChan channel发送数据,直到channel阻塞 + taskChan <- task } - // 生产者,不断地往taskChan channel发送数据,直到channel阻塞 - taskChan <- task } } }else if taskType == "ip_hostfile" { @@ -137,15 +157,20 @@ func Scan(taskType string) error{ } return err } - - for _, scheme := range vars.Schemes { - task := Task{ - Uri: fmt.Sprintf("%s://%s", scheme, *vars.Ip), - Host: host, + for _,ip := range ip_list{ + for _, scheme := range vars.Schemes { + handled_set := HandleCustomPorts(host, ip) + for _,item := range handled_set{ + task := Task{ + Uri: fmt.Sprintf("%s://%s", scheme, item.IP), + Host: item.Host, + } + // 生产者,不断地往taskChan channel发送数据,直到channel阻塞 + taskChan <- task + } } - // 生产者,不断地往taskChan channel发送数据,直到channel阻塞 - taskChan <- task } + } }else if taskType == "ipfile_hostfile" { ip_f, err := os.Open(*vars.IpFile) @@ -183,12 +208,15 @@ func Scan(taskType string) error{ } for _, scheme := range vars.Schemes { - task := Task{ - Uri: fmt.Sprintf("%s://%s", scheme, ip), - Host: host, + handled_set := HandleCustomPorts(host, ip) + for _,item := range handled_set{ + task := Task{ + Uri: fmt.Sprintf("%s://%s", scheme, item.IP), + Host: item.Host, + } + // 生产者,不断地往taskChan channel发送数据,直到channel阻塞 + taskChan <- task } - // 生产者,不断地往taskChan channel发送数据,直到channel阻塞 - taskChan <- task } } } diff --git a/core/task.go b/core/task.go index 5f561ae..9a1a777 100644 --- a/core/task.go +++ b/core/task.go @@ -38,6 +38,12 @@ func goScan(taskChan chan Task, wg *sync.WaitGroup){ } else { vars.ProcessBar.Add(1) body := utils.GetHttpBody(task.Uri, task.Host) + if len(body) == 0{ + if *vars.Verbose { + elog.Info(fmt.Sprintf("Uri: %s, Host: %s --> No Response Body", task.Uri, task.Host)) + } + continue + } title := getTitle(body) var result models.Result result.Uri = task.Uri @@ -45,10 +51,12 @@ func goScan(taskChan chan Task, wg *sync.WaitGroup){ result.Title = title resultStr, _ := json.Marshal(result) if len(title) > 0{ - elog.Notice(fmt.Sprintf("Uri: %s, Host: %s --> %s", task.Uri, task.Host, title)) + elog.Notice(fmt.Sprintf("Uri: %s, Host: %s <==> %s", task.Uri, task.Host, title)) utils.WriteLine(string(resultStr), *vars.OutFile) }else{ - elog.Warn(fmt.Sprintf("Uri: %s, Host: %s No title found", task.Uri, task.Host)) + if *vars.Verbose{ + elog.Warn(fmt.Sprintf("Uri: %s, Host: %s --> No title found", task.Uri, task.Host)) + } } } } diff --git a/core/type.go b/core/type.go index 8493af6..06fcf94 100644 --- a/core/type.go +++ b/core/type.go @@ -1,10 +1,16 @@ package core import ( + "fmt" "hostscan/vars" + "net" + "sort" + "strconv" + "strings" ) - +const PORTMAX = 65535 +const PORTMIN = 1 func GetTaskType() string{ if len(*vars.Ip) == 0 && len(*vars.IpFile) == 0{ @@ -31,3 +37,102 @@ func GetTaskType() string{ return "N/A" } + +func inc(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +} + +func HandleIpRange(ipRange string) []string{ + ip, ipNet, err := net.ParseCIDR(ipRange) + if err != nil { + return []string{} + } + + var ips []string + for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); inc(ip) { + ips = append(ips, ip.String()) + } + // Remove network address and broadcast address + return ips[1 : len(ips)-1] +} + +func parsePort(ports string) []int { + var scanPorts []int + slices := strings.Split(ports, ",") + var start_str, end_str string + for _, port := range slices { + port = strings.Trim(port, " ") + if len(port) == 0{ + continue + } + if strings.Contains(port, "-") { + ranges := strings.Split(port, "-") + if len(ranges) < 2 { + continue + } + sort.Strings(ranges) + start_str = ranges[0] + end_str = ranges[1] + start, err := strconv.Atoi(start_str) + if err != nil{ + continue + } + end, err := strconv.Atoi(end_str) + if err != nil{ + continue + } + if start < PORTMIN{ + start = PORTMIN + } + if end > PORTMAX{ + end = PORTMAX + } + for i := start; i <= end; i++ { + scanPorts = append(scanPorts, i) + } + }else{ + target_port, err := strconv.Atoi(port) + if err == nil{ + scanPorts = append(scanPorts, target_port) + } + } + + } + return scanPorts +} + +type TaskInput struct { + Host string + IP string +} + +func HandleCustomPorts(host, ip string) []TaskInput{ + handled_set := []TaskInput{} + clear_ip := strings.Split(ip, ":")[0] + + iports := parsePort(*vars.Iports) + + if len(iports) > 0 { + for _,iport := range iports{ + handled_set = append(handled_set, TaskInput{ + Host: host, + IP: fmt.Sprintf("%s:%d", clear_ip, iport), + }) + } + }else{ + handled_set = append(handled_set, TaskInput{ + Host: host, + IP: ip, + }) + } + + //fmt.Println(handled_set) + + return handled_set +} + diff --git a/main.go b/main.go index d30d047..5992e74 100644 --- a/main.go +++ b/main.go @@ -15,18 +15,21 @@ func main(){ flag.Parse() if *vars.Version { - elog.Info(fmt.Sprintf("Current hostscan version: %s", vars.VersionInfo)) + elog.Info(fmt.Sprintf("Current Hostscan Version: %s", vars.VersionInfo)) + vars.ProcessBar.Clear() return } - utils.SetUlimitMax() + elog.Info("Hostscan Start! Waiting for your good news...") taskType := core.GetTaskType() if taskType == "noip"{ elog.Error("No IP Found! Please use -i/-I to input single ip or ips in file") + vars.ProcessBar.Clear() return }else if taskType == "nohost"{ elog.Error("No Host Found! Please use -d/-D to input single host or hosts in file") + vars.ProcessBar.Clear() return } @@ -37,6 +40,7 @@ func main(){ } } + utils.SetUlimitMax() err := core.Scan(taskType) if err != nil { elog.Error(fmt.Sprintf("Scan Failed: %v", err)) diff --git a/utils/http.go b/utils/http.go index c63052e..2c766e1 100644 --- a/utils/http.go +++ b/utils/http.go @@ -6,6 +6,8 @@ import ( "hostscan/vars" "io/ioutil" "net/http" + "strconv" + "strings" "time" ) @@ -35,6 +37,7 @@ func GetHttpBody(url, host string) string{ } reqest.Header.Add("User-Agent", ua) + response, err := client.Do(reqest) if response != nil{ defer response.Body.Close() @@ -44,8 +47,34 @@ func GetHttpBody(url, host string) string{ //elog.Error(fmt.Sprintf("DoGet: %s [%s]", url, err)) return "" } + + filter_status_codes := []int{} + filters := strings.TrimSpace(*vars.FilterRespStatusCodes) + if len(filters) > 0{ + for _,status_code := range strings.Split(filters, ","){ + filter_status_code, err := strconv.Atoi(strings.TrimSpace(status_code)) + if err != nil{ + continue + } + filter_status_codes = append(filter_status_codes, filter_status_code) + } + if !containsStatusCode(response.StatusCode, filter_status_codes){ + return "" + } + } + bodyByte, _ := ioutil.ReadAll(response.Body) body := string(bodyByte) return body } + +func containsStatusCode(a int, l []int) bool { + for _,item := range l{ + if a == item{ + return true + } + } + + return false +} diff --git a/vars/flag.go b/vars/flag.go index 5c85c14..27a022d 100644 --- a/vars/flag.go +++ b/vars/flag.go @@ -5,11 +5,14 @@ import "flag" var ( Version = flag.Bool("v", false, "Show hostscan version") Host = flag.String("d", "", "Host to test") - Ip = flag.String("i", "", "Nginx IP") + Ip = flag.String("i", "", "Nginx IP. \nExample: 1.1.1.1 or 1.2.3.4/24") Timeout = flag.Int("t", 5, "Timeout for Http connection.") Thread = flag.Int("T", 3, "Thread for Http connection.") HostFile = flag.String("D", "", "Hosts in file to test") IpFile = flag.String("I", "", "Nginx Ip in file to test") + Iports = flag.String("p", "", "Port List of Nginx IP. If the flag is set, hostscan will ignore the port in origin IP input. \nExample: 80,8080,8000-8009") OutFile = flag.String("O", "result.txt", "Output File") IsRandUA = flag.Bool("U", false, "Open to send random UserAgent to avoid bot detection.") + Verbose = flag.Bool("V", false, "Output All scan Info. \nDefault is false, only output the result with title.") + FilterRespStatusCodes = flag.String("F", "", "Filter result with List of Response Status Code. \nExample: 200,201,302") ) \ No newline at end of file diff --git a/vars/var.go b/vars/var.go index d2c47c0..408e171 100644 --- a/vars/var.go +++ b/vars/var.go @@ -10,5 +10,5 @@ var ( var ProcessBar *progressbar.ProgressBar const ( - VersionInfo = "0.2.1" + VersionInfo = "0.2.2" ) \ No newline at end of file