5849898283
Unit tests functional and coverage back to 100% Add more routes to dictionary, add more credentials, add default port 5554, rename cameradar logs ENV variable, improve unit test readability, remove tmp file
284 lines
7.2 KiB
Go
284 lines
7.2 KiB
Go
package cmrdr
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
curl "github.com/andelf/go-curl"
|
|
"github.com/pkg/errors"
|
|
v "gopkg.in/go-playground/validator.v9"
|
|
)
|
|
|
|
// HTTP responses
|
|
const (
|
|
httpOK = 200
|
|
httpUnauthorized = 401
|
|
httpForbidden = 403
|
|
httpNotFound = 404
|
|
)
|
|
|
|
// CURL RTSP request types
|
|
const (
|
|
rtspDescribe = 2
|
|
rtspSetup = 4
|
|
)
|
|
|
|
// HACK: See https://stackoverflow.com/questions/3572397/lib-curl-in-c-disable-printing
|
|
func doNotWrite([]uint8, interface{}) bool {
|
|
return true
|
|
}
|
|
|
|
func routeAttack(c Curler, stream Stream, route string, timeout time.Duration, enableLogs bool) bool {
|
|
attackURL := fmt.Sprintf(
|
|
"rtsp://%s:%s@%s:%d/%s",
|
|
stream.Username,
|
|
stream.Password,
|
|
stream.Address,
|
|
stream.Port,
|
|
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, rtspDescribe)
|
|
// Set custom timeout
|
|
c.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
|
|
|
|
// Perform the request
|
|
err := c.Perform()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Get return code for the request
|
|
rc, err := c.Getinfo(curl.INFO_RESPONSE_CODE)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// If it's a 401 or 403, it means that the credentials are wrong but the route might be okay
|
|
// If it's a 200, the stream is accessed successfully
|
|
if rc == httpOK || rc == httpUnauthorized || rc == httpForbidden {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func credAttack(c Curler, stream Stream, username string, password string, timeout time.Duration, enableLogs bool) bool {
|
|
attackURL := fmt.Sprintf(
|
|
"rtsp://%s:%s@%s:%d/%s",
|
|
username,
|
|
password,
|
|
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 false
|
|
}
|
|
|
|
// Get return code for the request
|
|
rc, err := c.Getinfo(curl.INFO_RESPONSE_CODE)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// If it's a 404, it means that the route is incorrect but the credentials might be okay
|
|
// If it's a 200, the stream is accessed successfully
|
|
if rc == httpOK || rc == httpNotFound {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func validateStream(c Curler, stream Stream, timeout time.Duration, enableLogs bool) bool {
|
|
attackURL := fmt.Sprintf(
|
|
"rtsp://%s:%s@%s:%d/%s",
|
|
stream.Username,
|
|
stream.Password,
|
|
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_SETUP
|
|
c.Setopt(curl.OPT_RTSP_REQUEST, rtspSetup)
|
|
// Set custom timeout
|
|
c.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
|
|
|
|
c.Setopt(curl.OPT_RTSP_TRANSPORT, "RTP/AVP;unicast;client_port=33332-33333")
|
|
|
|
// Perform the request
|
|
err := c.Perform()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Get return code for the request
|
|
rc, err := c.Getinfo(curl.INFO_RESPONSE_CODE)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// If it's a 200, the stream is accessed successfully
|
|
if rc == httpOK {
|
|
return true
|
|
}
|
|
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
|
|
}
|