diff --git a/attack.go b/attack.go index de05c25..fc27ff4 100644 --- a/attack.go +++ b/attack.go @@ -21,6 +21,16 @@ const ( rtspSetup = 4 ) +// Authentication types. +const ( + authNone = 0 + authBasic = 1 + authDigest = 2 +) + +// Route that should never be a constructor default. +const dummyRoute = "/0x8b6c42" + // Attack attacks the given targets and returns the accessed streams. func (s *Scanner) Attack(targets []Stream) ([]Stream, error) { if len(targets) == 0 { @@ -76,20 +86,13 @@ func (s *Scanner) AttackCredentials(targets []Stream) []Stream { defer close(resChan) for i := range targets { - // TODO: Perf Improvement: Skip cameras with no auth type detected, and set their - // CredentialsFound value to true. go s.attackCameraCredentials(targets[i], resChan) } - attackResults := []Stream{} - // TODO: Change this into a for+select and make a successful result close the chan. for range targets { - attackResults = append(attackResults, <-resChan) - } - - for i := range attackResults { - if attackResults[i].CredentialsFound { - targets = replace(targets, attackResults[i]) + attackResult := <-resChan + if attackResult.CredentialsFound { + targets = replace(targets, attackResult) } } @@ -106,15 +109,10 @@ func (s *Scanner) AttackRoute(targets []Stream) []Stream { go s.attackCameraRoute(targets[i], resChan) } - attackResults := []Stream{} - // TODO: Change this into a for+select and make a successful result close the chan. for range targets { - attackResults = append(attackResults, <-resChan) - } - - for i := range attackResults { - if attackResults[i].RouteFound { - targets = replace(targets, attackResults[i]) + attackResult := <-resChan + if attackResult.RouteFound { + targets = replace(targets, attackResult) } } @@ -130,11 +128,11 @@ func (s *Scanner) DetectAuthMethods(targets []Stream) []Stream { var authMethod string switch targets[i].AuthenticationType { - case 0: + case authNone: authMethod = "no" - case 1: + case authBasic: authMethod = "basic" - case 2: + case authDigest: authMethod = "digest" } @@ -164,18 +162,27 @@ func (s *Scanner) attackCameraCredentials(target Stream, resChan chan<- Stream) } func (s *Scanner) attackCameraRoute(target Stream, resChan chan<- Stream) { + // If the stream responds positively to the dummy route, it means + // it doesn't require (or respect the RFC) a route and the attack + // can be skipped. + ok := s.routeAttack(target, dummyRoute) + if ok { + target.RouteFound = true + target.Routes = append(target.Routes, "/") + resChan <- target + return + } + + // Otherwise, bruteforce the routes. for _, route := range s.routes { ok := s.routeAttack(target, route) if ok { target.RouteFound = true - target.Route = route - resChan <- target - return + target.Routes = append(target.Routes, route) } time.Sleep(s.attackInterval) } - target.RouteFound = false resChan <- target } @@ -186,7 +193,7 @@ func (s *Scanner) detectAuthMethod(stream Stream) int { "rtsp://%s:%d/%s", stream.Address, stream.Port, - stream.Route, + stream.Route(), ) s.setCurlOptions(c) @@ -210,7 +217,7 @@ func (s *Scanner) detectAuthMethod(stream Stream) int { return -1 } - if s.verbose { + if s.debug { s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", authType) } @@ -255,7 +262,7 @@ func (s *Scanner) routeAttack(stream Stream, route string) bool { return false } - if s.verbose { + if s.debug { s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", rc) } // If it's a 401 or 403, it means that the credentials are wrong but the route might be okay. @@ -275,7 +282,7 @@ func (s *Scanner) credAttack(stream Stream, username string, password string) bo password, stream.Address, stream.Port, - stream.Route, + stream.Route(), ) s.setCurlOptions(c) @@ -304,7 +311,7 @@ func (s *Scanner) credAttack(stream Stream, username string, password string) bo return false } - if s.verbose { + if s.debug { s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", rc) } @@ -325,7 +332,7 @@ func (s *Scanner) validateStream(stream Stream) bool { stream.Password, stream.Address, stream.Port, - stream.Route, + stream.Route(), ) s.setCurlOptions(c) @@ -356,7 +363,7 @@ func (s *Scanner) validateStream(stream Stream) bool { return false } - if s.verbose { + if s.debug { s.term.Debugln("SETUP", attackURL, "RTSP/1.0 >", rc) } // If it's a 200, the stream is accessed successfully. @@ -375,6 +382,13 @@ func (s *Scanner) setCurlOptions(c Curler) { _ = c.Setopt(curl.OPT_NOBODY, 1) // Set custom timeout. _ = c.Setopt(curl.OPT_TIMEOUT_MS, int(s.timeout/time.Millisecond)) + + // Enable verbose logs if verbose mode is on. + if s.verbose { + _ = c.Setopt(curl.OPT_VERBOSE, 1) + } else { + _ = c.Setopt(curl.OPT_VERBOSE, 0) + } } // HACK: See https://stackoverflow.com/questions/3572397/lib-curl-in-c-disable-printing diff --git a/cmd/cameradar/cameradar.go b/cmd/cameradar/cameradar.go index a0fca32..564a645 100644 --- a/cmd/cameradar/cameradar.go +++ b/cmd/cameradar/cameradar.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/Ullaakut/cameradar" + "github.com/Ullaakut/cameradar/v5" "github.com/Ullaakut/disgo" "github.com/Ullaakut/disgo/style" "github.com/spf13/pflag" @@ -25,7 +25,7 @@ func parseArguments() error { 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)") - pflag.BoolP("debug", "d", true, "Enable the debug logs") + pflag.BoolP("debug", "d", false, "Enable the debug logs") pflag.BoolP("verbose", "v", false, "Enable the verbose logs") pflag.BoolP("help", "h", false, "displays this help message") diff --git a/helpers.go b/helpers.go index c29fa85..e385c48 100644 --- a/helpers.go +++ b/helpers.go @@ -18,7 +18,7 @@ func replace(streams []Stream, new Stream) []Stream { // GetCameraRTSPURL generates a stream's RTSP URL. func GetCameraRTSPURL(stream Stream) string { - return "rtsp://" + stream.Username + ":" + stream.Password + "@" + stream.Address + ":" + fmt.Sprint(stream.Port) + "/" + stream.Route + return "rtsp://" + stream.Username + ":" + stream.Password + "@" + stream.Address + ":" + fmt.Sprint(stream.Port) + "/" + stream.Route() } // GetCameraAdminPanelURL returns the URL to the camera's admin panel. diff --git a/helpers_test.go b/helpers_test.go index d4c278a..28b6303 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -61,7 +61,7 @@ func TestGetCameraRTSPURL(t *testing.T) { Address: "1.2.3.4", Username: "ullaakut", Password: "ba69897483886f0d2b0afb6345b76c0c", - Route: "cameradar.sdp", + Routes: []string{"cameradar.sdp"}, Port: 1337, } diff --git a/models.go b/models.go index d620a2b..9d44f9d 100644 --- a/models.go +++ b/models.go @@ -4,12 +4,12 @@ import "time" // Stream represents a camera's RTSP stream type Stream struct { - Device string `json:"device"` - Username string `json:"username"` - Password string `json:"password"` - Route string `json:"route"` - Address string `json:"address" validate:"required"` - Port uint16 `json:"port" validate:"required"` + Device string `json:"device"` + Username string `json:"username"` + Password string `json:"password"` + Routes []string `json:"route"` + Address string `json:"address" validate:"required"` + Port uint16 `json:"port" validate:"required"` CredentialsFound bool `json:"credentials_found"` RouteFound bool `json:"route_found"` @@ -18,6 +18,14 @@ type Stream struct { AuthenticationType int `json:"authentication_type"` } +// Route returns this stream's route if there is one. +func (s Stream) Route() string { + if len(s.Routes) > 0 { + return s.Routes[0] + } + return "" +} + // Credentials is a map of credentials // usernames are keys and passwords are values // creds['admin'] -> 'secure_password' diff --git a/summary.go b/summary.go index fd797ee..bac500b 100644 --- a/summary.go +++ b/summary.go @@ -46,11 +46,16 @@ func (s *Scanner) PrintStreams(streams []Stream) { s.term.Infof("\tPassword:\t\t%s\n", style.Failure("not found")) } + s.term.Infoln("\tRTSP routes:") if stream.RouteFound { - s.term.Infof("\tRTSP route:\t\t%s\n\n\n", style.Success("/"+stream.Route)) + for _, route := range stream.Routes { + s.term.Infoln(style.Success("\t\t\t\t/" + route)) + } } else { - s.term.Infof("\tRTSP route:\t\t%s\n\n\n", style.Failure("not found")) + s.term.Infoln(style.Failure("not found")) } + + s.term.Info("\n\n") } if success > 1 { diff --git a/summary_test.go b/summary_test.go index 84e2422..08fd090 100644 --- a/summary_test.go +++ b/summary_test.go @@ -39,7 +39,7 @@ var ( routeFound = Stream{ RouteFound: true, - Route: "r0ute", + Routes: []string{"r0ute"}, } )