Skip to content

Commit 7001edc

Browse files
feat: responses saving in output folder (for -sr / -srd) and printing in console as json output (for -json)
1 parent 9e5c233 commit 7001edc

File tree

9 files changed

+146
-82
lines changed

9 files changed

+146
-82
lines changed

cmd/cariddi/main.go

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,25 +69,26 @@ func main() {
6969
// Setup the config according to the flags that were
7070
// passed via the CLI
7171
config := &crawler.Scan{
72-
Delay: flags.Delay,
73-
Concurrency: flags.Concurrency,
74-
Ignore: flags.Ignore,
75-
IgnoreTxt: flags.IgnoreTXT,
76-
Cache: flags.Cache,
77-
JSON: flags.JSON,
78-
Timeout: flags.Timeout,
79-
Intensive: flags.Intensive,
80-
Rua: flags.Rua,
81-
Proxy: flags.Proxy,
82-
SecretsFlag: flags.Secrets,
83-
Plain: flags.Plain,
84-
EndpointsFlag: flags.Endpoints,
85-
FileType: flags.Extensions,
86-
ErrorsFlag: flags.Errors,
87-
InfoFlag: flags.Info,
88-
Debug: flags.Debug,
89-
UserAgent: flags.UserAgent,
90-
StoreResp: flags.StoreResp,
72+
Delay: flags.Delay,
73+
Concurrency: flags.Concurrency,
74+
Ignore: flags.Ignore,
75+
IgnoreTxt: flags.IgnoreTXT,
76+
Cache: flags.Cache,
77+
JSON: flags.JSON,
78+
Timeout: flags.Timeout,
79+
Intensive: flags.Intensive,
80+
Rua: flags.Rua,
81+
Proxy: flags.Proxy,
82+
SecretsFlag: flags.Secrets,
83+
Plain: flags.Plain,
84+
EndpointsFlag: flags.Endpoints,
85+
FileType: flags.Extensions,
86+
ErrorsFlag: flags.Errors,
87+
InfoFlag: flags.Info,
88+
Debug: flags.Debug,
89+
UserAgent: flags.UserAgent,
90+
StoreResp: flags.StoreResp,
91+
StoredRespPath: flags.StoredRespDir,
9192
}
9293

9394
// Read the targets from standard input.
@@ -118,18 +119,18 @@ func main() {
118119
// Create output files if needed (txt / html).
119120
config.Txt = ""
120121
if flags.TXTout != "" {
121-
config.Txt = fileUtils.CreateOutputFile(flags.TXTout, "results", "txt")
122+
config.Txt = fileUtils.CreateOutputFile(flags.TXTout, "results", "txt", config.StoredRespPath)
122123
}
123124

124125
var ResultHTML = ""
125126
if flags.HTMLout != "" {
126-
ResultHTML = fileUtils.CreateOutputFile(flags.HTMLout, "", "html")
127+
ResultHTML = fileUtils.CreateOutputFile(flags.HTMLout, "", "html", config.StoredRespPath)
127128
output.BannerHTML(ResultHTML)
128129
output.HeaderHTML("Results", ResultHTML)
129130
}
130131

131132
if config.StoreResp {
132-
fileUtils.CreateIndexOutputFile("index.responses.txt")
133+
fileUtils.CreateIndexOutputFile("index.responses.txt", config.StoredRespPath)
133134
}
134135

135136
// Read headers if needed

internal/file/file.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,16 @@ const (
4242
Permission0644 = 0644
4343
)
4444

45+
// constant defined in output.go as well, for circular dependency
46+
const (
47+
CariddiOutputFolder = "output-cariddi"
48+
)
49+
4550
// CreateOutputFolder creates the output folder
4651
// If it fails exits with an error message.
47-
func CreateOutputFolder() {
52+
func CreateOutputFolder(outputDir string) {
4853
// Create a folder/directory at a full qualified path
49-
err := os.Mkdir("output-cariddi", Permission0755)
54+
err := os.MkdirAll(outputDir, Permission0755)
5055
if err != nil {
5156
fmt.Println("Can't create output folder.")
5257
os.Exit(1)
@@ -56,9 +61,9 @@ func CreateOutputFolder() {
5661
// CreateHostOutputFolder creates the host output folder
5762
// for the HTTP responses.
5863
// If it fails exits with an error message.
59-
func CreateHostOutputFolder(host string) {
64+
func CreateHostOutputFolder(host string, outputDir string) {
6065
// Create a folder/directory at a full qualified path
61-
err := os.MkdirAll(filepath.Join("output-cariddi", host), Permission0755)
66+
err := os.MkdirAll(filepath.Join(outputDir, host), Permission0755)
6267
if err != nil {
6368
fmt.Println("Can't create host output folder.")
6469
os.Exit(1)
@@ -71,21 +76,25 @@ func CreateHostOutputFolder(host string) {
7176
// already exists, if yes asks the user if cariddi has to overwrite it;
7277
// if no cariddi creates it.
7378
// Whenever an instruction fails, it exits with an error message.
74-
func CreateOutputFile(target string, subcommand string, format string) string {
79+
func CreateOutputFile(target string, subcommand string, format string, outputDir string) string {
80+
if outputDir == "" {
81+
outputDir = CariddiOutputFolder
82+
}
83+
7584
target = ReplaceBadCharacterOutput(target)
7685

7786
var filename string
7887
if subcommand != "" {
79-
filename = filepath.Join("output-cariddi", target+"."+subcommand+"."+format)
88+
filename = filepath.Join(outputDir, target+"."+subcommand+"."+format)
8089
} else {
81-
filename = filepath.Join("output-cariddi", target+"."+format)
90+
filename = filepath.Join(outputDir, target+"."+format)
8291
}
8392

8493
_, err := os.Stat(filename)
8594

8695
if os.IsNotExist(err) {
87-
if _, err := os.Stat("output-cariddi/"); os.IsNotExist(err) {
88-
CreateOutputFolder()
96+
if _, err := os.Stat(outputDir + "/"); os.IsNotExist(err) {
97+
CreateOutputFolder(outputDir)
8998
}
9099
// If the file doesn't exist, create it.
91100
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, Permission0644)
@@ -119,15 +128,19 @@ func CreateOutputFile(target string, subcommand string, format string) string {
119128
// It creates the output folder if needed, then checks if the index output file
120129
// already exists, if no cariddi creates it.
121130
// Whenever an instruction fails, it exits with an error message.
122-
func CreateIndexOutputFile(filename string) {
131+
func CreateIndexOutputFile(filename string, outputDir string) {
132+
if outputDir == "" {
133+
outputDir = CariddiOutputFolder
134+
}
135+
123136
_, err := os.Stat(filename)
124137

125138
if os.IsNotExist(err) {
126-
if _, err := os.Stat("output-cariddi/"); os.IsNotExist(err) {
127-
CreateOutputFolder()
139+
if _, err := os.Stat(outputDir + "/"); os.IsNotExist(err) {
140+
CreateOutputFolder(outputDir)
128141
}
129142
// If the file doesn't exist, create it.
130-
filename = filepath.Join("output-cariddi", filename)
143+
filename = filepath.Join(outputDir, filename)
131144

132145
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, Permission0644)
133146
if err != nil {

pkg/crawler/colly.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,17 @@ func New(scan *Scan) *Results {
134134
fmt.Println(r.Request.URL)
135135
}
136136

137-
if scan.StoreResp {
138-
err := output.StoreHTTPResponse(r)
137+
var outputPath string
138+
139+
if scan.StoreResp || len(scan.StoredRespPath) > 0 {
140+
outputDir := scan.StoredRespPath
141+
142+
if outputDir == "" {
143+
outputDir = output.CariddiOutputFolder
144+
}
145+
146+
var err error
147+
outputPath, err = output.StoreHTTPResponse(r, outputDir)
139148
if err != nil {
140149
log.Println(err)
141150
}
@@ -193,7 +202,7 @@ func New(scan *Scan) *Results {
193202

194203
if scan.JSON {
195204
jsonOutput, err := output.GetJSONString(
196-
r, secrets, parameters, filetype, errors, infos,
205+
r, secrets, parameters, filetype, errors, infos, outputPath,
197206
)
198207

199208
if err == nil {

pkg/crawler/options.go

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,27 @@ type Results struct {
3939

4040
type Scan struct {
4141
// Flags
42-
Cache bool
43-
Debug bool
44-
EndpointsFlag bool
45-
ErrorsFlag bool
46-
InfoFlag bool
47-
Intensive bool
48-
Plain bool
49-
Rua bool
50-
SecretsFlag bool
51-
Ignore string
52-
IgnoreTxt string
53-
JSON bool
54-
HTML string
55-
Proxy string
56-
Target string
57-
Txt string
58-
UserAgent string
59-
FileType int
60-
Headers map[string]string
61-
StoreResp bool
42+
Cache bool
43+
Debug bool
44+
EndpointsFlag bool
45+
ErrorsFlag bool
46+
InfoFlag bool
47+
Intensive bool
48+
Plain bool
49+
Rua bool
50+
SecretsFlag bool
51+
Ignore string
52+
IgnoreTxt string
53+
JSON bool
54+
HTML string
55+
Proxy string
56+
Target string
57+
Txt string
58+
UserAgent string
59+
FileType int
60+
Headers map[string]string
61+
StoreResp bool
62+
StoredRespPath string
6263

6364
// Settings
6465
Concurrency int

pkg/input/flags.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ type Input struct {
9494
UserAgent string
9595
// StoreResp stores HTTP responses.
9696
StoreResp bool
97+
// StoredRespDir stores HTTP responses to the directory provided.
98+
StoredRespDir string
9799
}
98100

99101
// ScanFlag defines all the options taken
@@ -142,6 +144,8 @@ func ScanFlag() Input {
142144

143145
storeRespPtr := flag.Bool("sr", false, "Store HTTP responses.")
144146

147+
storedRespDirPtr := flag.String("srd", "", "Stores HTTP responses to the directory provided.")
148+
145149
flag.Parse()
146150

147151
result := Input{
@@ -173,6 +177,7 @@ func ScanFlag() Input {
173177
*debugPtr,
174178
*userAgentPtr,
175179
*storeRespPtr,
180+
*storedRespDirPtr,
176181
}
177182

178183
return result

pkg/output/jsonl.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type JSONData struct {
4444
ContentType string `json:"content_type,omitempty"`
4545
ContentLength int `json:"content_length,omitempty"`
4646
Matches *MatcherResults `json:"matches,omitempty"`
47+
OutputPath string `json:"output_path,omitempty"`
4748
// Host string `json:"host"` # TODO: Available in Colly 2.x
4849
}
4950

@@ -67,6 +68,7 @@ func GetJSONString(
6768
filetype *scanner.FileType,
6869
errors []scanner.ErrorMatched,
6970
infos []scanner.InfoMatched,
71+
outputPath string,
7072
) ([]byte, error) {
7173
// Parse response headers
7274
headers := r.Headers
@@ -136,6 +138,7 @@ func GetJSONString(
136138
ContentType: contentType,
137139
ContentLength: contentLength,
138140
Matches: matcherResults,
141+
OutputPath: outputPath,
139142
// Host: "", // TODO
140143
}
141144

pkg/output/jsonl_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func TestJSONOutput(t *testing.T) {
113113
errors []scanner.ErrorMatched
114114
infos []scanner.InfoMatched
115115
want string
116+
outputPath string
116117
}{
117118
{
118119
name: "test_all_findings",
@@ -123,6 +124,7 @@ func TestJSONOutput(t *testing.T) {
123124
errors: errors,
124125
infos: infos,
125126
want: `{"url":"http://test.com.pdf?id=5","method":"GET","status_code":200,"words":1,"lines":1,"content_type":"application/pdf","content_length":128,"matches":{"filetype":{"extension":"pdf","severity":7},"parameters":[{"name":"id","attacks":[]}],"errors":[{"name":"MySQL error","match":"it is a MySQL error happening"}],"infos":[{"name":"info1","match":"its my pleasure to inform you on this great day"}],"secrets":[{"name":"mysecret","match":"it's a random day for my secret regex to be found"}]}}`, //nolint:lll
127+
outputPath: "C:\\testDir1\\testDir2",
126128
},
127129
{
128130
name: "test_all_findings_nocontent",
@@ -133,6 +135,7 @@ func TestJSONOutput(t *testing.T) {
133135
errors: errors,
134136
infos: infos,
135137
want: `{"url":"http://test.com.pdf?id=5","method":"GET","status_code":200,"words":1,"lines":1,"matches":{"filetype":{"extension":"pdf","severity":7},"parameters":[{"name":"id","attacks":[]}],"errors":[{"name":"MySQL error","match":"it is a MySQL error happening"}],"infos":[{"name":"info1","match":"its my pleasure to inform you on this great day"}],"secrets":[{"name":"mysecret","match":"it's a random day for my secret regex to be found"}]}}`, //nolint:lll
138+
outputPath: "C:\\testDir1\\testDir2",
136139
},
137140
{
138141
name: "test_no_findings",
@@ -143,6 +146,7 @@ func TestJSONOutput(t *testing.T) {
143146
errors: []scanner.ErrorMatched{},
144147
infos: []scanner.InfoMatched{},
145148
want: `{"url":"http://test.com.pdf?id=5","method":"GET","status_code":200,"words":1,"lines":1,"content_type":"application/pdf","content_length":128}`, //nolint: all
149+
outputPath: "C:\\testDir1\\testDir2",
146150
},
147151
{
148152
name: "test_only_secrets",
@@ -153,6 +157,7 @@ func TestJSONOutput(t *testing.T) {
153157
errors: []scanner.ErrorMatched{},
154158
infos: []scanner.InfoMatched{},
155159
want: `{"url":"http://test.com.pdf?id=5","method":"GET","status_code":200,"words":1,"lines":1,"content_type":"application/pdf","content_length":128,"matches":{"secrets":[{"name":"mysecret","match":"it's a random day for my secret regex to be found"}]}}`, //nolint:lll
160+
outputPath: "C:\\testDir1\\testDir2",
156161
},
157162
{
158163
name: "test_only_params",
@@ -163,6 +168,7 @@ func TestJSONOutput(t *testing.T) {
163168
errors: []scanner.ErrorMatched{},
164169
infos: []scanner.InfoMatched{},
165170
want: `{"url":"http://test.com.pdf?id=5","method":"GET","status_code":200,"words":1,"lines":1,"content_type":"application/pdf","content_length":128,"matches":{"parameters":[{"name":"id","attacks":[]}]}}`, //nolint:lll
171+
outputPath: "C:\\testDir1\\testDir2",
166172
},
167173
{
168174
name: "test_only_errors",
@@ -173,6 +179,7 @@ func TestJSONOutput(t *testing.T) {
173179
errors: errors,
174180
infos: []scanner.InfoMatched{},
175181
want: `{"url":"http://test.com.pdf?id=5","method":"GET","status_code":200,"words":1,"lines":1,"content_type":"application/pdf","content_length":128,"matches":{"errors":[{"name":"MySQL error","match":"it is a MySQL error happening"}]}}`, //nolint:lll
182+
outputPath: "C:\\testDir1\\testDir2",
176183
},
177184
{
178185
name: "test_only_infos",
@@ -183,12 +190,24 @@ func TestJSONOutput(t *testing.T) {
183190
errors: []scanner.ErrorMatched{},
184191
infos: infos,
185192
want: `{"url":"http://test.com.pdf?id=5","method":"GET","status_code":200,"words":1,"lines":1,"content_type":"application/pdf","content_length":128,"matches":{"infos":[{"name":"info1","match":"its my pleasure to inform you on this great day"}]}}`, //nolint:lll
193+
outputPath: "C:\\testDir1\\testDir2",
194+
},
195+
{
196+
name: "test_no_outputPath",
197+
r: resp,
198+
secrets: []scanner.SecretMatched{},
199+
parameters: []scanner.Parameter{},
200+
filetype: &scanner.FileType{},
201+
errors: []scanner.ErrorMatched{},
202+
infos: infos,
203+
want: `{"url":"http://test.com.pdf?id=5","method":"GET","status_code":200,"words":1,"lines":1,"content_type":"application/pdf","content_length":128,"matches":{"infos":[{"name":"info1","match":"its my pleasure to inform you on this great day"}]}}`, //nolint:lll
204+
outputPath: "",
186205
},
187206
}
188207

189208
for _, tt := range tests {
190209
t.Run(tt.name, func(t *testing.T) {
191-
if got, _ := output.GetJSONString(tt.r, tt.secrets, tt.parameters, tt.filetype, tt.errors, tt.infos); !reflect.DeepEqual(string(got), tt.want) { //nolint:lll
210+
if got, _ := output.GetJSONString(tt.r, tt.secrets, tt.parameters, tt.filetype, tt.errors, tt.infos, tt.outputPath); !reflect.DeepEqual(string(got), tt.want) { //nolint:lll
192211
t.Errorf("GetJSONString\n%v", string(got))
193212
t.Errorf("want\n%v", tt.want)
194213
}

0 commit comments

Comments
 (0)