diff --git a/cmd/blazehttp/main.go b/cmd/blazehttp/main.go index 18587c24..7b90565e 100644 --- a/cmd/blazehttp/main.go +++ b/cmd/blazehttp/main.go @@ -27,6 +27,7 @@ var ( timeout = 1000 // default 1000 ms c = 10 // default 10 concurrent workers mHost string // modify host header + proxy string // proxy address, example: http://127.0.0.1:8083 requestPerSession bool // send request per session ) @@ -40,6 +41,7 @@ func init() { flag.StringVar(&glob, "g", "", "glob expression, example: *.http") flag.IntVar(&timeout, "timeout", 1000, "connection timeout, default 1000 ms") flag.StringVar(&mHost, "H", "", "modify host header") + flag.StringVar(&proxy, "proxy", "", "proxy address, example: http://127.0.0.1:8083") flag.BoolVar(&requestPerSession, "rps", true, "send request per session") flag.Parse() if url, err := url.Parse(target); err != nil || url.Scheme == "" || url.Host == "" { @@ -122,6 +124,7 @@ func main() { worker.WithReqPerSession(requestPerSession), worker.WithTimeout(timeout), worker.WithUseEmbedFS(glob == ""), // use embed test case fs when glob is empty + worker.WithProxy(proxy), worker.WithProgressBar(progressBar), ) c := make(chan os.Signal, 1) diff --git a/gui/main.go b/gui/main.go index 61c2148d..da033dbf 100644 --- a/gui/main.go +++ b/gui/main.go @@ -254,11 +254,15 @@ func MakeRunForm(w fyne.Window, outputCh chan string, resultCh chan *worker.Resu statusCode.SetText("403") statusCode.Validator = validation.NewRegexp(`^\d+$`, "StatusCode必须是数字") + // 代理地址 + proxy := widget.NewEntry() + advanceForm := &widget.Form{ Items: []*widget.FormItem{ {Text: "修改请求(Host)", Widget: reqHost, HintText: ""}, {Text: "请求超时", Widget: timeout, HintText: ""}, {Text: "拦截状态码(respCode)", Widget: statusCode, HintText: "一般情况下WAF拦截状态码为403"}, + {Text: "代理地址", Widget: proxy, HintText: "例如: http://127.0.0.1:8080"}, }, } @@ -307,7 +311,7 @@ func MakeRunForm(w fyne.Window, outputCh chan string, resultCh chan *worker.Resu statusCode := strings.TrimSpace(statusCode.Text) statusCodeI, _ := strconv.Atoi(statusCode) - err := run(target.Text, reqHost.Text, worksNum, statusCodeI, resultCh, stopCh) + err := run(target.Text, reqHost.Text, proxy.Text, worksNum, statusCodeI, resultCh, stopCh) if err != nil { outputCh <- err.Error() } @@ -460,7 +464,7 @@ func MakeTestCaseTab(w fyne.Window) fyne.CanvasObject { return container.NewBorder(tableFilterForm, nil, nil, exportBtn, table) } -func run(target, mHost string, c, statusCode int, resultCh chan *worker.Result, stopCh chan struct{}) error { +func run(target, mHost, proxy string, c, statusCode int, resultCh chan *worker.Result, stopCh chan struct{}) error { var addr string var isHttps bool @@ -491,6 +495,7 @@ func run(target, mHost string, c, statusCode int, resultCh chan *worker.Result, worker.WithConcurrence(c), worker.WithReqHost(mHost), worker.WithUseEmbedFS(true), // use embed test case fs when glob is empty + worker.WithProxy(proxy), worker.WithResultCh(resultCh), ) go func() { diff --git a/http/connect.go b/http/connect.go index 7d311b8c..f4087ea5 100644 --- a/http/connect.go +++ b/http/connect.go @@ -1,16 +1,92 @@ package http import ( + "bufio" "crypto/tls" + "encoding/base64" "fmt" "net" + "net/url" "regexp" "time" + + "golang.org/x/net/proxy" ) -func Connect(addr string, isHttps bool, timeout int) *net.Conn { - var n net.Conn - var err error +func dialProxy(addr, proxyString string, dialer *net.Dialer) (conn net.Conn, err error) { + + proxyUrl, err := url.Parse(proxyString) + if err != nil { + return nil, err + } + if proxyUrl.Scheme == "socks5" { + var auth *proxy.Auth = nil; + if proxyUrl.User != nil { + username := proxyUrl.User.Username() + password, _ := proxyUrl.User.Password() + auth = &proxy.Auth{ + User: username, + Password: password, + } + } + dialer, err := proxy.SOCKS5("tcp", proxyUrl.Host, auth, dialer) + if err != nil { + return nil, err + } + conn, err = dialer.Dial("tcp", addr) + if err != nil { + return nil, err + } + } else if proxyUrl.Scheme == "http" { + conn, err = dialer.Dial("tcp", proxyUrl.Host) + if err != nil { + return nil, err + } + request := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n", addr, addr) + if proxyUrl.User != nil { + username := proxyUrl.User.Username() + password, _ := proxyUrl.User.Password() + auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + request += "Proxy-Authorization: Basic " + auth + "\r\n" + } + request += "\r\n" + _, err = fmt.Fprint(conn, request) + if err != nil { + conn.Close() + return nil, err + } + + reader := bufio.NewReader(conn) + + line, err := reader.ReadString('\n') + if err != nil { + return nil, err + } + if m, err := regexp.MatchString("HTTP/[\\d\\.]+ 200 Connection established", line); !m { + conn.Close() + return nil, fmt.Errorf("CONNECT请求失败: %s %s", line, err) + } + + for { + line, err := reader.ReadString('\n') + if err != nil { + conn.Close() + return nil, fmt.Errorf("读取CONNECT响应头失败: %v", err) + } + if line == "\r\n" { + break + } + } + } else { + return nil, fmt.Errorf("不支持的代理类型: " + proxyUrl.Scheme) + } + if err != nil { + return nil, fmt.Errorf("连接失败: %v", err) + } + return conn, nil +} + +func Connect(addr string, proxyString string, isHttps bool, timeout int) (conn net.Conn, err error) { if m, _ := regexp.MatchString(`.*(]:)|(:)[0-9]+$`, addr); !m { if isHttps { addr = fmt.Sprintf("%s:443", addr) @@ -20,25 +96,40 @@ func Connect(addr string, isHttps bool, timeout int) *net.Conn { } retryCnt := 0 retry: - if isHttps { - n, err = tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) + dialer := net.Dialer{ + Timeout: time.Duration(timeout) * time.Millisecond * 2, + } + if proxyString != "" { + conn, err = dialProxy(addr, proxyString, &dialer) } else { - n, err = net.Dial("tcp", addr) + conn, err = dialer.Dial("tcp", addr) } if err != nil { retryCnt++ if retryCnt < 4 { goto retry } else { - return nil + return nil, err + } + } + if isHttps { + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + if err := tlsConn.Handshake(); err != nil { + retryCnt++ + if retryCnt < 4 { + goto retry + } else { + return nil, err + } } + conn = tlsConn } wDeadline := time.Now().Add(time.Duration(timeout) * time.Millisecond) rDeadline := time.Now().Add(time.Duration(timeout*2) * time.Millisecond) deadline := time.Now().Add(time.Duration(timeout*2) * time.Millisecond) - _ = n.SetDeadline(deadline) - _ = n.SetReadDeadline(rDeadline) - _ = n.SetWriteDeadline(wDeadline) + _ = conn.SetDeadline(deadline) + _ = conn.SetReadDeadline(rDeadline) + _ = conn.SetWriteDeadline(wDeadline) - return &n + return conn, nil } diff --git a/worker/worker.go b/worker/worker.go index d11dd56f..8e759a61 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -35,12 +35,20 @@ type Worker struct { reqHost string // request host of header reqPerSession bool // request per session useEmbedFS bool + proxy string resultCh chan *Result } type WorkerOption func(*Worker) +func WithProxy(proxy string) WorkerOption { + return func(w *Worker) { + w.proxy = proxy + } +} + func WithTimeout(timeout int) WorkerOption { + return func(w *Worker) { w.timeout = timeout } @@ -213,24 +221,24 @@ func (w *Worker) runWorker() { req.CalculateContentLength() start := time.Now() - conn := blazehttp.Connect(w.addr, w.isHttps, w.timeout) - if conn == nil { - job.Result.Err = fmt.Sprintf("connect to %s failed!\n", w.addr) + conn, err := blazehttp.Connect(w.addr, w.proxy, w.isHttps, w.timeout) + if err != nil { + job.Result.Err = fmt.Sprintf("connect to %s failed!\n", err) return } - nWrite, err := req.WriteTo(*conn) + defer conn.Close() + nWrite, err := req.WriteTo(conn) if err != nil { job.Result.Err = fmt.Sprintf("send request poc: %s length: %d error: %s", filePath, nWrite, err) return } rsp := new(blazehttp.Response) - if err = rsp.ReadConn(*conn); err != nil { + if err = rsp.ReadConn(conn); err != nil { job.Result.Err = fmt.Sprintf("read poc file: %s response, error: %s", filePath, err) return } elap := time.Since(start).Nanoseconds() - (*conn).Close() job.Result.Success = true if strings.HasSuffix(job.FilePath, "white") { job.Result.IsWhite = true // white case