ffuff/pkg/interactive/termhandler.go
2023-02-06 11:20:28 +02:00

320 lines
9.4 KiB
Go

package interactive
import (
"bufio"
"fmt"
"strconv"
"strings"
"time"
"github.com/ffuf/ffuf/v2/pkg/ffuf"
)
type interactive struct {
Job *ffuf.Job
paused bool
}
func Handle(job *ffuf.Job) error {
i := interactive{job, false}
tty, err := termHandle()
if err != nil {
return err
}
defer tty.Close()
inreader := bufio.NewScanner(tty)
inreader.Split(bufio.ScanLines)
for inreader.Scan() {
i.handleInput(inreader.Bytes())
}
return nil
}
func (i *interactive) handleInput(in []byte) {
instr := string(in)
args := strings.Split(strings.TrimSpace(instr), " ")
if len(args) == 1 && args[0] == "" {
// Enter pressed - toggle interactive state
i.paused = !i.paused
if i.paused {
i.Job.Pause()
time.Sleep(500 * time.Millisecond)
i.printBanner()
} else {
i.Job.Resume()
}
} else {
switch args[0] {
case "?":
i.printHelp()
case "help":
i.printHelp()
case "resume":
i.paused = false
i.Job.Resume()
case "restart":
i.Job.Reset(false)
i.paused = false
i.Job.Output.Info("Restarting the current ffuf job!")
i.Job.Resume()
case "show":
for _, r := range i.Job.Output.GetCurrentResults() {
i.Job.Output.PrintResult(r)
}
case "savejson":
if len(args) < 2 {
i.Job.Output.Error("Please define the filename")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"savejson\"")
} else {
err := i.Job.Output.SaveFile(args[1], "json")
if err != nil {
i.Job.Output.Error(fmt.Sprintf("%s", err))
} else {
i.Job.Output.Info("Output file successfully saved!")
}
}
case "fc":
if len(args) < 2 {
i.Job.Output.Error("Please define a value for status code filter, or \"none\" for removing it")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"fc\"")
} else {
i.updateFilter("status", args[1], true)
i.Job.Output.Info("New status code filter value set")
}
case "afc":
if len(args) < 2 {
i.Job.Output.Error("Please define a value to append to status code filter")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"afc\"")
} else {
i.appendFilter("status", args[1])
i.Job.Output.Info("New status code filter value set")
}
case "fl":
if len(args) < 2 {
i.Job.Output.Error("Please define a value for line count filter, or \"none\" for removing it")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"fl\"")
} else {
i.updateFilter("line", args[1], true)
i.Job.Output.Info("New line count filter value set")
}
case "afl":
if len(args) < 2 {
i.Job.Output.Error("Please define a value to append to line count filter")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"afl\"")
} else {
i.appendFilter("line", args[1])
i.Job.Output.Info("New line count filter value set")
}
case "fw":
if len(args) < 2 {
i.Job.Output.Error("Please define a value for word count filter, or \"none\" for removing it")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"fw\"")
} else {
i.updateFilter("word", args[1], true)
i.Job.Output.Info("New word count filter value set")
}
case "afw":
if len(args) < 2 {
i.Job.Output.Error("Please define a value to append to word count filter")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"afw\"")
} else {
i.appendFilter("word", args[1])
i.Job.Output.Info("New word count filter value set")
}
case "fs":
if len(args) < 2 {
i.Job.Output.Error("Please define a value for response size filter, or \"none\" for removing it")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"fs\"")
} else {
i.updateFilter("size", args[1], true)
i.Job.Output.Info("New response size filter value set")
}
case "afs":
if len(args) < 2 {
i.Job.Output.Error("Please define a value to append to size filter")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"afs\"")
} else {
i.appendFilter("size", args[1])
i.Job.Output.Info("New response size filter value set")
}
case "ft":
if len(args) < 2 {
i.Job.Output.Error("Please define a value for response time filter, or \"none\" for removing it")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"ft\"")
} else {
i.updateFilter("time", args[1], true)
i.Job.Output.Info("New response time filter value set")
}
case "aft":
if len(args) < 2 {
i.Job.Output.Error("Please define a value to append to response time filter")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"aft\"")
} else {
i.appendFilter("time", args[1])
i.Job.Output.Info("New response time filter value set")
}
case "queueshow":
i.printQueue()
case "queuedel":
if len(args) < 2 {
i.Job.Output.Error("Please define the index of a queued job to remove. Use \"queueshow\" for listing of jobs.")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"queuedel\"")
} else {
i.deleteQueue(args[1])
}
case "queueskip":
i.Job.SkipQueue()
i.Job.Output.Info("Skipping to the next queued job")
case "rate":
if len(args) < 2 {
i.Job.Output.Error("Please define the new rate")
} else if len(args) > 2 {
i.Job.Output.Error("Too many arguments for \"rate\"")
} else {
newrate, err := strconv.Atoi(args[1])
if err != nil {
i.Job.Output.Error(fmt.Sprintf("Could not adjust rate: %s", err))
} else {
i.Job.Rate.ChangeRate(newrate)
}
}
default:
if i.paused {
i.Job.Output.Warning(fmt.Sprintf("Unknown command: \"%s\". Enter \"help\" for a list of available commands", args[0]))
} else {
i.Job.Output.Error("NOPE")
}
}
}
if i.paused {
i.printPrompt()
}
}
func (i *interactive) refreshResults() {
results := make([]ffuf.Result, 0)
filters := i.Job.Config.MatcherManager.GetFilters()
for _, filter := range filters {
for _, res := range i.Job.Output.GetCurrentResults() {
fakeResp := &ffuf.Response{
StatusCode: res.StatusCode,
ContentLines: res.ContentLength,
ContentWords: res.ContentWords,
ContentLength: res.ContentLength,
}
filterOut, _ := filter.Filter(fakeResp)
if !filterOut {
results = append(results, res)
}
}
}
i.Job.Output.SetCurrentResults(results)
}
func (i *interactive) updateFilter(name, value string, replace bool) {
if value == "none" {
i.Job.Config.MatcherManager.RemoveFilter(name)
} else {
_ = i.Job.Config.MatcherManager.AddFilter(name, value, replace)
}
i.refreshResults()
}
func (i *interactive) appendFilter(name, value string) {
i.updateFilter(name, value, false)
}
func (i *interactive) printQueue() {
if len(i.Job.QueuedJobs()) > 0 {
i.Job.Output.Raw("Queued jobs:\n")
for index, job := range i.Job.QueuedJobs() {
postfix := ""
if index == 0 {
postfix = " (active job)"
}
i.Job.Output.Raw(fmt.Sprintf(" [%d] : %s%s\n", index, job.Url, postfix))
}
} else {
i.Job.Output.Info("Job queue is empty")
}
}
func (i *interactive) deleteQueue(in string) {
index, err := strconv.Atoi(in)
if err != nil {
i.Job.Output.Warning(fmt.Sprintf("Not a number: %s", in))
} else {
if index < 0 || index > len(i.Job.QueuedJobs())-1 {
i.Job.Output.Warning("No such queued job. Use \"queueshow\" to list the jobs in queue")
} else if index == 0 {
i.Job.Output.Warning("Cannot delete the currently running job. Use \"queueskip\" to advance to the next one")
} else {
i.Job.DeleteQueueItem(index)
i.Job.Output.Info("Job successfully deleted!")
}
}
}
func (i *interactive) printBanner() {
i.Job.Output.Raw("entering interactive mode\ntype \"help\" for a list of commands, or ENTER to resume.\n")
}
func (i *interactive) printPrompt() {
i.Job.Output.Raw("> ")
}
func (i *interactive) printHelp() {
var fc, fl, fs, ft, fw string
for name, filter := range i.Job.Config.MatcherManager.GetFilters() {
switch name {
case "status":
fc = "(active: " + filter.Repr() + ")"
case "line":
fl = "(active: " + filter.Repr() + ")"
case "word":
fw = "(active: " + filter.Repr() + ")"
case "size":
fs = "(active: " + filter.Repr() + ")"
case "time":
ft = "(active: " + filter.Repr() + ")"
}
}
rate := fmt.Sprintf("(active: %d)", i.Job.Config.Rate)
help := `
available commands:
afc [value] - append to status code filter %s
fc [value] - (re)configure status code filter %s
afl [value] - append to line count filter %s
fl [value] - (re)configure line count filter %s
afw [value] - append to word count filter %s
fw [value] - (re)configure word count filter %s
afs [value] - append to size filter %s
fs [value] - (re)configure size filter %s
aft [value] - append to time filter %s
ft [value] - (re)configure time filter %s
rate [value] - adjust rate of requests per second %s
queueshow - show job queue
queuedel [number] - delete a job in the queue
queueskip - advance to the next queued job
restart - restart and resume the current ffuf job
resume - resume current ffuf job (or: ENTER)
show - show results for the current job
savejson [filename] - save current matches to a file
help - you are looking at it
`
i.Job.Output.Raw(fmt.Sprintf(help, fc, fc, fl, fl, fw, fw, fs, fs, ft, ft, rate))
}