diff --git a/README.md b/README.md index ad1e7f9..b132c9f 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ If you have [VLC Media Player](http://www.videolan.org/vlc/), you should be able * **"-T, --timeout"**: (Default: `2000ms`) Set custom timeout value after which an attack attempt without an answer should give up. It's recommended to increase it when attempting to scan unstable and slow networks or to decrease it on fast and reliable networks. * **"-r, --custom-routes"**: (Default: `/dictionaries/routes`) Set custom dictionary path for routes * **"-c, --custom-credentials"**: (Default: `/dictionaries/credentials.json`) Set custom dictionary path for credentials -* **"-o, --nmap-output"**: (Default: `/tmp/cameradar_scan.xml`) Set custom nmap output path +* **"-o, --output-file"**: Output scan results as a JSON file. If not specified, results are not written to a file. * **"-d, --debug"**: Enable debug logs * **"-v, --verbose"**: Enable verbose curl logs (not recommended for most use) * **"-h"**: Display the usage information diff --git a/cmd/cameradar/cameradar.go b/cmd/cameradar/cameradar.go index 564a645..2d5db9f 100644 --- a/cmd/cameradar/cameradar.go +++ b/cmd/cameradar/cameradar.go @@ -22,6 +22,7 @@ func parseArguments() error { pflag.StringSliceP("ports", "p", []string{"554", "5554", "8554"}, "The ports on which to search for RTSP streams") pflag.StringP("custom-routes", "r", "${GOPATH}/src/github.com/Ullaakut/cameradar/dictionaries/routes", "The path on which to load a custom routes dictionary") pflag.StringP("custom-credentials", "c", "${GOPATH}/src/github.com/Ullaakut/cameradar/dictionaries/credentials.json", "The path on which to load a custom credentials JSON dictionary") + pflag.StringP("output-file", "o", "", "Output scan results as a JSON file. If not specified, results are not written to a file.") pflag.IntP("scan-speed", "s", 4, "The nmap speed preset to use for scanning (lower is stealthier)") pflag.DurationP("attack-interval", "I", 0, "The interval between each attack (i.e: 2000ms, higher is stealthier)") pflag.DurationP("timeout", "T", 2000*time.Millisecond, "The timeout to use for attack attempts (i.e: 2000ms)") @@ -87,6 +88,18 @@ func main() { printErr(err) } + if path := viper.GetString("output-file"); path != "" { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + printErr(fmt.Errorf("opening output file %s: %w", path, err)) + } + + err = c.Write(file, streams) + if err != nil { + printErr(fmt.Errorf("writing to output file %s: %w", path, err)) + } + } + c.PrintStreams(streams) } diff --git a/scan.go b/scan.go index b02ed94..599efae 100644 --- a/scan.go +++ b/scan.go @@ -10,14 +10,14 @@ import ( // // targets can be: // -// - a subnet (e.g.: 172.16.100.0/24) -// - an IP (e.g.: 172.16.100.10) -// - a hostname (e.g.: localhost) -// - a range of IPs (e.g.: 172.16.100.10-20) +// - a subnet (e.g.: 172.16.100.0/24) +// - an IP (e.g.: 172.16.100.10) +// - a hostname (e.g.: localhost) +// - a range of IPs (e.g.: 172.16.100.10-20) // // ports can be: // -// - one or multiple ports and port ranges separated by commas (e.g.: 554,8554-8560,18554-28554) +// - one or multiple ports and port ranges separated by commas (e.g.: 554,8554-8560,18554-28554) func (s *Scanner) Scan() ([]Stream, error) { s.term.StartStep("Scanning the network") diff --git a/summary.go b/summary.go index bac500b..f6d5093 100644 --- a/summary.go +++ b/summary.go @@ -1,6 +1,10 @@ package cameradar import ( + "encoding/json" + "fmt" + "io" + "github.com/Ullaakut/disgo/style" curl "github.com/Ullaakut/go-curl" ) @@ -66,3 +70,22 @@ func (s *Scanner) PrintStreams(streams []Stream) { s.term.Infof("%s Streams were found but none were accessed. They are most likely configured with secure credentials and routes. You can try adding entries to the dictionary or generating your own in order to attempt a bruteforce attack on the cameras.\n", style.Failure("\xE2\x9C\x96")) } } + +func (s *Scanner) Write(wc io.WriteCloser, streams []Stream) error { + if wc == nil { + return nil + } + defer wc.Close() + + jsonData, err := json.MarshalIndent(streams, "", " ") + if err != nil { + return fmt.Errorf("marshalling results: %w", err) + } + + _, err = wc.Write(jsonData) + if err != nil { + return fmt.Errorf("writing results to file: %w", err) + } + return nil + +}