fix: increment limit, added integration tests
This commit is contained in:
@@ -420,6 +420,8 @@ func (a Attacker) tryIncrementalRoutes(ctx context.Context,
|
||||
case <-time.After(a.attackInterval):
|
||||
}
|
||||
|
||||
attempts++
|
||||
|
||||
nextRoute := buildIncrementedRoute(match, nextNumber)
|
||||
if slices.Contains(target.Routes, nextRoute) {
|
||||
if !match.isChannel {
|
||||
@@ -443,7 +445,6 @@ func (a Attacker) tryIncrementalRoutes(ctx context.Context,
|
||||
))
|
||||
return target, nil
|
||||
}
|
||||
attempts++
|
||||
if !ok {
|
||||
return target, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package attack_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -50,7 +51,7 @@ func TestNew(t *testing.T) {
|
||||
|
||||
func TestAttacker_Attack_BasicAuth(t *testing.T) {
|
||||
addr, port := startRTSPServer(t, rtspServerConfig{
|
||||
allowedRoute: "stream",
|
||||
allowRoutes: []string{"stream"},
|
||||
requireAuth: true,
|
||||
username: "user",
|
||||
password: "pass",
|
||||
@@ -101,7 +102,7 @@ func TestAttacker_Attack_AuthVariants(t *testing.T) {
|
||||
{
|
||||
name: "no authentication",
|
||||
config: rtspServerConfig{
|
||||
allowedRoute: "stream",
|
||||
allowRoutes: []string{"stream"},
|
||||
requireAuth: false,
|
||||
authMethod: headers.AuthMethodBasic,
|
||||
},
|
||||
@@ -117,7 +118,7 @@ func TestAttacker_Attack_AuthVariants(t *testing.T) {
|
||||
{
|
||||
name: "digest authentication",
|
||||
config: rtspServerConfig{
|
||||
allowedRoute: "stream",
|
||||
allowRoutes: []string{"stream"},
|
||||
requireAuth: true,
|
||||
username: "user",
|
||||
password: "pass",
|
||||
@@ -193,7 +194,7 @@ func TestAttacker_Attack_ValidationErrors(t *testing.T) {
|
||||
|
||||
func TestAttacker_Attack_ReturnsErrorWhenRouteMissing(t *testing.T) {
|
||||
addr, port := startRTSPServer(t, rtspServerConfig{
|
||||
allowedRoute: "stream",
|
||||
allowRoutes: []string{"stream"},
|
||||
requireAuth: false,
|
||||
authMethod: headers.AuthMethodBasic,
|
||||
})
|
||||
@@ -221,7 +222,7 @@ func TestAttacker_Attack_ReturnsErrorWhenRouteMissing(t *testing.T) {
|
||||
|
||||
func TestAttacker_Attack_ReturnsErrorWhenCredentialsMissing(t *testing.T) {
|
||||
addr, port := startRTSPServer(t, rtspServerConfig{
|
||||
allowedRoute: "stream",
|
||||
allowRoutes: []string{"stream"},
|
||||
requireAuth: true,
|
||||
username: "user",
|
||||
password: "pass",
|
||||
@@ -254,7 +255,7 @@ func TestAttacker_Attack_CredentialAttemptFails(t *testing.T) {
|
||||
reporter := &recordingReporter{}
|
||||
|
||||
addr, port := startRTSPServer(t, rtspServerConfig{
|
||||
allowedRoute: "stream",
|
||||
allowRoutes: []string{"stream"},
|
||||
requireAuth: true,
|
||||
username: "user",
|
||||
password: "pass",
|
||||
@@ -310,7 +311,7 @@ func TestAttacker_Attack_AllowsDummyRoute(t *testing.T) {
|
||||
|
||||
func TestAttacker_Attack_ValidationFailsWhenSetupErrors(t *testing.T) {
|
||||
addr, port := startRTSPServer(t, rtspServerConfig{
|
||||
allowedRoute: "stream",
|
||||
allowRoutes: []string{"stream"},
|
||||
requireAuth: false,
|
||||
authMethod: headers.AuthMethodBasic,
|
||||
setupStatus: base.StatusUnsupportedTransport,
|
||||
@@ -335,6 +336,71 @@ func TestAttacker_Attack_ValidationFailsWhenSetupErrors(t *testing.T) {
|
||||
assert.True(t, got[0].RouteFound)
|
||||
}
|
||||
|
||||
func TestAttacker_Attack_IncrementalRoutesStopsOnFirstMissAndAvoidsDuplicates(t *testing.T) {
|
||||
addr, port := startRTSPServer(t, rtspServerConfig{
|
||||
allowRoutes: []string{"channel1", "channel2"},
|
||||
requireAuth: false,
|
||||
authMethod: headers.AuthMethodBasic,
|
||||
})
|
||||
|
||||
dict := testDictionary{
|
||||
routes: []string{"channel1", "channel2"},
|
||||
usernames: []string{"user"},
|
||||
passwords: []string{"pass"},
|
||||
}
|
||||
|
||||
attacker, err := attack.New(dict, 0, time.Second, ui.NopReporter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
streams := []cameradar.Stream{{
|
||||
Address: addr,
|
||||
Port: port,
|
||||
}}
|
||||
|
||||
got, err := attacker.Attack(t.Context(), streams)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
|
||||
assert.ElementsMatch(t, []string{"channel1", "channel2"}, got[0].Routes)
|
||||
assert.Equal(t, 1, countRoute(got[0].Routes, "channel2"))
|
||||
}
|
||||
|
||||
func TestAttacker_Attack_IncrementalRoutesStopsAtCap(t *testing.T) {
|
||||
allowedRoutes := make([]string, 0, 50)
|
||||
for i := 1; i <= 50; i++ {
|
||||
allowedRoutes = append(allowedRoutes, fmt.Sprintf("channel%d", i))
|
||||
}
|
||||
|
||||
addr, port := startRTSPServer(t, rtspServerConfig{
|
||||
allowRoutes: allowedRoutes,
|
||||
requireAuth: false,
|
||||
authMethod: headers.AuthMethodBasic,
|
||||
})
|
||||
|
||||
dict := testDictionary{
|
||||
routes: []string{"channel1"},
|
||||
usernames: []string{"user"},
|
||||
passwords: []string{"pass"},
|
||||
}
|
||||
|
||||
attacker, err := attack.New(dict, 0, time.Second, ui.NopReporter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
streams := []cameradar.Stream{{
|
||||
Address: addr,
|
||||
Port: port,
|
||||
}}
|
||||
|
||||
got, err := attacker.Attack(t.Context(), streams)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
|
||||
const expectedRoutes = 33 // channel1 + 32 incremental attempts
|
||||
assert.Len(t, got[0].Routes, expectedRoutes)
|
||||
assert.Contains(t, got[0].Routes, "channel33")
|
||||
assert.NotContains(t, got[0].Routes, "channel34")
|
||||
}
|
||||
|
||||
type testDictionary struct {
|
||||
routes []string
|
||||
usernames []string
|
||||
@@ -376,9 +442,10 @@ func (r *recordingReporter) Summary([]cameradar.Stream, error) {}
|
||||
|
||||
func (r *recordingReporter) Close() {}
|
||||
|
||||
func (r *recordingReporter) HasDebugContaining(value string) bool {
|
||||
func (r *recordingReporter) ContainsDebug(value string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
for _, message := range r.debugMessages {
|
||||
if strings.Contains(message, value) {
|
||||
return true
|
||||
@@ -386,3 +453,13 @@ func (r *recordingReporter) HasDebugContaining(value string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func countRoute(routes []string, route string) int {
|
||||
count := 0
|
||||
for _, value := range routes {
|
||||
if value == route {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
type rtspServerConfig struct {
|
||||
allowAll bool
|
||||
allowedRoute string
|
||||
allowRoutes []string
|
||||
requireAuth bool
|
||||
username string
|
||||
password string
|
||||
@@ -32,7 +32,7 @@ type rtspServerConfig struct {
|
||||
type testServerHandler struct {
|
||||
stream *gortsplib.ServerStream
|
||||
allowAll bool
|
||||
allowedRoute string
|
||||
allowRoutes []string
|
||||
requireAuth bool
|
||||
username string
|
||||
password string
|
||||
@@ -86,7 +86,16 @@ func (h *testServerHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*ba
|
||||
|
||||
func (h *testServerHandler) routeAllowed(path string) bool {
|
||||
path = strings.TrimLeft(path, "/")
|
||||
return h.allowAll || path == h.allowedRoute
|
||||
if h.allowAll {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, route := range h.allowRoutes {
|
||||
if path == route {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func startRTSPServer(t *testing.T, cfg rtspServerConfig) (netip.Addr, uint16) {
|
||||
@@ -94,7 +103,7 @@ func startRTSPServer(t *testing.T, cfg rtspServerConfig) (netip.Addr, uint16) {
|
||||
|
||||
handler := &testServerHandler{
|
||||
allowAll: cfg.allowAll,
|
||||
allowedRoute: cfg.allowedRoute,
|
||||
allowRoutes: cfg.allowRoutes,
|
||||
requireAuth: cfg.requireAuth,
|
||||
username: cfg.username,
|
||||
password: cfg.password,
|
||||
|
||||
Reference in New Issue
Block a user