Skip to content

Commit

Permalink
Add replace mode for executing a program
Browse files Browse the repository at this point in the history
  • Loading branch information
rtpt-alexanderneumann committed Jul 14, 2023
1 parent eb52e06 commit 3ddca2d
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 3 deletions.
20 changes: 19 additions & 1 deletion cmd/fuzz/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,25 @@ Try different passwords for the user admin with HTTP Basic authentication:
monsoon fuzz --file passwords.txt \
--hide-status 403 \
--user admin:FUZZ \
--user admin:FUZZ \
http://example.com
Load usernames and passwords from several files:
monsoon fuzz \
--replace USER:file:usernames.txt \
--replace PASS:file:passwords.txt \
--hide-status 403 \
--user USER:PASS \
http://example.com
Load usernames and generate passwords with a script:
monsoon fuzz \
--replace USER:file:usernames.txt \
--replace PASS:exec:./gen_password.py \
--hide-status 403 \
--user USER:PASS \
http://example.com
Expand Down
9 changes: 8 additions & 1 deletion cmd/fuzz/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ func AddCommand(c *cobra.Command) {
fs.StringVar(&opts.RangeFormat, "range-format", "%d", "set `format` for range (when used with --range)")
fs.StringVarP(&opts.Filename, "file", "f", "", "read values from `filename`")
fs.StringArrayVar(&opts.Replace, "replace", []string{}, "add replace var `name:type:options` (valid types: 'file','range', "+
"and 'value', e.g. 'FUZZ:range:1-100'), mutually exclusive with --range and --file")
"'exec', and 'value', e.g. 'FUZZ:range:1-100'), mutually exclusive with --range and --file")

fs.StringVar(&opts.Logfile, "logfile", "", "write copy of printed messages to `filename`.log")
fs.StringVar(&opts.Logdir, "logdir", os.Getenv("MONSOON_LOG_DIR"), "automatically log all output to files in `dir`")
Expand Down Expand Up @@ -404,6 +404,13 @@ func setupProducer(ctx context.Context, opts *Options) (*producer.Multiplexer, e
multiplexer.AddSource(r.Name, src)
case "value":
multiplexer.AddSource(r.Name, producer.NewValue(r.Options))
case "exec":
err := producer.CheckExec(r.Options)
if err != nil {
return nil, fmt.Errorf("check replace %v: %w", r.Name, err)
}

multiplexer.AddSource(r.Name, producer.NewExec(r.Options))
default:
return nil, fmt.Errorf("unknown replace type %q", r.Type)
}
Expand Down
18 changes: 17 additions & 1 deletion cmd/fuzz/replace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestParseReplace(t *testing.T) {
var tests = []struct {
tests := []struct {
input string
replace Replace
err bool
Expand Down Expand Up @@ -60,6 +60,22 @@ func TestParseReplace(t *testing.T) {
Options: "1-100",
},
},
{
input: "ID:exec:./gen_id.sh",
replace: Replace{
Name: "ID",
Type: "exec",
Options: "./gen_id.sh",
},
},
{
input: `ID:exec:./gen_id.sh from-to`,
replace: Replace{
Name: "ID",
Type: "exec",
Options: "./gen_id.sh from-to",
},
},
}

for _, test := range tests {
Expand Down
105 changes: 105 additions & 0 deletions producer/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package producer

import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/exec"

"github.com/RedTeamPentesting/monsoon/shell"
"golang.org/x/sync/errgroup"
)

// Exec runs a command and produces each line the command prints.
type Exec struct {
cmd string
}

// statically ensure that *Exec implements Source
var _ Source = &Exec{}

// NewFile creates a new producer from a reader. If seekable is set to false
// (e.g. for stdin), Yield() returns an error for subsequent runs.
func NewExec(cmd string) *Exec {
return &Exec{cmd: cmd}
}

// Yield runs the command and sends all lines printed by it to ch and the number
// of items to the channel count. Sending stops and ch and count are closed
// when an error occurs or the context is cancelled.
func (e *Exec) Yield(ctx context.Context, ch chan<- string, count chan<- int) (err error) {
defer close(ch)
defer close(count)

args, err := shell.Split(e.cmd)
if err != nil {
return fmt.Errorf("error splitting command %q: %w", e.cmd, err)
}

commandOutput, commandOutputWriter := io.Pipe()

cmd := exec.CommandContext(ctx, args[0], args[1:]...)
cmd.Stdout = commandOutputWriter
cmd.Stderr = os.Stderr

eg, localCtx := errgroup.WithContext(ctx)

eg.Go(func() error {
err := cmd.Run()

// close the writer, ignoring any errors
_ = commandOutputWriter.Close()

return err
})

eg.Go(func() error {
// io.Copy(os.Stdout, commandOutput)

num := 0
sc := bufio.NewScanner(commandOutput)

for sc.Scan() {
num++

select {
case <-localCtx.Done():
return nil
case ch <- sc.Text():
}
}

fmt.Printf("scanner: done, num %v\n", num)

select {
case <-localCtx.Done():
case count <- num:
}

fmt.Printf("scanner: done, err %v\n", sc.Err())

return sc.Err()
})

fmt.Printf("exec main, waiting\n")
err = eg.Wait()
fmt.Printf("exec main, done, err: %v\n", err)
return err
}

// allow testing an exec command early before setting up the producer.
func CheckExec(cmd string) error {
args, err := shell.Split(cmd)
if err != nil {
return err
}

_, err = exec.LookPath(args[0])
if err != nil {
return err
}

return nil
}

0 comments on commit 3ddca2d

Please sign in to comment.