Skip to content

Commit

Permalink
Support multi-route detection
Browse files Browse the repository at this point in the history
* Change stream model to support multiple routes
* Simplify attack algorithm
* Use dummy route to detect cameras which accept all routes
  • Loading branch information
Ullaakut committed May 4, 2020
1 parent fbc0b7a commit 8e7de3f
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 45 deletions.
78 changes: 46 additions & 32 deletions attack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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)
}
}

Expand All @@ -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"
}

Expand Down Expand Up @@ -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
}

Expand All @@ -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)
Expand All @@ -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)
}

Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}

Expand All @@ -325,7 +332,7 @@ func (s *Scanner) validateStream(stream Stream) bool {
stream.Password,
stream.Address,
stream.Port,
stream.Route,
stream.Route(),
)

s.setCurlOptions(c)
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions cmd/cameradar/cameradar.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")

Expand Down
2 changes: 1 addition & 1 deletion helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down
20 changes: 14 additions & 6 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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'
Expand Down
9 changes: 7 additions & 2 deletions summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion summary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var (

routeFound = Stream{
RouteFound: true,
Route: "r0ute",
Routes: []string{"r0ute"},
}
)

Expand Down

0 comments on commit 8e7de3f

Please sign in to comment.