diff --git a/README.md b/README.md index f3be60c..cecd440 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,60 @@ With the above result, the RTSP URL would be `rtsp://admin:12345@173.16.100.45:5 * **"-l, --log"**: Enable debug logs (nmap requests, curl describe requests, etc.) * **"-h"** : Display the usage information +## Environment Variables + +### `CAMERADAR_TARGET` + +This variable is mandatory and specifies the target that cameradar should scan and attempt to access RTSP streams on. + +Examples: + +* `172.16.100.0/24` +* `192.168.1.1` +* `localhost` + +### `CAMERADAR_PORTS` + +This variable is optional and allows you to specify the ports on which to run the scans. + +Default value: `554,8554` + +It is recommended not to change these except if you are certain that cameras have been configured to stream RTSP over a different port. 99.9% of cameras are streaming on these ports. + +### `CAMERADAR_NMAP_OUTPUT_FILE` + +This variable is optional and allows you to specify on which file nmap will write its output. + +Default value: `/tmp/cameradar_scan.xml` + +This can be useful only if you want to read the files yourself, if you don't want it to write in your `/tmp` folder, or if you want to use only the RunNmap function in cameradar, and do its parsing manually. + +### `CAMERADAR_CUSTOM_ROUTES`, `CAMERADAR_CUSTOM_CREDENTIALS` + +These variables are optional, allowing to replace the default dictionaries with custom ones, for the dictionary attack. + +Default values: `/dictionaries/routes` and `/dictionaries/credentials.json` + +### `CAMERADAR_SPEED` + +This optional variable allows you to set custom nmap discovery presets to improve speed or accuracy. It's recommended to lower it if you are attempting to scan an unstable and slow network, or to increase it if on a very performant and reliable network. See [this for more info on the nmap timing templates](https://nmap.org/book/man-performance.html). + +Default value: `4` + +### `CAMERADAR_TIMEOUT` + +This optional variable allows you to set custom timeout value in miliseconds 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 very performant and reliable networks. + +Default value: `2000` + +### `CAMERADAR_LOGS` + +This optional variable allows you to enable a more verbose output to have more information about what is going on. + +It will output nmap results, cURL requests, etc. + +Default: `false` + ## Contribution ### Build diff --git a/cameradar/cameradar.go b/cameradar/cameradar.go index 13d81ef..cee9253 100644 --- a/cameradar/cameradar.go +++ b/cameradar/cameradar.go @@ -13,6 +13,7 @@ package main import ( + "errors" "fmt" "os" "strings" @@ -21,29 +22,82 @@ import ( "github.com/EtixLabs/cameradar" "github.com/gernest/wow" "github.com/gernest/wow/spin" + "github.com/spf13/pflag" + "github.com/spf13/viper" "github.com/fatih/color" - "github.com/jessevdk/go-flags" ) type options struct { - Target string `short:"t" long:"target" description:"The target on which to scan for open RTSP streams - required (ex: 172.16.100.0/24)" required:"true"` - Ports string `short:"p" long:"ports" description:"The ports on which to search for RTSP streams" default:"554,8554"` - OutputFile string `short:"o" long:"nmap-output" description:"The path where nmap will create its XML result file" default:"/tmp/cameradar_scan.xml"` - Routes string `short:"r" long:"custom-routes" description:"The path on which to load a custom routes dictionary" default:"/src/github.com/EtixLabs/cameradar/dictionaries/routes"` - Credentials string `short:"c" long:"custom-credentials" description:"The path on which to load a custom credentials JSON dictionary" default:"/src/github.com/EtixLabs/cameradar/dictionaries/credentials.json"` - Speed int `short:"s" long:"speed" description:"The nmap speed preset to use" default:"4"` - Timeout int `short:"T" long:"timeout" description:"The timeout in miliseconds to use for attack attempts" default:"2000"` - EnableLogs bool `short:"l" long:"log" description:"Enable the logs for nmap's output to stdout"` + Target string + Ports string + OutputFile string + Routes string + Credentials string + Speed int + Timeout int + EnableLogs bool +} + +func parseArguments() error { + + viper.BindEnv("target", "CAMERADAR_TARGET") + viper.BindEnv("ports", "CAMERADAR_PORTS") + viper.BindEnv("nmap-output", "CAMERADAR_NMAP_OUTPUT_FILE") + viper.BindEnv("custom-routes", "CAMERADAR_CUSTOM_ROUTES") + viper.BindEnv("custom-credentials", "CAMERADAR_CUSTOM_CREDENTIALS") + viper.BindEnv("speed", "CAMERADAR_SPEED") + viper.BindEnv("timeout", "CAMERADAR_TIMEOUT") + viper.BindEnv("envlogs", "CAMERADAR_LOGS") + + pflag.StringP("target", "t", "", "The target on which to scan for open RTSP streams - required (ex: 172.16.100.0/24)") + pflag.StringP("ports", "p", "554,8554", "The ports on which to search for RTSP streams") + pflag.StringP("nmap-output", "o", "/tmp/cameradar_scan.xml", "The path where nmap will create its XML result file") + pflag.StringP("custom-routes", "r", "/src/github.com/EtixLabs/cameradar/dictionaries/routes", "The path on which to load a custom routes dictionary") + pflag.StringP("custom-credentials", "c", "/src/github.com/EtixLabs/cameradar/dictionaries/credentials.json", "The path on which to load a custom credentials JSON dictionary") + pflag.IntP("speed", "s", 4, "The nmap speed preset to use") + pflag.IntP("timeout", "T", 2000, "The timeout in miliseconds to use for attack attempts") + pflag.BoolP("log", "l", false, "Enable the logs for nmap's output to stdout") + pflag.BoolP("help", "h", false, "displays this help message") + + viper.AutomaticEnv() + + pflag.Parse() + + err := viper.BindPFlags(pflag.CommandLine) + if err != nil { + return err + } + + if viper.GetBool("help") { + pflag.Usage() + os.Exit(0) + } + + if viper.GetString("target") == "" { + return errors.New("target (-t, --target) argument required\n examples:\n - 172.16.100.0/24\n - localhost\n - 8.8.8.8") + } + + return nil } func main() { var options options - _, err := flags.ParseArgs(&options, os.Args[1:]) + + err := parseArguments() if err != nil { - os.Exit(0) + printErr(err) } + options.Credentials = viper.GetString("custom-credentials") + options.EnableLogs = viper.GetBool("log") || viper.GetBool("envlogs") + options.OutputFile = viper.GetString("nmap-output") + options.Ports = viper.GetString("ports") + options.Routes = viper.GetString("custom-routes") + options.Speed = viper.GetInt("speed") + options.Timeout = viper.GetInt("timeout") + options.Target = viper.GetString("target") + w := startSpinner(options.EnableLogs) updateSpinner(w, "Loading dictionaries...", options.EnableLogs) @@ -64,15 +118,24 @@ func main() { } updateSpinner(w, "Scanning the network...", options.EnableLogs) - streams, _ := cmrdr.Discover(options.Target, options.Ports, options.OutputFile, options.Speed, options.EnableLogs) + streams, err := cmrdr.Discover(options.Target, options.Ports, options.OutputFile, options.Speed, options.EnableLogs) + if err != nil && len(streams) > 0 { + printErr(err) + } // Most cameras will be accessed successfully with these two attacks updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Attacking their routes...", options.EnableLogs) - streams, _ = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) + streams, err = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) + if err != nil && len(streams) > 0 { + printErr(err) + } updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Attacking their credentials...", options.EnableLogs) - streams, _ = cmrdr.AttackCredentials(streams, credentials, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) + streams, err = cmrdr.AttackCredentials(streams, credentials, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) + if err != nil && len(streams) > 0 { + printErr(err) + } // But some cameras run GST RTSP Server which prioritizes 401 over 404 contrary to most cameras. // For these cameras, running another route attack will solve the problem. @@ -80,7 +143,10 @@ func main() { if stream.RouteFound == false || stream.CredentialsFound == false { updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Final attack...", options.EnableLogs) - streams, _ = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) + streams, err = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) + if err != nil && len(streams) > 0 { + printErr(err) + } break } } @@ -135,6 +201,12 @@ func prettyPrint(streams []cmrdr.Stream) { } } +func printErr(err error) { + red := color.New(color.FgRed, color.Bold).SprintFunc() + fmt.Printf("%s %v\n", red("\xE2\x9C\x96"), err) + os.Exit(1) +} + func updateSpinner(w *wow.Wow, text string, disabled bool) { if !disabled { w.Text(" " + text) diff --git a/cameradur b/cameradur new file mode 100755 index 0000000..f85a3c3 Binary files /dev/null and b/cameradur differ diff --git a/discover.go b/discover.go index 2c98359..37bb64a 100644 --- a/discover.go +++ b/discover.go @@ -18,6 +18,7 @@ import ( "fmt" "io/ioutil" "os/exec" + "strings" "github.com/pkg/errors" v "gopkg.in/go-playground/validator.v9" @@ -51,17 +52,21 @@ func NmapRun(targets, ports, resultFilePath string, nmapSpeed int, enableLogs bo return fmt.Errorf("invalid nmap speed value '%d'. Should be between '%d' and '%d'", nmapSpeed, PARANOIAC, INSANE) } - // Prepare nmap command - cmd := execCommand( - "nmap", - fmt.Sprintf("-T%d", nmapSpeed), - "-A", - "-p", + cmdArgs := fmt.Sprintf( + "-T%d -A -p %s -oX %s %s", + nmapSpeed, ports, - "-oX", resultFilePath, - targets, - ) + targets) + + if enableLogs { + fmt.Printf("command: nmap %s\n", cmdArgs) + } + + args := strings.Split(cmdArgs, " ") + + // Prepare nmap command + cmd := execCommand("nmap", args...) // Pipe stdout to be able to write the logs in realtime stdout, err := cmd.StdoutPipe() diff --git a/glide.lock b/glide.lock index 70f82ca..814e445 100644 --- a/glide.lock +++ b/glide.lock @@ -5,6 +5,8 @@ imports: version: f8b334df3789fbdf98df3b3b22815e958b956c19 - name: github.com/fatih/color version: 570b54cabe6b8eb0bc2dfce68d964677d63b5260 +- name: github.com/fsnotify/fsnotify + version: 4da3e2cfbabc9f751898f250b49f2439785783a1 - name: github.com/gernest/wow version: 57298475c6371a5a652e74dcf624c80edfc5ae1a subpackages: @@ -15,16 +17,72 @@ imports: version: b32fa301c9fe55953584134cb6853a13c87ec0a1 - name: github.com/gorilla/websocket version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b -- name: github.com/jessevdk/go-flags - version: 96dc06278ce32a0e9d957d590bb987c81ee66407 +- name: github.com/hashicorp/hcl + version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8 + subpackages: + - hcl/ast + - hcl/parser + - hcl/scanner + - hcl/strconv + - hcl/token + - json/parser + - json/scanner + - json/token +- name: github.com/magiconair/properties + version: 8d7837e64d3c1ee4e54a880c5a920ab4316fc90a +- name: github.com/mattn/go-colorable + version: 5411d3eea5978e6cdc258b30de592b60df6aba96 + repo: https://github.com/mattn/go-colorable +- name: github.com/mattn/go-isatty + version: 57fdcb988a5c543893cc61bce354a6e24ab70022 + repo: https://github.com/mattn/go-isatty +- name: github.com/mitchellh/mapstructure + version: 06020f85339e21b2478f756a78e295255ffa4d6a +- name: github.com/pelletier/go-toml + version: 4e9e0ee19b60b13eb79915933f44d8ed5f268bdd - name: github.com/pkg/errors version: 645ef00459ed84a119197bfb8d8205042c6df63d +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/spf13/afero + version: 5660eeed305fe5f69c8fc6cf899132a459a97064 + subpackages: + - mem +- name: github.com/spf13/cast + version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4 +- name: github.com/spf13/jwalterweatherman + version: 12bd96e66386c1960ab0f74ced1362f66f552f7b +- name: github.com/spf13/pflag + version: 97afa5e7ca8a08a383cb259e06636b5e2cc7897f +- name: github.com/spf13/viper + version: 8ef37cbca71638bf32f3d5e194117d4cb46da163 +- name: github.com/stretchr/objx + version: cbeaeb16a013161a98496fad62933b1d21786672 - name: github.com/stretchr/testify version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 subpackages: - mock +- name: golang.org/x/crypto + version: 2509b142fb2b797aa7587dad548f113b2c0f20ce + subpackages: + - ssh/terminal +- name: golang.org/x/sys + version: e24f485414aeafb646f6fca458b0bf869c0880a1 + repo: https://go.googlesource.com/sys + subpackages: + - unix + - windows +- name: golang.org/x/text + version: 6eab0e8f74e86c598ec3b6fad4888e0c11482d48 + subpackages: + - transform + - unicode/norm - name: gopkg.in/go-playground/validator.v9 version: a021b2ec9a8a8bb970f3f15bc42617cb520e8a64 - name: gopkg.in/tylerb/graceful.v1 version: 4654dfbb6ad53cb5e27f37d99b02e16c1872fbbb +- name: gopkg.in/yaml.v2 + version: eb3733d160e74a9c7e442f435eb3bea458e1d19f testImports: [] diff --git a/glide.yaml b/glide.yaml index 4dc20b8..be76fb6 100644 --- a/glide.yaml +++ b/glide.yaml @@ -8,8 +8,6 @@ import: - spin - package: github.com/gorilla/websocket version: ~1.2.0 -- package: github.com/jessevdk/go-flags - version: ~1.3.0 - package: github.com/pkg/errors version: ~0.8.0 - package: github.com/stretchr/testify @@ -24,3 +22,5 @@ import: version: ~0.16.0 - package: github.com/go-playground/locales version: ~0.11.2 +- package: github.com/spf13/pflag +- package: github.com/spf13/viper