Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/save responses to output directory provided in new -srd flag and also print if in json if -json flag enabled fixes #128, #129, #130 #161

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
16 changes: 11 additions & 5 deletions cmd/cariddi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ func main() {
StoreResp: flags.StoreResp,
}

config.OutputDir = output.CariddiOutputFolder
if flags.StoredRespDir != "" {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should perform some tests. What if config.StoreResp is not specified?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the two tags -sr and -srd, there are four possible scenarios:

  1. Both -sr and -srd are not set:

    • No HTTP response will be saved.
    • OutputDir will be set to its default value, and any other data that needs to be saved will be stored in this directory.
  2. Only -sr is set:

    • The HTTP response, along with any other data, will be saved in the OutputDir, which will be the default directory.
  3. Only -srd is set:

    • Everything, including the HTTP response, will be saved to the OutputDir provided with the -srd flag.
  4. Both -sr and -srd are set:

    • Everything will be saved to the OutputDir provided with the -srd flag.

In the config struct, I have used the StoreResp boolean as the key indicator of whether to store the response. Therefore, if the user provides only the -srd flag, we will set StoreResp to true.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can understand point 3 and 4 are the same right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just in point 3 we will set the StoreResp value to true

config.OutputDir = flags.StoredRespDir
config.StoreResp = true
}

// Read the targets from standard input.
targets := input.ScanTargets()

Expand Down Expand Up @@ -118,18 +124,18 @@ func main() {
// Create output files if needed (txt / html).
config.Txt = ""
if flags.TXTout != "" {
config.Txt = fileUtils.CreateOutputFile(flags.TXTout, "results", "txt")
config.Txt = fileUtils.CreateOutputFile(flags.TXTout, "results", "txt", config.OutputDir)
}

var ResultHTML = ""
if flags.HTMLout != "" {
ResultHTML = fileUtils.CreateOutputFile(flags.HTMLout, "", "html")
ResultHTML = fileUtils.CreateOutputFile(flags.HTMLout, "", "html", config.OutputDir)
output.BannerHTML(ResultHTML)
output.HeaderHTML("Results", ResultHTML)
}

if config.StoreResp {
fileUtils.CreateIndexOutputFile("index.responses.txt")
fileUtils.CreateIndexOutputFile("index.responses.txt", config.OutputDir)
}

// Read headers if needed
Expand Down Expand Up @@ -167,13 +173,13 @@ func main() {
// IF TXT OUTPUT >
if flags.TXTout != "" {
output.TxtOutput(flags, finalResults, finalSecret, finalEndpoints,
finalExtensions, finalErrors, finalInfos)
finalExtensions, finalErrors, finalInfos, config.OutputDir)
}

// IF HTML OUTPUT >
if flags.HTMLout != "" {
output.HTMLOutput(flags, ResultHTML, finalResults, finalSecret,
finalEndpoints, finalExtensions, finalErrors, finalInfos)
finalEndpoints, finalExtensions, finalErrors, finalInfos, config.OutputDir)
}

// If needed print secrets.
Expand Down
27 changes: 14 additions & 13 deletions internal/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ const (

// CreateOutputFolder creates the output folder
// If it fails exits with an error message.
func CreateOutputFolder() {
func CreateOutputFolder(outputDir string) {
// Create a folder/directory at a full qualified path
err := os.Mkdir("output-cariddi", Permission0755)
err := os.MkdirAll(outputDir, Permission0755)
if err != nil {
fmt.Println("Can't create output folder.")
os.Exit(1)
Expand All @@ -56,9 +56,9 @@ func CreateOutputFolder() {
// CreateHostOutputFolder creates the host output folder
// for the HTTP responses.
// If it fails exits with an error message.
func CreateHostOutputFolder(host string) {
func CreateHostOutputFolder(host string, outputDir string) {
// Create a folder/directory at a full qualified path
err := os.MkdirAll(filepath.Join("output-cariddi", host), Permission0755)
err := os.MkdirAll(filepath.Join(outputDir, host), Permission0755)
if err != nil {
fmt.Println("Can't create host output folder.")
os.Exit(1)
Expand All @@ -71,21 +71,21 @@ func CreateHostOutputFolder(host string) {
// already exists, if yes asks the user if cariddi has to overwrite it;
// if no cariddi creates it.
// Whenever an instruction fails, it exits with an error message.
func CreateOutputFile(target string, subcommand string, format string) string {
func CreateOutputFile(target string, subcommand string, format string, outputDir string) string {
target = ReplaceBadCharacterOutput(target)

var filename string
if subcommand != "" {
filename = filepath.Join("output-cariddi", target+"."+subcommand+"."+format)
filename = filepath.Join(outputDir, target+"."+subcommand+"."+format)
} else {
filename = filepath.Join("output-cariddi", target+"."+format)
filename = filepath.Join(outputDir, target+"."+format)
}

_, err := os.Stat(filename)

if os.IsNotExist(err) {
if _, err := os.Stat("output-cariddi/"); os.IsNotExist(err) {
CreateOutputFolder()
if _, err := os.Stat(fmt.Sprintf("%s/", outputDir)); os.IsNotExist(err) {
CreateOutputFolder(outputDir)
}
// If the file doesn't exist, create it.
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, Permission0644)
Expand Down Expand Up @@ -119,15 +119,15 @@ func CreateOutputFile(target string, subcommand string, format string) string {
// It creates the output folder if needed, then checks if the index output file
// already exists, if no cariddi creates it.
// Whenever an instruction fails, it exits with an error message.
func CreateIndexOutputFile(filename string) {
func CreateIndexOutputFile(filename string, outputDir string) {
_, err := os.Stat(filename)

if os.IsNotExist(err) {
if _, err := os.Stat("output-cariddi/"); os.IsNotExist(err) {
CreateOutputFolder()
if _, err := os.Stat(fmt.Sprintf("%s/", outputDir)); os.IsNotExist(err) {
CreateOutputFolder(outputDir)
}
// If the file doesn't exist, create it.
filename = filepath.Join("output-cariddi", filename)
filename = filepath.Join(outputDir, filename)

f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, Permission0644)
if err != nil {
Expand Down Expand Up @@ -161,6 +161,7 @@ func ReadFile(inputFile string) []string {
for scanner.Scan() {
text = append(text, scanner.Text())
}

file.Close()

return text
Expand Down
8 changes: 6 additions & 2 deletions pkg/crawler/colly.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,12 @@ func New(scan *Scan) *Results {
fmt.Println(r.Request.URL)
}

var outputPath string

if scan.StoreResp {
err := output.StoreHTTPResponse(r)
var err error
outputPath, err = output.StoreHTTPResponse(r, scan.OutputDir)

if err != nil {
log.Println(err)
}
Expand Down Expand Up @@ -193,7 +197,7 @@ func New(scan *Scan) *Results {

if scan.JSON {
jsonOutput, err := output.GetJSONString(
r, secrets, parameters, filetype, errors, infos,
r, secrets, parameters, filetype, errors, infos, outputPath,
)

if err == nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/crawler/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type Scan struct {
FileType int
Headers map[string]string
StoreResp bool
OutputDir string

// Settings
Concurrency int
Expand Down
6 changes: 5 additions & 1 deletion pkg/crawler/useragents.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import (
"time"
)

const (
maxRandomValue = 100
)

// genOsString generates a random OS string for a User Agent.
func genOsString() string {
source := rand.NewSource(time.Now().UnixNano())
Expand Down Expand Up @@ -151,7 +155,7 @@ func GenerateRandomUserAgent() string {
source := rand.NewSource(time.Now().UnixNano())
rng := rand.New(source)

decision := rng.Intn(100)
decision := rng.Intn(maxRandomValue)

var ua string
if decision%2 == 0 {
Expand Down
35 changes: 35 additions & 0 deletions pkg/input/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ package input
import (
"fmt"
"os"
"path/filepath"
"strings"

fileUtils "github.com/edoardottt/cariddi/internal/file"
Expand All @@ -47,6 +48,33 @@ func CheckOutputFile(input string) bool {
return true
}

// IsValidOutputPath checks if the directory is valid and, if created, cleans it up.
func CheckOutputPath(outputPath string) bool {
// Convert to absolute path if necessary
absPath, err := filepath.Abs(outputPath)
if err != nil {
return false
}

// Check if the directory already exists
_, err = os.Stat(absPath)
if err == nil {
// Directory exists, so it's valid, no need to delete
return true
}

// If the directory does not exist, try to create it
err = os.MkdirAll(absPath, os.ModePerm)
if err != nil {
return false
}

// Since we created the directory, clean it up
defer os.RemoveAll(absPath)

return true
}

// CheckFlags checks the flags taken as input.
func CheckFlags(flags Input) {
if flags.TXTout != "" {
Expand Down Expand Up @@ -127,4 +155,11 @@ func CheckFlags(flags Input) {
fmt.Println(" - cat urls | cariddi -headersfile headers.txt")
os.Exit(1)
}

if flags.StoredRespDir != "" {
if !CheckOutputPath(flags.StoredRespDir) {
fmt.Println("Validation failed for srd flag; there may be errors creating the output directory.")
os.Exit(1)
}
}
}
55 changes: 55 additions & 0 deletions pkg/input/check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package input_test

import (
"os"
"path/filepath"
"testing"

"github.com/edoardottt/cariddi/pkg/input"
)

// TestCheckOutputPath tests the CheckOutputPath function for valid and invalid cases.
func TestCheckOutputPath(t *testing.T) {
// Create a temporary directory for testing
tmpDir, err := os.MkdirTemp("", "testdir")
defer os.RemoveAll(tmpDir) // Cleanup after test

if err != nil {
t.Fatalf("Failed to create temporary directory: %v", err)
}

// Valid case: Existing directory
if !input.CheckOutputPath(tmpDir) {
t.Errorf("CheckOutputPath(%s) = false; want true", tmpDir)
}

// Verify that the existing directory is still present after check
_, err = os.Stat(tmpDir)
if err != nil {
t.Errorf("Existing directory %s should still be present after CheckOutputPath, but got error: %v", tmpDir, err)
}

// Cross-platform invalid cases
invalidPaths := []string{
// Null character is invalid on all platforms
filepath.Join(tmpDir, "invalid\000path"),
}

for _, invalidPath := range invalidPaths {
if input.CheckOutputPath(invalidPath) {
t.Errorf("CheckOutputPath(%s) = true; want false", invalidPath)
}
}

// Valid case: New directory creation and cleanup
newDir := filepath.Join(tmpDir, "newdir")
if !input.CheckOutputPath(newDir) {
t.Errorf("CheckOutputPath(%s) = false; want true", newDir)
}

// After check, the directory should be removed
_, err = os.Stat(newDir)
if err == nil || !os.IsNotExist(err) {
t.Errorf("CheckOutputPath should remove the directory, but it still exists: %s", newDir)
}
}
5 changes: 5 additions & 0 deletions pkg/input/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ type Input struct {
UserAgent string
// StoreResp stores HTTP responses.
StoreResp bool
// StoredRespDir stores HTTP responses to the directory provided.
StoredRespDir string
}

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

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

storedRespDirPtr := flag.String("srd", "", "Stores HTTP responses to the directory provided.")

flag.Parse()

result := Input{
Expand Down Expand Up @@ -173,6 +177,7 @@ func ScanFlag() Input {
*debugPtr,
*userAgentPtr,
*storeRespPtr,
*storedRespDirPtr,
}

return result
Expand Down
3 changes: 3 additions & 0 deletions pkg/output/jsonl.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type JSONData struct {
ContentType string `json:"content_type,omitempty"`
ContentLength int `json:"content_length,omitempty"`
Matches *MatcherResults `json:"matches,omitempty"`
OutputPath string `json:"output_path,omitempty"`
// Host string `json:"host"` # TODO: Available in Colly 2.x
}

Expand All @@ -67,6 +68,7 @@ func GetJSONString(
filetype *scanner.FileType,
errors []scanner.ErrorMatched,
infos []scanner.InfoMatched,
outputPath string,
) ([]byte, error) {
// Parse response headers
headers := r.Headers
Expand Down Expand Up @@ -136,6 +138,7 @@ func GetJSONString(
ContentType: contentType,
ContentLength: contentLength,
Matches: matcherResults,
OutputPath: outputPath,
// Host: "", // TODO
}

Expand Down
Loading