diff --git a/cmd/file.go b/cmd/file.go index cc19de09..2ed9c2c3 100644 --- a/cmd/file.go +++ b/cmd/file.go @@ -23,243 +23,169 @@ import ( var fileCmd = &cobra.Command{ Use: "file [filePath] [flags]", Short: "Use file mode(targets list or rawdata)", - Run: func(cmd *cobra.Command, args []string) { - sf, _ := cmd.Flags().GetBool("silence-force") - if sf { - options.Silence = sf - } - printing.Banner(options) - tMethod := options.Method - options.Method = "FILE Mode" - if len(args) == 0 { - printing.DalLog("ERROR", "Input file path", options) - printing.DalLog("ERROR", "e.g dalfox file ./targets.txt or ./rawdata.raw", options) - return - } - printing.Summary(options, args[0]) - options.Method = tMethod - var targets []string - mutex := &sync.Mutex{} - options.Mutex = mutex - if len(args) >= 1 { - rawdata, _ := cmd.Flags().GetBool("rawdata") - har, _ := cmd.Flags().GetBool("har") - if rawdata { - printing.DalLog("SYSTEM", "Using file mode(rawdata)", options) - ff, err := voltFile.ReadLinesOrLiteral(args[0]) - _ = err - var path, body, host, target string - bodyswitch := false - for index, line := range ff { - if index == 0 { - parse := strings.Split(line, " ") - if len(parse) > 1 { - options.Method = parse[0] - path = parse[1] - } else { - printing.DalLog("ERROR", "HTTP Raw Request Format Error", options) - os.Exit(1) - } - } else { - if strings.Index(line, "Host: ") != -1 { - host = line[6:] - } else { - parse := strings.Split(line, ":") - if len(parse) > 1 { - options.Header = append(options.Header, line) - } - } - if bodyswitch { - body = body + line - } - if len(line) == 0 { - bodyswitch = true - } - } - } - options.Data = body - http, _ := cmd.Flags().GetBool("http") - if strings.Index(path, "http") == 0 { - target = path - } else { - if host == "" { - printing.DalLog("ERROR", "HTTP Raw Request Format Error - Not found Host", options) - os.Exit(1) - } - if http { - target = "http://" + host + path - } else { - target = "https://" + host + path - } - } - _, _ = scanning.Scan(target, options, "single") + Run: runFileCmd, +} - } else if har { - printing.DalLog("SYSTEM", "Using file mode(targets list from HAR)", options) - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject = spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(os.Stderr)) // Build our new spinner - } - var harObject voltHar.HARObject - harFile, err := os.ReadFile(args[0]) - if err == nil { - err = json.Unmarshal(harFile, &harObject) - if options.Format == "json" { - printing.DalLog("PRINT", "[", options) - } - for i, entry := range harObject.Log.Entries { - var turl string - options.NowURL = i + 1 - if len(entry.Request.QueryString) > 0 { - var tquery string - for _, query := range entry.Request.QueryString { - tquery = tquery + query.Name + "=" + query.Value + "&" - } - turl = entry.Request.URL + "?" + tquery - } else { - turl = entry.Request.URL - } - if entry.Request.PostData.Text != "" { - options.Data = entry.Request.PostData.Text - } - options.Method = entry.Request.Method - _, _ = scanning.Scan(turl, options, strconv.Itoa(i)) - if (!options.NoSpinner || !options.Silence) && !sf { - mutex.Lock() - options.NowURL = options.NowURL + 1 - percent := fmt.Sprintf("%0.2f%%", float64(options.NowURL)/float64(options.AllURLS)*100) - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(options.AllURLS) + " Tasks][" + percent + "] Multiple scanning from file" - mutex.Unlock() - } - } - if options.Format == "json" { - printing.DalLog("PRINT", "{}]", options) - } - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Stop() - } - } +func runFileCmd(cmd *cobra.Command, args []string) { + sf, _ := cmd.Flags().GetBool("silence-force") + if sf { + options.Silence = sf + } + printing.Banner(options) + tMethod := options.Method + options.Method = "FILE Mode" + if len(args) == 0 { + printFileErrorAndUsage() + return + } + printing.Summary(options, args[0]) + options.Method = tMethod + mutex := &sync.Mutex{} + options.Mutex = mutex + if len(args) >= 1 { + rawdata, _ := cmd.Flags().GetBool("rawdata") + har, _ := cmd.Flags().GetBool("har") + if rawdata { + runRawDataMode(args[0], cmd) + } else if har { + runHarMode(args[0], cmd, sf) + } else { + runFileMode(args[0], cmd, sf) + } + } else { + printFileErrorAndUsage() + } +} +func runRawDataMode(filePath string, cmd *cobra.Command) { + printing.DalLog("SYSTEM", "Using file mode(rawdata)", options) + ff, err := voltFile.ReadLinesOrLiteral(filePath) + if err != nil { + printing.DalLog("ERROR", "Failed to read file: "+err.Error(), options) + return + } + var path, body, host, target string + bodyswitch := false + for index, line := range ff { + if index == 0 { + parse := strings.Split(line, " ") + if len(parse) > 1 { + options.Method = parse[0] + path = parse[1] } else { - printing.DalLog("SYSTEM", "Using file mode(targets list)", options) - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject = spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(os.Stderr)) // Build our new spinner + printing.DalLog("ERROR", "HTTP Raw Request Format Error", options) + os.Exit(1) + } + } else { + if strings.Index(line, "Host: ") != -1 { + host = line[6:] + } else { + parse := strings.Split(line, ":") + if len(parse) > 1 { + options.Header = append(options.Header, line) } + } + if bodyswitch { + body = body + line + } + if len(line) == 0 { + bodyswitch = true + } + } + } + options.Data = body + http, _ := cmd.Flags().GetBool("http") + if strings.Index(path, "http") == 0 { + target = path + } else { + if host == "" { + printing.DalLog("ERROR", "HTTP Raw Request Format Error - Not found Host", options) + os.Exit(1) + } + if http { + target = "http://" + host + path + } else { + target = "https://" + host + path + } + } + _, _ = scanning.Scan(target, options, "single") +} - ff, err := voltFile.ReadLinesOrLiteral(args[0]) - _ = err - targets = append(targets, ff...) - - // Remove Deplicated value - targets = voltUtils.UniqueStringSlice(targets) - printing.DalLog("SYSTEM", "Loaded "+strconv.Itoa(len(targets))+" target urls", options) - multi, _ := cmd.Flags().GetBool("multicast") - mass, _ := cmd.Flags().GetBool("mass") - if multi || mass { - printing.DalLog("SYSTEM", "Using multicasting mode", options) - options.Silence = true - options.MulticastMode = true - t := scanning.MakeTargetSlice(targets) - tt := 0 - //mutex := &sync.Mutex{} - - for k, v := range t { - _ = k - tt = tt + len(v) - } - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Prefix = " " - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(tt) + " Tasks][0%] Parallel scanning from file" - if !options.NoColor { - options.SpinnerObject.Color("red", "bold") - } - options.SpinnerObject.Start() - } - var wg sync.WaitGroup - tasks := make(chan model.MassJob) - options.NowURL = 0 - concurrency, _ := cmd.Flags().GetInt("mass-worker") - for k, v := range t { - if !options.Silence || !sf { - printing.DalLog("SYSTEM-M", "Parallel testing to '"+k+"' => "+strconv.Itoa(len(v))+" urls", options) - } - } - for task := 0; task < concurrency; task++ { - wg.Add(1) - go func() { - defer wg.Done() - for kv := range tasks { - v := kv.URLs - for i := range v { - _, _ = scanning.Scan(v[i], options, strconv.Itoa(len(v))) - if (!options.NoSpinner || !options.Silence) && !sf { - mutex.Lock() - options.NowURL = options.NowURL + 1 - percent := fmt.Sprintf("%0.2f%%", float64(options.NowURL)/float64(tt)*100) - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(tt) + " Tasks][" + percent + "] Parallel scanning from file" - mutex.Unlock() - } - } - } - }() - } - if options.Format == "json" { - printing.DalLog("PRINT", "[", options) - } - for k, v := range t { - temp := model.MassJob{ - Name: k, - URLs: v, - } - tasks <- temp - } - close(tasks) - wg.Wait() - if options.Format == "json" { - printing.DalLog("PRINT", "{}]", options) - } - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Stop() - } - if !options.Silence || !sf { - printing.DalLog("SYSTEM-M", "Finish massive scan!", options) - } - } else { - options.AllURLS = len(targets) - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Prefix = " " - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(options.AllURLS) + " Tasks][0%] Multiple scanning from file" - if !options.NoColor { - options.SpinnerObject.Color("red", "bold") - } - options.SpinnerObject.Start() - } - if options.Format == "json" { - printing.DalLog("PRINT", "[", options) - } - for i := range targets { - options.NowURL = i + 1 - _, _ = scanning.Scan(targets[i], options, strconv.Itoa(i)) - if (!options.NoSpinner || !options.Silence) && !sf { - mutex.Lock() - options.NowURL = options.NowURL + 1 - percent := fmt.Sprintf("%0.2f%%", float64(options.NowURL)/float64(options.AllURLS)*100) - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(options.AllURLS) + " Tasks][" + percent + "] Multiple scanning from file" - mutex.Unlock() - } - } - if options.Format == "json" { - printing.DalLog("PRINT", "{}]", options) - } - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Stop() - } +func runHarMode(filePath string, cmd *cobra.Command, sf bool) { + printing.DalLog("SYSTEM", "Using file mode(targets list from HAR)", options) + if (!options.NoSpinner || !options.Silence) && !sf { + options.SpinnerObject = spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(os.Stderr)) // Build our new spinner + } + var harObject voltHar.HARObject + harFile, err := os.ReadFile(filePath) + if err == nil { + err = json.Unmarshal(harFile, &harObject) + if options.Format == "json" { + printing.DalLog("PRINT", "[", options) + } + for i, entry := range harObject.Log.Entries { + var turl string + options.NowURL = i + 1 + if len(entry.Request.QueryString) > 0 { + var tquery string + for _, query := range entry.Request.QueryString { + tquery = tquery + query.Name + "=" + query.Value + "&" } + turl = entry.Request.URL + "?" + tquery + } else { + turl = entry.Request.URL } - } else { - printing.DalLog("ERROR", "Input file path", options) - printing.DalLog("ERROR", "e.g dalfox file ./targets.txt or ./rawdata.raw", options) + if entry.Request.PostData.Text != "" { + options.Data = entry.Request.PostData.Text + } + options.Method = entry.Request.Method + _, _ = scanning.Scan(turl, options, strconv.Itoa(i)) + updateSpinner(options, sf, i, len(harObject.Log.Entries)) } - }, + if options.Format == "json" { + printing.DalLog("PRINT", "{}]", options) + } + if (!options.NoSpinner || !options.Silence) && !sf { + options.SpinnerObject.Stop() + } + } +} + +func runFileMode(filePath string, cmd *cobra.Command, sf bool) { + printing.DalLog("SYSTEM", "Using file mode(targets list)", options) + if (!options.NoSpinner || !options.Silence) && !sf { + options.SpinnerObject = spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(os.Stderr)) // Build our new spinner + } + + ff, err := voltFile.ReadLinesOrLiteral(filePath) + if err != nil { + printing.DalLog("ERROR", "Failed to read file: "+err.Error(), options) + return + } + targets := voltUtils.UniqueStringSlice(ff) + printing.DalLog("SYSTEM", "Loaded "+strconv.Itoa(len(targets))+" target urls", options) + multi, _ := cmd.Flags().GetBool("multicast") + mass, _ := cmd.Flags().GetBool("mass") + if multi || mass { + runMulticastMode(targets, cmd, sf) + } else { + runSingleMode(targets, sf) + } +} + +func updateSpinner(options model.Options, sf bool, current, total int) { + if (!options.NoSpinner || !options.Silence) && !sf { + options.Mutex.Lock() + options.NowURL++ + percent := fmt.Sprintf("%0.2f%%", float64(options.NowURL)/float64(total)*100) + options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(total) + " Tasks][" + percent + "] Parallel scanning from file" + options.Mutex.Unlock() + } +} + +func printFileErrorAndUsage() { + printing.DalLog("ERROR", "Input target file", options) + printing.DalLog("ERROR", "e.g dalfox file ./targets.txt or ./rawdata.raw", options) } func init() { diff --git a/cmd/payload.go b/cmd/payload.go index fe974f43..bace28ea 100644 --- a/cmd/payload.go +++ b/cmd/payload.go @@ -27,37 +27,42 @@ type Object struct { var payloadCmd = &cobra.Command{ Use: "payload", Short: "Payload mode, make and enum payloads", - Run: func(cmd *cobra.Command, args []string) { - printing.Banner(options) - var objects []Object - objects = append(objects, Object{Use: makeBulk, Name: "Bulk-XSS", Listener: generating.GenerateBulkPayload}) - objects = append(objects, Object{Use: enumCommon, Name: "Enum-Common-XSS", Listener: scanning.GetCommonPayload}) - objects = append(objects, Object{Use: enumHTML, Name: "Enum-HTML-XSS", Listener: scanning.GetHTMLPayload}) - objects = append(objects, Object{Use: enumAttr, Name: "Enum-Attribute-XSS", Listener: scanning.GetAttrPayload}) - objects = append(objects, Object{Use: enumInJS, Name: "Enum-inJS-XSS", Listener: scanning.GetInJsPayload}) - objects = append(objects, Object{Use: remotePayloadbox, Name: "Remote-Payloadbox-Payloads", Listener: scanning.GetPayloadBoxPayload}) - objects = append(objects, Object{Use: remotePortswigger, Name: "Remote-Portswigger-Paylaods", Listener: scanning.GetPortswiggerPayload}) - objects = append(objects, Object{Use: entityGF, Name: "Entity-GF-Patterns", Listener: scanning.InterfaceGetGfXSS}) - objects = append(objects, Object{Use: entityEventHandler, Name: "Entity-Event-Handlers", Listener: scanning.InterfaceGetEventHandlers}) - objects = append(objects, Object{Use: entityUsefulTags, Name: "Entity-Useful-Tags", Listener: scanning.InterfaceGetTags}) - objects = append(objects, Object{Use: entitySpecialChars, Name: "Entity-Special-Chars", Listener: scanning.InterfaceGetSpecialChar}) + Run: runPayloadCmd, +} - for _, object := range objects { - if object.Use { - lst, s := object.Listener() - printing.DalLog("INFO", "["+object.Name+"][Line: "+strconv.Itoa(s)+"]", options) - plst := optimization.SetPayloadValue(lst, options) - for i, v := range plst { - _ = i - if urlEncode { - printing.DalLog("YELLOW", optimization.UrlEncode(v), options) - } else { - printing.DalLog("YELLOW", v, options) - } +func runPayloadCmd(cmd *cobra.Command, args []string) { + printing.Banner(options) + objects := initializeObjects() + for _, object := range objects { + if object.Use { + lst, s := object.Listener() + printing.DalLog("INFO", "["+object.Name+"][Line: "+strconv.Itoa(s)+"]", options) + plst := optimization.SetPayloadValue(lst, options) + for _, v := range plst { + if urlEncode { + printing.DalLog("YELLOW", optimization.UrlEncode(v), options) + } else { + printing.DalLog("YELLOW", v, options) } } } - }, + } +} + +func initializeObjects() []Object { + return []Object{ + {Use: makeBulk, Name: "Bulk-XSS", Listener: generating.GenerateBulkPayload}, + {Use: enumCommon, Name: "Enum-Common-XSS", Listener: scanning.GetCommonPayload}, + {Use: enumHTML, Name: "Enum-HTML-XSS", Listener: scanning.GetHTMLPayload}, + {Use: enumAttr, Name: "Enum-Attribute-XSS", Listener: scanning.GetAttrPayload}, + {Use: enumInJS, Name: "Enum-inJS-XSS", Listener: scanning.GetInJsPayload}, + {Use: remotePayloadbox, Name: "Remote-Payloadbox-Payloads", Listener: scanning.GetPayloadBoxPayload}, + {Use: remotePortswigger, Name: "Remote-Portswigger-Paylaods", Listener: scanning.GetPortswiggerPayload}, + {Use: entityGF, Name: "Entity-GF-Patterns", Listener: scanning.InterfaceGetGfXSS}, + {Use: entityEventHandler, Name: "Entity-Event-Handlers", Listener: scanning.InterfaceGetEventHandlers}, + {Use: entityUsefulTags, Name: "Entity-Useful-Tags", Listener: scanning.InterfaceGetTags}, + {Use: entitySpecialChars, Name: "Entity-Special-Chars", Listener: scanning.InterfaceGetSpecialChar}, + } } func init() { @@ -73,6 +78,5 @@ func init() { payloadCmd.Flags().BoolVar(&entityEventHandler, "entity-event-handler", false, "Enumerate a event handlers for xss") payloadCmd.Flags().BoolVar(&entityUsefulTags, "entity-useful-tags", false, "Enumerate a useful tags for xss") payloadCmd.Flags().BoolVar(&entitySpecialChars, "entity-special-chars", false, "Enumerate a special chars for xss") - payloadCmd.Flags().BoolVar(&urlEncode, "encoder-url", false, "Encoding output [URL]") } diff --git a/cmd/pipe.go b/cmd/pipe.go index aa6e974c..c6e5f236 100644 --- a/cmd/pipe.go +++ b/cmd/pipe.go @@ -20,132 +20,141 @@ import ( var pipeCmd = &cobra.Command{ Use: "pipe [flags]", Short: "Use pipeline mode", - Run: func(cmd *cobra.Command, args []string) { - sf, _ := cmd.Flags().GetBool("silence-force") - if sf { - options.Silence = sf - } - printing.Banner(options) - tMethod := options.Method - options.Method = "Pipe Mode" - printing.Summary(options, "Stdin (pipeline)") - options.Method = tMethod - var targets []string - mutex := &sync.Mutex{} - options.Mutex = mutex - sc := bufio.NewScanner(os.Stdin) - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject = spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(os.Stderr)) // Build our new spinner + Run: runPipeCmd, +} + +func runPipeCmd(cmd *cobra.Command, args []string) { + sf, _ := cmd.Flags().GetBool("silence-force") + if sf { + options.Silence = sf + } + printing.Banner(options) + tMethod := options.Method + options.Method = "Pipe Mode" + printing.Summary(options, "Stdin (pipeline)") + options.Method = tMethod + var targets []string + mutex := &sync.Mutex{} + options.Mutex = mutex + sc := bufio.NewScanner(os.Stdin) + if (!options.NoSpinner || !options.Silence) && !sf { + options.SpinnerObject = spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(os.Stderr)) // Build our new spinner + } + printing.DalLog("SYSTEM", "Using pipeline mode", options) + for sc.Scan() { + target := sc.Text() + targets = append(targets, target) + } + targets = voltUtils.UniqueStringSlice(targets) + printing.DalLog("SYSTEM", "Loaded "+strconv.Itoa(len(targets))+" target urls", options) + + multi, _ := cmd.Flags().GetBool("multicast") + mass, _ := cmd.Flags().GetBool("mass") + if multi || mass { + runMulticastMode(targets, cmd, sf) + } else { + runSingleMode(targets, sf) + } +} + +func runMulticastMode(targets []string, cmd *cobra.Command, sf bool) { + printing.DalLog("SYSTEM", "Using multicasting mode", options) + options.Silence = true + options.MulticastMode = true + t := scanning.MakeTargetSlice(targets) + tt := 0 + for _, v := range t { + tt += len(v) + } + if (!options.NoSpinner || !options.Silence) && !sf { + options.SpinnerObject.Prefix = " " + options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(tt) + " Tasks][0%] Parallel scanning from pipe" + if !options.NoColor { + options.SpinnerObject.Color("red", "bold") } - printing.DalLog("SYSTEM", "Using pipeline mode", options) - for sc.Scan() { - target := sc.Text() - targets = append(targets, target) + options.SpinnerObject.Start() + } + var wg sync.WaitGroup + tasks := make(chan model.MassJob) + options.NowURL = 0 + concurrency, _ := cmd.Flags().GetInt("mass-worker") + for k, v := range t { + if !options.Silence || !sf { + printing.DalLog("SYSTEM-M", "Parallel testing to '"+k+"' => "+strconv.Itoa(len(v))+" urls", options) } - targets = voltUtils.UniqueStringSlice(targets) - printing.DalLog("SYSTEM", "Loaded "+strconv.Itoa(len(targets))+" target urls", options) - - multi, _ := cmd.Flags().GetBool("multicast") - mass, _ := cmd.Flags().GetBool("mass") - if multi || mass { - printing.DalLog("SYSTEM", "Using multicasting mode", options) - options.Silence = true - options.MulticastMode = true - t := scanning.MakeTargetSlice(targets) - tt := 0 - for k, v := range t { - _ = k - tt = tt + len(v) - } - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Prefix = " " - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(tt) + " Tasks][0%] Parallel scanning from pipe" - if !options.NoColor { - options.SpinnerObject.Color("red", "bold") - } - options.SpinnerObject.Start() - } - var wg sync.WaitGroup - tasks := make(chan model.MassJob) - options.NowURL = 0 - concurrency, _ := cmd.Flags().GetInt("mass-worker") - for k, v := range t { - if !options.Silence || !sf { - printing.DalLog("SYSTEM-M", "Parallel testing to '"+k+"' => "+strconv.Itoa(len(v))+" urls", options) - } - } - for task := 0; task < concurrency; task++ { - wg.Add(1) - go func() { - defer wg.Done() - for kv := range tasks { - v := kv.URLs - for i := range v { - _, _ = scanning.Scan(v[i], options, strconv.Itoa(len(v))) - if (!options.NoSpinner || !options.Silence) && !sf { - options.Mutex.Lock() - options.NowURL = options.NowURL + 1 - percent := fmt.Sprintf("%0.2f%%", float64(options.NowURL)/float64(tt)*100) - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(tt) + " Tasks][" + percent + "] Parallel scanning from pipe" - options.Mutex.Unlock() - } - } + } + for task := 0; task < concurrency; task++ { + wg.Add(1) + go func() { + defer wg.Done() + for kv := range tasks { + v := kv.URLs + for i := range v { + _, _ = scanning.Scan(v[i], options, strconv.Itoa(len(v))) + if (!options.NoSpinner || !options.Silence) && !sf { + options.Mutex.Lock() + options.NowURL++ + percent := fmt.Sprintf("%0.2f%%", float64(options.NowURL)/float64(tt)*100) + options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(tt) + " Tasks][" + percent + "] Parallel scanning from pipe" + options.Mutex.Unlock() } - }() - } - if options.Format == "json" { - printing.DalLog("PRINT", "[", options) - } - for k, v := range t { - temp := model.MassJob{ - Name: k, - URLs: v, } - tasks <- temp - } - close(tasks) - wg.Wait() - if options.Format == "json" { - printing.DalLog("PRINT", "{}]", options) - } - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Stop() - } - if !options.Silence || !sf { - printing.DalLog("SYSTEM-M", "Finish massive scan!", options) - } - } else { - options.AllURLS = len(targets) - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Prefix = " " - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(options.AllURLS) + " Tasks][0%] Multiple scanning from pipe" - if !options.NoColor { - options.SpinnerObject.Color("red", "bold") - } - options.SpinnerObject.Start() - } - if options.Format == "json" { - printing.DalLog("PRINT", "[", options) - } - for i := range targets { - options.NowURL = i + 1 - _, _ = scanning.Scan(targets[i], options, strconv.Itoa(i)) - if (!options.NoSpinner || !options.Silence) && !sf { - mutex.Lock() - options.NowURL = options.NowURL + 1 - percent := fmt.Sprintf("%0.2f%%", float64(options.NowURL)/float64(options.AllURLS)*100) - options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(options.AllURLS) + " Tasks][" + percent + "] Multiple scanning from pipe" - mutex.Unlock() - } - } - if options.Format == "json" { - printing.DalLog("PRINT", "{}]", options) - } - if (!options.NoSpinner || !options.Silence) && !sf { - options.SpinnerObject.Stop() } + }() + } + if options.Format == "json" { + printing.DalLog("PRINT", "[", options) + } + for k, v := range t { + temp := model.MassJob{ + Name: k, + URLs: v, + } + tasks <- temp + } + close(tasks) + wg.Wait() + if options.Format == "json" { + printing.DalLog("PRINT", "{}]", options) + } + if (!options.NoSpinner || !options.Silence) && !sf { + options.SpinnerObject.Stop() + } + if !options.Silence || !sf { + printing.DalLog("SYSTEM-M", "Finish massive scan!", options) + } +} + +func runSingleMode(targets []string, sf bool) { + options.AllURLS = len(targets) + if (!options.NoSpinner || !options.Silence) && !sf { + options.SpinnerObject.Prefix = " " + options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(options.AllURLS) + " Tasks][0%] Multiple scanning from pipe" + if !options.NoColor { + options.SpinnerObject.Color("red", "bold") + } + options.SpinnerObject.Start() + } + if options.Format == "json" { + printing.DalLog("PRINT", "[", options) + } + for i := range targets { + options.NowURL = i + 1 + _, _ = scanning.Scan(targets[i], options, strconv.Itoa(i)) + if (!options.NoSpinner || !options.Silence) && !sf { + options.Mutex.Lock() + options.NowURL++ + percent := fmt.Sprintf("%0.2f%%", float64(options.NowURL)/float64(options.AllURLS)*100) + options.SpinnerObject.Suffix = " [" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(options.AllURLS) + " Tasks][" + percent + "] Multiple scanning from pipe" + options.Mutex.Unlock() } - }, + } + if options.Format == "json" { + printing.DalLog("PRINT", "{}]", options) + } + if (!options.NoSpinner || !options.Silence) && !sf { + options.SpinnerObject.Stop() + } } func init() { diff --git a/cmd/root.go b/cmd/root.go index e60a730e..ff323a91 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,7 +8,6 @@ import ( "time" "github.com/hahwul/dalfox/v2/pkg/har" - "github.com/hahwul/dalfox/v2/pkg/model" "github.com/hahwul/dalfox/v2/pkg/printing" "github.com/logrusorgru/aurora" @@ -172,24 +171,14 @@ func initConfig() { OutputResponse: outputResponse, UseBAV: useBAV, } - // var skipMiningDom, skipMiningDict, skipMiningAll, skipXSSScan, skipBAV bool if harFilePath != "" { - f, err := os.Create(harFilePath) - if err != nil { - fmt.Println(err) - } else { - options.HarWriter, err = har.NewWriter(f, &har.Creator{Name: "dalfox", Version: printing.VERSION}) - if err != nil { - fmt.Println(err) - } - } + initHarWriter() } if skipMiningAll { options.FindingDOM = false options.Mining = false - } else { if skipMiningDom { options.FindingDOM = false @@ -204,34 +193,42 @@ func initConfig() { } if grep != "" { - // Open our jsonFile - jsonFile, err := os.Open(grep) - // if we os.Open returns an error then handle it - if err != nil { - fmt.Println(err) - } - printing.DalLog("SYSTEM", "Loaded "+grep+" file for grepping", options) - // defer the closing of our jsonFile so that we can parse it later on - defer jsonFile.Close() - byteValue, _ := io.ReadAll(jsonFile) - options.Grep = string(byteValue) - + loadFile(grep, "grepping") } + if config != "" { - // Open our jsonFile - jsonFile, err := os.Open(config) - // if we os.Open returns an error then handle it + loadFile(config, "config option") + } +} + +func initHarWriter() { + f, err := os.Create(harFilePath) + if err != nil { + fmt.Println(err) + } else { + options.HarWriter, err = har.NewWriter(f, &har.Creator{Name: "dalfox", Version: printing.VERSION}) if err != nil { fmt.Println(err) } - printing.DalLog("SYSTEM", "Loaded "+config+" file for config option", options) - // defer the closing of our jsonFile so that we can parse it later on - defer jsonFile.Close() + } +} - byteValue, _ := io.ReadAll(jsonFile) - err = json.Unmarshal([]byte(byteValue), &options) +func loadFile(filePath, fileType string) { + jsonFile, err := os.Open(filePath) + if err != nil { + fmt.Println(err) + return + } + printing.DalLog("SYSTEM", "Loaded "+filePath+" file for "+fileType, options) + defer jsonFile.Close() + + byteValue, _ := io.ReadAll(jsonFile) + if fileType == "config option" { + err = json.Unmarshal(byteValue, &options) if err != nil { printing.DalLog("SYSTEM", "Error while parsing config file", options) } + } else { + options.Grep = string(byteValue) } } diff --git a/cmd/server.go b/cmd/server.go index db12ca4a..e9097ad0 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,8 +1,8 @@ package cmd import ( - printing "github.com/hahwul/dalfox/v2/pkg/printing" - server "github.com/hahwul/dalfox/v2/pkg/server" + "github.com/hahwul/dalfox/v2/pkg/printing" + "github.com/hahwul/dalfox/v2/pkg/server" "github.com/spf13/cobra" ) @@ -13,14 +13,16 @@ var host string var serverCmd = &cobra.Command{ Use: "server", Short: "Start API Server", - Run: func(cmd *cobra.Command, args []string) { - printing.Banner(options) - printing.DalLog("SYSTEM", "Starting API Server", options) - options.ServerHost = host - options.ServerPort = port - printing.Summary(options, "REST API Mode") - server.RunAPIServer(options) - }, + Run: runServerCmd, +} + +func runServerCmd(cmd *cobra.Command, args []string) { + printing.Banner(options) + printing.DalLog("SYSTEM", "Starting API Server", options) + options.ServerHost = host + options.ServerPort = port + printing.Summary(options, "REST API Mode") + server.RunAPIServer(options) } func init() { diff --git a/cmd/sxss.go b/cmd/sxss.go index 3bf08d65..b48fee42 100644 --- a/cmd/sxss.go +++ b/cmd/sxss.go @@ -13,39 +13,42 @@ var sequence int var sxssCmd = &cobra.Command{ Use: "sxss [target] [flags]", Short: "Use Stored XSS mode", - Run: func(cmd *cobra.Command, args []string) { - printing.Banner(options) - if len(args) == 0 { - printing.DalLog("ERROR", "Input target url", options) - printing.DalLog("ERROR", "e.g dalfox sxss https://google.com/?q=1 --trigger https://target/profile", options) - return + Run: runSxssCmd, +} + +func runSxssCmd(cmd *cobra.Command, args []string) { + printing.Banner(options) + if len(args) == 0 { + printSXSSErrorAndUsage() + return + } + + printing.Summary(options, args[0]) + options.Trigger = trigger + options.Sequence = sequence + options.TriggerMethod = requestMethod + options.Concurrence = 1 + if options.Delay <= 1500 { + options.Delay = 1500 + } + + if options.Trigger != "" { + printing.DalLog("SYSTEM", "Using Stored XSS mode", options) + if options.Format == "json" { + printing.DalLog("PRINT", "[", options) } - printing.Summary(options, args[0]) - if len(args) >= 1 { - options.Trigger = trigger - options.Sequence = sequence - options.TriggerMethod = requestMethod - options.Concurrence = 1 - if options.Delay <= 1500 { - options.Delay = 1500 - } - if options.Trigger != "" { - printing.DalLog("SYSTEM", "Using Stored XSS mode", options) - if options.Format == "json" { - printing.DalLog("PRINT", "[", options) - } - _, _ = scanning.Scan(args[0], options, "Single") - if options.Format == "json" { - printing.DalLog("PRINT", "{}]", options) - } - } else { - printing.DalLog("ERROR", "Please input trigger url with --trigger option", options) - } - } else { - printing.DalLog("ERROR", "Input target url", options) - printing.DalLog("ERROR", "e.g dalfox sxss https://google.com/?q=1 --trigger https://target/profile", options) + _, _ = scanning.Scan(args[0], options, "Single") + if options.Format == "json" { + printing.DalLog("PRINT", "{}]", options) } - }, + } else { + printing.DalLog("ERROR", "Please input trigger url with --trigger option", options) + } +} + +func printSXSSErrorAndUsage() { + printing.DalLog("ERROR", "Input target url", options) + printing.DalLog("ERROR", "e.g dalfox sxss https://google.com/?q=1 --trigger https://target/profile", options) } func init() { diff --git a/cmd/url.go b/cmd/url.go index 911f2231..50aa5aa4 100644 --- a/cmd/url.go +++ b/cmd/url.go @@ -10,28 +10,30 @@ import ( var urlCmd = &cobra.Command{ Use: "url [target] [flags]", Short: "Use single target mode", - Run: func(cmd *cobra.Command, args []string) { - printing.Banner(options) - if len(args) == 0 { - printing.DalLog("ERROR", "Input target url", options) - printing.DalLog("ERROR", "e.g dalfox url https://google.com/?q=1", options) - return - } - printing.Summary(options, args[0]) - if len(args) >= 1 { - printing.DalLog("SYSTEM", "Using single target mode", options) - if options.Format == "json" { - printing.DalLog("PRINT", "[", options) - } - _, _ = scanning.Scan(args[0], options, "Single") - if options.Format == "json" { - printing.DalLog("PRINT", "{}]", options) - } - } else { - printing.DalLog("ERROR", "Input target url", options) - printing.DalLog("ERROR", "e.g dalfox url https://google.com/?q=1", options) - } - }, + Run: runURLCmd, +} + +func runURLCmd(cmd *cobra.Command, args []string) { + printing.Banner(options) + if len(args) == 0 { + printUrlErrorAndUsage() + return + } + + printing.Summary(options, args[0]) + printing.DalLog("SYSTEM", "Using single target mode", options) + if options.Format == "json" { + printing.DalLog("PRINT", "[", options) + } + _, _ = scanning.Scan(args[0], options, "Single") + if options.Format == "json" { + printing.DalLog("PRINT", "{}]", options) + } +} + +func printUrlErrorAndUsage() { + printing.DalLog("ERROR", "Input target url", options) + printing.DalLog("ERROR", "e.g dalfox url https://google.com/?q=1", options) } func init() { diff --git a/cmd/version.go b/cmd/version.go index 78ae62ea..7fa3eb0f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -17,14 +17,4 @@ var versionCmd = &cobra.Command{ func init() { rootCmd.AddCommand(versionCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // versionCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/pkg/generating/bulk.go b/pkg/generating/bulk.go index 72d7d860..f15de311 100644 --- a/pkg/generating/bulk.go +++ b/pkg/generating/bulk.go @@ -15,49 +15,44 @@ type objectPayload struct { func GenerateBulkPayload() ([]string, int) { var result []string size := 0 - var objs []objectPayload + var objs = []objectPayload{ + {Listener: scanning.GetPortswiggerPayload}, + {Listener: scanning.GetCommonPayload}, + {Listener: scanning.GetHTMLPayload}, + {Listener: scanning.GetInJsPayload}, + {Listener: scanning.GetAttrPayload}, + } seq := 0 - outSeq := 0 - _ = outSeq - objs = append(objs, objectPayload{Listener: scanning.GetPortswiggerPayload}) - objs = append(objs, objectPayload{Listener: scanning.GetCommonPayload}) - objs = append(objs, objectPayload{Listener: scanning.GetHTMLPayload}) - objs = append(objs, objectPayload{Listener: scanning.GetInJsPayload}) - objs = append(objs, objectPayload{Listener: scanning.GetAttrPayload}) + for _, obj := range objs { lst, _ := obj.Listener() nlst, outSeq := setPayloadVauleForBulk(lst, seq) seq = outSeq - for i, v := range nlst { - size = size + i - result = append(result, v) - } + result = append(result, nlst...) + size += len(nlst) } return result, size } -// setPayloadVauleForBulk is change alert/prompt/conrifm value using sequence +// setPayloadVauleForBulk is change alert/prompt/confirm value using sequence func setPayloadVauleForBulk(payloads []string, inSeq int) ([]string, int) { var result []string seq := inSeq + replacements := []string{ + "alert(1)", "alert(document.domain)", "\\u0061lert(1)", "\\u{61}lert(1)", "\\u{0000000061}lert(1)", + "1lert(1)", "alert()", "\\/@PortSwiggerRes\\/", "throw 1", "alert`1`", "alert,1", "alert\\x281", + } + for _, payload := range payloads { - temp := strings.ReplaceAll(payload, "alert(1)", "alert(DALFOX_ALERT_VALUE)") - temp = strings.ReplaceAll(temp, "alert(document.domain)", "alert(DALFOX_ALERT_VALUE)") - temp = strings.ReplaceAll(temp, "\\u0061lert(1)", "\\u0061lert(DALFOX_ALERT_VALUE)") - temp = strings.ReplaceAll(temp, "\\u{61}lert(1)", "\\u{61}lert(DALFOX_ALERT_VALUE)") - temp = strings.ReplaceAll(temp, "\\u{0000000061}lert(1)", "\\u{0000000061}lert(DALFOX_ALERT_VALUE)") - temp = strings.ReplaceAll(temp, "1lert(1)", "1lert(DALFOX_ALERT_VALUE)") - temp = strings.ReplaceAll(temp, "alert()", "alert(DALFOX_ALERT_VALUE)") - temp = strings.ReplaceAll(temp, "\\/@PortSwiggerRes\\/", "\\/DALFOX_ALERT_VALUE\\/") - temp = strings.ReplaceAll(temp, "throw 1", "throw DALFOX_ALERT_VALUE") - temp = strings.ReplaceAll(temp, "alert`1`", "alert`DALFOX_ALERT_VALUE`") - temp = strings.ReplaceAll(temp, "alert,1", "alert,DALFOX_ALERT_VALUE") - temp = strings.ReplaceAll(temp, "alert\\x281", "alert\\x28DALFOX_ALERT_VALUE") + temp := payload + for _, r := range replacements { + temp = strings.ReplaceAll(temp, r, "alert(DALFOX_ALERT_VALUE)") + } if strings.Contains(temp, "DALFOX_ALERT_VALUE") { tmp := strings.ReplaceAll(temp, "DALFOX_ALERT_VALUE", strconv.Itoa(seq)) result = append(result, tmp) - seq = seq + 1 + seq++ } } return result, seq diff --git a/pkg/optimization/inspectionParam.go b/pkg/optimization/inspectionParam.go index 696aabe1..3bac4ea9 100644 --- a/pkg/optimization/inspectionParam.go +++ b/pkg/optimization/inspectionParam.go @@ -6,20 +6,25 @@ import ( // CheckInspectionParam is Checking Inspection func CheckInspectionParam(options model.Options, k string) bool { - if len(options.UniqParam) > 0 { - for _, selectedParam := range options.UniqParam { - if selectedParam == k { - return true - } - } - return false + uniqParams := make(map[string]struct{}, len(options.UniqParam)) + for _, param := range options.UniqParam { + uniqParams[param] = struct{}{} } - if len(options.IgnoreParams) > 0 { - for _, ignoreParam := range options.IgnoreParams { - if ignoreParam == k { - return false - } - } + + ignoreParams := make(map[string]struct{}, len(options.IgnoreParams)) + for _, param := range options.IgnoreParams { + ignoreParams[param] = struct{}{} + } + + if len(uniqParams) > 0 { + _, exists := uniqParams[k] + return exists } + + if len(ignoreParams) > 0 { + _, exists := ignoreParams[k] + return !exists + } + return true } diff --git a/pkg/optimization/replace.go b/pkg/optimization/replace.go index 16a6e235..dee2612b 100644 --- a/pkg/optimization/replace.go +++ b/pkg/optimization/replace.go @@ -6,17 +6,19 @@ import ( model "github.com/hahwul/dalfox/v2/pkg/model" ) -// SetPayloadValue is change alert/prompt/conrifm value and type +// SetPayloadValue is change alert/prompt/confirm value and type func SetPayloadValue(payloads []string, options model.Options) []string { var result []string catype := strings.Split(options.CustomAlertType, ",") - for _,payload := range payloads { - for _,k := range catype { - if k == "none" { - temp := strings.ReplaceAll(payload, "DALFOX_ALERT_VALUE", options.CustomAlertValue) + for _, payload := range payloads { + for _, k := range catype { + var temp string + switch k { + case "none": + temp = strings.ReplaceAll(payload, "DALFOX_ALERT_VALUE", options.CustomAlertValue) result = append(result, temp) - } else if k == "str" { - temp := strings.ReplaceAll(payload, "DALFOX_ALERT_VALUE", "\""+options.CustomAlertValue+"\"") + case "str": + temp = strings.ReplaceAll(payload, "DALFOX_ALERT_VALUE", "\""+options.CustomAlertValue+"\"") result = append(result, temp) temp = strings.ReplaceAll(payload, "DALFOX_ALERT_VALUE", "'"+options.CustomAlertValue+"'") result = append(result, temp) diff --git a/pkg/report/report.go b/pkg/report/report.go index 51d22aae..77cced9e 100644 --- a/pkg/report/report.go +++ b/pkg/report/report.go @@ -16,6 +16,11 @@ func GenerateReport(scanResult model.Result, options model.Options) { fmt.Println("+ End: " + scanResult.EndTime.String()) fmt.Println("+ Duration: " + scanResult.Duration.String()) + renderTable(scanResult.Params, options) + renderPoCTable(scanResult.PoCs, options) +} + +func renderTable(params []model.ParamResult, options model.Options) { table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{ "Param", @@ -26,7 +31,7 @@ func GenerateReport(scanResult model.Result, options model.Options) { "Chars", }) - for _, v := range scanResult.Params { + for _, v := range params { chars := strings.Join(v.Chars, " ") reflected := "false" if v.Reflected { @@ -45,7 +50,9 @@ func GenerateReport(scanResult model.Result, options model.Options) { } fmt.Println(options.AuroraObject.BrightGreen("\n[ Parameter Analysis ]")) table.Render() +} +func renderPoCTable(pocs []model.PoC, options model.Options) { pocTable := tablewriter.NewWriter(os.Stdout) pocTable.SetHeader([]string{ "#", @@ -57,7 +64,7 @@ func GenerateReport(scanResult model.Result, options model.Options) { "CWE", }) - for i, v := range scanResult.PoCs { + for i, v := range pocs { line := []string{ "#" + strconv.Itoa(i), v.Type, @@ -71,7 +78,7 @@ func GenerateReport(scanResult model.Result, options model.Options) { } fmt.Println(options.AuroraObject.BrightGreen("\n[ XSS PoCs ]")) pocTable.Render() - for i, v := range scanResult.PoCs { + for i, v := range pocs { fmt.Printf("[#%s] %s\n", strconv.Itoa(i), v.Data) } } diff --git a/pkg/scanning/codeview.go b/pkg/scanning/codeview.go index adb3f2fc..3f2ea7f6 100644 --- a/pkg/scanning/codeview.go +++ b/pkg/scanning/codeview.go @@ -5,32 +5,35 @@ import ( "strings" ) -//CodeView is showing reflected code function +// CodeView is showing reflected code function func CodeView(resbody, pattern string) string { - var code string if resbody == "" { return "" } + + var builder strings.Builder bodyarr := strings.Split(resbody, "\n") + for bk, bv := range bodyarr { if strings.Contains(bv, pattern) { - max := len(bv) - if max > 80 { - index := strings.Index(bv, pattern) - if index < 20 { - code = code + strconv.Itoa(bk+1) + " line: " + bv[:80] + "\n " - } else { - if max < index+60 { - code = code + strconv.Itoa(bk+1) + " line: " + bv[index-20:max] + "\n " - } else { - code = code + strconv.Itoa(bk+1) + " line: " + bv[index-20:index+60] + "\n " - } - } - } else { - code = code + strconv.Itoa(bk+1) + " line: " + bv + "\n " + index := strings.Index(bv, pattern) + start := 0 + if index > 20 { + start = index - 20 + } + end := start + 80 + if end > len(bv) { + end = len(bv) } + + builder.WriteString(strconv.Itoa(bk + 1)) + builder.WriteString(" line: ") + builder.WriteString(bv[start:end]) + builder.WriteString("\n ") } } + + code := builder.String() if len(code) > 4 { return code[:len(code)-5] } diff --git a/pkg/scanning/csp.go b/pkg/scanning/csp.go index dfa03759..544ea773 100644 --- a/pkg/scanning/csp.go +++ b/pkg/scanning/csp.go @@ -6,156 +6,30 @@ import ( // checkCSP is bypass CSP for StaticAnalysis func checkCSP(policy string) string { - var result string - var arr []string - if strings.Contains(policy, ".doubleclick.net") { - arr = append(arr, ".doubleclick.net") - } - if strings.Contains(policy, ".googleadservices.com") { - arr = append(arr, ".googleadservices.com") - } - if strings.Contains(policy, "cse.google.com") { - arr = append(arr, "cse.google.com") - } - if strings.Contains(policy, "accounts.google.com") { - arr = append(arr, "accounts.google.com") - } - if strings.Contains(policy, "*.google.com") { - arr = append(arr, "*.google.com") - } - if strings.Contains(policy, "www.blogger.com") { - arr = append(arr, "www.blogger.com") - } - if strings.Contains(policy, "*.blogger.com") { - arr = append(arr, "*.blogger.com") - } - if strings.Contains(policy, "translate.yandex.net") { - arr = append(arr, "translate.yandex.net") - } - if strings.Contains(policy, "api-metrika.yandex.ru") { - arr = append(arr, "api-metrika.yandex.ru") - } - if strings.Contains(policy, "api.vk.comm") { - arr = append(arr, "api.vk.com") - } - if strings.Contains(policy, "*.vk.com") { - arr = append(arr, "*.vk.com") - } - if strings.Contains(policy, "*.yandex.ru") { - arr = append(arr, "*.yandex.ru") - } - if strings.Contains(policy, "*.yandex.net") { - arr = append(arr, "*.yandex.het") - } - if strings.Contains(policy, "app-sjint.marketo.com") { - arr = append(arr, "app-sjint.marketo.com") - } - if strings.Contains(policy, "app-e.marketo.com") { - arr = append(arr, "app-e.marketo.com") - } - if strings.Contains(policy, "*.marketo.com") { - arr = append(arr, "*.marketo.com") - } - if strings.Contains(policy, "detector.alicdn.com") { - arr = append(arr, "detector.alicdn.com") - } - if strings.Contains(policy, "suggest.taobao.com") { - arr = append(arr, "suggest.taobao.com") - } - if strings.Contains(policy, "ount.tbcdn.cn") { - arr = append(arr, "ount.tbcdn.cn") - } - if strings.Contains(policy, "bebezoo.1688.com") { - arr = append(arr, "bebezoo.1688.com") - } - if strings.Contains(policy, "wb.amap.com") { - arr = append(arr, "wb.amap.com") - } - if strings.Contains(policy, "a.sm.cn") { - arr = append(arr, "a.sm.cn") - } - if strings.Contains(policy, "api.m.sm.cn") { - arr = append(arr, "api.m.sm.cn") - } - if strings.Contains(policy, "*.alicdn.com") { - arr = append(arr, "*.alicdn.com") - } - if strings.Contains(policy, "*.taobao.com") { - arr = append(arr, "*.taobao.com") - } - if strings.Contains(policy, "*.tbcdn.cn") { - arr = append(arr, "*.tbcdn.cn") + domains := []string{ + ".doubleclick.net", ".googleadservices.com", "cse.google.com", "accounts.google.com", "*.google.com", + "www.blogger.com", "*.blogger.com", "translate.yandex.net", "api-metrika.yandex.ru", "api.vk.com", + "*.vk.com", "*.yandex.ru", "*.yandex.net", "app-sjint.marketo.com", "app-e.marketo.com", "*.marketo.com", + "detector.alicdn.com", "suggest.taobao.com", "ount.tbcdn.cn", "bebezoo.1688.com", "wb.amap.com", + "a.sm.cn", "api.m.sm.cn", "*.alicdn.com", "*.taobao.com", "*.tbcdn.cn", "*.1688.com", "*.amap.com", + "*.sm.cn", "mkto.uber.com", "*.uber.com", "ads.yap.yahoo.com", "mempf.yahoo.co.jp", "suggest-shop.yahooapis.jp", + "www.aol.com", "df-webservices.comet.aol.com", "api.cmi.aol.com", "ui.comet.aol.com", "portal.pf.aol.com", + "*.yahoo.com", "*.yahoo.jp", "*.yahooapis.jp", "*.aol.com", "search.twitter.com", "twitter.com", "*.twitter.com", + "ajax.googleapis.com", "*.googleapis.com", } - if strings.Contains(policy, "*.1688.com") { - arr = append(arr, "*.1688.com") - } - if strings.Contains(policy, "*.amap.com") { - arr = append(arr, "*.amap.com") - } - if strings.Contains(policy, "*.sm.cn") { - arr = append(arr, "*.sm.cn") - } - if strings.Contains(policy, "mkto.uber.com") { - arr = append(arr, "mkto.uber.com") - } - if strings.Contains(policy, "*.uber.com") { - arr = append(arr, "*.uber.com") - } - if strings.Contains(policy, "ads.yap.yahoo.com") { - arr = append(arr, "ads.yap.yahoo.com") - } - if strings.Contains(policy, "mempf.yahoo.co.jp") { - arr = append(arr, "mempf.yahoo.co.jp") - } - if strings.Contains(policy, "suggest-shop.yahooapis.jp") { - arr = append(arr, "suggest-shop.yahooapis.jp") - } - if strings.Contains(policy, "www.aol.com") { - arr = append(arr, "www.aol.com") - } - if strings.Contains(policy, "df-webservices.comet.aol.com") { - arr = append(arr, "df-webservices.comet.aol.com") - } - if strings.Contains(policy, "api.cmi.aol.com") { - arr = append(arr, "api.cmi.aol.com") - } - if strings.Contains(policy, "ui.comet.aol.com") { - arr = append(arr, "ui.comet.aol.com") - } - if strings.Contains(policy, "portal.pf.aol.com") { - arr = append(arr, "portal.pf.aol.com") - } - if strings.Contains(policy, "*.yahoo.com") { - arr = append(arr, "*.yahoo.com") - } - if strings.Contains(policy, "*.yahoo.jp") { - arr = append(arr, "*.yahoo.jp") - } - if strings.Contains(policy, "*.yahooapis.jp") { - arr = append(arr, "*.yahooapis.jp") - } - if strings.Contains(policy, "*.aol.com") { - arr = append(arr, "*.aol.com") - } - if strings.Contains(policy, "search.twitter.com") { - arr = append(arr, "search.twitter.com") - } - if strings.Contains(policy, "twitter.com") { - arr = append(arr, "twitter.com") - } - if strings.Contains(policy, "*.twitter.com") { - arr = append(arr, "*.twitter.com") - } - if strings.Contains(policy, "ajax.googleapis.com") { - arr = append(arr, "ajax.googleapis.com") - } - if strings.Contains(policy, "*.googleapis.com") { - arr = append(arr, "*googleapis.com") + + var arr []string + for _, domain := range domains { + if strings.Contains(policy, domain) { + arr = append(arr, domain) + } } + if len(arr) > 0 { - result = strings.Join(arr[:], " ") - result = result + "\n" + " Needs manual testing. please refer to it. https://t.co/lElLxtainw?amp=1" + result := strings.Join(arr, " ") + result += "\n Needs manual testing. please refer to it. https://t.co/lElLxtainw?amp=1" + return result } - // https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/master/XSS%20Injection/Intruders/jsonp_endpoint.txt - return result + + return "" } diff --git a/pkg/scanning/entity.go b/pkg/scanning/entity.go index 0dbce9fd..c4f653df 100644 --- a/pkg/scanning/entity.go +++ b/pkg/scanning/entity.go @@ -2,319 +2,99 @@ package scanning // InterfaceGetGfXSS is exported interface func InterfaceGetGfXSS() ([]string, int) { - lst := GetGfXSS() - return lst, len(lst) + return getListAndLength(GetGfXSS) } // InterfaceGetEventHandlers is exported interface func InterfaceGetEventHandlers() ([]string, int) { - lst := GetEventHandlers() - return lst, len(lst) + return getListAndLength(GetEventHandlers) } // InterfaceGetTags is exported interface func InterfaceGetTags() ([]string, int) { - lst := GetTags() - return lst, len(lst) + return getListAndLength(GetTags) } // InterfaceGetSpecialChar is exported interface func InterfaceGetSpecialChar() ([]string, int) { - lst := GetSpecialChar() - return lst, len(lst) + return getListAndLength(GetSpecialChar) } // InterfaceGetUsefulCode is exported interface func InterfaceGetUsefulCode() ([]string, int) { - lst := GetUsefulCode() + return getListAndLength(GetUsefulCode) +} + +// getListAndLength is a helper function to get list and its length +func getListAndLength(f func() []string) ([]string, int) { + lst := f() return lst, len(lst) } // GetGfXSS is get cool parameter name from Gf-Patterns func GetGfXSS() []string { - // https://github.com/1ndianl33t/Gf-Patterns/blob/master/xss.json - params := []string{ - "q", - "s", - "search", - "lang", - "keyword", - "query", - "page", - "keywords", - "year", - "view", - "email", - "type", - "cat", - "name", - "p", - "callback", - "jsonp", - "api_key", - "api", - "password", - "email", - "emailto", - "token", - "username", - "csrf_token", - "unsubscribe_token", - "id", - "item", - "page_id", - "month", - "immagine", - "list_type", - "url", - "terms", - "categoryid", - "key", - "l", - "begindate", - "enddate", - "go", - "goto", - "host", - "html", - "image_url", - "img_url", - "data", - "domain", - "dir", - "feed", - "file", - "file_name", - "file_url", - "rurl", - "show", - "window", - "return", - } - return params + return gfXSSParams } // GetEventHandlers is return event handlers (array type) // level: 1(none trigger) / 2(user interaction) / 3(direct trigger) func GetEventHandlers() []string { - handlers := []string{ - "onabort", - "onactivate", - "onafterprint", - "onafterscriptexecute", - "onafterupdate", - "onanimationcancel", - "onanimationstart", - "onauxclick", - "onbeforeactivate", - "onbeforecopy", - "onbeforecut", - "onbeforedeactivate", - "onbeforeeditfocus", - "onbeforepaste", - "onbeforeprint", - "onbeforescriptexecute", - "onbeforeunload", - "onbeforeupdate", - "onbegin", - "onblur", - "onbounce", - "oncanplay", - "oncanplaythrough", - "oncellchange", - "onchange", - "onclick", - "oncontextmenu", - "oncontrolselect", - "oncopy", - "oncut", - "oncuechange", - "ondataavailable", - "ondatasetchanged", - "ondatasetcomplete", - "ondurationchange", - "ondblclick", - "ondeactivate", - "ondrag", - "ondragdrop", - "ondragend", - "ondragenter", - "ondragleave", - "ondragover", - "ondragstart", - "ondrop", - "onend", - "onerror", - "onerrorupdate", - "onfilterchange", - "onfinish", - "onfocus", - "onfocusin", - "onfocusout", - "onhashchange", - "onhelp", - "oninput", - "oninvalid", - "onkeydown", - "onkeypress", - "onkeyup", - "onlayoutcomplete", - "onload", - "onloadend", - "onloadstart", - "onloadstart", - "onlosecapture", - "onmediacomplete", - "onmediaerror", - "onmessage", - "onmousedown", - "onmouseenter", - "onmouseleave", - "onmousemove", - "onmouseout", - "onmouseover", - "onmouseup", - "onmousewheel", - "onmove", - "onmoveend", - "onmovestart", - "onoffline", - "ononline", - "onoutofsync", - "onpageshow", - "onpaste", - "onpause", - "onplay", - "onplaying", - "onpointerdown", - "onpointerenter", - "onpointerleave", - "onpointermove", - "onpointerout", - "onpointerover", - "onpointerup", - "onpopstate", - "onprogress", - "onpropertychange", - "onreadystatechange", - "onredo", - "onrepeat", - "onreset", - "onresize", - "onresizeend", - "onresizestart", - "onresume", - "onreverse", - "onrowdelete", - "onrowexit", - "onrowinserted", - "onrowsenter", - "onrowsdelete", - "onrowsinserted", - "onscroll", - "onsearch", - "onseek", - "onselect", - "onselectionchange", - "onselectstart", - "onshow", - "onstart", - "onstop", - "onstorage", - "onsubmit", - "onsyncrestored", - "ontimeerror", - "ontimeupdate", - "ontoggle", - "ontouchend", - "ontouchmove", - "ontouchstart", - "ontrackchange", - "ontransitionstart", - "ontransitioncancel", - "ontransitionend", - "ontransitionrun", - "onundo", - "onunhandledrejection", - "onunload", - "onurlflip", - "onvolumechange", - "onwaiting", - "onwebkitanimationiteration", - "onwheel", - "whatthe=\"'onload", - "onpointerrawupdate", - "onpagehide", - } - return handlers + return eventHandlers } // GetTags is return tag list (array type) func GetTags() []string { - tags := []string{ - "script", - "iframe", - "svg", - "img", - "video", - "audio", - "meta", - "object", - "embed", - "style", - "frame", - "frameset", - "applet", - } return tags } // GetSpecialChar is return chars (array type) func GetSpecialChar() []string { - chars := []string{ - ">", - "<", - "\"", - "'", - "`", - ";", - "|", - "(", - ")", - "{", - "}", - "[", - "]", - ":", - ".", - ",", - "+", - "-", - "=", - "$", - "\\", - } - return chars + return specialChars } // GetUsefulCode is return code list (array type) func GetUsefulCode() []string { - codes := []string{ - "javascript:", - "JaVasCriPt:", - "jaVas%0dcRipt:", - "jaVas%0acRipt:", - "jaVas%09cRipt:", - "data:", - "alert(", - "alert`", - "prompt(", - "prompt`", - "confirm(", - "confirm`", - "document.location", - "document.cookie", - "window.location", - } - return codes + return usefulCodes +} + +var gfXSSParams = []string{ + "q", "s", "search", "lang", "keyword", "query", "page", "keywords", "year", "view", "email", "type", "cat", "name", + "p", "callback", "jsonp", "api_key", "api", "password", "email", "emailto", "token", "username", "csrf_token", + "unsubscribe_token", "id", "item", "page_id", "month", "immagine", "list_type", "url", "terms", "categoryid", "key", + "l", "begindate", "enddate", "go", "goto", "host", "html", "image_url", "img_url", "data", "domain", "dir", "feed", + "file", "file_name", "file_url", "rurl", "show", "window", "return", +} + +var eventHandlers = []string{ + "onabort", "onactivate", "onafterprint", "onafterscriptexecute", "onafterupdate", "onanimationcancel", "onanimationstart", + "onauxclick", "onbeforeactivate", "onbeforecopy", "onbeforecut", "onbeforedeactivate", "onbeforeeditfocus", "onbeforepaste", + "onbeforeprint", "onbeforescriptexecute", "onbeforeunload", "onbeforeupdate", "onbegin", "onblur", "onbounce", "oncanplay", + "oncanplaythrough", "oncellchange", "onchange", "onclick", "oncontextmenu", "oncontrolselect", "oncopy", "oncut", "oncuechange", + "ondataavailable", "ondatasetchanged", "ondatasetcomplete", "ondurationchange", "ondblclick", "ondeactivate", "ondrag", + "ondragdrop", "ondragend", "ondragenter", "ondragleave", "ondragover", "ondragstart", "ondrop", "onend", "onerror", + "onerrorupdate", "onfilterchange", "onfinish", "onfocus", "onfocusin", "onfocusout", "onhashchange", "onhelp", "oninput", + "oninvalid", "onkeydown", "onkeypress", "onkeyup", "onlayoutcomplete", "onload", "onloadend", "onloadstart", "onloadstart", + "onlosecapture", "onmediacomplete", "onmediaerror", "onmessage", "onmousedown", "onmouseenter", "onmouseleave", "onmousemove", + "onmouseout", "onmouseover", "onmouseup", "onmousewheel", "onmove", "onmoveend", "onmovestart", "onoffline", "ononline", + "onoutofsync", "onpageshow", "onpaste", "onpause", "onplay", "onplaying", "onpointerdown", "onpointerenter", "onpointerleave", + "onpointermove", "onpointerout", "onpointerover", "onpointerup", "onpopstate", "onprogress", "onpropertychange", "onreadystatechange", + "onredo", "onrepeat", "onreset", "onresize", "onresizeend", "onresizestart", "onresume", "onreverse", "onrowdelete", "onrowexit", + "onrowinserted", "onrowsenter", "onrowsdelete", "onrowsinserted", "onscroll", "onsearch", "onseek", "onselect", "onselectionchange", + "onselectstart", "onshow", "onstart", "onstop", "onstorage", "onsubmit", "onsyncrestored", "ontimeerror", "ontimeupdate", "ontoggle", + "ontouchend", "ontouchmove", "ontouchstart", "ontrackchange", "ontransitionstart", "ontransitioncancel", "ontransitionend", + "ontransitionrun", "onundo", "onunhandledrejection", "onunload", "onurlflip", "onvolumechange", "onwaiting", "onwebkitanimationiteration", + "onwheel", "whatthe=\"'onload", "onpointerrawupdate", "onpagehide", +} + +var tags = []string{ + "script", "iframe", "svg", "img", "video", "audio", "meta", "object", "embed", "style", "frame", "frameset", "applet", +} + +var specialChars = []string{ + ">", "<", "\"", "'", "`", ";", "|", "(", ")", "{", "}", "[", "]", ":", ".", ",", "+", "-", "=", "$", "\\", +} + +var usefulCodes = []string{ + "javascript:", "JaVasCriPt:", "jaVas%0dcRipt:", "jaVas%0acRipt:", "jaVas%09cRipt:", "data:", "alert(", "alert`", "prompt(", + "prompt`", "confirm(", "confirm`", "document.location", "document.cookie", "window.location", } diff --git a/pkg/scanning/grep.go b/pkg/scanning/grep.go index a7603fa9..1197581b 100644 --- a/pkg/scanning/grep.go +++ b/pkg/scanning/grep.go @@ -4,135 +4,24 @@ import "regexp" // Grepping is function for checking pattern func Grepping(data, regex string) []string { - byteData := []byte(data) - var bodySlice []string - var pattern = regexp.MustCompile(regex) - result := pattern.FindAllIndex(byteData, -1) - _ = result - - for _, v := range result { - bodySlice = append(bodySlice, data[v[0]:v[1]]) - } - return bodySlice + pattern := regexp.MustCompile(regex) + return pattern.FindAllString(data, -1) } // builtinGrep is dalfox build-in grep pattern func builtinGrep(data string) map[string][]string { - // "pattern name":["list of grep"] - result := make(map[string][]string) - // "pattern name":"regex" - pattern := map[string]string{ - "dalfox-ssti": "2958816", - "dalfox-esii": "", - "dalfox-rsa-key": "-----BEGIN RSA PRIVATE KEY-----|-----END RSA PRIVATE KEY-----", - "dalfox-priv-key": "-----BEGIN PRIVATE KEY-----|-----END PRIVATE KEY-----", - "dalfox-aws-s3": "s3\\.amazonaws.com[/]+|[a-zA-Z0-9_-]*\\.s3\\.amazonaws.com", - "dalfox-aws-appsync-graphql": "da2-[a-z0-9]{26}", - "dalfox-slack-webhook1": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}", - "dalfox-slack-webhook2": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8,10}/B[a-zA-Z0-9_]{8,10}/[a-zA-Z0-9_]{24}", - "dalfox-slack-token": "(xox[p|b|o|a]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32})", - "dalfox-facebook-oauth": "[f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K].{0,30}['\"\\s][0-9a-f]{32}['\"\\s]", - "dalfox-twitter-oauth": "[t|T][w|W][i|I][t|T][t|T][e|E][r|R].{0,30}['\"\\s][0-9a-zA-Z]{35,44}['\"\\s]", - "dalfox-heroku-api": "[h|H][e|E][r|R][o|O][k|K][u|U].{0,30}[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}", - "dalfox-mailgun-api": "key-[0-9a-zA-Z]{32}", - "dalfox-mailchamp-api": "[0-9a-f]{32}-us[0-9]{1,2}", - "dalfox-picatic-api": "sk_live_[0-9a-z]{32}", - "dalfox-google-oauth-id": "[0-9(+-[0-9A-Za-z_]{32}.apps.qooqleusercontent.com", - "dalfox-google-api": "AIza[0-9A-Za-z-_]{35}", - "dalfox-google-oauth": "ya29\\.[0-9A-Za-z\\-_]+", - "dalfox-aws-access-key": "AKIA[0-9A-Z]{16}", - "dalfox-amazon-mws-auth-token": "amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", - "dalfox-facebook-access-token": "EAACEdEose0cBA[0-9A-Za-z]+", - "dalfox-github-access-token": "[a-zA-Z0-9_-]*:[a-zA-Z0-9_\\-]+@github\\.com*", - "dalfox-github": "[gG][iI][tT][hH][uU][bB].*['|\"][0-9a-zA-Z]{35,40}['|\"]", - "dalfox-azure-storage": "[a-zA-Z0-9_-]*\\.file.core.windows.net", - "dalfox-telegram-bot-api-key": "[0-9]+:AA[0-9A-Za-z\\-_]{33}", - "dalfox-square-access-token": "sq0atp-[0-9A-Za-z\\-_]{22}", - "dalfox-square-oauth-secret": "sq0csp-[0-9A-Za-z\\-_]{43}", - "dalfox-twitter-access-token": "[tT][wW][iI][tT][tT][eE][rR].*[1-9][0-9]+-[0-9a-zA-Z]{40}", - "dalfox-twilio-api-key": "SK[0-9a-fA-F]{32}", - "dalfox-braintree-token": "access_token\\$production\\$[0-9a-z]{16}\\$[0-9a-f]{32}", - "dalfox-stripe-api-key": "sk_live_[0-9a-zA-Z]{24}", - "dalfox-stripe-restricted-key": "rk_live_[0-9a-zA-Z]{24}", - "dalfox-error-mysql": "(SQL syntax.*MySQL|Warning.*mysql_.*|MySqlException \\(0x|valid MySQL result|check the manual that corresponds to your (MySQL|MariaDB) server version|MySqlClient\\.|com\\.mysql\\.jdbc\\.exceptions)", - "dalfox-error-postgresql": "(PostgreSQL.*ERROR|Warning.*\\Wpg_.*|valid PostgreSQL result|Npgsql\\.|PG::SyntaxError:|org\\.postgresql\\.util\\.PSQLException|ERROR:\\s\\ssyntax error at or near)", - "dalfox-error-mssql": "(Driver.* SQL[\\-\\_\\ ]*Server|OLE DB.* SQL Server|\bSQL Server.*Driver|Warning.*mssql_.*|\bSQL Server.*[0-9a-fA-F]{8}|[\\s\\S]Exception.*\\WSystem\\.Data\\.SqlClient\\.|[\\s\\S]Exception.*\\WRoadhouse\\.Cms\\.|Microsoft SQL Native Client.*[0-9a-fA-F]{8})", - "dalfox-error-msaccess": "(Microsoft Access (\\d+ )?Driver|JET Database Engine|Access Database Engine|ODBC Microsoft Access)", - "dalfox-error-oracle": "(\\bORA-\\d{5}|Oracle error|Oracle.*Driver|Warning.*\\Woci_.*|Warning.*\\Wora_.*)", - "dalfox-error-ibmdb2": "(CLI Driver.*DB2|DB2 SQL error|\\bdb2_\\w+\\(|SQLSTATE.+SQLCODE)", - "dalfox-error-informix": "(Exception.*Informix)", - "dalfox-error-firebird": "(Dynamic SQL Error|Warning.*ibase_.*)", - "dalfox-error-sqlite": "(SQLite\\/JDBCDriver|SQLite.Exception|System.Data.SQLite.SQLiteException|Warning.*sqlite_.*|Warning.*SQLite3::|\\[SQLITE_ERROR\\])", - "dalfox-error-sapdb": "(SQL error.*POS([0-9]+).*|Warning.*maxdb.*)", - "dalfox-error-sybase": "(Warning.*sybase.*|Sybase message|Sybase.*Server message.*|SybSQLException|com\\.sybase\\.jdbc)", - "dalfox-error-ingress": "(Warning.*ingres_|Ingres SQLSTATE|Ingres\\W.*Driver)", - "dalfox-error-frontbase": "(Exception (condition )?\\d+. Transaction rollback.)", - "dalfox-error-hsqldb": "(org\\.hsqldb\\.jdbc|Unexpected end of command in statement \\[|Unexpected token.*in statement \\[)", - - //sqli - ///////////////////////////////////////////////////////// - - //mysql - "dalfox-error-mysql1": "SQL syntax.*?MySQL", - "dalfox-error-mysql2": "Warning.*?mysqli?", - "dalfox-error-mysql3": "MySQLSyntaxErrorException", - "dalfox-error-mysql4": "valid MySQL result", - "dalfox-error-mysql5": "check the manual that (corresponds to|fits) your MySQL server version", - "dalfox-error-mysql6": "check the manual that (corresponds to|fits) your MariaDB server version", - "dalfox-error-mysql7": "check the manual that (corresponds to|fits) your Drizzle server version", - "dalfox-error-mysql8": "Unknown column '[^ ]+' in 'field list'", - "dalfox-error-mysql9": "com\\.mysql\\.jdbc", - "dalfox-error-mysql10": "Zend_Db_(Adapter|Statement)_Mysqli_Exception", - "dalfox-error-mysql11": "MySqlException", - "dalfox-error-mysql12": "Syntax error or access violation", - - //psql - "dalfox-error-psql1": "PostgreSQL.*?ERROR", - "dalfox-error-psql2": "Warning.*?\\Wpg_", - "dalfox-error-psql3": "valid PostgreSQL result", - "dalfox-error-psql4": "Npgsql\\.", - "dalfox-error-psql5": "PG::SyntaxError:", - "dalfox-error-psql6": "org\\.postgresql\\.util\\.PSQLException", - "dalfox-error-psql7": "ERROR:\\s\\ssyntax error at or near", - "dalfox-error-psql8": "ERROR: parser: parse error at or near", - "dalfox-error-psql9": "PostgreSQL query failed", - "dalfox-error-psql10": "org\\.postgresql\\.jdbc", - "dalfox-error-psql11": "PSQLException", - - //mssql - "dalfox-error-mssql1": "Driver.*? SQL[\\-\\_\\ ]*Server", - "dalfox-error-mssql2": "OLE DB.*? SQL Server", - "dalfox-error-mssql3": "\bSQL Server[^<"]+Driver", - "dalfox-error-mssql4": "Warning.*?\\W(mssql|sqlsrv)_", - "dalfox-error-mssql5": "\bSQL Server[^<"]+[0-9a-fA-F]{8}", - "dalfox-error-mssql6": "System\\.Data\\.SqlClient\\.SqlException", - "dalfox-error-mssql7": "(?s)Exception.*?\bRoadhouse\\.Cms\\.", - "dalfox-error-mssql8": "Microsoft SQL Native Client error '[0-9a-fA-F]{8}", - "dalfox-error-mssql9": "\\[SQL Server\\]", - "dalfox-error-mssql10": "ODBC SQL Server Driver", - "dalfox-error-mssql11": "ODBC Driver \\d+ for SQL Server", - "dalfox-error-mssql12": "SQLServer JDBC Driver", - "dalfox-error-mssql13": "com\\.jnetdirect\\.jsql", - "dalfox-error-mssql14": "macromedia\\.jdbc\\.sqlserver", - "dalfox-error-mssql15": "Zend_Db_(Adapter|Statement)_Sqlsrv_Exception", - "dalfox-error-mssql16": "com\\.microsoft\\.sqlserver\\.jdbc", - "dalfox-error-mssql18": "SQL(Srv|Server)Exception", - } - for k, v := range pattern { - resultArr := Grepping(data, v) - if len(resultArr) > 0 { - result[k] = resultArr - } - } - - return result + return grepPatterns(data, builtinPatterns) } // customGrep is user custom grep pattern func customGrep(data string, pattern map[string]string) map[string][]string { - // "pattern name":"" + return grepPatterns(data, pattern) +} + +// grepPatterns is a helper function to grep patterns from data +func grepPatterns(data string, patterns map[string]string) map[string][]string { result := make(map[string][]string) - for k, v := range pattern { + for k, v := range patterns { resultArr := Grepping(data, v) if len(resultArr) > 0 { result[k] = resultArr @@ -140,3 +29,102 @@ func customGrep(data string, pattern map[string]string) map[string][]string { } return result } + +// builtinPatterns is a map of dalfox built-in grep patterns +var builtinPatterns = map[string]string{ + "dalfox-ssti": "2958816", + "dalfox-esii": "", + "dalfox-rsa-key": "-----BEGIN RSA PRIVATE KEY-----|-----END RSA PRIVATE KEY-----", + "dalfox-priv-key": "-----BEGIN PRIVATE KEY-----|-----END PRIVATE KEY-----", + "dalfox-aws-s3": "s3\\.amazonaws.com[/]+|[a-zA-Z0-9_-]*\\.s3\\.amazonaws.com", + "dalfox-aws-appsync-graphql": "da2-[a-z0-9]{26}", + "dalfox-slack-webhook1": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}", + "dalfox-slack-webhook2": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8,10}/B[a-zA-Z0-9_]{8,10}/[a-zA-Z0-9_]{24}", + "dalfox-slack-token": "(xox[p|b|o|a]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32})", + "dalfox-facebook-oauth": "[f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K].{0,30}['\"\\s][0-9a-f]{32}['\"\\s]", + "dalfox-twitter-oauth": "[t|T][w|W][i|I][t|T][t|T][e|E][r|R].{0,30}['\"\\s][0-9a-zA-Z]{35,44}['\"\\s]", + "dalfox-heroku-api": "[h|H][e|E][r|R][o|O][k|K][u|U].{0,30}[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}", + "dalfox-mailgun-api": "key-[0-9a-zA-Z]{32}", + "dalfox-mailchamp-api": "[0-9a-f]{32}-us[0-9]{1,2}", + "dalfox-picatic-api": "sk_live_[0-9a-z]{32}", + "dalfox-google-oauth-id": "[0-9(+-[0-9A-Za-z_]{32}.apps.qooqleusercontent.com", + "dalfox-google-api": "AIza[0-9A-Za-z-_]{35}", + "dalfox-google-oauth": "ya29\\.[0-9A-Za-z\\-_]+", + "dalfox-aws-access-key": "AKIA[0-9A-Z]{16}", + "dalfox-amazon-mws-auth-token": "amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", + "dalfox-facebook-access-token": "EAACEdEose0cBA[0-9A-Za-z]+", + "dalfox-github-access-token": "[a-zA-Z0-9_-]*:[a-zA-Z0-9_\\-]+@github\\.com*", + "dalfox-github": "[gG][iI][tT][hH][uU][bB].*['|\"][0-9a-zA-Z]{35,40}['|\"]", + "dalfox-azure-storage": "[a-zA-Z0-9_-]*\\.file.core.windows.net", + "dalfox-telegram-bot-api-key": "[0-9]+:AA[0-9A-Za-z\\-_]{33}", + "dalfox-square-access-token": "sq0atp-[0-9A-Za-z\\-_]{22}", + "dalfox-square-oauth-secret": "sq0csp-[0-9A-Za-z\\-_]{43}", + "dalfox-twitter-access-token": "[tT][wW][iI][tT][tT][eE][rR].*[1-9][0-9]+-[0-9a-zA-Z]{40}", + "dalfox-twilio-api-key": "SK[0-9a-fA-F]{32}", + "dalfox-braintree-token": "access_token\\$production\\$[0-9a-z]{16}\\$[0-9a-f]{32}", + "dalfox-stripe-api-key": "sk_live_[0-9a-zA-Z]{24}", + "dalfox-stripe-restricted-key": "rk_live_[0-9a-zA-Z]{24}", + "dalfox-error-mysql": "(SQL syntax.*MySQL|Warning.*mysql_.*|MySqlException \\(0x|valid MySQL result|check the manual that corresponds to your (MySQL|MariaDB) server version|MySqlClient\\.|com\\.mysql\\.jdbc\\.exceptions)", + "dalfox-error-postgresql": "(PostgreSQL.*ERROR|Warning.*\\Wpg_.*|valid PostgreSQL result|Npgsql\\.|PG::SyntaxError:|org\\.postgresql\\.util\\.PSQLException|ERROR:\\s\\ssyntax error at or near)", + "dalfox-error-mssql": "(Driver.* SQL[\\-\\_\\ ]*Server|OLE DB.* SQL Server|\bSQL Server.*Driver|Warning.*mssql_.*|\bSQL Server.*[0-9a-fA-F]{8}|[\\s\\S]Exception.*\\WSystem\\.Data\\.SqlClient\\.|[\\s\\S]Exception.*\\WRoadhouse\\.Cms\\.|Microsoft SQL Native Client.*[0-9a-fA-F]{8})", + "dalfox-error-msaccess": "(Microsoft Access (\\d+ )?Driver|JET Database Engine|Access Database Engine|ODBC Microsoft Access)", + "dalfox-error-oracle": "(\\bORA-\\d{5}|Oracle error|Oracle.*Driver|Warning.*\\Woci_.*|Warning.*\\Wora_.*)", + "dalfox-error-ibmdb2": "(CLI Driver.*DB2|DB2 SQL error|\\bdb2_\\w+\\(|SQLSTATE.+SQLCODE)", + "dalfox-error-informix": "(Exception.*Informix)", + "dalfox-error-firebird": "(Dynamic SQL Error|Warning.*ibase_.*)", + "dalfox-error-sqlite": "(SQLite\\/JDBCDriver|SQLite.Exception|System.Data.SQLite.SQLiteException|Warning.*sqlite_.*|Warning.*SQLite3::|\\[SQLITE_ERROR\\])", + "dalfox-error-sapdb": "(SQL error.*POS([0-9]+).*|Warning.*maxdb.*)", + "dalfox-error-sybase": "(Warning.*sybase.*|Sybase message|Sybase.*Server message.*|SybSQLException|com\\.sybase\\.jdbc)", + "dalfox-error-ingress": "(Warning.*ingres_|Ingres SQLSTATE|Ingres\\W.*Driver)", + "dalfox-error-frontbase": "(Exception (condition )?\\d+. Transaction rollback.)", + "dalfox-error-hsqldb": "(org\\.hsqldb\\.jdbc|Unexpected end of command in statement \\[|Unexpected token.*in statement \\[)", + + //sqli + ///////////////////////////////////////////////////////// + + //mysql + "dalfox-error-mysql1": "SQL syntax.*?MySQL", + "dalfox-error-mysql2": "Warning.*?mysqli?", + "dalfox-error-mysql3": "MySQLSyntaxErrorException", + "dalfox-error-mysql4": "valid MySQL result", + "dalfox-error-mysql5": "check the manual that (corresponds to|fits) your MySQL server version", + "dalfox-error-mysql6": "check the manual that (corresponds to|fits) your MariaDB server version", + "dalfox-error-mysql7": "check the manual that (corresponds to|fits) your Drizzle server version", + "dalfox-error-mysql8": "Unknown column '[^ ]+' in 'field list'", + "dalfox-error-mysql9": "com\\.mysql\\.jdbc", + "dalfox-error-mysql10": "Zend_Db_(Adapter|Statement)_Mysqli_Exception", + "dalfox-error-mysql11": "MySqlException", + "dalfox-error-mysql12": "Syntax error or access violation", + + //psql + "dalfox-error-psql1": "PostgreSQL.*?ERROR", + "dalfox-error-psql2": "Warning.*?\\Wpg_", + "dalfox-error-psql3": "valid PostgreSQL result", + "dalfox-error-psql4": "Npgsql\\.", + "dalfox-error-psql5": "PG::SyntaxError:", + "dalfox-error-psql6": "org\\.postgresql\\.util\\.PSQLException", + "dalfox-error-psql7": "ERROR:\\s\\ssyntax error at or near", + "dalfox-error-psql8": "ERROR: parser: parse error at or near", + "dalfox-error-psql9": "PostgreSQL query failed", + "dalfox-error-psql10": "org\\.postgresql\\.jdbc", + "dalfox-error-psql11": "PSQLException", + + //mssql + "dalfox-error-mssql1": "Driver.*? SQL[\\-\\_\\ ]*Server", + "dalfox-error-mssql2": "OLE DB.*? SQL Server", + "dalfox-error-mssql3": "\bSQL Server[^<"]+Driver", + "dalfox-error-mssql4": "Warning.*?\\W(mssql|sqlsrv)_", + "dalfox-error-mssql5": "\bSQL Server[^<"]+[0-9a-fA-F]{8}", + "dalfox-error-mssql6": "System\\.Data\\.SqlClient\\.SqlException", + "dalfox-error-mssql7": "(?s)Exception.*?\bRoadhouse\\.Cms\\.", + "dalfox-error-mssql8": "Microsoft SQL Native Client error '[0-9a-fA-F]{8}", + "dalfox-error-mssql9": "\\[SQL Server\\]", + "dalfox-error-mssql10": "ODBC SQL Server Driver", + "dalfox-error-mssql11": "ODBC Driver \\d+ for SQL Server", + "dalfox-error-mssql12": "SQLServer JDBC Driver", + "dalfox-error-mssql13": "com\\.jnetdirect\\.jsql", + "dalfox-error-mssql14": "macromedia\\.jdbc\\.sqlserver", + "dalfox-error-mssql15": "Zend_Db_(Adapter|Statement)_Sqlsrv_Exception", + "dalfox-error-mssql16": "com\\.microsoft\\.sqlserver\\.jdbc", + "dalfox-error-mssql18": "SQL(Srv|Server)Exception", +} diff --git a/pkg/scanning/ignore.go b/pkg/scanning/ignore.go index 860b945c..248d2eab 100644 --- a/pkg/scanning/ignore.go +++ b/pkg/scanning/ignore.go @@ -4,19 +4,20 @@ import "strings" // isAllowType is checking content-type func isAllowType(contentType string) bool { - notScanningType := []string{ - "application/json", - "application/javascript", - "text/javascript", - "text/plain", - "text/css", - "image/jpeg", - "image/png", - "image/bmp", - "image/gif", - "application/rss+xml", + notScanningType := map[string]struct{}{ + "application/json": {}, + "application/javascript": {}, + "text/javascript": {}, + "text/plain": {}, + "text/css": {}, + "image/jpeg": {}, + "image/png": {}, + "image/bmp": {}, + "image/gif": {}, + "application/rss+xml": {}, } - for _, n := range notScanningType { + + for n := range notScanningType { if strings.Contains(contentType, n) { return false } diff --git a/pkg/scanning/ratelimit.go b/pkg/scanning/ratelimit.go index cec6897a..397bc508 100644 --- a/pkg/scanning/ratelimit.go +++ b/pkg/scanning/ratelimit.go @@ -9,7 +9,7 @@ import ( // on a per-key basis. I.e. only one operation for // a given key can be done within the delay time type rateLimiter struct { - sync.Mutex + sync.RWMutex delay time.Duration ops map[string]time.Time } @@ -29,12 +29,12 @@ func (r *rateLimiter) Block(key string) { now := time.Now() r.Lock() + defer r.Unlock() // if there's nothing in the map we can // return straight away - if _, ok := r.ops[key]; !ok { + if t, ok := r.ops[key]; !ok || now.After(t.Add(r.delay)) { r.ops[key] = now - r.Unlock() return } @@ -43,7 +43,6 @@ func (r *rateLimiter) Block(key string) { deadline := t.Add(r.delay) if now.After(deadline) { r.ops[key] = now - r.Unlock() return } @@ -51,8 +50,7 @@ func (r *rateLimiter) Block(key string) { // Set the time of the operation r.ops[key] = now.Add(remaining) - r.Unlock() // Block for the remaining time - <-time.After(remaining) + time.Sleep(remaining) } diff --git a/pkg/scanning/staticAnlaysis.go b/pkg/scanning/staticAnlaysis.go index b7c03587..6698526b 100644 --- a/pkg/scanning/staticAnlaysis.go +++ b/pkg/scanning/staticAnlaysis.go @@ -1,6 +1,7 @@ package scanning import ( + "net/http" "strconv" "strings" @@ -13,96 +14,74 @@ func StaticAnalysis(target string, options model.Options, rl *rateLimiter) (map[ policy := make(map[string]string) pathReflection := make(map[int]string) req := optimization.GenerateNewRequest(target, "", options) - resbody, resp, _, _, err := SendReq(req, "", options) + _, resp, _, _, err := SendReq(req, "", options) if err != nil { return policy, pathReflection } - _ = resbody - if resp.Header["Content-Type"] != nil { - policy["Content-Type"] = resp.Header["Content-Type"][0] - } - if resp.Header["Content-Security-Policy"] != nil { - policy["Content-Security-Policy"] = resp.Header["Content-Security-Policy"][0] - result := checkCSP(policy["Content-Security-Policy"]) - if result != "" { - policy["BypassCSP"] = result - } - } - if resp.Header["X-Frame-Options"] != nil { - policy["X-Frame-Options"] = resp.Header["X-Frame-Options"][0] - } - if resp.Header["Strict-Transport-Security"] != nil { - policy["Strict-Transport-Security"] = resp.Header["Strict-Transport-Security"][0] - } - if resp.Header["Access-Control-Allow-Origin"] != nil { - policy["Access-Control-Allow-Origin"] = resp.Header["Access-Control-Allow-Origin"][0] - } + + extractPolicyHeaders(resp.Header, policy) + paths := strings.Split(target, "/") // case of https://domain/ + @ for idx := range paths { if idx > 2 { id := idx - 3 - _ = id - //var tempPath []string - //copy(tempPath, paths) tempPath := strings.Split(target, "/") tempPath[idx] = "dalfoxpathtest" - tempURL := strings.Join(tempPath, "/") - req := optimization.GenerateNewRequest(tempURL, "", options) - rl.Block(req.Host) - resbody, _, _, vrs, err := SendReq(req, "dalfoxpathtest", options) - if err != nil { - return policy, pathReflection - } - if vrs { - pointer := optimization.Abstraction(resbody, "dalfoxpathtest") - smap := "Injected: " - tempSmap := make(map[string]int) - - for _, v := range pointer { - if tempSmap[v] == 0 { - tempSmap[v] = 1 - } else { - tempSmap[v] = tempSmap[v] + 1 - } - } - for k, v := range tempSmap { - smap = smap + "/" + k + "(" + strconv.Itoa(v) + ")" - } - pathReflection[id] = smap - } + checkPathReflection(tempURL, id, options, rl, pathReflection) } } // case of https://domain if len(paths) == 3 { - tempURL := target + "/dalfoxpathtest" - req := optimization.GenerateNewRequest(tempURL, "", options) - rl.Block(req.Host) - resbody, _, _, vrs, err := SendReq(req, "dalfoxpathtest", options) - if err != nil { - return policy, pathReflection + checkPathReflection(tempURL, 0, options, rl, pathReflection) + } + + return policy, pathReflection +} + +func extractPolicyHeaders(header http.Header, policy map[string]string) { + if contentType := header.Get("Content-Type"); contentType != "" { + policy["Content-Type"] = contentType + } + if csp := header.Get("Content-Security-Policy"); csp != "" { + policy["Content-Security-Policy"] = csp + if result := checkCSP(csp); result != "" { + policy["BypassCSP"] = result } - if vrs { - pointer := optimization.Abstraction(resbody, "dalfoxpathtest") - smap := "Injected: " - tempSmap := make(map[string]int) + } + if xFrameOptions := header.Get("X-Frame-Options"); xFrameOptions != "" { + policy["X-Frame-Options"] = xFrameOptions + } + if hsts := header.Get("Strict-Transport-Security"); hsts != "" { + policy["Strict-Transport-Security"] = hsts + } + if acao := header.Get("Access-Control-Allow-Origin"); acao != "" { + policy["Access-Control-Allow-Origin"] = acao + } +} - for _, v := range pointer { - if tempSmap[v] == 0 { - tempSmap[v] = 1 - } else { - tempSmap[v] = tempSmap[v] + 1 - } - } - for k, v := range tempSmap { - smap = smap + "/" + k + "(" + strconv.Itoa(v) + ")" - } - pathReflection[0] = smap +func checkPathReflection(tempURL string, id int, options model.Options, rl *rateLimiter, pathReflection map[int]string) { + req := optimization.GenerateNewRequest(tempURL, "", options) + rl.Block(req.Host) + resbody, _, _, vrs, err := SendReq(req, "dalfoxpathtest", options) + if err != nil { + return + } + if vrs { + pointer := optimization.Abstraction(resbody, "dalfoxpathtest") + smap := "Injected: " + tempSmap := make(map[string]int) + + for _, v := range pointer { + tempSmap[v]++ + } + for k, v := range tempSmap { + smap += "/" + k + "(" + strconv.Itoa(v) + ")" } + pathReflection[id] = smap } - return policy, pathReflection } diff --git a/pkg/scanning/utils.go b/pkg/scanning/utils.go index 8ec461b4..f6297658 100644 --- a/pkg/scanning/utils.go +++ b/pkg/scanning/utils.go @@ -12,36 +12,28 @@ func indexOf(element string, data []string) int { return k } } - return -1 //not found. + return -1 // not found } func duplicatedResult(result []model.PoC, rst model.PoC) bool { + types := make(map[string]struct{}, len(result)) for _, v := range result { - if v.Type == rst.Type { - return true - } + types[v.Type] = struct{}{} } - return false + _, exists := types[rst.Type] + return exists } func containsFromArray(slice []string, item string) bool { set := make(map[string]struct{}, len(slice)) - t := strings.Split(item, "(") - i := t[0] for _, s := range slice { set[s] = struct{}{} } - + i := strings.Split(item, "(")[0] _, ok := set[i] return ok } func checkPType(str string) bool { - if strings.Contains(str, "toBlind") { - return false - } - if strings.Contains(str, "toGrepping") { - return false - } - return true + return !strings.Contains(str, "toBlind") && !strings.Contains(str, "toGrepping") } diff --git a/pkg/scanning/waf.go b/pkg/scanning/waf.go index c386d69c..637c55f8 100644 --- a/pkg/scanning/waf.go +++ b/pkg/scanning/waf.go @@ -12,351 +12,110 @@ type WAFPattern struct { Header string } -func checkWAF(header http.Header, body string) (bool, string) { - var patterns []WAFPattern - patterns = append(patterns, WAFPattern{ - Name: "360 Web Application Firewall (360)", - Body: "/wzws-waf-cgi/", - Header: "X-Powered-By-360wzb", - }) - patterns = append(patterns, WAFPattern{ - Name: "aeSecure", - Body: "aesecure_denied.png", - Header: "aeSecure-code", - }) - patterns = append(patterns, WAFPattern{ - Name: "Airlock", - Body: "", - Header: "AL[_-]?(SESS|LB)", - }) - patterns = append(patterns, WAFPattern{ - Name: "Anquanbao Web Application Firewall", - Body: "", - Header: "X-Powered-By-Anquanba", - }) - patterns = append(patterns, WAFPattern{ - Name: "Armor Protection (Armor Defense)", - Body: "This request has been blocked by website protection from Armor", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Application Security Manager (F5 Networks)", - Body: "The requested URL was rejected. Please consult with your administrator.", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Amazon Web Services Web Application Firewall (Amazon)", - Body: "", - Header: "AWS", - }) - patterns = append(patterns, WAFPattern{ - Name: "Yunjiasu Web Application Firewall (Baidu)", - Body: "", - Header: "yunjiasu-nginx", - }) - patterns = append(patterns, WAFPattern{ - Name: "Barracuda Web Application Firewall (Barracuda Networks)", - Body: "", - Header: "barra_counter_session=", - }) - patterns = append(patterns, WAFPattern{ - Name: "BIG-IP Application Security Manager (F5 Networks)", - Body: "", - Header: "BigIP", - }) - patterns = append(patterns, WAFPattern{ - Name: "BinarySEC Web Application Firewall (BinarySEC)", - Body: "", - Header: "binarysec", - }) - patterns = append(patterns, WAFPattern{ - Name: "BlockDoS", - Body: "", - Header: "BlockDos.net", - }) - patterns = append(patterns, WAFPattern{ - Name: "ChinaCache (ChinaCache Networks)", - Body: "", - Header: "Powered-By-ChinaCache", - }) - patterns = append(patterns, WAFPattern{ - Name: "Cisco ACE XML Gateway (Cisco Systems)", - Body: "", - Header: "ACE XML Gateway", - }) - patterns = append(patterns, WAFPattern{ - Name: "Cloudbric Web Application Firewall (Cloudbric)", - Body: "Cloudbric", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "CloudFlare Web Application Firewall (CloudFlare)", - Body: "Attention Required!", - Header: "cloudflare|__cfduid=|cf-ray", - }) - patterns = append(patterns, WAFPattern{ - Name: "CloudFront (Amazon)", - Body: "", - Header: "Error from cloudfront", - }) - patterns = append(patterns, WAFPattern{ - Name: "Comodo Web Application Firewall (Comodo)", - Body: "", - Header: "Protected by COMODO WAF", - }) - patterns = append(patterns, WAFPattern{ - Name: "CrawlProtect (Jean-Denis Brun)", - Body: "This site is protected by CrawlProtect", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "IBM WebSphere DataPower (IBM)", - Body: "", - Header: "X-Backside-Transport", - }) - patterns = append(patterns, WAFPattern{ - Name: "Deny All Web Application Firewall (DenyAll)", - Body: "Condition Intercepted", - Header: "sessioncookie", - }) - patterns = append(patterns, WAFPattern{ - Name: "Distil Web Application Firewall Security (Distil Networks)", - Body: "", - Header: "x-distil-cs", - }) - patterns = append(patterns, WAFPattern{ - Name: "DOSarrest (DOSarrest Internet Security)", - Body: "", - Header: "DOSarrest|X-DIS-Request-ID", - }) - patterns = append(patterns, WAFPattern{ - Name: "dotDefender (Applicure Technologies)", - Body: "dotDefender Blocked Your Request", - Header: "X-dotDefender-denied", - }) - patterns = append(patterns, WAFPattern{ - Name: "EdgeCast Web Application Firewall (Verizon)", - Body: "", - Header: "SERVER.*?ECDF", - }) - patterns = append(patterns, WAFPattern{ - Name: "ExpressionEngine (EllisLab)", - Body: "Invalid (GET|POST) Data", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "FortiWeb Web Application Firewall (Fortinet)", - Body: "", - Header: "FORTIWAFSID=|cookiesession1=", - }) - patterns = append(patterns, WAFPattern{ - Name: "Hyperguard Web Application Firewall (art of defence)", - Body: "", - Header: "ODSESSION=", - }) - patterns = append(patterns, WAFPattern{ - Name: "Incapsula Web Application Firewall (Incapsula/Imperva)", - Body: "", - Header: "X-Iinfo|incap_ses|visid_incap", - }) - patterns = append(patterns, WAFPattern{ - Name: "ISA Server (Microsoft)", - Body: "The server denied the specified Uniform Resource Locator (URL)", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Jiasule Web Application Firewall (Jiasule)", - Body: "", - Header: "jiasule-WAF|__jsluid=|jsl_tracking", - }) - patterns = append(patterns, WAFPattern{ - Name: "KS-WAF (Knownsec)", - Body: "ks-waf-error.png'", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "KONA Security Solutions (Akamai Technologies)", - Body: "", - Header: "AkamaiGHost", - }) - patterns = append(patterns, WAFPattern{ - Name: "ModSecurity: Open Source Web Application Firewall (Trustwave)", - Body: "", - Header: "Mod_Security|NOYB", - }) - patterns = append(patterns, WAFPattern{ - Name: "NAXSI (NBS System)", - Body: "", - Header: "NCI__SessionId=", - }) - patterns = append(patterns, WAFPattern{ - Name: "NetScaler (Citrix Systems)", - Body: "", - Header: "ns_af=|citrix_ns_id|NSC_|NS-CACHE", - }) - patterns = append(patterns, WAFPattern{ - Name: "Newdefend Web Application Firewall (Newdefend)", - Body: "", - Header: "newdefend", - }) - patterns = append(patterns, WAFPattern{ - Name: "NSFOCUS Web Application Firewall (NSFOCUS)", - Body: "", - Header: "NSFocus", - }) - patterns = append(patterns, WAFPattern{ - Name: "Palo Alto Firewall (Palo Alto Networks)", - Body: "has been blocked in accordance with company policy", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Profense Web Application Firewall (Armorlogic)", - Body: "", - Header: "PLBSID=|Profense", - }) - patterns = append(patterns, WAFPattern{ - Name: "AppWall (Radware)", - Body: "Unauthorized Activity Has Been Detected", - Header: "X-SL-CompState", - }) - patterns = append(patterns, WAFPattern{ - Name: "Reblaze Web Application Firewall (Reblaze)", - Body: "", - Header: "rbzid=|Reblaze Secure Web Gateway", - }) - patterns = append(patterns, WAFPattern{ - Name: "ASP.NET RequestValidationMode (Microsoft)", - Body: "ASP.NET has detected data in the request that is potentially dangerous|Request Validation has detected a potentially dangerous client input value|HttpRequestValidationException", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Safe3 Web Application Firewall", - Body: "", - Header: "Safe3", - }) - patterns = append(patterns, WAFPattern{ - Name: "Safedog Web Application Firewall (Safedog)", - Body: "", - Header: "WAF/2.0|safedog", - }) - patterns = append(patterns, WAFPattern{ - Name: "SecureIIS Web Server Security (BeyondTrust)", - Body: "SecureIIS.*?Web Server Protection|http://www.eeye.com/SecureIIS/|?subject=[^>]*SecureIIS Error", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "SEnginx (Neusoft Corporation)", - Body: "SENGINX-ROBOT-MITIGATION", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "TrueShield Web Application Firewall (SiteLock)", - Body: "SiteLock Incident ID|sitelock-site-verification|sitelock_shield_logo", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "SonicWALL (Dell)", - Body: "This request is blocked by the SonicWALL|#shd|#nsa_banner|Web Site Blocked.*?nsa_banner", - Header: "SonicWALL", - }) - patterns = append(patterns, WAFPattern{ - Name: "UTM Web Protection (Sophos)", - Body: "Powered by UTM Web Protection", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Stingray Application Firewall (Riverbed / Brocade)", - Body: "", - Header: "X-Mapping-", - }) - patterns = append(patterns, WAFPattern{ - Name: "CloudProxy WebSite Firewall (Sucuri)", - Body: "Access Denied.*?Sucuri Website Firewall|Sucuri WebSite Firewall.*?Access Denied|Questions?.*?cloudproxy@sucuri.net", - Header: "Sucuri/Cloudproxy|X-Sucuri", - }) - patterns = append(patterns, WAFPattern{ - Name: "Tencent Cloud Web Application Firewall (Tencent Cloud Computing)", - Body: "waf.tencent-cloud.com", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Teros/Citrix Application Firewall Enterprise (Teros/Citrix Systems)", - Body: "", - Header: "st8(id|_wat|_wlf)", - }) - patterns = append(patterns, WAFPattern{ - Name: "TrafficShield (F5 Networks)", - Body: "", - Header: "F5-TrafficShield|ASINFO=", - }) - patterns = append(patterns, WAFPattern{ - Name: "UrlScan (Microsoft)", - Body: "Rejected-By-UrlScan", - Header: "Rejected-By-UrlScan", - }) - patterns = append(patterns, WAFPattern{ - Name: "USP Secure Entry Server (United Security Providers)", - Body: "", - Header: "Secure Entry Server", - }) - patterns = append(patterns, WAFPattern{ - Name: "Varnish FireWall (OWASP)", - Body: "Request rejected by xVarnish-WAF", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Wallarm Web Application Firewall (Wallarm)", - Body: "", - Header: "nginx-wallarm", - }) - patterns = append(patterns, WAFPattern{ - Name: "WatchGuard (WatchGuard Technologies)", - Body: "", - Header: "WatchGuard", - }) - patterns = append(patterns, WAFPattern{ - Name: "WebKnight Application Firewall (AQTRONIX)", - Body: "WebKnight Application Firewall Alert|AQTRONIX WebKnight", - Header: "WebKnight", - }) - patterns = append(patterns, WAFPattern{ - Name: "Wordfence (Feedjit)", - Body: "This response was generated by Wordfence|Your access to this site has been limited", - Header: "", - }) - patterns = append(patterns, WAFPattern{ - Name: "Zenedge Web Application Firewall (Zenedge)", - Body: "", - Header: "ZENEDGE", - }) - patterns = append(patterns, WAFPattern{ - Name: "Yundun Web Application Firewall (Yundun)", - Body: "", - Header: "YUNDUN", - }) - patterns = append(patterns, WAFPattern{ - Name: "Yunsuo Web Application Firewall (Yunsuo)", - Body: "", - Header: "yunsuo_session", - }) +var patterns = []WAFPattern{ + {Name: "360 Web Application Firewall (360)", Body: "/wzws-waf-cgi/", Header: "X-Powered-By-360wzb"}, + {Name: "aeSecure", Body: "aesecure_denied.png", Header: "aeSecure-code"}, + {Name: "Airlock", Body: "", Header: "AL[_-]?(SESS|LB)"}, + {Name: "Anquanbao Web Application Firewall", Body: "", Header: "X-Powered-By-Anquanba"}, + {Name: "Armor Protection (Armor Defense)", Body: "This request has been blocked by website protection from Armor", Header: ""}, + {Name: "Application Security Manager (F5 Networks)", Body: "The requested URL was rejected. Please consult with your administrator.", Header: ""}, + {Name: "Amazon Web Services Web Application Firewall (Amazon)", Body: "", Header: "AWS"}, + {Name: "Yunjiasu Web Application Firewall (Baidu)", Body: "", Header: "yunjiasu-nginx"}, + {Name: "Barracuda Web Application Firewall (Barracuda Networks)", Body: "", Header: "barra_counter_session="}, + {Name: "BIG-IP Application Security Manager (F5 Networks)", Body: "", Header: "BigIP"}, + {Name: "BinarySEC Web Application Firewall (BinarySEC)", Body: "", Header: "binarysec"}, + {Name: "BlockDoS", Body: "", Header: "BlockDos.net"}, + {Name: "ChinaCache (ChinaCache Networks)", Body: "", Header: "Powered-By-ChinaCache"}, + {Name: "Cisco ACE XML Gateway (Cisco Systems)", Body: "", Header: "ACE XML Gateway"}, + {Name: "Cloudbric Web Application Firewall (Cloudbric)", Body: "Cloudbric", Header: ""}, + {Name: "CloudFlare Web Application Firewall (CloudFlare)", Body: "Attention Required!", Header: "cloudflare|__cfduid=|cf-ray"}, + {Name: "CloudFront (Amazon)", Body: "", Header: "Error from cloudfront"}, + {Name: "Comodo Web Application Firewall (Comodo)", Body: "", Header: "Protected by COMODO WAF"}, + {Name: "CrawlProtect (Jean-Denis Brun)", Body: "This site is protected by CrawlProtect", Header: ""}, + {Name: "IBM WebSphere DataPower (IBM)", Body: "", Header: "X-Backside-Transport"}, + {Name: "Deny All Web Application Firewall (DenyAll)", Body: "Condition Intercepted", Header: "sessioncookie"}, + {Name: "Distil Web Application Firewall Security (Distil Networks)", Body: "", Header: "x-distil-cs"}, + {Name: "DOSarrest (DOSarrest Internet Security)", Body: "", Header: "DOSarrest|X-DIS-Request-ID"}, + {Name: "dotDefender (Applicure Technologies)", Body: "dotDefender Blocked Your Request", Header: "X-dotDefender-denied"}, + {Name: "EdgeCast Web Application Firewall (Verizon)", Body: "", Header: "SERVER.*?ECDF"}, + {Name: "ExpressionEngine (EllisLab)", Body: "Invalid (GET|POST) Data", Header: ""}, + {Name: "FortiWeb Web Application Firewall (Fortinet)", Body: "", Header: "FORTIWAFSID=|cookiesession1="}, + {Name: "Hyperguard Web Application Firewall (art of defence)", Body: "", Header: "ODSESSION="}, + {Name: "Incapsula Web Application Firewall (Incapsula/Imperva)", Body: "", Header: "X-Iinfo|incap_ses|visid_incap"}, + {Name: "ISA Server (Microsoft)", Body: "The server denied the specified Uniform Resource Locator (URL)", Header: ""}, + {Name: "Jiasule Web Application Firewall (Jiasule)", Body: "", Header: "jiasule-WAF|__jsluid=|jsl_tracking"}, + {Name: "KS-WAF (Knownsec)", Body: "ks-waf-error.png'", Header: ""}, + {Name: "KONA Security Solutions (Akamai Technologies)", Body: "", Header: "AkamaiGHost"}, + {Name: "ModSecurity: Open Source Web Application Firewall (Trustwave)", Body: "", Header: "Mod_Security|NOYB"}, + {Name: "NAXSI (NBS System)", Body: "", Header: "NCI__SessionId="}, + {Name: "NetScaler (Citrix Systems)", Body: "", Header: "ns_af=|citrix_ns_id|NSC_|NS-CACHE"}, + {Name: "Newdefend Web Application Firewall (Newdefend)", Body: "", Header: "newdefend"}, + {Name: "NSFOCUS Web Application Firewall (NSFOCUS)", Body: "", Header: "NSFocus"}, + {Name: "Palo Alto Firewall (Palo Alto Networks)", Body: "has been blocked in accordance with company policy", Header: ""}, + {Name: "Profense Web Application Firewall (Armorlogic)", Body: "", Header: "PLBSID=|Profense"}, + {Name: "AppWall (Radware)", Body: "Unauthorized Activity Has Been Detected", Header: "X-SL-CompState"}, + {Name: "Reblaze Web Application Firewall (Reblaze)", Body: "", Header: "rbzid=|Reblaze Secure Web Gateway"}, + {Name: "ASP.NET RequestValidationMode (Microsoft)", Body: "ASP.NET has detected data in the request that is potentially dangerous|Request Validation has detected a potentially dangerous client input value|HttpRequestValidationException", Header: ""}, + {Name: "Safe3 Web Application Firewall", Body: "", Header: "Safe3"}, + {Name: "Safedog Web Application Firewall (Safedog)", Body: "", Header: "WAF/2.0|safedog"}, + {Name: "SecureIIS Web Server Security (BeyondTrust)", Body: "SecureIIS.*?Web Server Protection|http://www.eeye.com/SecureIIS/|?subject=[^>]*SecureIIS Error", Header: ""}, + {Name: "SEnginx (Neusoft Corporation)", Body: "SENGINX-ROBOT-MITIGATION", Header: ""}, + {Name: "TrueShield Web Application Firewall (SiteLock)", Body: "SiteLock Incident ID|sitelock-site-verification|sitelock_shield_logo", Header: ""}, + {Name: "SonicWALL (Dell)", Body: "This request is blocked by the SonicWALL|#shd|#nsa_banner|Web Site Blocked.*?nsa_banner", Header: "SonicWALL"}, + {Name: "UTM Web Protection (Sophos)", Body: "Powered by UTM Web Protection", Header: ""}, + {Name: "Stingray Application Firewall (Riverbed / Brocade)", Body: "", Header: "X-Mapping-"}, + {Name: "CloudProxy WebSite Firewall (Sucuri)", Body: "Access Denied.*?Sucuri Website Firewall|Sucuri WebSite Firewall.*?Access Denied|Questions?.*?cloudproxy@sucuri.net", Header: "Sucuri/Cloudproxy|X-Sucuri"}, + {Name: "Tencent Cloud Web Application Firewall (Tencent Cloud Computing)", Body: "waf.tencent-cloud.com", Header: ""}, + {Name: "Teros/Citrix Application Firewall Enterprise (Teros/Citrix Systems)", Body: "", Header: "st8(id|_wat|_wlf)"}, + {Name: "TrafficShield (F5 Networks)", Body: "", Header: "F5-TrafficShield|ASINFO="}, + {Name: "UrlScan (Microsoft)", Body: "Rejected-By-UrlScan", Header: "Rejected-By-UrlScan"}, + {Name: "USP Secure Entry Server (United Security Providers)", Body: "", Header: "Secure Entry Server"}, + {Name: "Varnish FireWall (OWASP)", Body: "Request rejected by xVarnish-WAF", Header: ""}, + {Name: "Wallarm Web Application Firewall (Wallarm)", Body: "", Header: "nginx-wallarm"}, + {Name: "WatchGuard (WatchGuard Technologies)", Body: "", Header: "WatchGuard"}, + {Name: "WebKnight Application Firewall (AQTRONIX)", Body: "WebKnight Application Firewall Alert|AQTRONIX WebKnight", Header: "WebKnight"}, + {Name: "Wordfence (Feedjit)", Body: "This response was generated by Wordfence|Your access to this site has been limited", Header: ""}, + {Name: "Zenedge Web Application Firewall (Zenedge)", Body: "", Header: "ZENEDGE"}, + {Name: "Yundun Web Application Firewall (Yundun)", Body: "", Header: "YUNDUN"}, + {Name: "Yunsuo Web Application Firewall (Yunsuo)", Body: "", Header: "yunsuo_session"}, +} +func checkWAF(header http.Header, body string) (bool, string) { for _, p := range patterns { matchBody := false matchHeader := false + var err error + if p.Body != "" { - matchBody, _ = regexp.MatchString(p.Body, body) + matchBody, err = regexp.MatchString(p.Body, body) + if err != nil { + continue + } } + if p.Header != "" { for k, v := range header { - km, _ := regexp.MatchString(p.Header, k) - vm := false + km, err := regexp.MatchString(p.Header, k) + if err != nil { + continue + } + if km { + matchHeader = true + break + } for _, vh := range v { - headerValueMatch, _ := regexp.MatchString(p.Header, vh) + headerValueMatch, err := regexp.MatchString(p.Header, vh) + if err != nil { + continue + } if headerValueMatch { - vm = true + matchHeader = true + break } } - matchHeader = km || vm + if matchHeader { + break + } } } diff --git a/pkg/server/scan.go b/pkg/server/scan.go index 18bd9320..92a14eff 100644 --- a/pkg/server/scan.go +++ b/pkg/server/scan.go @@ -31,8 +31,7 @@ func ScanFromAPI(url string, rqOptions model.Options, options model.Options, sid } else { newOptions.Method = "GET" } - escapedURL := strings.Replace(url, "\n", "", -1) - escapedURL = strings.Replace(escapedURL, "\r", "", -1) + escapedURL := cleanURL(url) vLog.WithField("data1", sid).Debug(escapedURL) vLog.WithField("data1", sid).Debug(newOptions) _, _ = scan.Scan(url, newOptions, sid) @@ -57,6 +56,17 @@ func GetScan(sid string, options model.Options) model.Scan { // @Produce json // @Success 200 {array} string // @Router /scans [get] -func GetScans() { +func GetScans(options model.Options) []string { + var scans []string + for sid := range options.Scan { + scans = append(scans, sid) + } + return scans +} +// cleanURL removes newline and carriage return characters from the URL +func cleanURL(url string) string { + escapedURL := strings.Replace(url, "\n", "", -1) + escapedURL = strings.Replace(escapedURL, "\r", "", -1) + return escapedURL } diff --git a/pkg/server/scan_test.go b/pkg/server/scan_test.go new file mode 100644 index 00000000..2458e9e6 --- /dev/null +++ b/pkg/server/scan_test.go @@ -0,0 +1,122 @@ +package server + +import ( + "reflect" + "testing" + + "github.com/hahwul/dalfox/v2/pkg/model" +) + +func TestGetScan(t *testing.T) { + type args struct { + sid string + options model.Options + } + tests := []struct { + name string + args args + want model.Scan + }{ + { + name: "Existing scan", + args: args{ + sid: "test-scan", + options: model.Options{ + Scan: map[string]model.Scan{ + "test-scan": {URL: "http://example.com"}, + }, + }, + }, + want: model.Scan{URL: "http://example.com"}, + }, + { + name: "Non-existing scan", + args: args{ + sid: "non-existing-scan", + options: model.Options{ + Scan: map[string]model.Scan{}, + }, + }, + want: model.Scan{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetScan(tt.args.sid, tt.args.options); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetScan() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetScans(t *testing.T) { + tests := []struct { + name string + options model.Options + want []string + }{ + { + name: "Empty scans", + options: model.Options{ + Scan: map[string]model.Scan{}, + }, + want: []string{}, + }, + { + name: "Non-empty scans", + options: model.Options{ + Scan: map[string]model.Scan{ + "scan1": {}, + "scan2": {}, + }, + }, + want: []string{"scan1", "scan2"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetScans(tt.options); !reflect.DeepEqual(len(got), len(tt.want)) { + t.Errorf("GetScans() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_cleanURL(t *testing.T) { + type args struct { + url string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "URL with newline", + args: args{url: "http://example.com\n"}, + want: "http://example.com", + }, + { + name: "URL with carriage return", + args: args{url: "http://example.com\r"}, + want: "http://example.com", + }, + { + name: "URL with both newline and carriage return", + args: args{url: "http://example.com\r\n"}, + want: "http://example.com", + }, + { + name: "URL without newline or carriage return", + args: args{url: "http://example.com"}, + want: "http://example.com", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := cleanURL(tt.args.url); got != tt.want { + t.Errorf("cleanURL() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/server/server.go b/pkg/server/server.go index 98e7675f..901ce140 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -28,6 +28,12 @@ import ( // RunAPIServer is Running Echo server with swag func RunAPIServer(options model.Options) { var scans []string + e := setupEchoServer(options, &scans) + printing.DalLog("SYSTEM", "Listen "+e.Server.Addr, options) + graceful.ListenAndServe(e.Server, 5*time.Second) +} + +func setupEchoServer(options model.Options, scans *[]string) *echo.Echo { e := echo.New() options.IsAPI = true e.Server.Addr = options.ServerHost + ":" + strconv.Itoa(options.ServerPort) @@ -43,63 +49,75 @@ func RunAPIServer(options model.Options) { `"latency_human":"${latency_human}","bytes_in":${bytes_in},` + `"bytes_out":${bytes_out}}` + "\n", })) - e.GET("/health", func(c echo.Context) error { - r := &Res{ - Code: 200, - Msg: "ok", - } - return c.JSON(http.StatusOK, r) - }) + e.GET("/health", healthHandler) e.GET("/swagger/*", echoSwagger.WrapHandler) e.GET("/scans", func(c echo.Context) error { - r := &Scans{ - Code: 200, - Scans: scans, - } - return c.JSON(http.StatusNotFound, r) + return scansHandler(c, scans) }) e.GET("/scan/:sid", func(c echo.Context) error { - sid := c.Param("sid") - if !contains(scans, sid) { - r := &Res{ - Code: 404, - Msg: "Not found scanid", - } - return c.JSON(http.StatusNotFound, r) - - } - r := &Res{ - Code: 200, - } - scan := GetScan(sid, options) - if len(scan.URL) == 0 { - r.Msg = "scanning" - } else { - r.Msg = "finish" - r.Data = scan.Results - } - return c.JSON(http.StatusOK, r) + return scanHandler(c, scans, options) }) e.POST("/scan", func(c echo.Context) error { - rq := new(Req) - if err := c.Bind(rq); err != nil { - r := &Res{ - Code: 500, - Msg: "Parameter Bind error", - } - return c.JSON(http.StatusInternalServerError, r) + return postScanHandler(c, scans, options) + }) + return e +} + +func healthHandler(c echo.Context) error { + r := &Res{ + Code: 200, + Msg: "ok", + } + return c.JSON(http.StatusOK, r) +} + +func scansHandler(c echo.Context, scans *[]string) error { + r := &Scans{ + Code: 200, + Scans: *scans, + } + return c.JSON(http.StatusNotFound, r) +} + +func scanHandler(c echo.Context, scans *[]string, options model.Options) error { + sid := c.Param("sid") + if !contains(*scans, sid) { + r := &Res{ + Code: 404, + Msg: "Not found scanid", } - sid := GenerateRandomToken(rq.URL) + return c.JSON(http.StatusNotFound, r) + } + r := &Res{ + Code: 200, + } + scan := GetScan(sid, options) + if len(scan.URL) == 0 { + r.Msg = "scanning" + } else { + r.Msg = "finish" + r.Data = scan.Results + } + return c.JSON(http.StatusOK, r) +} + +func postScanHandler(c echo.Context, scans *[]string, options model.Options) error { + rq := new(Req) + if err := c.Bind(rq); err != nil { r := &Res{ - Code: 200, - Msg: sid, + Code: 500, + Msg: "Parameter Bind error", } - scans = append(scans, sid) - go ScanFromAPI(rq.URL, rq.Options, options, sid) - return c.JSON(http.StatusOK, r) - }) - printing.DalLog("SYSTEM", "Listen "+e.Server.Addr, options) - graceful.ListenAndServe(e.Server, 5*time.Second) + return c.JSON(http.StatusInternalServerError, r) + } + sid := GenerateRandomToken(rq.URL) + r := &Res{ + Code: 200, + Msg: sid, + } + *scans = append(*scans, sid) + go ScanFromAPI(rq.URL, rq.Options, options, sid) + return c.JSON(http.StatusOK, r) } func contains(slice []string, item string) bool { diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go new file mode 100644 index 00000000..c08deb8c --- /dev/null +++ b/pkg/server/server_test.go @@ -0,0 +1,79 @@ +package server + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/hahwul/dalfox/v2/pkg/model" + _ "github.com/hahwul/dalfox/v2/pkg/server/docs" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +func Test_contains(t *testing.T) { + type args struct { + slice []string + item string + } + tests := []struct { + name string + args args + want bool + }{ + {name: "Item exists", args: args{slice: []string{"a", "b", "c"}, item: "b"}, want: true}, + {name: "Item does not exist", args: args{slice: []string{"a", "b", "c"}, item: "d"}, want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := contains(tt.args.slice, tt.args.item); got != tt.want { + t.Errorf("contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_scanHandler(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/scan/test-scan", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + scans := []string{"test-scan"} + options := model.Options{ + Scan: map[string]model.Scan{ + "test-scan": {URL: "http://example.com", Results: []model.PoC{{Type: "finish"}}}, + }, + } + + if assert.NoError(t, scanHandler(c, &scans, options)) { + assert.Equal(t, http.StatusNotFound, rec.Code) + assert.Contains(t, rec.Body.String(), "Not found") + } +} + +func Test_scansHandler(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/scans", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + scans := []string{"test-scan"} + + if assert.NoError(t, scansHandler(c, &scans)) { + assert.Equal(t, http.StatusNotFound, rec.Code) + assert.Contains(t, rec.Body.String(), "test-scan") + } +} + +func Test_healthHandler(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/health", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + if assert.NoError(t, healthHandler(c)) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Contains(t, rec.Body.String(), "ok") + } +} diff --git a/pkg/server/utils_test.go b/pkg/server/utils_test.go new file mode 100644 index 00000000..0628e5e2 --- /dev/null +++ b/pkg/server/utils_test.go @@ -0,0 +1,29 @@ +package server + +import "testing" + +func TestGenerateRandomToken(t *testing.T) { + type args struct { + url string + } + tests := []struct { + name string + args args + }{ + {name: "Test case 1", args: args{url: "http://example.com"}}, + {name: "Test case 2", args: args{url: "http://example.org"}}, + {name: "Test case 3", args: args{url: "http://example.net"}}, + {name: "Test case 4", args: args{url: "http://example.edu"}}, + {name: "Test case 5", args: args{url: "http://example.gov"}}, + } + tokens := make(map[string]bool) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GenerateRandomToken(tt.args.url) + if _, exists := tokens[got]; exists { + t.Errorf("GenerateRandomToken() generated a duplicate token: %v", got) + } + tokens[got] = true + }) + } +} diff --git a/pkg/verification/verify.go b/pkg/verification/verify.go index 37af12bc..ec4e6e19 100644 --- a/pkg/verification/verify.go +++ b/pkg/verification/verify.go @@ -2,57 +2,35 @@ package verification import ( "fmt" - "io/ioutil" "strings" "github.com/PuerkitoBio/goquery" ) -// VerifyReflectionWithLine is check reflected param for mining +// VerifyReflectionWithLine checks reflected param for mining func VerifyReflectionWithLine(body, payload string) (bool, int) { bodyArray := strings.Split(body, "\n") - count := 0 for l, v := range bodyArray { if strings.Contains(v, payload) { - count = count + l + 1 + return true, l + 1 } } - if count != 0 { - return true, count - } return false, 0 } -// VerifyReflection is check reflected param for xss and mining +// VerifyReflection checks reflected param for xss and mining func VerifyReflection(body, payload string) bool { - if strings.Contains(body, payload) { - return true - } - return false + return strings.Contains(body, payload) } -// VerifyDOM is check success inject on code -func VerifyDOM(s string) bool { //(body io.ReadCloser) bool { - - body := ioutil.NopCloser(strings.NewReader(s)) // r type is io.ReadCloser - defer body.Close() - - // Load the HTML document - doc, err := goquery.NewDocumentFromReader(body) - check := false +// VerifyDOM checks success inject on code +func VerifyDOM(s string) bool { + doc, err := goquery.NewDocumentFromReader(strings.NewReader(s)) if err != nil { fmt.Println(err) return false } - // Find the review items - doc.Find(".dalfox").Each(func(i int, s *goquery.Selection) { - check = true - }) - if !check { - doc.Find("#dalfox").Each(func(i int, s *goquery.Selection) { - // For each item found, get the band and title - check = true - }) - } - return check + + // Check for elements with class "dalfox" or id "dalfox" + return doc.Find(".dalfox").Length() > 0 || doc.Find("#dalfox").Length() > 0 }