From 72daf24ace3d007ba5238d3353a2c7a14be44dcf Mon Sep 17 00:00:00 2001 From: bufsnake Date: Wed, 4 Aug 2021 10:44:03 +0800 Subject: [PATCH] add passive scan and can -b delete junk data --- README.md | 28 +++- cmd/blueming/filter.sh | 12 -- cmd/blueming/main.go | 146 ++++++++++++++++- config/main.go | 32 ++-- go.mod | 8 +- go.sum | 18 +++ internal/core/core.go | 295 ++++++++++++++++++---------------- internal/core/listen.go | 272 +++++++++++++++++++++++++++++++ internal/core/utils.go | 64 ++++++++ pkg/general-file-name/main.go | 89 +++++++--- pkg/http-request/main.go | 59 +++++-- pkg/parseip/main.go | 185 +++++++++++++++++++++ 12 files changed, 989 insertions(+), 219 deletions(-) delete mode 100755 cmd/blueming/filter.sh create mode 100644 internal/core/listen.go create mode 100644 internal/core/utils.go create mode 100644 pkg/parseip/main.go diff --git a/README.md b/README.md index 08af901..8483dab 100644 --- a/README.md +++ b/README.md @@ -18,34 +18,50 @@ go build -v ## 使用 ```bash -└> ./blueming Usage of ./blueming: - -es string - dirscan filter status(200,206,301,302,400,401,403,404,405,500,501,502,503,504,600,etc.) (default "404") + -b filter output data + -crt string + listen cert (default "ca.crt") -f string set url file -i string - set wordlist index(ex: test.php) + set wordlist index(exp: test.php) + -key string + listen key (default "ca.key") -l string set log level(trace,debug,info,warn,fatal) (default "debug") + -listen string + listen to scan dir (default "127.0.0.1:9099") -p string - set download proxy + set proxy, support http proxy(exp: http://localhost:8080) -s int set timeout (default 10) -t int - set thread (default 10) + set thread (default 100) -u string set url + -v int + log level -w string set wordlist ``` +> ./blueming -b 可删除output下的垃圾数据(必须使用) + ## TODO > 基本满足以下要求即可 +- [ ] 常见文件泄露扫描 .git .hg .idea .DS_Store ... +- [x] 开启被动扫描模式,配合httpx自动进行目录扫描(二级、三级、四级...) - [x] 通过URL自动生成文件名 - [x] 根据后缀名将URL定义为对应的文件格式,如zip、tar.gz等 - [x] 自动下载备份文件,并进行重命名 - [x] 能够自定义字典 - [x] 优化内存占用 +- [x] filter.sh 移至程序内部 +- [x] 目录扫描部分添加 页面相似度比较,每个新产生的都会与前面所有的请求进行比较一次(耗时) + - 比较时,各网站相互独立,采用协程的方式 +- [x] 采用 GET 请求,查看文件过大时的response + - 文件过大导致的超时 则获取 header,比较历史记录中的length + - 正常情况,比较body diff --git a/cmd/blueming/filter.sh b/cmd/blueming/filter.sh deleted file mode 100755 index 477aae5..0000000 --- a/cmd/blueming/filter.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -for i in $(find ./output) -do - if [[ $(file $i | grep $i": data") == "" && $(file $i | grep "image data") == "" && $(file $i | grep "HTML") == "" && $(file $i | grep "empty") == "" && $(file $i | grep "JSON") == "" && $(file $i | grep "text") == "" ]] - then - echo $i - file $i - else - rm -rf $i - fi -done diff --git a/cmd/blueming/main.go b/cmd/blueming/main.go index cc79e58..1f2b92c 100644 --- a/cmd/blueming/main.go +++ b/cmd/blueming/main.go @@ -6,27 +6,90 @@ import ( "github.com/bufsnake/blueming/config" "github.com/bufsnake/blueming/internal/core" "github.com/bufsnake/blueming/pkg/log" + "golang.org/x/text/encoding/simplifiedchinese" "io/ioutil" + "math" "net/url" "os" + "os/exec" + "runtime" "strings" + "sync" + "syscall" "time" ) func main() { conf := config.Config{} - flag.IntVar(&conf.Thread, "t", 10, "set thread") + flag.IntVar(&conf.Thread, "t", 100, "set thread") flag.IntVar(&conf.Timeout, "s", 10, "set timeout") flag.StringVar(&conf.Url, "u", "", "set url") flag.StringVar(&conf.Urlfile, "f", "", "set url file") flag.StringVar(&conf.Loglevel, "l", log.DEBUG, "set log level(trace,debug,info,warn,fatal)") flag.StringVar(&conf.Wordlist, "w", "", "set wordlist") - flag.StringVar(&conf.Index, "i", "", "set wordlist index(ex: test.php)") - flag.StringVar(&conf.Proxy, "p", "", "set download proxy") - flag.StringVar(&conf.ExcludeStatus, "es", "404", "dirscan filter status(200,206,301,302,307,400,401,402,403,404,405,406,424,500,501,502,503,504,600,etc.)") - flag.StringVar(&conf.ResultFile, "rf", "", "parse result file") + flag.StringVar(&conf.Index, "i", "", "set wordlist index(exp: test.php)") + flag.StringVar(&conf.Proxy, "p", "", "set proxy, support http proxy(exp: http://localhost:8080)") + flag.StringVar(&conf.Listen, "listen", "127.0.0.1:9099", "listen to scan dir") + flag.StringVar(&conf.URLStrs, "urls", "", "set url file") + flag.StringVar(&conf.Cert, "crt", "ca.crt", "listen cert") + flag.StringVar(&conf.Key, "key", "ca.key", "listen key") + flag.BoolVar(&conf.FilterOutput, "b", false, "filter output data") + // 暂不考虑 + //flag.StringVar(&conf.ResultFile, "rf", "", "parse result file") flag.Parse() + // 开启多核模式 + runtime.GOMAXPROCS(runtime.NumCPU() * 3 / 4) + // 关闭 GIN Debug模式 + // 设置工具可打开的文件描述符 + var rLimit syscall.Rlimit + rLimit.Max = 999999 + rLimit.Cur = 999999 + if runtime.GOOS == "darwin" { + rLimit.Cur = 10240 + } + err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + _ = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) log.SetLevel(conf.Loglevel) + if conf.FilterOutput { + // 获取 output 下的所有文件 不包含文件夹 + allfiles, _ := ioutil.ReadDir("./output") + for _,f := range allfiles { + if !f.IsDir() { + if f.Size() <= 1048576 { + err = os.Remove("./output/" + f.Name()) + if err != nil { + log.Fatal(err) + } + } + } + } + + wait := sync.WaitGroup{} + files, _ := ioutil.ReadDir("./output") + fmt.Println("current exist", len(files), "files") + go func() { + for { + fmt.Printf("\r%.2f%%", math.Trunc(((increase/float64(len(files)))*100)*1e2)*1e-2) + time.Sleep(1 * time.Second / 10) + } + }() + for _, f := range files { + if !f.IsDir() { + wait.Add(1) + go filter(&wait, strings.ReplaceAll("./output/"+f.Name(), " ", ` `), float64(len(files))) + } else { + increaseAdd() + fmt.Printf("\r%.2f%%", math.Trunc(((increase/float64(len(files)))*100)*1e2)*1e-2) + } + } + wait.Wait() + // function filter { if [[ $(file $1 | grep $1": data") == "" && $(file $1 | grep "image data") == "" && $(file $1 | grep "HTML") == "" && $(file $1 | grep "empty") == "" && $(file $1 | grep "JSON") == "" && $(file $1 | grep "text") == "" ]]; then file $1; else rm -rf $1; fi } && filter logs/data.tar.gz + os.Exit(1) + } urls := []string{} if conf.Url != "" { urls = append(urls, conf.Url) @@ -88,12 +151,43 @@ func main() { } } return + } else if conf.Listen != "" { + if conf.Wordlist == "" { + log.Fatal("If passive scanning is started, a dictionary must be specified") + } + if conf.URLStrs == "" { + log.Fatal("urls must be specified") + } + passive := core.NewPassive(conf) + err = passive.Start() + if err != nil { + log.Fatal(err) + } } else { flag.Usage() return } + // 判断 output 文件夹是否存在 + if !exists("./output") { + log.Info("create output file path") + err := os.Mkdir("./output/", os.ModePerm) + if err != nil { + log.Warn("create output file path error", err) + os.Exit(1) + } + } + // 创建 Log 文件夹 + if !exists("./logs") { + log.Info("create logs file path") + err := os.Mkdir("./logs/", os.ModePerm) + if err != nil { + log.Warn("create logs file path error", err) + os.Exit(1) + } + } + log.Info(len(urls), "个URL,", conf.Thread, "线程,", conf.Timeout, "超时") - config.LogFileName = "Log-" + time.Now().Format("2006-01-02 15:04:05") + config.LogFileName = "./logs/Log-" + time.Now().Format("2006-01-02 15:04:05") create, err := os.Create(config.LogFileName) if err != nil { log.Warn(err) @@ -105,3 +199,43 @@ func main() { newCore := core.NewCore(urls, conf) newCore.Core() } + +func exists(path string) bool { + _, err := os.Stat(path) //os.Stat获取文件信息 + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} + +var increase float64 = 0 +var inc_l sync.Mutex + +func increaseAdd() { + inc_l.Lock() + defer inc_l.Unlock() + increase++ +} + +func filter(wait *sync.WaitGroup, filename string, totalcount float64) { + defer wait.Done() + bin := []string{"-c", "function filter { if [[ $(file $1 | grep $1\": data\") == \"\" && $(file $1 | grep \"image data\") == \"\" && $(file $1 | grep \"HTML\") == \"\" && $(file $1 | grep \"empty\") == \"\" && $(file $1 | grep \"JSON\") == \"\" && $(file $1 | grep \"text\") == \"\" ]]; then file $1; else rm -rf $1; fi } && filter '" + filename + "'"} + // 其他的shell环境太烦了 + run := exec.Command("/bin/zsh", bin...) + output, err := run.Output() + if err != nil { + log.Fatal(err) + } + output, err = simplifiedchinese.GB18030.NewDecoder().Bytes(output) + if err != nil { + log.Fatal(err) + } + if len(output) != 0 { + fmt.Print("\r" + string(output)) + } + increaseAdd() + fmt.Printf("\r%.2f%%", math.Trunc(((increase/totalcount)*100)*1e2)*1e-2) +} diff --git a/config/main.go b/config/main.go index 8a0f3a4..a45154f 100644 --- a/config/main.go +++ b/config/main.go @@ -1,16 +1,28 @@ package config type Config struct { - Thread int - Timeout int - Url string - Urlfile string - Loglevel string - Wordlist string - Index string - Proxy string - ExcludeStatus string - ResultFile string + Thread int + Timeout int + Url string + Urlfile string + Loglevel string + Wordlist string + Index string + Proxy string + ResultFile string + FilterOutput bool // 过滤 output 文件夹中的垃圾数据 + Listen string + URLStrs string + Cert string + Key string } var LogFileName string + +type HTTPStatus struct { + URL string + Status int + ContentType string + Size string + Body string +} diff --git a/go.mod b/go.mod index 3a244d9..3543cd1 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,10 @@ module github.com/bufsnake/blueming go 1.14 -require github.com/logrusorgru/aurora v2.0.3+incompatible +require ( + github.com/antlabs/strsim v0.0.2 + github.com/google/martian v2.1.0+incompatible + github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/weppos/publicsuffix-go v0.15.0 + golang.org/x/text v0.3.0 +) diff --git a/go.sum b/go.sum index 463110f..75f7e06 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,20 @@ +github.com/antlabs/strsim v0.0.2 h1:R4qjokEegYTrw+fkcYj3/UndG9Cn136fH+fpw9TIz9k= +github.com/antlabs/strsim v0.0.2/go.mod h1:95XAAF2dJK9IiZMc0Ue6H9t477/i6fvYoMoeey8sEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/weppos/publicsuffix-go v0.15.0 h1:2uQCwDczZ8YZe5uD0mM3sXRoZYA74xxPuiKK8LdPcGQ= +github.com/weppos/publicsuffix-go v0.15.0/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/core/core.go b/internal/core/core.go index 933539c..a9ab9cd 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "github.com/antlabs/strsim" "github.com/bufsnake/blueming/config" file_download "github.com/bufsnake/blueming/pkg/file-download" general_file_name "github.com/bufsnake/blueming/pkg/general-file-name" @@ -9,43 +10,29 @@ import ( "github.com/bufsnake/blueming/pkg/log" . "github.com/logrusorgru/aurora" "io/ioutil" + "net/http" url2 "net/url" "os" "regexp" - "strconv" "strings" "sync" ) type core struct { - config config.Config - url []string - excludestatus []int - wordlist []string - first map[string]string - lock sync.Mutex + config config.Config + url []string + wordlist []string + htmlsimilarity map[string][]string // 耗时又占内存 + hslock sync.Mutex } func NewCore(url []string, config config.Config) core { - statuss := make([]int, 0) - split := strings.Split(config.ExcludeStatus, ",") - for i := 0; i < len(split); i++ { - if len(split[i]) == 0 { - continue - } - status, err := strconv.Atoi(split[i]) - if err != nil { - log.Fatal(split[i], err) - } - statuss = append(statuss, status) - } wordlist := make([]string, 0) if config.Wordlist != "" { file, err := ioutil.ReadFile(config.Wordlist) if err != nil { log.Fatal(err) } - wordlist = append(wordlist, "/admino.ini") split := strings.Split(string(file), "\n") flag := false if config.Index == "" { @@ -67,42 +54,46 @@ func NewCore(url []string, config config.Config) core { log.Fatal("specify index not found") } } - return core{url: url, config: config, excludestatus: statuss, wordlist: wordlist} + hs := make(map[string][]string) + return core{htmlsimilarity: hs, url: url, config: config, wordlist: wordlist} } +// 目录扫描 和 备份文件扫描 分开 func (c *core) Core() { - c.first = make(map[string]string) - index := 0 // -again: - if c.config.Wordlist != "" && len(c.wordlist) == index { - return - } - requestlist := make([][]string, 0) -min: - for i := 0; i < len(c.url); i++ { - uri := "" - if len(c.wordlist) != 0 { - uri = c.wordlist[index] - } - genURL, err := general_file_name.NewGenURL(c.url[i], uri) - if err != nil { - log.Warn(err) - continue - } - getURL := genURL.GetURL() - requestlist = append(requestlist, *getURL) + if c.config.Wordlist != "" { // 目录扫描 + c.dirscan() + } else { // 备份文件扫描 + c.backup() } - if len(c.url) < 10 && len(c.wordlist) != 0 && index < len(c.wordlist)-1 { - index++ - goto min +} + +func (c *core) dirscan() { + httpr := sync.WaitGroup{} + httpc := make(chan string, c.config.Thread) + for i := 0; i < c.config.Thread; i++ { + httpr.Add(1) + go c.httprequest(&httpr, httpc, nil, c.config.Timeout) } - if len(requestlist) == 0 { - index++ - goto again + length := general_file_name.InitGeneral(c.wordlist) + for w := 0; w < length; w++ { + for i := 0; i < len(c.url); i++ { + genURL, err := general_file_name.NewGenURL(c.url[i]) + if err != nil { + log.Warn(err) + continue + } + httpc <- genURL.GetDirURI(w) + } } + close(httpc) + httpr.Wait() +} + +func (c *core) backup() { + log.Info("start scan backup") httpr := sync.WaitGroup{} httpc := make(chan string, c.config.Thread) - httpd := make(chan string, c.config.Thread) + httpd := make(chan config.HTTPStatus, c.config.Thread) for i := 0; i < c.config.Thread; i++ { httpr.Add(1) go c.httprequest(&httpr, httpc, httpd, c.config.Timeout) @@ -112,133 +103,151 @@ min: down.Add(1) go c.httpdownload(&down, httpd) } - i := 0 + // 一阶段 扫描 固定死的URI + length := general_file_name.InitGeneral([]string{}) + for index := 0; index < length; index++ { + for i := 0; i < len(c.url); i++ { + genURL, err := general_file_name.NewGenURL(c.url[i]) + if err != nil { + log.Warn(err) + continue + } + httpc <- genURL.GetBackupURI(index) + } + } + + // 扫描生成的URI + index := 0 for { - flag := 0 - for j := 0; j < len(requestlist); j++ { - if i >= len(requestlist[j]) { - flag++ + flag := true + for i := 0; i < len(c.url); i++ { + genURL, err := general_file_name.NewGenURL(c.url[i]) + if err != nil { + log.Warn(err) + continue + } + getURL := genURL.GetBackupExtURI() + if len(*getURL) <= index { continue } - httpc <- requestlist[j][i] + flag = false + httpc <- (*getURL)[index] } - i++ - if flag == len(requestlist) { + if flag { break } + index++ } + // 分析内存占用 + //memStat := new(runtime.MemStats) + //runtime.ReadMemStats(memStat) + //fmt.Println(len(to), memStat.Alloc) close(httpc) httpr.Wait() close(httpd) down.Wait() - - if c.config.Wordlist == "" { - return - } - index++ - goto again } -func (c *core) httprequest(wait *sync.WaitGroup, httpc, httpd chan string, timeout int) { +func (c *core) httprequest(wait *sync.WaitGroup, httpc chan string, httpd chan config.HTTPStatus, timeout int) { defer wait.Done() for url := range httpc { - parse, _ := url2.Parse(url) - log.Trace(url) - status, ct, size, err := http_request.HTTPRequest(url, timeout) - c.lock.Lock() + var ( + status int + ct string + size string + body string + err error + ) + if c.config.Wordlist == "" { // 备份文件扫描 + status, ct, size, body, err = http_request.HTTPRequest(http.MethodHead, url, c.config.Proxy, timeout) + } else { // 目录扫描 + status, ct, size, body, err = http_request.HTTPRequest(http.MethodGet, url, c.config.Proxy, timeout) + } + log.Trace(status, ct, size, body, err, url) if err != nil && c.config.Wordlist == "" { - if parse.Path == "/admino.ini" { - c.first[parse.Scheme+parse.Host] = "err" - } log.Warn(err) - c.lock.Unlock() - continue } - if parse.Path == "/admino.ini" { - c.first[parse.Scheme+parse.Host] = size + if err != nil && strings.Contains(err.Error(), "proxyconnect tcp") { + log.Warn(err) } - c.lock.Unlock() - if c.config.Wordlist != "" { - if size == "0.0B" || size == "-1.0B" { + if status == 404 || status == 0 { continue } - c.lock.Lock() - _, ok := c.first[parse.Scheme+parse.Host] - if ok { - if c.first[parse.Scheme+parse.Host] == size { - c.lock.Unlock() - log.Trace("rm", url) - continue - } + // 计算页面相似度 -- 耗时严重 - 默认使用head请求 + // 与当前URL的所有历史记录进行匹配 + // 相似值低于0.75则追加 + parse, err := url2.Parse(url) + if err != nil { + log.Warn(err) + continue } - c.lock.Unlock() - flag := true - for i := 0; i < len(c.excludestatus); i++ { - if status == c.excludestatus[i] { - flag = false + c.hslock.Lock() + similarity := false + for i := 0; i < len(c.htmlsimilarity[parse.Host]); i++ { + // 2.516400257s + //compare := strsim.Compare(body, c.htmlsimilarity[parse.Host][i], strsim.DiceCoefficient(1)) + + // 2.916750287s + //compare := strsim.Compare(body, c.htmlsimilarity[parse.Host][i], strsim.Jaro()) + + // 1.839468012s + compare := strsim.Compare(body, c.htmlsimilarity[parse.Host][i], strsim.Hamming()) + if compare >= 0.75 { + similarity = true + break } } - if flag && status != 0 && len(c.url) > 1 { - pr := make([]interface{}, 0) - pr = append(pr, BrightWhite(url)) - if status >= 200 && status < 300 { - pr = append(pr, BrightGreen(status).String()) - } else if status >= 300 && status < 400 { - pr = append(pr, BrightYellow(status).String()) - } else if status >= 400 && status < 500 { - pr = append(pr, BrightMagenta(status).String()) - } else { - pr = append(pr, BrightWhite(status).String()) - } - pr = append(pr, BrightCyan(size).String()) - if ct == "" { - pr = append(pr, "null") - } else { - pr = append(pr, ct) - } - fmt.Println(pr...) - } else if flag && status != 0 { - pr := make([]interface{}, 0) - if status >= 200 && status < 300 { - pr = append(pr, BrightGreen(status).String()) - } else if status >= 300 && status < 400 { - pr = append(pr, BrightYellow(status).String()) - } else if status >= 400 && status < 500 { - pr = append(pr, BrightMagenta(status).String()) - } else { - pr = append(pr, BrightWhite(status).String()) - } - pr = append(pr, BrightCyan(size).String()) - pr = append(pr, BrightWhite(url)) - if ct == "" { - pr = append(pr, "null") - } else { - pr = append(pr, ct) + + if similarity { // 相似 退出 + c.hslock.Unlock() + continue + } + c.htmlsimilarity[parse.Host] = append(c.htmlsimilarity[parse.Host], body) + c.hslock.Unlock() + + pr := make([]interface{}, 0) + if status >= 200 && status < 300 { + pr = append(pr, "["+BrightGreen(status).String()+"]") + } else if status >= 300 && status < 400 { + pr = append(pr, "["+BrightYellow(status).String()+"]") + } else if status >= 400 && status < 500 { + pr = append(pr, "["+BrightMagenta(status).String()+"]") + } else { + pr = append(pr, "["+BrightWhite(status).String()+"]") + } + pr = append(pr, "["+BrightCyan(size).String()+"]") + pr = append(pr, "["+BrightWhite(url).String()+"]") + if ct == "" { + pr = append(pr, "["+"null"+"]") + } else { + pr = append(pr, "["+ct+"]") + } + fmt.Println(pr...) + } else { + if status != 200 && status != 206 { + continue + } + if size == "0B" || size == "0.0B" { + continue + } + matchString, err := regexp.MatchString("application/[-\\w.]+", ct) + if err == nil && matchString { + log.Info(size, ct, url) + httpd <- config.HTTPStatus{ + URL: url, + Size: size, + ContentType: ct, } - fmt.Println(pr...) } - continue - } - if status == 200 && (size == "0B" || size == "0.0B") { - log.Debug(status, size, ct, url) - } - matchString, err := regexp.MatchString("application/[-\\w.]+", ct) - if err != nil { - log.Warn(err) - continue - } - if size != "0B" && size != "0.0B" && status == 200 && matchString { - log.Info(size, ct, url) - httpd <- url } } } -func (c *core) httpdownload(wait *sync.WaitGroup, httpd chan string) { +func (c *core) httpdownload(wait *sync.WaitGroup, httpd chan config.HTTPStatus) { defer wait.Done() for url := range httpd { - err := file_download.DownloadFile(url, c.config.Proxy) + err := file_download.DownloadFile(url.URL, c.config.Proxy) if err != nil { log.Info("file download error", err) // 将URL保存到文件 @@ -247,7 +256,7 @@ func (c *core) httpdownload(wait *sync.WaitGroup, httpd chan string) { log.Warn(err) continue } - _, err = file.WriteString(url + "\n") + _, err = file.WriteString(url.ContentType + " " + url.Size + " " + url.URL + "\n") if err != nil { file.Close() log.Warn(err) diff --git a/internal/core/listen.go b/internal/core/listen.go new file mode 100644 index 0000000..18e7955 --- /dev/null +++ b/internal/core/listen.go @@ -0,0 +1,272 @@ +package core + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "github.com/bufsnake/blueming/config" + "github.com/bufsnake/blueming/pkg/log" + "github.com/bufsnake/blueming/pkg/parseip" + "github.com/google/martian" + log2 "github.com/google/martian/log" + "github.com/google/martian/mitm" + "github.com/weppos/publicsuffix-go/publicsuffix" + "io/ioutil" + "math/big" + "net" + "net/http" + "net/url" + "os" + "os/signal" + "strings" + "time" +) + +type passive struct { + conf config.Config +} + +func NewPassive(conf_ config.Config) passive { + return passive{conf: conf_} +} + +// REF: https://github.com/google/martian/blob/master/cmd/proxy/main.go +func (c *passive) Start() error { + file, err := ioutil.ReadFile(c.conf.URLStrs) + if err != nil { + return err + } + urls := make([]string, 0) + split := strings.Split(string(file), "\n") + for i := 0; i < len(split); i++ { + trim := strings.Trim(split[i], "\r") + if trim == "" { + continue + } + if strings.HasPrefix(trim, "http://") || strings.HasPrefix(trim, "https://") { + parse, err := url.Parse(trim) + if err != nil { + log.Warn(err) + continue + } + trim = parse.Host + } + if strings.Contains(trim, ":") { + trim = strings.Split(trim, ":")[0] + } + // IP 解析 + _, _, err = parseip.ParseIP(trim) + if err != nil { + domain, err := publicsuffix.Domain(trim) + if err != nil { + log.Warn(err) + continue + } + urls = append(urls, domain) + continue + } + urls = append(urls, trim) + } + log.Info("number of assets that can be scanned", len(urls)) + // 启动代理 // 建立请求队列 // 筛选过滤HTTP链接 // 进行漏洞扫描 + log2.SetLevel(-1) + martian.Init() + p := martian.NewProxy() + defer p.Close() + l, err := net.Listen("tcp", c.conf.Listen) + if err != nil { + log.Fatal(err) + } + tr := &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 60 * time.Second, + KeepAlive: 60 * time.Second, + }).Dial, + TLSHandshakeTimeout: 60 * time.Second, + ExpectContinueTimeout: time.Second, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + p.SetRoundTripper(tr) + + var x509c *x509.Certificate + var priv interface{} + var raw []byte + _, err = ioutil.ReadFile(c.conf.Cert) + if err != nil && strings.HasSuffix(err.Error(), "no such file or directory") { + x509c, priv, raw, err = NewAuthority("blueming", "localhost", 365*24*time.Hour) + if err != nil { + log.Fatal(err) + } + certOut, _ := os.Create("./ca.crt") + err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: raw}) + err = certOut.Close() + + priv_ := &rsa.PrivateKey{} + switch priv.(type) { + case *rsa.PrivateKey: + priv_ = priv.(*rsa.PrivateKey) + } + keyOut, _ := os.Create("./ca.key") + err = pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv_)}) + err = keyOut.Close() + log.Info("generate certificate success in current dir") + } else if c.conf.Cert != "" && c.conf.Key != "" { + tlsc, err := tls.LoadX509KeyPair(c.conf.Cert, c.conf.Key) + if err != nil { + log.Fatal(err) + } + priv = tlsc.PrivateKey + + x509c, err = x509.ParseCertificate(tlsc.Certificate[0]) + if err != nil { + log.Fatal(err) + } + } + log.Info("loading cert from", c.conf.Cert, "and", c.conf.Key) + log.Info(fmt.Sprintf("martian: starting proxy on %s", l.Addr().String())) + + var mitm_config *mitm.Config + mitm_config, err = mitm.NewConfig(x509c, priv) + if err != nil { + log.Fatal(err) + } + mitm_config.SkipTLSVerify(false) + p.SetMITM(mitm_config) + + // 设置下级代理 + if c.conf.Proxy != "" { + var url_proxy *url.URL + url_proxy, err = url.Parse(c.conf.Proxy) + if err != nil { + log.Fatal(err) + } + p.SetDownstreamProxy(url_proxy) + } + + urlstrs := make(chan string, 1000000) + m := modify{urlstrs: urlstrs} + p.SetRequestModifier(&m) + + go func() { + err = p.Serve(l) + if err != nil { + log.Fatal(err) + } + }() + urlstr_ := make(map[string]bool) + urlstrc := make(chan string, 1000) + for i := 0; i < c.conf.Thread; i++ { + go func() { + // 获取链接并进行扫描 + for urlstr := range urlstrc { + flag := false + for _, host := range urls { + if strings.Contains(urlstr, host) { + flag = true + break + } + } + if !flag { + continue + } + newCore := NewCore([]string{urlstr}, c.conf) + newCore.Core() + } + }() + } + + for urlstr := range urlstrs { + urlstr = strings.ReplaceAll(urlstr, " ", "") + urlstrs_, err := c.getUrlLayerDirectory(urlstr) + if err != nil { + log.Warn(err) + continue + } + for i := 0; i < len(urlstrs_); i++ { + temp := strings.Trim(urlstrs_[i], "/") + "/" + if _, ok := urlstr_[temp]; !ok { + urlstr_[temp] = true + urlstrc <- temp + } + } + } + + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, os.Interrupt) + <-sigc + log.Fatal("martian: shutting down") + return nil +} + +type modify struct { + urlstrs chan string +} + +func (v *modify) ModifyRequest(req *http.Request) error { + if strings.ToUpper(req.Method) == "OPTIONS" || strings.ToUpper(req.Method) == "CONNECT" { + return nil + } + v.urlstrs <- req.URL.String() + return nil +} + +func NewAuthority(name, organization string, validity time.Duration) (x509c *x509.Certificate, priv *rsa.PrivateKey, raw []byte, err error) { + priv, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, nil, err + } + pub := priv.Public() + + // Subject Key Identifier support for end entity certificate. + // https://www.ietf.org/rfc/rfc3280.txt (section 4.2.1.2) + pkixpub, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, nil, nil, err + } + h := sha1.New() + h.Write(pkixpub) + keyID := h.Sum(nil) + + // TODO: keep a map of used serial numbers to avoid potentially reusing a + // serial multiple times. + serial, err := rand.Int(rand.Reader, big.NewInt(0).SetBytes(bytes.Repeat([]byte{255}, 20))) + if err != nil { + return nil, nil, nil, err + } + + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{ + CommonName: name, + Organization: []string{organization}, + }, + SubjectKeyId: keyID, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + NotBefore: time.Now().Add(-validity), + NotAfter: time.Now().Add(validity), + DNSNames: []string{name}, + IsCA: true, + } + + raw, err = x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) + if err != nil { + return nil, nil, nil, err + } + + x509c, err = x509.ParseCertificate(raw) + if err != nil { + return nil, nil, nil, err + } + + return x509c, priv, raw, nil +} diff --git a/internal/core/utils.go b/internal/core/utils.go new file mode 100644 index 0000000..a01c09a --- /dev/null +++ b/internal/core/utils.go @@ -0,0 +1,64 @@ +package core + +import ( + "net/url" + "strings" +) + +// 解析发送的URL,获取二级、三级、四级目录 +func (c *passive) getUrlLayerDirectory(urlstr string) (ret []string, err error) { + parse, err := url.Parse(urlstr) + if err != nil { + return nil, err + } + url_ := parse.Scheme + "://" + parse.Host + "/" + path := strings.Split(parse.Path, "/") + for i := 0; i < len(path); i++ { + if path[i] == "" { + continue + } + if strings.Contains(path[i], ".") && i == len(path)-1 { + continue + } + url_c := concat(path, url_, i) + if strings.Trim(url_c, "/") == strings.Trim(url_, "/") || strings.Trim(url_c, "/") == strings.Trim(urlstr, "/") { + continue + } + ret = append(ret, url_c) + } + if !strings.Contains(urlstr, "?") && !strings.Contains(urlstr, ".") { + if !strings.HasSuffix(urlstr, "/") { + urlstr = urlstr + "/" + } + } + ret = append(ret, urlstr) + ret = append(ret, url_) + return ret, nil +} + +func concat(path []string, urlstr string, index int) string { + urlstr = strings.Trim(urlstr, "/") + for i := 0; i <= index; i++ { + urlstr += strings.Trim(path[i], "/") + "/" + } + return urlstr +} + +// 解析获取HOST,不包含端口/包含端口 +// RET: HOST,PATH +func (c *passive) getUrlHostAndPath(urlstr string, contain_port bool) (host string, path string, err error) { + parse, err := url.Parse(urlstr) + if err != nil { + return "", "", err + } + if parse.RawQuery != "" { + parse.Path = parse.Path + "?" + parse.RawQuery + } + if !contain_port && strings.Contains(parse.Host, ":") { + split := strings.Split(parse.Host, ":") + if len(split) == 2 { + return split[0], parse.Path, nil + } + } + return parse.Host, parse.Path, nil +} diff --git a/pkg/general-file-name/main.go b/pkg/general-file-name/main.go index ad0e3b0..381eed1 100644 --- a/pkg/general-file-name/main.go +++ b/pkg/general-file-name/main.go @@ -1,51 +1,92 @@ package general_file_name import ( + "github.com/bufsnake/blueming/pkg/log" + "github.com/weppos/publicsuffix-go/publicsuffix" "net/url" "regexp" "strings" ) type general_file_name struct { - url string - wordlist string + url string + backupuri []string } -func NewGenURL(url string, wordlist string) (*general_file_name, error) { - return &general_file_name{url: strings.TrimRight(url, "/"), wordlist: wordlist}, nil -} +var ret = []string{} +var wordlist = []string{} + +func InitGeneral(wordlists []string) int { + prefix := []string{"index", "site", "db", "archive", "auth", "website", "backup", "test", "sql", "2016", "com", "dump", "master", "sales", "1", "2013", "members", "wwwroot", "clients", "back", "php", "localhost", "local", "127.0.0.1", "2019", "joomla", "wp", "html", "home", "tar", "vb", "database", "2012", "2020", "engine", "error_log", "mysql", "2018", "my", "new", "wordpress", "user", "2015", "customers", "dat", "media", "2014", "users", "2011", "2021", "old", "code", "jsp", "js", "store", "www", "2017", "web", "orders", "admin", "forum", "aspx", "data", "2010", "backups", "files", "bin"} + suffix := []string{".zip", ".rar", ".tar.gz", ".tgz", ".tar.bz2", ".tar", ".jar", ".war", ".7z", ".bak", ".sql"} -func (g *general_file_name) GetURL() *[]string { - ret := make([]string, 0) - if len(g.wordlist) != 0 { - ret = append(ret, g.url+"/"+strings.TrimLeft(g.wordlist, "/")) - return &ret + for i := 0; i < len(prefix); i++ { + for j := 0; j < len(suffix); j++ { + ret = append(ret, "/"+prefix[i]+suffix[j]) + } + } + wordlist = wordlists + if len(wordlist) != 0 { + return len(wordlist) } + return len(ret) +} + +func NewGenURL(url string) (*general_file_name, error) { + return &general_file_name{backupuri: ret, url: strings.TrimRight(url, "/")}, nil +} + +func (g *general_file_name) GetDirURI(index int) string { + return g.url + "/" + strings.TrimLeft(wordlist[index], "/") +} + +func (g *general_file_name) GetBackupURI(index int) string { + return g.url + g.backupuri[index] +} - prefix := []string{"data", "backup", "db", "database", "code", "test", "user", "sql", "www", "admin", "wwwroot", "web"} +func (g *general_file_name) GetBackupExtURI() *[]string { + rets := make([]string, 0) + // *** 属于拓展 URI 每个URL不同 单独进行获取 + prefix := []string{} suffix := []string{".zip", ".rar", ".tar.gz", ".tgz", ".tar.bz2", ".tar", ".jar", ".war", ".7z", ".bak", ".sql"} + // 去掉域名 www. 前缀 添加到prefix + // 获取域名根域,添加到prefix + // 获取IP,添加到prefix + + // 获取host parse, err := url.Parse(g.url) - if err == nil { - if strings.Contains(parse.Host, ":") { - parse.Host = strings.Split(parse.Host, ":")[0] + if err != nil { + log.Warn(err) + return nil + } + parse.Host = strings.TrimLeft(parse.Host, "www") + parse.Host = strings.TrimLeft(parse.Host, ".") + if strings.Contains(parse.Host, ":") { + parse.Host = strings.Split(parse.Host, ":")[0] + } + prefix = append(prefix, parse.Host) + if isdomain(g.url) { // 域名 - 获取根域 + domain, err := publicsuffix.Domain(parse.Host) + if err != nil { + log.Warn(err) + return nil } - parse.Host = strings.TrimLeft(parse.Host, "www") - parse.Host = strings.TrimLeft(parse.Host, ".") - prefix = append(prefix, parse.Host) - if isdomain(parse.Host) { - split := strings.Split(parse.Host, ".") - if len(split) > 2 { - prefix = append(prefix, split[len(split)-2]+"."+split[len(split)-1]) - prefix = append(prefix, split[len(split)-2]) + exist := false + for _, vv := range prefix { + if vv == domain { + exist = true } } + if !exist { + prefix = append(prefix, domain) + } } for i := 0; i < len(prefix); i++ { for j := 0; j < len(suffix); j++ { - ret = append(ret, g.url+"/"+prefix[i]+suffix[j]) + rets = append(rets, g.url+"/"+prefix[i]+suffix[j]) } } - return &ret + return &rets } func isdomain(str string) bool { diff --git a/pkg/http-request/main.go b/pkg/http-request/main.go index b3a3c43..8b15459 100644 --- a/pkg/http-request/main.go +++ b/pkg/http-request/main.go @@ -6,28 +6,43 @@ import ( "github.com/bufsnake/blueming/config" "github.com/bufsnake/blueming/pkg/log" "github.com/bufsnake/blueming/pkg/useragent" - "io" "io/ioutil" "net/http" + url2 "net/url" + "os" "time" ) -func HTTPRequest(url string, timeout int) (status int, contenttype, size string, err error) { +func HTTPRequest(method, url, proxyx string, timeout int) (status int, contenttype, size string, body string, err error) { client := &http.Client{ - Timeout: time.Duration(timeout) * time.Second, - Transport: &http.Transport{ - DisableKeepAlives: true, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, + Timeout: time.Duration(timeout) * time.Second, + Transport: nil, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } - req, err := http.NewRequest(http.MethodHead, url, nil) + transport := &http.Transport{ + DisableKeepAlives: true, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + if proxyx != "" { + proxy, err := url2.Parse(proxyx) + if err != nil { + return 0, "", "", "", err + } + transport.Proxy = http.ProxyURL(proxy) + client.Transport = transport + } else { + client.Transport = transport + } + req, err := http.NewRequest(method, url, nil) if err != nil { - return 0, "", "0B", err + return 0, "", "0B", "", err + } + if method == http.MethodGet { + req.Header.Add("Range", "bytes=0-8100") } req.Header.Add("Accept", "*/*") req.Header.Add("Referer", "http://www.baidu.com") @@ -36,12 +51,13 @@ func HTTPRequest(url string, timeout int) (status int, contenttype, size string, req.Header.Add("User-Agent", useragent.RandomUserAgent()) do, err := client.Do(req) if err != nil { - return 0, "", "0B", err + return 0, "", "0B", "", err } defer do.Body.Close() - _, err = io.Copy(ioutil.Discard, do.Body) + all, err := ioutil.ReadAll(do.Body) + flag := false if err != nil { - return 0, "", "0B", err + flag = true } temp := float64(do.ContentLength) SIZE := []string{"B", "K", "M", "G", "T"} @@ -60,11 +76,20 @@ func HTTPRequest(url string, timeout int) (status int, contenttype, size string, length = fmt.Sprintf("%0.1f%s", temp, SIZE[i]) } if do.ContentLength > 104857600 { - err := ioutil.WriteFile(config.LogFileName, []byte(do.Header.Get("Content-Type")+" "+length+" "+url+"\n"), 644) + file, err := os.OpenFile(config.LogFileName, os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + log.Warn(err) + } + _, err = file.WriteString(do.Header.Get("Content-Type") + " " + length + " " + url + "\n") if err != nil { + file.Close() log.Warn(err) } - return 0, "", "0B", err + file.Close() + return 0, "", "0B", "", err + } + if flag { // GET 获取 body 失败时 + return do.StatusCode, do.Header.Get("Content-Type"), length, "", nil } - return do.StatusCode, do.Header.Get("Content-Type"), length, nil + return do.StatusCode, do.Header.Get("Content-Type"), length, string(all), nil } diff --git a/pkg/parseip/main.go b/pkg/parseip/main.go new file mode 100644 index 0000000..26d0c0e --- /dev/null +++ b/pkg/parseip/main.go @@ -0,0 +1,185 @@ +package parseip + +import ( + "errors" + "fmt" + "net" + "strconv" + "strings" +) + +// 支持常见的ip格式 +// 192.168.113.159 +// 192.168.113.159-254 +// 192.168.113.159-192.168.113.254 +// 192.168.113.0/24 +// 191.168.113.159-192.168.114.254 +// 192.167.113.159-192.168.114.254 +// 192.168.113.159-192.168.114.254 +func ParseIP(ip string) (startx uint32, endx uint32, err error) { + if strings.Contains(ip, "-") { + if len(strings.Split(ip, "-")[1]) <= 3 { + return multipleip(ip) + } else { + return multipleip2(ip) + } + } else if strings.Contains(ip, "/") { + return multipleip3(ip) + } else { + return singleip(ip) + } +} + +// 192.168.113.159 +func singleip(ip string) (startx uint32, endx uint32, err error) { + for _, val := range strings.Split(ip, ".") { + ips, err := strconv.Atoi(val) + if err != nil { + return 0, 0, errors.New(ip + " " + err.Error() + " ip parse error") + } + if ips > 255 { + return 0, 0, errors.New(ip + " ip parse error") + } + } + startx, err = ip2UInt32(ip) + if err != nil { + return 0, 0, err + } + endx, err = ip2UInt32(ip) + if err != nil { + return 0, 0, err + } + return startx, endx, nil +} + +// 192.168.113.159-255 +func multipleip(ips string) (startx uint32, endx uint32, err error) { + host := strings.Split(ips, "-") + ip := host[0] + if len(strings.Split(ip, ".")) != 4 { + return 0, 0, errors.New("multipleip error " + ips) + } + start, err := strconv.Atoi(strings.Split(ip, ".")[3]) + if err != nil { + return 0, 0, errors.New(ips + " " + err.Error() + " ip parse error") + } + end, err := strconv.Atoi(host[1]) + if err != nil { + return 0, 0, errors.New(ips + " " + err.Error() + " ip parse error") + } + if start > end { + return 0, 0, errors.New(ips + " ip parse error") + } + if start < 0 { + start = 0 + } + if end > 255 { + end = 255 + } + temp := strings.Split(ip, ".") + start_t, err := ip2UInt32(temp[0] + "." + temp[1] + "." + temp[2] + "." + strconv.Itoa(start)) + if err != nil { + return 0, 0, err + } + end_t, err := ip2UInt32(temp[0] + "." + temp[1] + "." + temp[2] + "." + strconv.Itoa(end)) + if err != nil { + return 0, 0, err + } + return start_t, end_t, nil +} + +// 192.168.113.159-192.168.113.254 +func multipleip2(ips string) (startx uint32, endx uint32, err error) { + start, err := ip2UInt32(strings.Split(ips, "-")[0]) + if err != nil { + return 0, 0, err + } + end, err := ip2UInt32(strings.Split(ips, "-")[1]) + if err != nil { + return 0, 0, err + } + if start > end { + return 0, 0, errors.New(ips + " error") + } + return start, end, nil +} + +// 192.168.113.0/24 +func multipleip3(ips string) (startx uint32, endx uint32, err error) { + host := strings.Split(ips, "/")[0] + mask, err := strconv.Atoi(strings.Split(ips, "/")[1]) + if err != nil { + return 0, 0, errors.New(ips + " " + err.Error() + " ip parse error") + } + if len(strings.Split(host, ".")) != 4 { + return 0, 0, errors.New(ips + " ip parse error") + } + a, err := strconv.Atoi(strings.Split(host, ".")[0]) + b, err := strconv.Atoi(strings.Split(host, ".")[1]) + c, err := strconv.Atoi(strings.Split(host, ".")[2]) + d, err := strconv.Atoi(strings.Split(host, ".")[3]) + if err != nil { + return 0, 0, errors.New(ips + " ip parse error") + } + ipbin := fmt.Sprintf("%08s", strconv.FormatInt(int64(a), 2)) + + fmt.Sprintf("%08s", strconv.FormatInt(int64(b), 2)) + + fmt.Sprintf("%08s", strconv.FormatInt(int64(c), 2)) + + fmt.Sprintf("%08s", strconv.FormatInt(int64(d), 2)) + + start := ipbin[:mask] + end := ipbin[:mask] + for i := 0; i < len(ipbin)-mask; i++ { + start += "0" + end += "1" + } + start1, err := strconv.ParseUint(start, 2, 32) + if err != nil { + return 0, 0, errors.New(ips + " ip parse error: " + err.Error()) + } + end2, err := strconv.ParseUint(end, 2, 32) + if err != nil { + return 0, 0, errors.New(ips + " ip parse error: " + err.Error()) + } + return uint32(start1), uint32(end2), nil +} + +func ip2UInt32(ipnr string) (uint32, error) { + bits := strings.Split(ipnr, ".") + if len(bits) != 4 { + return 0, errors.New("ip2Uint32 error " + ipnr) + } + + b0, err := strconv.Atoi(bits[0]) + if err != nil { + return 0, err + } + b1, err := strconv.Atoi(bits[1]) + if err != nil { + return 0, err + } + b2, err := strconv.Atoi(bits[2]) + if err != nil { + return 0, err + } + b3, err := strconv.Atoi(bits[3]) + if err != nil { + return 0, err + } + + var sum uint32 + sum += uint32(b0) << 24 + sum += uint32(b1) << 16 + sum += uint32(b2) << 8 + sum += uint32(b3) + return sum, nil +} + +func UInt32ToIP(intIP uint32) string { + var bytes [4]byte + bytes[0] = byte(intIP & 0xFF) + bytes[1] = byte((intIP >> 8) & 0xFF) + bytes[2] = byte((intIP >> 16) & 0xFF) + bytes[3] = byte((intIP >> 24) & 0xFF) + + return net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0]).String() +}