Increase test coverage, mock libcurl & uniformize error messages

This commit is contained in:
Brendan LE GLAUNEC
2018-02-14 13:49:28 +01:00
committed by Brendan Le Glaunec
parent c1ea6b167c
commit 74672f6625
8 changed files with 478 additions and 203 deletions
+92 -112
View File
@@ -14,120 +14,110 @@ func doNotWrite([]uint8, interface{}) bool {
return true return true
} }
func routeAttack(camera Stream, route string, timeout time.Duration, enableLogs bool) bool { func routeAttack(c Curler, camera Stream, route string, timeout time.Duration, enableLogs bool) bool {
easy := curl.EasyInit() attackURL := fmt.Sprintf(
defer easy.Cleanup() "rtsp://%s:%s@%s:%d/%s",
camera.Username,
camera.Password,
camera.Address,
camera.Port,
route,
)
if easy != nil { if enableLogs {
attackURL := fmt.Sprintf( // Debug logs when logs are enabled
"rtsp://%s:%s@%s:%d/%s", c.Setopt(curl.OPT_VERBOSE, 1)
camera.Username, } else {
camera.Password, // Do not write sdp in stdout
camera.Address, c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
camera.Port, }
route,
)
if enableLogs { // Do not send a body in the describe request
// Debug logs when logs are enabled c.Setopt(curl.OPT_NOBODY, 1)
easy.Setopt(curl.OPT_VERBOSE, 1) // Send a request to the URL of the camera we want to attack
} else { c.Setopt(curl.OPT_URL, attackURL)
// Do not write sdp in stdout // Set the RTSP STREAM URI as the camera URL
easy.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite) 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))
// Do not send a body in the describe request // Perform the request
easy.Setopt(curl.OPT_NOBODY, 1) err := c.Perform()
// Send a request to the URL of the camera we want to attack if err != nil {
easy.Setopt(curl.OPT_URL, attackURL) fmt.Printf("\nERROR: curl timeout on camera '%s' reached after %s.\nconsider increasing the timeout (-T, --timeout parameter) to at least 5000ms if scanning an unstable network.\n", camera.Address, timeout.String())
// Set the RTSP STREAM URI as the camera URL return false
easy.Setopt(curl.OPT_RTSP_STREAM_URI, attackURL) }
// 2 is CURL_RTSPREQ_DESCRIBE
easy.Setopt(curl.OPT_RTSP_REQUEST, 2)
// Set custom timeout
easy.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
// Perform the request // Get return code for the request
err := easy.Perform() rc, err := c.Getinfo(curl.INFO_RESPONSE_CODE)
if err != nil { if err != nil {
fmt.Printf("\nERROR: curl timeout on camera '%s' reached after %s.\nconsider increasing the timeout (-T, --timeout parameter) to at least 5000ms if scanning an unstable network.\n", camera.Address, timeout.String()) return false
return false }
}
// Get return code for the request // If it's a 401 or 403, it means that the credentials are wrong but the route might be okay
rc, err := easy.Getinfo(curl.INFO_RESPONSE_CODE) // If it's a 200, the camera is accessed successfully
if err != nil { if rc == 200 || rc == 401 || rc == 403 {
return false return true
}
// 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 camera is accessed successfully
if rc == 200 || rc == 401 || rc == 403 {
return true
}
} }
return false return false
} }
func credAttack(camera Stream, username string, password string, timeout time.Duration, enableLogs bool) bool { func credAttack(c Curler, camera Stream, username string, password string, timeout time.Duration, enableLogs bool) bool {
easy := curl.EasyInit() attackURL := fmt.Sprintf(
defer easy.Cleanup() "rtsp://%s:%s@%s:%d/%s",
username,
password,
camera.Address,
camera.Port,
camera.Route,
)
if easy != nil { if enableLogs {
attackURL := fmt.Sprintf( // Debug logs when logs are enabled
"rtsp://%s:%s@%s:%d/%s", c.Setopt(curl.OPT_VERBOSE, 1)
username, } else {
password, // Do not write sdp in stdout
camera.Address, c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
camera.Port, }
camera.Route,
)
if enableLogs { // Do not send a body in the describe request
// Debug logs when logs are enabled c.Setopt(curl.OPT_NOBODY, 1)
easy.Setopt(curl.OPT_VERBOSE, 1) // Send a request to the URL of the camera we want to attack
} else { c.Setopt(curl.OPT_URL, attackURL)
// Do not write sdp in stdout // Set the RTSP STREAM URI as the camera URL
easy.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite) 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))
// Do not send a body in the describe request // Perform the request
easy.Setopt(curl.OPT_NOBODY, 1) err := c.Perform()
// Send a request to the URL of the camera we want to attack if err != nil {
easy.Setopt(curl.OPT_URL, attackURL) fmt.Printf("\nERROR: curl timeout on camera '%s' reached after %s.\nconsider increasing the timeout (-T, --timeout parameter) to at least 5000ms if scanning an unstable network.\n", camera.Address, timeout.String())
// Set the RTSP STREAM URI as the camera URL return false
easy.Setopt(curl.OPT_RTSP_STREAM_URI, attackURL) }
// 2 is CURL_RTSPREQ_DESCRIBE
easy.Setopt(curl.OPT_RTSP_REQUEST, 2)
// Set custom timeout
easy.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
// Perform the request // Get return code for the request
err := easy.Perform() rc, err := c.Getinfo(curl.INFO_RESPONSE_CODE)
if err != nil { if err != nil {
fmt.Printf("\nERROR: curl timeout on camera '%s' reached after %s.\nconsider increasing the timeout (-T, --timeout parameter) to at least 5000ms if scanning an unstable network.\n", camera.Address, timeout.String()) return false
return false }
}
// Get return code for the request // If it's a 404, it means that the route is incorrect but the credentials might be okay
rc, err := easy.Getinfo(curl.INFO_RESPONSE_CODE) // If it's a 200, the camera is accessed successfully
if err != nil { if rc == 200 || rc == 404 {
return false return true
}
// If it's a 404, it means that the route is incorrect but the credentials might be okay
// If it's a 200, the camera is accessed successfully
if rc == 200 || rc == 404 {
return true
}
} }
return false return false
} }
func attackCameraCredentials(target Stream, credentials Credentials, resultsChan chan<- Stream, timeout time.Duration, log bool) { func attackCameraCredentials(c Curler, target Stream, credentials Credentials, resultsChan chan<- Stream, timeout time.Duration, log bool) {
for _, username := range credentials.Usernames { for _, username := range credentials.Usernames {
for _, password := range credentials.Passwords { for _, password := range credentials.Passwords {
ok := credAttack(target, username, password, timeout, log) ok := credAttack(c, target, username, password, timeout, log)
if ok { if ok {
target.CredentialsFound = true target.CredentialsFound = true
target.Username = username target.Username = username
@@ -141,9 +131,9 @@ func attackCameraCredentials(target Stream, credentials Credentials, resultsChan
resultsChan <- target resultsChan <- target
} }
func attackCameraRoute(target Stream, routes Routes, resultsChan chan<- Stream, timeout time.Duration, log bool) { func attackCameraRoute(c Curler, target Stream, routes Routes, resultsChan chan<- Stream, timeout time.Duration, log bool) {
for _, route := range routes { for _, route := range routes {
ok := routeAttack(target, route, timeout, log) ok := routeAttack(c, target, route, timeout, log)
if ok { if ok {
target.RouteFound = true target.RouteFound = true
target.Route = route target.Route = route
@@ -157,12 +147,7 @@ func attackCameraRoute(target Stream, routes Routes, resultsChan chan<- Stream,
// AttackCredentials attempts to guess the provided targets' credentials using the given // AttackCredentials attempts to guess the provided targets' credentials using the given
// dictionary or the default dictionary if none was provided by the user. // dictionary or the default dictionary if none was provided by the user.
func AttackCredentials(targets []Stream, credentials Credentials, timeout time.Duration, log bool) ([]Stream, error) { func AttackCredentials(c Curler, targets []Stream, credentials Credentials, timeout time.Duration, log bool) ([]Stream, error) {
err := curl.GlobalInit(curl.GLOBAL_ALL)
if err != nil {
return targets, errors.Wrap(err, "could not initialize curl")
}
attacks := make(chan Stream) attacks := make(chan Stream)
defer close(attacks) defer close(attacks)
@@ -170,10 +155,10 @@ func AttackCredentials(targets []Stream, credentials Credentials, timeout time.D
for _, target := range targets { for _, target := range targets {
err := validate.Struct(target) err := validate.Struct(target)
if err != nil { if err != nil {
return targets, errors.Wrap(err, "invalid streams") return targets, errors.Wrap(err, "invalid targets")
} }
go attackCameraCredentials(target, credentials, attacks, timeout, log) go attackCameraCredentials(c, target, credentials, attacks, timeout, log)
} }
attackResults := []Stream{} attackResults := []Stream{}
@@ -197,12 +182,7 @@ func AttackCredentials(targets []Stream, credentials Credentials, timeout time.D
// AttackRoute attempts to guess the provided targets' streaming routes using the given // AttackRoute attempts to guess the provided targets' streaming routes using the given
// dictionary or the default dictionary if none was provided by the user. // dictionary or the default dictionary if none was provided by the user.
func AttackRoute(targets []Stream, routes Routes, timeout time.Duration, log bool) ([]Stream, error) { func AttackRoute(c Curler, targets []Stream, routes Routes, timeout time.Duration, log bool) ([]Stream, error) {
err := curl.GlobalInit(curl.GLOBAL_ALL)
if err != nil {
return targets, errors.Wrap(err, "could not initialize curl")
}
attacks := make(chan Stream) attacks := make(chan Stream)
defer close(attacks) defer close(attacks)
@@ -210,10 +190,10 @@ func AttackRoute(targets []Stream, routes Routes, timeout time.Duration, log boo
for _, target := range targets { for _, target := range targets {
err := validate.Struct(target) err := validate.Struct(target)
if err != nil { if err != nil {
return targets, errors.Wrap(err, "invalid streams") return targets, errors.Wrap(err, "invalid targets")
} }
go attackCameraRoute(target, routes, attacks, timeout, log) go attackCameraRoute(c, target, routes, attacks, timeout, log)
} }
attackResults := []Stream{} attackResults := []Stream{}
+208 -25
View File
@@ -1,16 +1,35 @@
package cmrdr package cmrdr
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"testing" "testing"
"time" "time"
curl "github.com/andelf/go-curl"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
) )
// Again, since these tests use the curl library, I don't want to spend ages trying to mock type CurlerMock struct {
// the lib right now. mock.Mock
}
func (m *CurlerMock) Setopt(opt int, param interface{}) error {
args := m.Called(opt, param)
return args.Error(0)
}
func (m *CurlerMock) Perform() error {
args := m.Called()
return args.Error(0)
}
func (m *CurlerMock) Getinfo(info curl.CurlInfo) (interface{}, error) {
args := m.Called(info)
return args.Int(0), args.Error(1)
}
func TestAttackCredentials(t *testing.T) { func TestAttackCredentials(t *testing.T) {
validStream1 := Stream{ validStream1 := Stream{
@@ -25,58 +44,135 @@ func TestAttackCredentials(t *testing.T) {
Port: 1337, Port: 1337,
} }
invalidStream := Stream{
Device: "InvalidDevice",
}
fakeTargets := []Stream{validStream1, validStream2} fakeTargets := []Stream{validStream1, validStream2}
invalidTargets := []Stream{invalidStream}
fakeCredentials := Credentials{ fakeCredentials := Credentials{
Usernames: []string{"admin", "root"}, Usernames: []string{"admin", "root"},
Passwords: []string{"12345", "root"}, Passwords: []string{"12345", "root"},
} }
vectors := []struct { testCases := []struct {
targets []Stream targets []Stream
credentials Credentials credentials Credentials
timeout time.Duration timeout time.Duration
log bool log bool
status int
performErr error
getInfoErr error
invalidTargets bool
expectedStreams []Stream expectedStreams []Stream
expectedErrMsg string expectedErrMsg string
}{ }{
// Valid baseline // Credentials found
{
targets: fakeTargets,
credentials: fakeCredentials,
timeout: 1 * time.Millisecond,
status: 404,
expectedStreams: fakeTargets,
},
// Camera accessed
{
targets: fakeTargets,
credentials: fakeCredentials,
timeout: 1 * time.Millisecond,
status: 200,
expectedStreams: fakeTargets,
},
// Invalid targets
{
targets: invalidTargets,
credentials: fakeCredentials,
timeout: 1 * time.Millisecond,
invalidTargets: true,
expectedErrMsg: "invalid targets",
expectedStreams: invalidTargets,
},
// curl perform fails
{
targets: fakeTargets,
credentials: fakeCredentials,
timeout: 1 * time.Millisecond,
performErr: errors.New("dummy error"),
expectedStreams: fakeTargets,
expectedErrMsg: "no credentials found",
},
// curl getinfo fails
{
targets: fakeTargets,
credentials: fakeCredentials,
timeout: 1 * time.Millisecond,
getInfoErr: errors.New("dummy error"),
expectedStreams: fakeTargets,
expectedErrMsg: "no credentials found",
},
// Credentials not found
{ {
targets: fakeTargets, targets: fakeTargets,
credentials: fakeCredentials, credentials: fakeCredentials,
timeout: 1 * time.Millisecond, timeout: 1 * time.Millisecond,
log: true, log: true,
status: 403,
expectedStreams: fakeTargets, expectedStreams: fakeTargets,
expectedErrMsg: "no credentials found", expectedErrMsg: "no credentials found",
}, },
// Valid baseline without logs // Logging disabled
{ {
targets: fakeTargets, targets: fakeTargets,
credentials: fakeCredentials, credentials: fakeCredentials,
timeout: 1 * time.Millisecond, timeout: 1 * time.Millisecond,
log: false, log: false,
status: 403,
expectedStreams: fakeTargets, expectedStreams: fakeTargets,
expectedErrMsg: "no credentials found", expectedErrMsg: "no credentials found",
}, },
// TODO: Refacto and make tests with all possible error cases
} }
for i, vector := range vectors { for i, test := range testCases {
results, err := AttackCredentials(vector.targets, vector.credentials, vector.timeout, vector.log) curlerMock := &CurlerMock{}
if len(vector.expectedErrMsg) > 0 { if !test.invalidTargets {
curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
curlerMock.On("Perform").Return(test.performErr)
if test.performErr == nil {
curlerMock.On("Getinfo", mock.Anything).Return(test.status, test.getInfoErr)
}
}
results, err := AttackCredentials(curlerMock, test.targets, test.credentials, test.timeout, test.log)
if len(test.expectedErrMsg) > 0 {
if err == nil { if err == nil {
fmt.Printf("unexpected success in AttackCredentials test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg) fmt.Printf("unexpected success in AttackCredentials test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
os.Exit(1) os.Exit(1)
} }
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message") assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
} else { } else {
if err != nil { if err != nil {
fmt.Printf("unexpected error in AttackCredentials test, iteration %d: %v\n", i, err) fmt.Printf("unexpected error in AttackCredentials test, iteration %d: %v\n", i, err)
os.Exit(1) os.Exit(1)
} }
for _, stream := range vector.expectedStreams { for _, stream := range test.expectedStreams {
foundStream := false foundStream := false
for _, result := range results { for _, result := range results {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port { if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
@@ -86,8 +182,8 @@ func TestAttackCredentials(t *testing.T) {
assert.Equal(t, true, foundStream, "wrong streams parsed") assert.Equal(t, true, foundStream, "wrong streams parsed")
} }
} }
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed") assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
curlerMock.AssertExpectations(t)
} }
} }
@@ -104,19 +200,92 @@ func TestAttackRoute(t *testing.T) {
Port: 1337, Port: 1337,
} }
invalidStream := Stream{
Device: "InvalidDevice",
}
fakeTargets := []Stream{validStream1, validStream2} fakeTargets := []Stream{validStream1, validStream2}
fakeRoutes := Routes{"live.sdp", "media.amp"} fakeRoutes := Routes{"live.sdp", "media.amp"}
invalidTargets := []Stream{invalidStream}
vectors := []struct { testCases := []struct {
targets []Stream targets []Stream
routes Routes routes Routes
timeout time.Duration timeout time.Duration
log bool log bool
status int
performErr error
getInfoErr error
invalidTargets bool
expectedStreams []Stream expectedStreams []Stream
expectedErrMsg string expectedErrMsg string
}{ }{
// Valid baseline // Route found
{
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
status: 403,
expectedStreams: fakeTargets,
},
// Route found
{
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
status: 401,
expectedStreams: fakeTargets,
},
// Camera accessed
{
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
status: 200,
expectedStreams: fakeTargets,
},
// Invalid targets
{
targets: invalidTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
invalidTargets: true,
expectedErrMsg: "invalid targets",
expectedStreams: invalidTargets,
},
// curl perform fails
{
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
performErr: errors.New("dummy error"),
expectedStreams: fakeTargets,
expectedErrMsg: "no routes found",
},
// curl getinfo fails
{
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
getInfoErr: errors.New("dummy error"),
expectedStreams: fakeTargets,
expectedErrMsg: "no routes found",
},
// Routes not found
{ {
targets: fakeTargets, targets: fakeTargets,
routes: fakeRoutes, routes: fakeRoutes,
@@ -126,7 +295,7 @@ func TestAttackRoute(t *testing.T) {
expectedStreams: fakeTargets, expectedStreams: fakeTargets,
expectedErrMsg: "no routes found", expectedErrMsg: "no routes found",
}, },
// Valid baseline without logs // Logs disabled
{ {
targets: fakeTargets, targets: fakeTargets,
routes: fakeRoutes, routes: fakeRoutes,
@@ -136,23 +305,32 @@ func TestAttackRoute(t *testing.T) {
expectedStreams: fakeTargets, expectedStreams: fakeTargets,
expectedErrMsg: "no routes found", expectedErrMsg: "no routes found",
}, },
// TODO: Refacto and make tests with all possible error cases
} }
for i, vector := range vectors { for i, test := range testCases {
results, err := AttackRoute(vector.targets, vector.routes, vector.timeout, vector.log) curlerMock := &CurlerMock{}
if len(vector.expectedErrMsg) > 0 { if !test.invalidTargets {
curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
curlerMock.On("Perform").Return(test.performErr)
if test.performErr == nil {
curlerMock.On("Getinfo", mock.Anything).Return(test.status, test.getInfoErr)
}
}
results, err := AttackRoute(curlerMock, test.targets, test.routes, test.timeout, test.log)
if len(test.expectedErrMsg) > 0 {
if err == nil { if err == nil {
fmt.Printf("unexpected success in AttackRoute test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg) fmt.Printf("unexpected success in AttackRoute test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
os.Exit(1) os.Exit(1)
} }
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message") assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
} else { } else {
if err != nil { if err != nil {
fmt.Printf("unexpected error in AttackRoute test, iteration %d: %v\n", i, err) fmt.Printf("unexpected error in AttackRoute test, iteration %d: %v\n", i, err)
os.Exit(1) os.Exit(1)
} }
for _, stream := range vector.expectedStreams { for _, stream := range test.expectedStreams {
foundStream := false foundStream := false
for _, result := range results { for _, result := range results {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port { if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
@@ -162,6 +340,11 @@ func TestAttackRoute(t *testing.T) {
assert.Equal(t, true, foundStream, "wrong streams parsed") assert.Equal(t, true, foundStream, "wrong streams parsed")
} }
} }
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed") assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
curlerMock.AssertExpectations(t)
} }
} }
func TestDotWrite(t *testing.T) {
assert.Equal(t, true, doNotWrite(nil, nil))
}
+13 -6
View File
@@ -8,12 +8,13 @@ import (
"time" "time"
"github.com/EtixLabs/cameradar" "github.com/EtixLabs/cameradar"
curl "github.com/andelf/go-curl"
"github.com/fatih/color"
"github.com/gernest/wow" "github.com/gernest/wow"
"github.com/gernest/wow/spin" "github.com/gernest/wow/spin"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/fatih/color"
) )
type options struct { type options struct {
@@ -92,6 +93,13 @@ func main() {
w := startSpinner(options.EnableLogs) w := startSpinner(options.EnableLogs)
err = curl.GlobalInit(curl.GLOBAL_ALL)
c := curl.EasyInit()
if err != nil || c == nil {
printErr(errors.New("libcurl initialization failed"))
}
defer curl.GlobalCleanup()
updateSpinner(w, "Loading dictionaries...", options.EnableLogs) updateSpinner(w, "Loading dictionaries...", options.EnableLogs)
gopath := os.Getenv("GOPATH") gopath := os.Getenv("GOPATH")
options.Credentials = strings.Replace(options.Credentials, "<GOPATH>", gopath, 1) options.Credentials = strings.Replace(options.Credentials, "<GOPATH>", gopath, 1)
@@ -116,15 +124,14 @@ func main() {
} }
// Most cameras will be accessed successfully with these two attacks // Most cameras will be accessed successfully with these two attacks
updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Attacking their routes...", options.EnableLogs) updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Attacking their routes...", options.EnableLogs)
streams, err = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) streams, err = cmrdr.AttackRoute(c, streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
if err != nil && len(streams) > 0 { if err != nil && len(streams) > 0 {
printErr(err) printErr(err)
} }
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(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 {
printErr(err) printErr(err)
} }
@@ -135,7 +142,7 @@ func main() {
if stream.RouteFound == false || stream.CredentialsFound == false { if stream.RouteFound == false || stream.CredentialsFound == false {
updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Final attack...", options.EnableLogs) updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Final attack...", options.EnableLogs)
streams, err = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs) streams, err = cmrdr.AttackRoute(c, streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
if err != nil && len(streams) > 0 { if err != nil && len(streams) > 0 {
printErr(err) printErr(err)
} }
BIN
View File
Binary file not shown.
+13
View File
@@ -0,0 +1,13 @@
package cmrdr
import (
curl "github.com/andelf/go-curl"
)
// Curler is an interface that implements the CURL interface of the go-curl library
// Used for mocking
type Curler interface {
Setopt(opt int, param interface{}) error
Perform() error
Getinfo(info curl.CurlInfo) (interface{}, error)
}
+31 -31
View File
@@ -37,7 +37,7 @@ func TestNmapRun(t *testing.T) {
execCommand = fakeExecCommand execCommand = fakeExecCommand
defer func() { execCommand = exec.Command }() defer func() { execCommand = exec.Command }()
vectors := []struct { testCases := []struct {
targets string targets string
ports string ports string
resultFilePath string resultFilePath string
@@ -65,14 +65,14 @@ func TestNmapRun(t *testing.T) {
expectedErrMsg: "invalid nmap speed value", expectedErrMsg: "invalid nmap speed value",
}, },
} }
for _, vector := range vectors { for _, test := range testCases {
err := NmapRun(vector.targets, vector.ports, vector.resultFilePath, vector.nmapSpeed, vector.enableLogs) err := NmapRun(test.targets, test.ports, test.resultFilePath, test.nmapSpeed, test.enableLogs)
if len(vector.expectedErrMsg) > 0 { if len(test.expectedErrMsg) > 0 {
if err == nil { if err == nil {
fmt.Printf("unexpected success. expected error: %s\n", vector.expectedErrMsg) fmt.Printf("unexpected success. expected error: %s\n", test.expectedErrMsg)
os.Exit(1) os.Exit(1)
} }
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message") assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
} else { } else {
if err != nil { if err != nil {
fmt.Printf("unexpected error: %v\n", err) fmt.Printf("unexpected error: %v\n", err)
@@ -107,7 +107,7 @@ func TestNmapParseResults(t *testing.T) {
Port: 1337, Port: 1337,
} }
vectors := []struct { testCases := []struct {
fileExists bool fileExists bool
streamsXML *nmapResult streamsXML *nmapResult
@@ -165,7 +165,7 @@ func TestNmapParseResults(t *testing.T) {
fileExists: true, fileExists: true,
}, },
// File exists // File exists
// Two invalid streams, no error // Two invalid targets, no error
{ {
fileExists: true, fileExists: true,
expectedStreams: []Stream{invalidStreamNoPort, invalidStreamNoAddress}, expectedStreams: []Stream{invalidStreamNoPort, invalidStreamNoAddress},
@@ -261,11 +261,11 @@ func TestNmapParseResults(t *testing.T) {
expectedErrMsg: "expected element type <nmaprun> but have <failure>", expectedErrMsg: "expected element type <nmaprun> but have <failure>",
}, },
} }
for i, vector := range vectors { for i, test := range testCases {
filePath := "/tmp/cameradar_test_parse_results_" + fmt.Sprint(i) + ".xml" filePath := "/tmp/cameradar_test_parse_results_" + fmt.Sprint(i) + ".xml"
// create file // create file
if vector.fileExists { if test.fileExists {
_, err := os.Create(filePath) _, err := os.Create(filePath)
if err != nil { if err != nil {
fmt.Printf("could not create xml file for NmapParseResults: %v. iteration: %d. file path: %s\n", err, i, filePath) fmt.Printf("could not create xml file for NmapParseResults: %v. iteration: %d. file path: %s\n", err, i, filePath)
@@ -273,10 +273,10 @@ func TestNmapParseResults(t *testing.T) {
} }
// marshal and write // marshal and write
if vector.streamsXML != nil { if test.streamsXML != nil {
streams, err := xml.Marshal(vector.streamsXML) streams, err := xml.Marshal(test.streamsXML)
if err != nil { if err != nil {
fmt.Printf("invalid streams for NmapParseResults: %v. iteration: %d. streams: %v\n", err, i, vector.streamsXML) fmt.Printf("invalid targets for NmapParseResults: %v. iteration: %d. streams: %v\n", err, i, test.streamsXML)
os.Exit(1) os.Exit(1)
} }
@@ -295,18 +295,18 @@ func TestNmapParseResults(t *testing.T) {
} }
results, err := NmapParseResults(filePath) results, err := NmapParseResults(filePath)
if len(vector.expectedErrMsg) > 0 { if len(test.expectedErrMsg) > 0 {
if err == nil { if err == nil {
fmt.Printf("unexpected success. expected error: %s\n", vector.expectedErrMsg) fmt.Printf("unexpected success. expected error: %s\n", test.expectedErrMsg)
os.Exit(1) os.Exit(1)
} }
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message") assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
} else { } else {
if err != nil { if err != nil {
fmt.Printf("unexpected error: %v\n", err) fmt.Printf("unexpected error: %v\n", err)
os.Exit(1) os.Exit(1)
} }
for _, stream := range vector.expectedStreams { for _, stream := range test.expectedStreams {
foundStream := false foundStream := false
for _, result := range results { for _, result := range results {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port { if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
@@ -316,7 +316,7 @@ func TestNmapParseResults(t *testing.T) {
assert.Equal(t, true, foundStream, "wrong streams parsed") assert.Equal(t, true, foundStream, "wrong streams parsed")
} }
} }
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed") assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
} }
} }
@@ -348,7 +348,7 @@ func TestDiscover(t *testing.T) {
Port: 1337, Port: 1337,
} }
vectors := []struct { testCases := []struct {
targets string targets string
ports string ports string
resultFilePath string resultFilePath string
@@ -526,7 +526,7 @@ func TestDiscover(t *testing.T) {
enableLogs: false, enableLogs: false,
}, },
// File exists // File exists
// Two invalid streams, no error // Two invalid targets, no error
{ {
fileExists: true, fileExists: true,
expectedStreams: []Stream{invalidStreamNoPort, invalidStreamNoAddress}, expectedStreams: []Stream{invalidStreamNoPort, invalidStreamNoAddress},
@@ -642,11 +642,11 @@ func TestDiscover(t *testing.T) {
enableLogs: false, enableLogs: false,
}, },
} }
for i, vector := range vectors { for i, test := range testCases {
filePath := "/tmp/cameradar_test_discover_" + fmt.Sprint(i) + ".xml" filePath := "/tmp/cameradar_test_discover_" + fmt.Sprint(i) + ".xml"
// create file // create file
if vector.fileExists { if test.fileExists {
_, err := os.Create(filePath) _, err := os.Create(filePath)
if err != nil { if err != nil {
fmt.Printf("could not create xml file for Discover: %v. iteration: %d. file path: %s\n", err, i, filePath) fmt.Printf("could not create xml file for Discover: %v. iteration: %d. file path: %s\n", err, i, filePath)
@@ -654,10 +654,10 @@ func TestDiscover(t *testing.T) {
} }
// marshal and write // marshal and write
if vector.streamsXML != nil { if test.streamsXML != nil {
streams, err := xml.Marshal(vector.streamsXML) streams, err := xml.Marshal(test.streamsXML)
if err != nil { if err != nil {
fmt.Printf("invalid streams for Discover: %v. iteration: %d. streams: %v\n", err, i, vector.streamsXML) fmt.Printf("invalid targets for Discover: %v. iteration: %d. streams: %v\n", err, i, test.streamsXML)
os.Exit(1) os.Exit(1)
} }
@@ -675,20 +675,20 @@ func TestDiscover(t *testing.T) {
} }
} }
results, err := Discover(vector.targets, vector.ports, filePath, vector.nmapSpeed, vector.enableLogs) results, err := Discover(test.targets, test.ports, filePath, test.nmapSpeed, test.enableLogs)
if len(vector.expectedErrMsg) > 0 { if len(test.expectedErrMsg) > 0 {
if err == nil { if err == nil {
fmt.Printf("unexpected success in Discover test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg) fmt.Printf("unexpected success in Discover test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
os.Exit(1) os.Exit(1)
} }
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message") assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
} else { } else {
if err != nil { if err != nil {
fmt.Printf("unexpected error in Discover test, iteration %d: %v\n", i, err) fmt.Printf("unexpected error in Discover test, iteration %d: %v\n", i, err)
os.Exit(1) os.Exit(1)
} }
for _, stream := range vector.expectedStreams { for _, stream := range test.expectedStreams {
foundStream := false foundStream := false
for _, result := range results { for _, result := range results {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port { if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
@@ -698,6 +698,6 @@ func TestDiscover(t *testing.T) {
assert.Equal(t, true, foundStream, "wrong streams parsed") assert.Equal(t, true, foundStream, "wrong streams parsed")
} }
} }
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed") assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
} }
} }
+12 -12
View File
@@ -31,7 +31,7 @@ func TestReplace(t *testing.T) {
Port: 1337, Port: 1337,
} }
vectors := []struct { testCases := []struct {
streams []Stream streams []Stream
newStream Stream newStream Stream
@@ -45,10 +45,10 @@ func TestReplace(t *testing.T) {
expectedStreams: []Stream{validStream1, validStream2, invalidStreamNoPortModified}, expectedStreams: []Stream{validStream1, validStream2, invalidStreamNoPortModified},
}, },
} }
for _, vector := range vectors { for _, test := range testCases {
streams := replace(vector.streams, vector.newStream) streams := replace(test.streams, test.newStream)
for _, stream := range vector.streams { for _, stream := range test.streams {
foundStream := false foundStream := false
for _, result := range streams { for _, result := range streams {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port { if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
@@ -69,7 +69,7 @@ func TestGetCameraRTSPURL(t *testing.T) {
Port: 1337, Port: 1337,
} }
vectors := []struct { testCases := []struct {
stream Stream stream Stream
expectedRTSPURL string expectedRTSPURL string
@@ -81,9 +81,9 @@ func TestGetCameraRTSPURL(t *testing.T) {
expectedRTSPURL: "rtsp://ullaakut:ba69897483886f0d2b0afb6345b76c0c@1.2.3.4:1337/cameradar.sdp", expectedRTSPURL: "rtsp://ullaakut:ba69897483886f0d2b0afb6345b76c0c@1.2.3.4:1337/cameradar.sdp",
}, },
} }
for _, vector := range vectors { for _, test := range testCases {
output := GetCameraRTSPURL(vector.stream) output := GetCameraRTSPURL(test.stream)
assert.Equal(t, vector.expectedRTSPURL, output, "wrong RTSP URL generated") assert.Equal(t, test.expectedRTSPURL, output, "wrong RTSP URL generated")
} }
} }
@@ -92,7 +92,7 @@ func TestGetCameraAdminPanelURL(t *testing.T) {
Address: "1.2.3.4", Address: "1.2.3.4",
} }
vectors := []struct { testCases := []struct {
stream Stream stream Stream
expectedRTSPURL string expectedRTSPURL string
@@ -104,8 +104,8 @@ func TestGetCameraAdminPanelURL(t *testing.T) {
expectedRTSPURL: "http://1.2.3.4/", expectedRTSPURL: "http://1.2.3.4/",
}, },
} }
for _, vector := range vectors { for _, test := range testCases {
output := GetCameraAdminPanelURL(vector.stream) output := GetCameraAdminPanelURL(test.stream)
assert.Equal(t, vector.expectedRTSPURL, output, "wrong Admin Panel URL generated") assert.Equal(t, test.expectedRTSPURL, output, "wrong Admin Panel URL generated")
} }
} }
+109 -17
View File
@@ -16,7 +16,7 @@ func TestLoadCredentials(t *testing.T) {
Passwords: []string{"12345", "root"}, Passwords: []string{"12345", "root"},
} }
vectors := []struct { testCases := []struct {
input []byte input []byte
fileExists bool fileExists bool
@@ -47,17 +47,17 @@ func TestLoadCredentials(t *testing.T) {
input: []byte("{\"invalid\":\"json\"}"), input: []byte("{\"invalid\":\"json\"}"),
}, },
} }
for i, vector := range vectors { for i, test := range testCases {
filePath := "/tmp/cameradar_test_load_credentials_" + fmt.Sprint(i) + ".xml" filePath := "/tmp/cameradar_test_load_credentials_" + fmt.Sprint(i) + ".xml"
// create file // create file
if vector.fileExists { if test.fileExists {
_, err := os.Create(filePath) _, err := os.Create(filePath)
if err != nil { if err != nil {
fmt.Printf("could not create xml file for LoadCredentials: %v. iteration: %d. file path: %s\n", err, i, filePath) fmt.Printf("could not create xml file for LoadCredentials: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1) os.Exit(1)
} }
err = ioutil.WriteFile(filePath, vector.input, 0644) err = ioutil.WriteFile(filePath, test.input, 0644)
if err != nil { if err != nil {
fmt.Printf("could not write xml file for LoadCredentials: %v. iteration: %d. file path: %s\n", err, i, filePath) fmt.Printf("could not write xml file for LoadCredentials: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1) os.Exit(1)
@@ -65,18 +65,18 @@ func TestLoadCredentials(t *testing.T) {
} }
result, err := LoadCredentials(filePath) result, err := LoadCredentials(filePath)
if len(vector.expectedErrMsg) > 0 { if len(test.expectedErrMsg) > 0 {
if err == nil { if err == nil {
fmt.Printf("unexpected success in LoadCredentials test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg) fmt.Printf("unexpected success in LoadCredentials test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
os.Exit(1) os.Exit(1)
} }
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message") assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
} else { } else {
if err != nil { if err != nil {
fmt.Printf("unexpected error in LoadCredentials test, iteration %d: %v\n", i, err) fmt.Printf("unexpected error in LoadCredentials test, iteration %d: %v\n", i, err)
os.Exit(1) os.Exit(1)
} }
for _, expectedUsername := range vector.expectedOutput.Usernames { for _, expectedUsername := range test.expectedOutput.Usernames {
foundUsername := false foundUsername := false
for _, username := range result.Usernames { for _, username := range result.Usernames {
if username == expectedUsername { if username == expectedUsername {
@@ -85,7 +85,7 @@ func TestLoadCredentials(t *testing.T) {
} }
assert.Equal(t, true, foundUsername, "wrong usernames parsed") assert.Equal(t, true, foundUsername, "wrong usernames parsed")
} }
for _, expectedPassword := range vector.expectedOutput.Passwords { for _, expectedPassword := range test.expectedOutput.Passwords {
foundPassword := false foundPassword := false
for _, password := range result.Passwords { for _, password := range result.Passwords {
if password == expectedPassword { if password == expectedPassword {
@@ -102,7 +102,7 @@ func TestLoadRoutes(t *testing.T) {
routesJSONString := []byte("admin\nroot") routesJSONString := []byte("admin\nroot")
validRoutes := Routes{"admin", "root"} validRoutes := Routes{"admin", "root"}
vectors := []struct { testCases := []struct {
input []byte input []byte
fileExists bool fileExists bool
@@ -127,17 +127,17 @@ func TestLoadRoutes(t *testing.T) {
input: []byte(""), input: []byte(""),
}, },
} }
for i, vector := range vectors { for i, test := range testCases {
filePath := "/tmp/cameradar_test_load_routes_" + fmt.Sprint(i) + ".xml" filePath := "/tmp/cameradar_test_load_routes_" + fmt.Sprint(i) + ".xml"
// create file // create file
if vector.fileExists { if test.fileExists {
_, err := os.Create(filePath) _, err := os.Create(filePath)
if err != nil { if err != nil {
fmt.Printf("could not create xml file for LoadRoutes: %v. iteration: %d. file path: %s\n", err, i, filePath) fmt.Printf("could not create xml file for LoadRoutes: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1) os.Exit(1)
} }
err = ioutil.WriteFile(filePath, vector.input, 0644) err = ioutil.WriteFile(filePath, test.input, 0644)
if err != nil { if err != nil {
fmt.Printf("could not write xml file for LoadRoutes: %v. iteration: %d. file path: %s\n", err, i, filePath) fmt.Printf("could not write xml file for LoadRoutes: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1) os.Exit(1)
@@ -145,18 +145,18 @@ func TestLoadRoutes(t *testing.T) {
} }
result, err := LoadRoutes(filePath) result, err := LoadRoutes(filePath)
if len(vector.expectedErrMsg) > 0 { if len(test.expectedErrMsg) > 0 {
if err == nil { if err == nil {
fmt.Printf("unexpected success in LoadRoutes test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg) fmt.Printf("unexpected success in LoadRoutes test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
os.Exit(1) os.Exit(1)
} }
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message") assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
} else { } else {
if err != nil { if err != nil {
fmt.Printf("unexpected error in LoadRoutes test, iteration %d: %v\n", i, err) fmt.Printf("unexpected error in LoadRoutes test, iteration %d: %v\n", i, err)
os.Exit(1) os.Exit(1)
} }
for _, expectedRoute := range vector.expectedOutput { for _, expectedRoute := range test.expectedOutput {
foundRoute := false foundRoute := false
for _, route := range result { for _, route := range result {
if route == expectedRoute { if route == expectedRoute {
@@ -168,3 +168,95 @@ func TestLoadRoutes(t *testing.T) {
} }
} }
} }
func TestParseCredentialsFromString(t *testing.T) {
defaultCredentials := Credentials{
Usernames: []string{
"",
"admin",
"Admin",
"Administrator",
"root",
"supervisor",
"ubnt",
"service",
"Dinion",
"administrator",
"admin1",
},
Passwords: []string{
"",
"admin",
"9999",
"123456",
"pass",
"camera",
"1234",
"12345",
"fliradmin",
"system",
"jvc",
"meinsm",
"root",
"4321",
"111111",
"1111111",
"password",
"ikwd",
"supervisor",
"ubnt",
"wbox123",
"service",
},
}
testCases := []struct {
str string
expectedResult Credentials
}{
{
str: "{\"usernames\":[\"\",\"admin\",\"Admin\",\"Administrator\",\"root\",\"supervisor\",\"ubnt\",\"service\",\"Dinion\",\"administrator\",\"admin1\"],\"passwords\":[\"\",\"admin\",\"9999\",\"123456\",\"pass\",\"camera\",\"1234\",\"12345\",\"fliradmin\",\"system\",\"jvc\",\"meinsm\",\"root\",\"4321\",\"111111\",\"1111111\",\"password\",\"ikwd\",\"supervisor\",\"ubnt\",\"wbox123\",\"service\"]}",
expectedResult: defaultCredentials,
},
{
str: "{}",
expectedResult: Credentials{},
},
{
str: "{\"invalid_field\":42}",
expectedResult: Credentials{},
},
{
str: "not json",
expectedResult: Credentials{},
},
}
for _, test := range testCases {
parsedCredentials, _ := ParseCredentialsFromString(test.str)
assert.Equal(t, test.expectedResult, parsedCredentials, "unexpected result, parse error")
}
}
func TestParseRoutesFromString(t *testing.T) {
testCases := []struct {
str string
expectedResult Routes
}{
{
str: "a\nb\nc",
expectedResult: []string{"a", "b", "c"},
},
{
str: "a",
expectedResult: []string{"a"},
},
{
str: "",
expectedResult: []string{""},
},
}
for _, test := range testCases {
parsedRoutes := ParseRoutesFromString(test.str)
assert.Equal(t, test.expectedResult, parsedRoutes, "unexpected result, parse error")
}
}