Passed
Pull Request — master (#52)
by Stefano
02:59
created

cmd.strigifyCookies   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
package cmd
2
3
import (
4
	"fmt"
5
	"net/http"
6
	"net/url"
7
	"time"
8
9
	"github.com/pkg/errors"
10
	"github.com/sirupsen/logrus"
11
	"github.com/spf13/cobra"
12
	"github.com/stefanoj3/dirstalk/pkg/dictionary"
13
	"github.com/stefanoj3/dirstalk/pkg/scan"
14
	"github.com/stefanoj3/dirstalk/pkg/scan/client"
15
	"github.com/stefanoj3/dirstalk/pkg/scan/producer"
16
)
17
18
func NewScanCommand(logger *logrus.Logger) (*cobra.Command, error) {
0 ignored issues
show
introduced by
exported function NewScanCommand should have comment or be unexported
Loading history...
19
	cmd := &cobra.Command{
20
		Use:   "scan [url]",
21
		Short: "Scan the given URL",
22
		RunE:  buildScanFunction(logger),
23
	}
24
25
	cmd.Flags().StringP(
26
		flagDictionary,
27
		flagDictionaryShort,
28
		"",
29
		"dictionary to use for the scan (path to local file or remote url)",
30
	)
31
	err := cmd.MarkFlagFilename(flagDictionary)
32
	if err != nil {
33
		return nil, err
34
	}
35
36
	err = cmd.MarkFlagRequired(flagDictionary)
37
	if err != nil {
38
		return nil, err
39
	}
40
41
	cmd.Flags().StringSlice(
42
		flagHTTPMethods,
43
		[]string{"GET"},
44
		"comma separated list of http methods to use; eg: GET,POST,PUT",
45
	)
46
47
	cmd.Flags().IntP(
48
		flagThreads,
49
		flagThreadsShort,
50
		3,
51
		"amount of threads for concurrent requests",
52
	)
53
54
	cmd.Flags().IntP(
55
		flagHTTPTimeout,
56
		"",
57
		5000,
58
		"timeout in milliseconds",
59
	)
60
61
	cmd.Flags().IntP(
62
		flagScanDepth,
63
		"",
64
		3,
65
		"scan depth",
66
	)
67
68
	cmd.Flags().StringP(
69
		flagSocks5Host,
70
		"",
71
		"",
72
		"socks5 host to use",
73
	)
74
75
	cmd.Flags().StringP(
76
		flagUserAgent,
77
		"",
78
		"",
79
		"user agent to use for http requests",
80
	)
81
82
	cmd.Flags().BoolP(
83
		flagCookieJar,
84
		"",
85
		false,
86
		"enables the use of a cookie jar: it will retain any cookie sent "+
87
			"from the server and send them for the following requests",
88
	)
89
90
	cmd.Flags().StringArray(
91
		flagCookie,
92
		[]string{},
93
		"cookie to add to each request; eg name=value (can be specified multiple times)",
94
	)
95
96
	cmd.Flags().StringArray(
97
		flagHeader,
98
		[]string{},
99
		"header to add to each request; eg name=value (can be specified multiple times)",
100
	)
101
102
	return cmd, nil
103
}
104
105
func buildScanFunction(logger *logrus.Logger) func(cmd *cobra.Command, args []string) error {
106
	f := func(cmd *cobra.Command, args []string) error {
107
		u, err := getURL(args)
108
		if err != nil {
109
			return err
110
		}
111
112
		cnf, err := scanConfigFromCmd(cmd)
113
		if err != nil {
114
			return errors.Wrap(err, "failed to build config")
115
		}
116
117
		return StartScan(logger, cnf, u)
118
	}
119
120
	return f
121
}
122
123
func getURL(args []string) (*url.URL, error) {
124
	if len(args) == 0 {
125
		return nil, errors.New("no URL provided")
126
	}
127
128
	arg := args[0]
129
130
	u, err := url.ParseRequestURI(arg)
131
	if err != nil {
132
		return nil, errors.Wrap(err, "the first argument must be a valid url")
133
	}
134
135
	return u, nil
136
}
137
138
// StartScan is a convenience method that wires together all the dependencies needed to start a scan
139
func StartScan(logger *logrus.Logger, cnf *scan.Config, u *url.URL) error {
140
	c, err := client.NewClientFromConfig(
141
		cnf.TimeoutInMilliseconds,
142
		cnf.Socks5Url,
143
		cnf.UserAgent,
144
		cnf.UseCookieJar,
145
		cnf.Cookies,
146
		cnf.Headers,
147
		u,
148
	)
149
	if err != nil {
150
		return errors.Wrap(err, "failed to build client")
151
	}
152
153
	dict, err := dictionary.NewDictionaryFrom(cnf.DictionaryPath, c)
154
	if err != nil {
155
		return errors.Wrap(err, "failed to build dictionary")
156
	}
157
158
	targetProducer := producer.NewDictionaryProducer(cnf.HTTPMethods, dict, cnf.ScanDepth)
159
	decoratedProducer := producer.NewReProducer(
160
		cnf.HTTPMethods,
161
		dict,
162
		targetProducer,
163
		time.Millisecond*(time.Duration(cnf.TimeoutInMilliseconds)+50),
164
	)
165
166
	s := scan.NewScanner(
167
		c,
168
		decoratedProducer,
169
		logger,
170
	)
171
172
	logger.WithFields(logrus.Fields{
173
		"url":               u.String(),
174
		"threads":           cnf.Threads,
175
		"dictionary-length": len(dict),
176
		"scan-depth":        cnf.ScanDepth,
177
		"timeout":           cnf.TimeoutInMilliseconds,
178
		"socks5":            cnf.Socks5Url,
179
		"cookies":           strigifyCookies(cnf.Cookies),
180
		"cookie-jar":        cnf.UseCookieJar,
181
		"headers":           stringyfyHeaders(cnf.Headers),
182
		"user-agent":        cnf.UserAgent,
183
	}).Info("Starting scan")
184
185
	resultLogger := scan.NewResultLogger(logger)
186
	summarizer := scan.NewResultSummarizer(logger.Out)
187
	for result := range s.Scan(u, cnf.Threads) {
188
		decoratedProducer.Reproduce(result)
189
		resultLogger.Log(result)
190
		summarizer.Add(result)
191
	}
192
193
	summarizer.Summarize()
194
195
	logger.Info("Finished scan")
196
197
	return nil
198
}
199
200
func strigifyCookies(cookies []*http.Cookie) string {
201
	result := ""
202
203
	for _, cookie := range cookies {
204
		result += fmt.Sprintf("{%s=%s}", cookie.Name, cookie.Value)
205
	}
206
207
	return result
208
}
209
210
func stringyfyHeaders(headers map[string]string) string {
211
	result := ""
212
213
	for name, value := range headers {
214
		result += fmt.Sprintf("{%s:%s}", name, value)
215
	}
216
217
	return result
218
}
219