diff --git a/.golangci.yml b/.golangci.yml index 6c36914..cfc0617 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,6 @@ linters: enable: - asciicheck - bodyclose - - depguard - dogsled - dupl - errcheck diff --git a/README.md b/README.md index e487455..9f04995 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ License

-Preview :bar_chart: +Preview 📊 ---------- @@ -69,11 +69,13 @@ Installation 📡 ---------- ### Using Snap + ```bash sudo snap install cariddi ``` ### Using Go + ```bash go install -v github.com/edoardottt/cariddi/cmd/cariddi@latest ``` @@ -97,7 +99,7 @@ You need [Go](https://golang.org/). - `git clone https://github.com/edoardottt/cariddi.git` - `cd cariddi` - `go get ./...` - - `.\make.bat windows` (to install) + - `.\make.bat windows` (to install) - `.\make.bat unwindows` (to uninstall) Get Started 🎉 @@ -108,99 +110,101 @@ Get Started 🎉 ``` Usage of cariddi: -c int - Concurrency level. (default 20) + Concurrency level. (default 20) -cache - Use the .cariddi_cache folder as cache. + Use the .cariddi_cache folder as cache. -d int - Delay between a page crawled and another. + Delay between a page crawled and another. -debug - Print debug information while crawling. - -e Hunt for juicy endpoints. + Print debug information while crawling. + -e Hunt for juicy endpoints. -ef string - Use an external file (txt, one per line) to use custom parameters for endpoints hunting. + Use an external file (txt, one per line) to use custom parameters for endpoints hunting. -err - Hunt for errors in websites. + Hunt for errors in websites. -examples - Print the examples. + Print the examples. -ext int - Hunt for juicy file extensions. Integer from 1(juicy) to 7(not juicy). - -h Print the help. + Hunt for juicy file extensions. Integer from 1(juicy) to 7(not juicy). + -h Print the help. -headers string - Use custom headers for each request E.g. -headers "Cookie: auth=yes;;Client: type=2". + Use custom headers for each request E.g. -headers "Cookie: auth=yes;;Client: type=2". -headersfile string - Read from an external file custom headers (same format of headers flag). + Read from an external file custom headers (same format of headers flag). -json - Print the output as JSON in stdout. + Print the output as JSON in stdout. -i string - Ignore the URL containing at least one of the elements of this array. + Ignore the URL containing at least one of the elements of this array. -info - Hunt for useful informations in websites. + Hunt for useful informations in websites. -intensive - Crawl searching for resources matching 2nd level domain. + Crawl searching for resources matching 2nd level domain. -it string - Ignore the URL containing at least one of the lines of this file. + Ignore the URL containing at least one of the lines of this file. -oh string - Write the output into an HTML file. + Write the output into an HTML file. -ot string - Write the output into a TXT file. + Write the output into a TXT file. -plain - Print only the results. + Print only the results. -proxy string - Set a Proxy to be used (http and socks5 supported). + Set a Proxy to be used (http and socks5 supported). -rua - Use a random browser user agent on every request. - -s Hunt for secrets. + Use a random browser user agent on every request. + -s Hunt for secrets. -sf string - Use an external file (txt, one per line) to use custom regexes for secrets hunting. + Use an external file (txt, one per line) to use custom regexes for secrets hunting. + -sr + Store HTTP responses. -t int - Set timeout for the requests. (default 10) + Set timeout for the requests. (default 10) -ua string - Use a custom User Agent. + Use a custom User Agent. -version - Print the version. + Print the version. ``` - Examples 💡 ---------- - - `cariddi -version` (Print the version) - - `cariddi -h` (Print the help) - - `cariddi -examples` (Print the examples) - - `cat urls | cariddi -s` (Hunt for secrets) - - `cat urls | cariddi -d 2` (2 seconds between a page crawled and another) - - `cat urls | cariddi -c 200` (Set the concurrency level to 200) - - `cat urls | cariddi -e` (Hunt for juicy endpoints) - - `cat urls | cariddi -plain` (Print only results) - - `cat urls | cariddi -ot target_name` (Results in txt file) - - `cat urls | cariddi -oh target_name` (Results in html file) - - `cat urls | cariddi -ext 2` (Hunt for juicy (level 2 out of 7) files) - - `cat urls | cariddi -e -ef endpoints_file` (Hunt for custom endpoints) - - `cat urls | cariddi -s -sf secrets_file` (Hunt for custom secrets) - - `cat urls | cariddi -i forum,blog,community,open` (Ignore urls containing these words) - - `cat urls | cariddi -it ignore_file` (Ignore urls containing at least one line in the input file) - - `cat urls | cariddi -cache` (Use the .cariddi_cache folder as cache) - - `cat urls | cariddi -t 5` (Set the timeout for the requests) - - `cat urls | cariddi -intensive` (Crawl searching also subdomains, same as `*.target.com`) - - `cat urls | cariddi -rua` (Use a random browser user agent on every request) - - `cat urls | cariddi -proxy http://127.0.0.1:8080` (Set a Proxy, http and socks5 supported) - - `cat urls | cariddi -headers "Cookie: auth=admin;type=2;; X-Custom: customHeader"` - - `cat urls | cariddi -headersfile headers.txt` (Read from an external file custom headers) - - `cat urls | cariddi -err` (Hunt for errors in websites) - - `cat urls | cariddi -info` (Hunt for useful informations in websites) - - `cat urls | cariddi -debug` (Print debug information while crawling) - - `cat urls | cariddi -ua "Custom User Agent"` (Use a custom User Agent) - - `cat urls | cariddi -json` (Print the output as JSON in stdout) - - `cat urls | cariddi -json | jq .` (Pipe the JSON output into jq) +- `cariddi -version` (Print the version) +- `cariddi -h` (Print the help) +- `cariddi -examples` (Print the examples) +- `cat urls | cariddi -s` (Hunt for secrets) +- `cat urls | cariddi -d 2` (2 seconds between a page crawled and another) +- `cat urls | cariddi -c 200` (Set the concurrency level to 200) +- `cat urls | cariddi -e` (Hunt for juicy endpoints) +- `cat urls | cariddi -plain` (Print only results) +- `cat urls | cariddi -ot target_name` (Results in txt file) +- `cat urls | cariddi -oh target_name` (Results in html file) +- `cat urls | cariddi -ext 2` (Hunt for juicy (level 2 out of 7) files) +- `cat urls | cariddi -e -ef endpoints_file` (Hunt for custom endpoints) +- `cat urls | cariddi -s -sf secrets_file` (Hunt for custom secrets) +- `cat urls | cariddi -i forum,blog,community,open` (Ignore urls containing these words) +- `cat urls | cariddi -it ignore_file` (Ignore urls containing at least one line in the input file) +- `cat urls | cariddi -cache` (Use the .cariddi_cache folder as cache) +- `cat urls | cariddi -t 5` (Set the timeout for the requests) +- `cat urls | cariddi -intensive` (Crawl searching also subdomains, same as `*.target.com`) +- `cat urls | cariddi -rua` (Use a random browser user agent on every request) +- `cat urls | cariddi -proxy http://127.0.0.1:8080` (Set a Proxy, http and socks5 supported) +- `cat urls | cariddi -headers "Cookie: auth=admin;type=2;; X-Custom: customHeader"` +- `cat urls | cariddi -headersfile headers.txt` (Read from an external file custom headers) +- `cat urls | cariddi -err` (Hunt for errors in websites) +- `cat urls | cariddi -info` (Hunt for useful informations in websites) +- `cat urls | cariddi -debug` (Print debug information while crawling) +- `cat urls | cariddi -ua "Custom User Agent"` (Use a custom User Agent) +- `cat urls | cariddi -json` (Print the output as JSON in stdout) +- `cat urls | cariddi -sr` (Store HTTP responses) - - For Windows: - - use `powershell.exe -Command "cat urls | .\cariddi.exe"` inside the Command prompt - - or just `cat urls | cariddi.exe` using PowerShell +- For Windows: + - use `powershell.exe -Command "cat urls | .\cariddi.exe"` inside the Command prompt + - or just `cat urls | cariddi.exe` using PowerShell - - To integrate cariddi with Burpsuite [make sure to follow these steps](https://github.com/edoardottt/cariddi/wiki/BurpSuite-Integration). +- To integrate cariddi with Burpsuite [make sure to follow these steps](https://github.com/edoardottt/cariddi/wiki/BurpSuite-Integration). Changelog 📌 ------- + Detailed changes for each release are documented in the [release notes](https://github.com/edoardottt/cariddi/releases). Contributing 🛠 @@ -209,16 +213,17 @@ Contributing 🛠 Just open an [issue](https://github.com/edoardottt/cariddi/issues)/[pull request](https://github.com/edoardottt/cariddi/pulls). Before opening a pull request, download [golangci-lint](https://golangci-lint.run/usage/install/) and run + ```bash golangci-lint run ``` + If there aren't errors, go ahead :) **Help me building this!** Special thanks to: [go-colly](http://go-colly.org/), [zricethezav](https://github.com/zricethezav/gitleaks/blob/master/config/default.go), [projectdiscovery](https://github.com/projectdiscovery/nuclei-templates/tree/master/file/keys), [tomnomnom](https://github.com/tomnomnom/gf/tree/master/examples), [RegexPassive](https://github.com/hahwul/RegexPassive) and [all the contributors](https://github.com/edoardottt/cariddi/wiki/Contributors). - License 📝 ------- diff --git a/cmd/cariddi/main.go b/cmd/cariddi/main.go index 23f9c31..384b232 100644 --- a/cmd/cariddi/main.go +++ b/cmd/cariddi/main.go @@ -86,6 +86,7 @@ func main() { InfoFlag: flags.Info, Debug: flags.Debug, UserAgent: flags.UserAgent, + StoreResp: flags.StoreResp, } // Read the targets from standard input. @@ -126,6 +127,10 @@ func main() { output.HeaderHTML("Results", ResultHTML) } + if config.StoreResp { + fileUtils.CreateIndexOutputFile("index.responses.txt") + } + // Read headers if needed if flags.HeadersFile != "" || flags.Headers != "" { var headersInput string diff --git a/internal/file/file.go b/internal/file/file.go index d2e0b4d..19c01be 100644 --- a/internal/file/file.go +++ b/internal/file/file.go @@ -33,6 +33,7 @@ import ( "log" "net/http" "os" + "path/filepath" "strings" ) @@ -52,20 +53,32 @@ func CreateOutputFolder() { } } -// CreateOutputFile takes a target (of the attack), a subcommand +// CreateHostOutputFolder creates the host output folder +// for the HTTP responses. +// If it fails exits with an error message. +func CreateHostOutputFolder(host string) { + // Create a folder/directory at a full qualified path + err := os.MkdirAll(filepath.Join("output-cariddi", host), Permission0755) + if err != nil { + fmt.Println("Can't create host output folder.") + os.Exit(1) + } +} + +// CreateOutputFile takes as input a target (of the attack), a subcommand // (PORT-DNS-DIR-SUBDOMAIN-REPORT) and a format (json-html-txt). // It creates the output folder if needed, then checks if the output file -// already exists, if yes asks the user if scilla has to overwrite it; -// if no scilla creates it. +// 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 { target = ReplaceBadCharacterOutput(target) var filename string if subcommand != "" { - filename = "output-cariddi" + "/" + target + "." + subcommand + "." + format + filename = filepath.Join("output-cariddi", target+"."+subcommand+"."+format) } else { - filename = "output-cariddi" + "/" + target + "." + format + filename = filepath.Join("output-cariddi", target+"."+format) } _, err := os.Stat(filename) @@ -101,6 +114,29 @@ func CreateOutputFile(target string, subcommand string, format string) string { return filename } +// CreateIndexOutputFile takes as input the name of the index file. +// 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) { + _, err := os.Stat(filename) + + if os.IsNotExist(err) { + if _, err := os.Stat("output-cariddi/"); os.IsNotExist(err) { + CreateOutputFolder() + } + // If the file doesn't exist, create it. + filename = filepath.Join("output-cariddi", filename) + f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, Permission0644) + if err != nil { + fmt.Println("Can't create output file.") + os.Exit(1) + } + + f.Close() + } +} + // ReplaceBadCharacterOutput replaces forward-slashes // with dashes (to avoid problems with output folder). func ReplaceBadCharacterOutput(input string) string { diff --git a/pkg/crawler/colly.go b/pkg/crawler/colly.go index a21b920..ae4d3f7 100644 --- a/pkg/crawler/colly.go +++ b/pkg/crawler/colly.go @@ -48,59 +48,6 @@ import ( "github.com/gocolly/colly/extensions" ) -type Results struct { - URLs []string - Secrets []scanner.SecretMatched - Endpoints []scanner.EndpointMatched - Extensions []scanner.FileTypeMatched - Errors []scanner.ErrorMatched - Infos []scanner.InfoMatched -} - -type Scan struct { - // Flags - Cache bool - Debug bool - EndpointsFlag bool - ErrorsFlag bool - InfoFlag bool - Intensive bool - Plain bool - Rua bool - SecretsFlag bool - Ignore string - IgnoreTxt string - JSON bool - HTML string - Proxy string - Target string - Txt string - UserAgent string - FileType int - Headers map[string]string - - // Settings - Concurrency int - Delay int - Timeout int - - // Storage - SecretsSlice []string - EndpointsSlice []string -} - -type Event struct { - ProtocolTemp string - TargetTemp string - Target string - Intensive bool - Ignore bool - Debug bool - JSON bool - IgnoreSlice []string - URLs *[]string -} - // New it's the actual crawler engine. // It controls all the behaviours of a scan // (event handlers, secrets, errors, extensions and endpoints scanning). @@ -182,6 +129,17 @@ func New(scan *Scan) *Results { } c.OnResponse(func(r *colly.Response) { + if !scan.JSON { + fmt.Println(r.Request.URL) + } + + if scan.StoreResp { + err := output.StoreHTTPResponse(r) + if err != nil { + log.Println(err) + } + } + minBodyLentgh := 10 lengthOk := len(string(r.Body)) > minBodyLentgh secrets := []scanner.SecretMatched{} @@ -231,10 +189,12 @@ func New(scan *Scan) *Results { infos = append(infos, infosSlice...) } } + if scan.JSON { jsonOutput, err := output.GetJSONString( r, secrets, parameters, filetype, errors, infos, ) + if err == nil { fmt.Println(string(jsonOutput)) } else { @@ -374,13 +334,6 @@ func CreateColly(delayTime int, concurrency int, cache bool, timeout int, // registerHTMLEvents registers the associated functions for each // HTML event triggering an action. func registerHTMLEvents(c *colly.Collector, event *Event) { - // On every request that Colly is making, print the URL it's currently visiting - c.OnRequest(func(e *colly.Request) { - if !event.JSON { - fmt.Println(e.URL.String()) - } - }) - // On every a element which has href attribute call callback c.OnHTML("a[href]", func(e *colly.HTMLElement) { link := e.Attr("href") diff --git a/pkg/crawler/options.go b/pkg/crawler/options.go new file mode 100644 index 0000000..73ab694 --- /dev/null +++ b/pkg/crawler/options.go @@ -0,0 +1,83 @@ +/* +========== +Cariddi +========== + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/. + + @Repository: https://github.com/edoardottt/cariddi + + @Author: edoardottt, https://www.edoardoottavianelli.it + + @License: https://github.com/edoardottt/cariddi/blob/main/LICENSE + +*/ + +package crawler + +import "github.com/edoardottt/cariddi/pkg/scanner" + +type Results struct { + URLs []string + Secrets []scanner.SecretMatched + Endpoints []scanner.EndpointMatched + Extensions []scanner.FileTypeMatched + Errors []scanner.ErrorMatched + Infos []scanner.InfoMatched +} + +type Scan struct { + // Flags + Cache bool + Debug bool + EndpointsFlag bool + ErrorsFlag bool + InfoFlag bool + Intensive bool + Plain bool + Rua bool + SecretsFlag bool + Ignore string + IgnoreTxt string + JSON bool + HTML string + Proxy string + Target string + Txt string + UserAgent string + FileType int + Headers map[string]string + StoreResp bool + + // Settings + Concurrency int + Delay int + Timeout int + + // Storage + SecretsSlice []string + EndpointsSlice []string +} + +type Event struct { + ProtocolTemp string + TargetTemp string + Target string + Intensive bool + Ignore bool + Debug bool + JSON bool + IgnoreSlice []string + URLs *[]string +} diff --git a/pkg/input/flags.go b/pkg/input/flags.go index c3fcfb0..be2fdca 100644 --- a/pkg/input/flags.go +++ b/pkg/input/flags.go @@ -65,6 +65,7 @@ type Input struct { Info bool Debug bool UserAgent string + StoreResp bool } // ScanFlag defines all the options taken @@ -111,6 +112,8 @@ func ScanFlag() Input { userAgentPtr := flag.String("ua", "", "Use a custom User Agent.") + storeRespPtr := flag.Bool("sr", false, "Store HTTP responses.") + flag.Parse() result := Input{ @@ -141,6 +144,7 @@ func ScanFlag() Input { *infoPtr, *debugPtr, *userAgentPtr, + *storeRespPtr, } return result diff --git a/pkg/output/banner.go b/pkg/output/banner.go index ae5e4d0..ac29b3f 100644 --- a/pkg/output/banner.go +++ b/pkg/output/banner.go @@ -35,7 +35,7 @@ import ( // nolint: checknoglobals const ( - version = "v1.3.1" + version = "v1.3.2" banner = ` _ _ _ _ (_) | | | (_) ___ __ _ _ __ _ __| | __| |_ diff --git a/pkg/output/examples.go b/pkg/output/examples.go index 279013e..0adb87b 100644 --- a/pkg/output/examples.go +++ b/pkg/output/examples.go @@ -72,19 +72,19 @@ func PrintExamples() { cat urls | cariddi -proxy http://127.0.0.1:8080 (Set a Proxy to be used (http and socks5 supported)) - cat urls | cariddi -headers "Cookie: auth=admin;type=2;; X-Custom: customHeader" + cat urls | cariddi -headers "Cookie: auth=admin;type=2;; X-Custom: customHeader (Use custom headers)" - cat urls | cariddi -headersfile headers.txt + cat urls | cariddi -headersfile headers.txt (Read from an external file custom headers) - cat urls | cariddi -err + cat urls | cariddi -err (Hunt for errors) - cat urls | cariddi -info + cat urls | cariddi -info (Hunt for useful information) - cat urls | cariddi -debug + cat urls | cariddi -debug (Print debug information) - cat urls | cariddi -ua "Custom User Agent" + cat urls | cariddi -ua "Custom User Agent" (Use a custom User Agent) - cat urls | cariddi -json + cat urls | cariddi -json (Print the output as JSON) - cat urls | cariddi -json | jq .`) + cat urls | cariddi -sr (Store HTTP responses)`) } diff --git a/pkg/output/help.go b/pkg/output/help.go index 80b0a5b..961d6d2 100644 --- a/pkg/output/help.go +++ b/pkg/output/help.go @@ -59,7 +59,7 @@ func PrintHelp() { -i string Ignore the URL containing at least one of the elements of this array. -info - Hunt for useful informations in websites. + Hunt for useful information in websites. -intensive Crawl searching for resources matching 2nd level domain. -it string @@ -77,6 +77,8 @@ func PrintHelp() { -s Hunt for secrets. -sf string Use an external file (txt, one per line) to use custom regexes for secrets hunting. + -sr + Store HTTP responses. -t int Set timeout for the requests. (default 10) -ua diff --git a/pkg/output/output.go b/pkg/output/output.go index c849bec..43fceee 100644 --- a/pkg/output/output.go +++ b/pkg/output/output.go @@ -36,6 +36,10 @@ import ( "github.com/edoardottt/cariddi/pkg/scanner" ) +const ( + CariddiOutputFolder = "output-cariddi" +) + // PrintSimpleOutput prints line by line. func PrintSimpleOutput(out []string) { for _, elem := range out { @@ -48,7 +52,7 @@ func PrintSimpleOutput(out []string) { func TxtOutput(flags input.Input, finalResults []string, finalSecret []scanner.SecretMatched, finalEndpoints []scanner.EndpointMatched, finalExtensions []scanner.FileTypeMatched, finalErrors []scanner.ErrorMatched, finalInfos []scanner.InfoMatched) { - exists, err := fileUtils.ElementExists("output-cariddi") + exists, err := fileUtils.ElementExists(CariddiOutputFolder) if err != nil { fmt.Println("Error while creating the output directory.") os.Exit(1) @@ -120,7 +124,7 @@ func TxtOutput(flags input.Input, finalResults []string, finalSecret []scanner.S func HTMLOutput(flags input.Input, resultFilename string, finalResults []string, finalSecret []scanner.SecretMatched, finalEndpoints []scanner.EndpointMatched, finalExtensions []scanner.FileTypeMatched, finalErrors []scanner.ErrorMatched, finalInfos []scanner.InfoMatched) { - exists, err := fileUtils.ElementExists("output-cariddi") + exists, err := fileUtils.ElementExists(CariddiOutputFolder) if err != nil { fmt.Println("Error while creating the output directory.") diff --git a/pkg/output/responses.go b/pkg/output/responses.go new file mode 100644 index 0000000..fc3decb --- /dev/null +++ b/pkg/output/responses.go @@ -0,0 +1,187 @@ +/* +========== +Cariddi +========== + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/. + + @Repository: https://github.com/edoardottt/cariddi + + @Author: edoardottt, https://www.edoardoottavianelli.it + + @License: https://github.com/edoardottt/cariddi/blob/main/LICENSE + +*/ + +package output + +import ( + "bytes" + "crypto/sha1" + "encoding/hex" + "errors" + "fmt" + "io" + "log" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/gocolly/colly" + + fileUtils "github.com/edoardottt/cariddi/internal/file" +) + +const ( + index = "index.responses.txt" +) + +var ( + ErrHTTPResp = errors.New("cannot store HTTP response") +) + +func getResponseHash(url string) string { + hash := sha1.Sum([]byte(url)) + return hex.EncodeToString(hash[:]) +} + +// FormatResponse formats an HTTP response ready to be written in a file. +func FormatResponse(resp *colly.Response) ([]byte, error) { + builder := &bytes.Buffer{} + + builder.WriteString(resp.Request.URL.String()) + builder.WriteString("\n\n\n") + + builder.WriteString(resp.Request.Method) + builder.WriteString(" ") + + path := resp.Request.URL.Path + if resp.Request.URL.Fragment != "" { + path = path + "#" + resp.Request.URL.Fragment + } + + builder.WriteString(path) + builder.WriteString(" ") + builder.WriteString("HTTP/1.1") + builder.WriteString("\n") + builder.WriteString("Host: " + resp.Request.URL.Host) + builder.WriteRune('\n') + + for k, v := range *resp.Request.Headers { + builder.WriteString(k + ": " + strings.Join(v, "; ") + "\n") + } + + if resp.Request.Body != nil { + bodyResp, _ := io.ReadAll(resp.Request.Body) + if string(bodyResp) != "" { + builder.WriteString("\n") + builder.WriteString(string(bodyResp)) + } + } + + builder.WriteString("\n\n") + builder.WriteString("HTTP/1.1") + builder.WriteString(" ") + builder.WriteString(fmt.Sprint(resp.StatusCode)) + builder.WriteString("\n") + + for k, v := range *resp.Headers { + builder.WriteString(k + ": " + strings.Join(v, "; ") + "\n") + } + + builder.WriteString("\n") + + body, _ := io.ReadAll(bytes.NewReader(resp.Body)) + + builder.WriteString(string(body)) + + return builder.Bytes(), nil +} + +func getResponseFileName(folder, url string) string { + file := getResponseHash(url) + ".txt" + return filepath.Join(folder, file) +} + +// UpdateIndex updates the index file with the +// correct information linking to HTTP responses files. +// If it fails returns an error. +func UpdateIndex(resp *colly.Response) error { + index, err := os.OpenFile(filepath.Join(CariddiOutputFolder, index), + os.O_APPEND|os.O_WRONLY, + fileUtils.Permission0644) + if err != nil { + return err + } + + defer index.Close() + + builder := &bytes.Buffer{} + + builder.WriteString(getResponseFileName(filepath.Join(CariddiOutputFolder, resp.Request.URL.Host), + resp.Request.URL.String())) + builder.WriteRune(' ') + builder.WriteString(resp.Request.URL.String()) + builder.WriteRune(' ') + builder.WriteString("(" + fmt.Sprint(resp.StatusCode) + ")") + builder.WriteRune('\n') + + if _, writeErr := index.Write(builder.Bytes()); writeErr != nil { + return fmt.Errorf("%w %s", err, "could not update index") + } + + return nil +} + +// WriteHTTPResponse creates an HTTP response output file and +// writes the HTTP response inside it. +// If it fails returns an error. +func WriteHTTPResponse(inputURL *url.URL, response []byte) error { + file := getResponseFileName(filepath.Join(CariddiOutputFolder, inputURL.Host), inputURL.String()) + + outFile, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY, fileUtils.Permission0644) + if err != nil { + return err + } + + if _, writeErr := outFile.Write(response); writeErr != nil { + return ErrHTTPResp + } + + return nil +} + +// StoreHTTPResponse stores an HTTP response in a file. +// If it fails returns an error. +func StoreHTTPResponse(r *colly.Response) error { + fileUtils.CreateHostOutputFolder(r.Request.URL.Host) + + err := UpdateIndex(r) + if err != nil { + log.Println(err) + } + + response, err := FormatResponse(r) + if err != nil { + log.Println(err) + } + + err = WriteHTTPResponse(r.Request.URL, response) + if err != nil { + log.Println(err) + } + + return nil +} diff --git a/snapcraft.yaml b/snapcraft.yaml index 24ea194..44b51f0 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -2,7 +2,7 @@ name: cariddi summary: Fast web crawler and scanner description: | Take a list of domains, crawl urls and scan for endpoints, secrets, api keys, file extensions, tokens and more -version: 1.3.1 +version: 1.3.2 grade: stable base: core20