Add digest authentication support

This commit is contained in:
Ullaakut
2019-05-21 11:03:02 +02:00
committed by Brendan Le Glaunec
parent 1d5d606085
commit 260a9645be
4 changed files with 241 additions and 141 deletions
+4
View File
@@ -289,6 +289,10 @@ You forgot the `-t` flag before `ullaakut/cameradar` in your command-line. This
Simply run `docker run -p 8554:8554 -e RTSP_USERNAME=admin -e RTSP_PASSWORD=12345 -e RTSP_PORT=8554 ullaakut/rtspatt` and then run cameradar and it should guess that the username is admin and the password is 12345. You can try this with any default constructor credentials (they can be found [here](dictionaries/credentials.json)) Simply run `docker run -p 8554:8554 -e RTSP_USERNAME=admin -e RTSP_PASSWORD=12345 -e RTSP_PORT=8554 ullaakut/rtspatt` and then run cameradar and it should guess that the username is admin and the password is 12345. You can try this with any default constructor credentials (they can be found [here](dictionaries/credentials.json))
> What authentication types does Cameradar support?
Cameradar supports both basic and digest authentication.
## Examples ## Examples
> Running cameradar on your own machine to scan for default ports > Running cameradar on your own machine to scan for default ports
+178 -101
View File
@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"time" "time"
curl "github.com/ullaakut/go-curl"
"github.com/pkg/errors" "github.com/pkg/errors"
curl "github.com/ullaakut/go-curl"
v "gopkg.in/go-playground/validator.v9" v "gopkg.in/go-playground/validator.v9"
) )
@@ -23,11 +23,176 @@ const (
rtspSetup = 4 rtspSetup = 4
) )
// ValidateStreams tries to setup the stream to validate whether or not it is available
func ValidateStreams(c Curler, targets []Stream, timeout time.Duration, log bool) ([]Stream, error) {
for i := range targets {
targets[i].Available = validateStream(c, targets[i], timeout, log)
}
return targets, nil
}
// AttackCredentials attempts to guess the provided targets' credentials using the given
// dictionary or the default dictionary if none was provided by the user.
func AttackCredentials(c Curler, targets []Stream, credentials Credentials, timeout time.Duration, log bool) ([]Stream, error) {
attacks := make(chan Stream)
defer close(attacks)
validate := v.New()
for _, target := range targets {
err := validate.Struct(target)
if err != nil {
return targets, errors.Wrap(err, "invalid targets")
}
// TODO: Perf Improvement: Skip cameras with no auth type detected, and set their
// CredentialsFound value to true.
go attackCameraCredentials(c, target, credentials, attacks, timeout, log)
}
attackResults := []Stream{}
for range targets {
attackResults = append(attackResults, <-attacks)
}
for _, result := range attackResults {
if result.CredentialsFound {
targets = replace(targets, result)
}
}
return targets, nil
}
// AttackRoute attempts to guess the provided targets' streaming routes using the given
// dictionary or the default dictionary if none was provided by the user.
func AttackRoute(c Curler, targets []Stream, routes Routes, timeout time.Duration, log bool) ([]Stream, error) {
attacks := make(chan Stream)
defer close(attacks)
validate := v.New()
for _, target := range targets {
err := validate.Struct(target)
if err != nil {
return targets, errors.Wrap(err, "invalid targets")
}
go attackCameraRoute(c, target, routes, attacks, timeout, log)
}
attackResults := []Stream{}
for range targets {
attackResults = append(attackResults, <-attacks)
}
for _, result := range attackResults {
if result.RouteFound {
targets = replace(targets, result)
}
}
return targets, nil
}
// DetectAuthMethods attempts to guess the provided targets' authentication types, between
// digest, basic auth or none at all.
func DetectAuthMethods(c Curler, targets []Stream, timeout time.Duration, log bool) ([]Stream, error) {
attacks := make(chan Stream)
defer close(attacks)
validate := v.New()
for i := range targets {
err := validate.Struct(targets[i])
if err != nil {
return targets, errors.Wrap(err, "invalid targets")
}
targets[i].AuthenticationType = detectAuthMethod(c, targets[i], timeout, log)
}
return targets, nil
}
func attackCameraCredentials(c Curler, target Stream, credentials Credentials, resultsChan chan<- Stream, timeout time.Duration, log bool) {
for _, username := range credentials.Usernames {
for _, password := range credentials.Passwords {
ok := credAttack(c.Duphandle(), target, username, password, timeout, log)
if ok {
target.CredentialsFound = true
target.Username = username
target.Password = password
resultsChan <- target
return
}
}
}
target.CredentialsFound = false
resultsChan <- target
}
func attackCameraRoute(c Curler, target Stream, routes Routes, resultsChan chan<- Stream, timeout time.Duration, log bool) {
for _, route := range routes {
ok := routeAttack(c.Duphandle(), target, route, timeout, log)
if ok {
target.RouteFound = true
target.Route = route
resultsChan <- target
return
}
}
target.RouteFound = false
resultsChan <- target
}
// HACK: See https://stackoverflow.com/questions/3572397/lib-curl-in-c-disable-printing // HACK: See https://stackoverflow.com/questions/3572397/lib-curl-in-c-disable-printing
func doNotWrite([]uint8, interface{}) bool { func doNotWrite([]uint8, interface{}) bool {
return true return true
} }
func detectAuthMethod(c Curler, stream Stream, timeout time.Duration, enableLogs bool) int {
attackURL := fmt.Sprintf(
"rtsp://%s:%d/%s",
stream.Address,
stream.Port,
stream.Route,
)
if enableLogs {
// Debug logs when logs are enabled
c.Setopt(curl.OPT_VERBOSE, 1)
} else {
// Do not write sdp in stdout
c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
}
// Do not use signals (would break multithreading)
c.Setopt(curl.OPT_NOSIGNAL, 1)
// Do not send a body in the describe request
c.Setopt(curl.OPT_NOBODY, 1)
// Send a request to the URL of the stream we want to attack
c.Setopt(curl.OPT_URL, attackURL)
// Set the RTSP STREAM URI as the stream URL
c.Setopt(curl.OPT_RTSP_STREAM_URI, attackURL)
// 2 is CURL_RTSPREQ_DESCRIBE
c.Setopt(curl.OPT_RTSP_REQUEST, 2)
// Set custom timeout
c.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
// Perform the request
err := c.Perform()
if err != nil {
return -1
}
authType, err := c.Getinfo(curl.INFO_HTTPAUTH_AVAIL)
if err != nil {
return -1
}
return authType.(int)
}
func routeAttack(c Curler, stream Stream, route string, timeout time.Duration, enableLogs bool) bool { func routeAttack(c Curler, stream Stream, route string, timeout time.Duration, enableLogs bool) bool {
attackURL := fmt.Sprintf( attackURL := fmt.Sprintf(
"rtsp://%s:%s@%s:%d/%s", "rtsp://%s:%s@%s:%d/%s",
@@ -46,6 +211,10 @@ func routeAttack(c Curler, stream Stream, route string, timeout time.Duration, e
c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite) c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
} }
// Set proper authentication type.
c.Setopt(curl.OPT_HTTPAUTH, stream.AuthenticationType)
c.Setopt(curl.OPT_USERPWD, fmt.Sprint(stream.Username, ":", stream.Password))
// Do not use signals (would break multithreading) // Do not use signals (would break multithreading)
c.Setopt(curl.OPT_NOSIGNAL, 1) c.Setopt(curl.OPT_NOSIGNAL, 1)
// Do not send a body in the describe request // Do not send a body in the describe request
@@ -97,6 +266,10 @@ func credAttack(c Curler, stream Stream, username string, password string, timeo
c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite) c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
} }
// Set proper authentication type.
c.Setopt(curl.OPT_HTTPAUTH, stream.AuthenticationType)
c.Setopt(curl.OPT_USERPWD, fmt.Sprint(username, ":", password))
// Do not use signals (would break multithreading) // Do not use signals (would break multithreading)
c.Setopt(curl.OPT_NOSIGNAL, 1) c.Setopt(curl.OPT_NOSIGNAL, 1)
// Do not send a body in the describe request // Do not send a body in the describe request
@@ -148,6 +321,10 @@ func validateStream(c Curler, stream Stream, timeout time.Duration, enableLogs b
c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite) c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
} }
// Set proper authentication type.
c.Setopt(curl.OPT_HTTPAUTH, stream.AuthenticationType)
c.Setopt(curl.OPT_USERPWD, fmt.Sprint(stream.Username, ":", stream.Password))
// Do not use signals (would break multithreading) // Do not use signals (would break multithreading)
c.Setopt(curl.OPT_NOSIGNAL, 1) c.Setopt(curl.OPT_NOSIGNAL, 1)
// Do not send a body in the describe request // Do not send a body in the describe request
@@ -181,103 +358,3 @@ func validateStream(c Curler, stream Stream, timeout time.Duration, enableLogs b
} }
return false return false
} }
// ValidateStreams tries to setup the stream to validate whether or not it is available
func ValidateStreams(c Curler, targets []Stream, timeout time.Duration, log bool) ([]Stream, error) {
for idx, target := range targets {
targets[idx].Available = validateStream(c, target, timeout, log)
}
return targets, nil
}
func attackCameraCredentials(c Curler, target Stream, credentials Credentials, resultsChan chan<- Stream, timeout time.Duration, log bool) {
for _, username := range credentials.Usernames {
for _, password := range credentials.Passwords {
ok := credAttack(c.Duphandle(), target, username, password, timeout, log)
if ok {
target.CredentialsFound = true
target.Username = username
target.Password = password
resultsChan <- target
return
}
}
}
target.CredentialsFound = false
resultsChan <- target
}
func attackCameraRoute(c Curler, target Stream, routes Routes, resultsChan chan<- Stream, timeout time.Duration, log bool) {
for _, route := range routes {
ok := routeAttack(c.Duphandle(), target, route, timeout, log)
if ok {
target.RouteFound = true
target.Route = route
resultsChan <- target
return
}
}
target.RouteFound = false
resultsChan <- target
}
// AttackCredentials attempts to guess the provided targets' credentials using the given
// dictionary or the default dictionary if none was provided by the user.
func AttackCredentials(c Curler, targets []Stream, credentials Credentials, timeout time.Duration, log bool) ([]Stream, error) {
attacks := make(chan Stream)
defer close(attacks)
validate := v.New()
for _, target := range targets {
err := validate.Struct(target)
if err != nil {
return targets, errors.Wrap(err, "invalid targets")
}
go attackCameraCredentials(c, target, credentials, attacks, timeout, log)
}
attackResults := []Stream{}
for range targets {
attackResults = append(attackResults, <-attacks)
}
for _, result := range attackResults {
if result.CredentialsFound {
targets = replace(targets, result)
}
}
return targets, nil
}
// AttackRoute attempts to guess the provided targets' streaming routes using the given
// dictionary or the default dictionary if none was provided by the user.
func AttackRoute(c Curler, targets []Stream, routes Routes, timeout time.Duration, log bool) ([]Stream, error) {
attacks := make(chan Stream)
defer close(attacks)
validate := v.New()
for _, target := range targets {
err := validate.Struct(target)
if err != nil {
return targets, errors.Wrap(err, "invalid targets")
}
go attackCameraRoute(c, target, routes, attacks, timeout, log)
}
attackResults := []Stream{}
for range targets {
attackResults = append(attackResults, <-attacks)
}
for _, result := range attackResults {
if result.RouteFound {
targets = replace(targets, result)
}
}
return targets, nil
}
+57 -40
View File
@@ -130,6 +130,9 @@ func main() {
printErr(term, err) printErr(term, err)
} }
updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Detecting their authentication methods...", options.EnableLogs)
streams, err = cmrdr.DetectAuthMethods(c, streams, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Attacking their credentials...", options.EnableLogs) updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Attacking their credentials...", options.EnableLogs)
streams, err = cmrdr.AttackCredentials(c, streams, credentials, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) streams, err = cmrdr.AttackCredentials(c, streams, credentials, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
if err != nil && len(streams) > 0 { if err != nil && len(streams) > 0 {
@@ -163,48 +166,62 @@ func main() {
func prettyPrint(term *log.Terminal, streams []cmrdr.Stream) { func prettyPrint(term *log.Terminal, streams []cmrdr.Stream) {
success := 0 success := 0
if len(streams) > 0 { if len(streams) == 0 {
for _, stream := range streams {
if stream.CredentialsFound && stream.RouteFound && stream.Available {
term.Infof("%s\tDevice RTSP URL:\t%s\n", style.Success(style.SymbolRightTriangle), style.Link(cmrdr.GetCameraRTSPURL(stream)))
success++
} else {
term.Infof("%s\tAdmin panel URL:\t%s You can use this URL to try attacking the camera's admin panel instead.\n", style.Failure(style.SymbolCross), style.Link(cmrdr.GetCameraAdminPanelURL(stream)))
}
term.Infof("\tDevice model:\t\t%s\n\n", stream.Device)
if stream.Available {
term.Infof("\tAvailable:\t\t%s\n", style.Success(style.SymbolCheck))
} else {
term.Infof("\tAvailable:\t\t%s\n", style.Failure(style.SymbolCross))
}
term.Infof("\tIP address:\t\t%s\n", stream.Address)
term.Infof("\tRTSP port:\t\t%d\n", stream.Port)
if stream.CredentialsFound {
term.Infof("\tUsername:\t\t%s\n", style.Success(stream.Username))
term.Infof("\tPassword:\t\t%s\n", style.Success(stream.Password))
} else {
term.Infof("\tUsername:\t\t%s\n", style.Failure("not found"))
term.Infof("\tPassword:\t\t%s\n", style.Failure("not found"))
}
if stream.RouteFound {
term.Infof("\tRTSP route:\t\t%s\n\n\n", style.Success("/"+stream.Route))
} else {
term.Infof("\tRTSP route:\t\t%s\n\n\n", style.Failure("not found"))
}
}
if success > 1 {
term.Infof("%s Successful attack: %s devices were accessed", style.Success(style.SymbolCheck), style.Success(len(streams)))
} else if success == 1 {
term.Infof("%s Successful attack: %s device was accessed", style.Success(style.SymbolCheck), style.Success(len(streams)))
} else {
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"))
}
} else {
term.Infof("%s No streams were found. Please make sure that your target is on an accessible network.\n", style.Failure(style.SymbolCross)) term.Infof("%s No streams were found. Please make sure that your target is on an accessible network.\n", style.Failure(style.SymbolCross))
} }
for _, stream := range streams {
if stream.CredentialsFound && stream.RouteFound && stream.Available {
term.Infof("%s\tDevice RTSP URL:\t%s\n", style.Success(style.SymbolRightTriangle), style.Link(cmrdr.GetCameraRTSPURL(stream)))
success++
} else {
term.Infof("%s\tAdmin panel URL:\t%s You can use this URL to try attacking the camera's admin panel instead.\n", style.Failure(style.SymbolCross), style.Link(cmrdr.GetCameraAdminPanelURL(stream)))
}
if len(stream.Device) > 0 {
term.Infof("\tDevice model:\t\t%s\n\n", stream.Device)
}
if stream.Available {
term.Infof("\tAvailable:\t\t%s\n", style.Success(style.SymbolCheck))
} else {
term.Infof("\tAvailable:\t\t%s\n", style.Failure(style.SymbolCross))
}
term.Infof("\tIP address:\t\t%s\n", stream.Address)
term.Infof("\tRTSP port:\t\t%d\n", stream.Port)
switch stream.AuthenticationType {
case curl.AUTH_NONE:
term.Infoln("\tThis camera does not require authentication")
case curl.AUTH_BASIC:
term.Infoln("\tAuth type:\t\tbasic")
case curl.AUTH_DIGEST:
term.Infoln("\tAuth type:\t\tdigest")
}
if stream.CredentialsFound {
term.Infof("\tUsername:\t\t%s\n", style.Success(stream.Username))
term.Infof("\tPassword:\t\t%s\n", style.Success(stream.Password))
} else {
term.Infof("\tUsername:\t\t%s\n", style.Failure("not found"))
term.Infof("\tPassword:\t\t%s\n", style.Failure("not found"))
}
if stream.RouteFound {
term.Infof("\tRTSP route:\t\t%s\n\n\n", style.Success("/"+stream.Route))
} else {
term.Infof("\tRTSP route:\t\t%s\n\n\n", style.Failure("not found"))
}
}
if success > 1 {
term.Infof("%s Successful attack: %s devices were accessed", style.Success(style.SymbolCheck), style.Success(len(streams)))
} else if success == 1 {
term.Infof("%s Successful attack: %s device was accessed", style.Success(style.SymbolCheck), style.Success(len(streams)))
} else {
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 printErr(term *log.Terminal, err error) { func printErr(term *log.Terminal, err error) {
+2
View File
@@ -14,6 +14,8 @@ type Stream struct {
CredentialsFound bool `json:"credentials_found"` CredentialsFound bool `json:"credentials_found"`
RouteFound bool `json:"route_found"` RouteFound bool `json:"route_found"`
Available bool `json:"available"` Available bool `json:"available"`
AuthenticationType int `json:"authentication_type"`
} }
// Credentials is a map of credentials // Credentials is a map of credentials