feat: v6 rewrite

This commit is contained in:
Brendan Le Glaunec
2025-07-08 17:36:48 +02:00
parent f586940b6c
commit e81eeb0c4d
81 changed files with 7430 additions and 4099 deletions
+81
View File
@@ -0,0 +1,81 @@
{
"usernames": [
"",
"666666",
"888888",
"Admin",
"admin",
"admin1",
"administrator",
"Administrator",
"aiphone",
"Dinion",
"none",
"root",
"Root",
"service",
"supervisor",
"ubnt"
],
"passwords": [
"",
"0000",
"00000",
"1111",
"111111",
"1111111",
"123",
"1234",
"12345",
"123456",
"1234567",
"12345678",
"123456789",
"12345678910",
"4321",
"666666",
"6fJjMKYx",
"888888",
"9999",
"admin",
"admin123456",
"admin pass",
"Admin",
"admin123",
"administrator",
"Administrator",
"aiphone",
"camera",
"Camera",
"fliradmin",
"GRwvcj8j",
"hikvision",
"hikadmin",
"HuaWei123",
"ikwd",
"jvc",
"kj3TqCWv",
"meinsm",
"pass",
"Pass",
"password",
"password123",
"qwerty",
"qwerty123",
"Recorder",
"reolink",
"root",
"service",
"supervisor",
"support",
"system",
"tlJwpbo6",
"toor",
"tp-link",
"ubnt",
"user",
"wbox",
"wbox123",
"Y5eIMz3C"
]
}
+196
View File
@@ -0,0 +1,196 @@
/live/ch01_0
0/1:1/main
0/usrnm:pwd/main
0/video1
1
1.AMP
1/h264major
1/stream1
11
12
125
1080p
1440p
480p
4K
666
720p
AVStream1_1
CAM_ID.password.mp2
CH001.sdp
GetData.cgi
HD
HighResolutionVideo
LowResolutionVideo
MediaInput/h264
MediaInput/mpeg4
ONVIF/MediaInput
ONVIF/MediaInput?profile=4_def_profile6
StdCh1
Streaming/Channels/1
Streaming/Unicast/channels/101
StreamingSetting?version=1.0&action=getRTSPStream&ChannelID=1&ChannelName=Channel1
VideoInput/1/h264/1
VideoInput/1/mpeg4/1
access_code
access_name_for_stream_1_to_5
api/mjpegvideo.cgi
av0_0
av2
avc
avn=2
axis-media/media.amp
axis-media/media.amp?camera=1
axis-media/media.amp?videocodec=h264
cam
cam/realmonitor
cam/realmonitor?channel=0&subtype=0
cam/realmonitor?channel=1&subtype=0
cam/realmonitor?channel=1&subtype=1
cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
cam0
cam0_0
cam0_1
cam1
cam1/h264
cam1/h264/multicast
cam1/mjpeg
cam1/mpeg4
cam1/mpeg4?user='username'&pwd='password'
cam1/onvif-h264
camera.stm
ch0
ch00/0
ch001.sdp
ch01.264
ch01.264?
ch01.264?ptype=tcp
ch1_0
ch2_0
ch3_0
ch4_0
ch1/0
ch2/0
ch3/0
ch4/0
ch0_0.h264
ch0_unicast_firststream
ch0_unicast_secondstream
ch1-s1
channel1
gnz_media/main
h264
h264.sdp
h264/ch1/sub/av_stream
h264/media.amp
h264Preview_01_main
h264Preview_01_sub
h264_vga.sdp
h264_stream
image.mpg
img/media.sav
img/media.sav?channel=1
img/video.asf
img/video.sav
ioImage/1
ipcam.sdp
ipcam_h264.sdp
ipcam_mjpeg.sdp
live
live.sdp
live/av0
live/ch0
live/ch00_0
live/ch01_0
live/h264
live/main
live/main0
live/mpeg4
live1.sdp
live3.sdp
live_mpeg4.sdp
live_st1
livestream
main
media
media.amp
media.amp?streamprofile=Profile1
media/media.amp
media/video1
medias2
mjpeg/media.smp
mp4
mpeg/media.amp
mpeg4
mpeg4/1/media.amp
mpeg4/media.amp
mpeg4/media.smp
mpeg4unicast
mpg4/rtsp.amp
multicaststream
now.mp4
nph-h264.cgi
nphMpeg4/g726-640x
nphMpeg4/g726-640x48
nphMpeg4/g726-640x480
nphMpeg4/nil-320x240
onvif-media/media.amp
onvif1
pass@10.0.0.5:6667/blinkhd
play1.sdp
play2.sdp
profile0
profile1
profile2
profile2/media.smp
profile5/media.smp
rtpvideo1.sdp
rtsp_live0
rtsp_live1
rtsp_live2
rtsp_tunnel
rtsph264
rtsph2641080p
snap.jpg
stream
stream/0
stream/1
stream/live.sdp
stream.sdp
stream1
streaming/channels/0
streaming/channels/1
streaming/channels/101
tcp/av0_0
test
tmpfs/auto.jpg
trackID=1
ucast/11
udp/av0_0
udp/unicast/aiphone_H264
udpstream
user.pin.mp2
user=admin&password=&channel=1&stream=0.sdp?
user=admin&password=&channel=1&stream=0.sdp?real_stream
user=admin_password=?????_channel=1_stream=0.sdp?real_stream
user=admin_password=R5XFY888_channel=1_stream=0.sdp?real_stream
user_defined
v2
video
video.3gp
video.h264
video.mjpg
video.mp4
video.pro1
video.pro2
video.pro3
video0
video0.sdp
video1
video1.sdp
video1+audio1
videoMain
videoinput_1/h264_1/media.stm
videostream.asf
vis
wfov
+11
View File
@@ -0,0 +1,11 @@
package dict
import (
_ "embed"
)
//go:embed assets/credentials.json
var defaultCredentials []byte
//go:embed assets/routes
var defaultRoutes string
+134
View File
@@ -0,0 +1,134 @@
package dict
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
)
// credentials is a map of credentials.
type credentials struct {
Usernames []string `json:"usernames"`
Passwords []string `json:"passwords"`
}
// routes is a slice of routes.
type routes []string
// Dictionary groups routes and credentials for attacks.
type Dictionary struct {
creds credentials
routes routes
}
// Usernames returns the usernames list.
func (d Dictionary) Usernames() []string {
return d.creds.Usernames
}
// Passwords returns the passwords list.
func (d Dictionary) Passwords() []string {
return d.creds.Passwords
}
// Routes returns the routes list.
func (d Dictionary) Routes() []string {
return d.routes
}
// New loads a dictionary using the provided configuration.
func New(credentialsPath, routesPath string) (Dictionary, error) {
creds, err := loadCredentials(credentialsPath)
if err != nil {
return Dictionary{}, err
}
routes, err := loadRoutes(routesPath)
if err != nil {
return Dictionary{}, err
}
return Dictionary{
creds: creds,
routes: routes,
}, nil
}
// loadCredentials loads credentials from a custom path or embedded defaults.
func loadCredentials(credentialsPath string) (credentials, error) {
if strings.TrimSpace(credentialsPath) != "" {
content, err := os.ReadFile(credentialsPath)
if err != nil {
return credentials{}, fmt.Errorf("reading credentials dictionary %q: %w", credentialsPath, err)
}
creds, err := parseCredentials(content)
if err != nil {
return credentials{}, err
}
return creds, nil
}
creds, err := parseCredentials(defaultCredentials)
if err != nil {
return credentials{}, err
}
return creds, nil
}
// loadRoutes loads routes from a custom path or embedded defaults.
func loadRoutes(routesPath string) (routes, error) {
if strings.TrimSpace(routesPath) != "" {
file, err := os.Open(routesPath)
if err != nil {
return nil, fmt.Errorf("opening routes dictionary %q: %w", routesPath, err)
}
defer file.Close()
routes, err := parseRoutes(file)
if err != nil {
return nil, err
}
return routes, nil
}
reader := strings.NewReader(defaultRoutes)
routes, err := parseRoutes(io.NopCloser(reader))
if err != nil {
return nil, err
}
return routes, nil
}
func parseCredentials(content []byte) (credentials, error) {
if len(content) == 0 {
return credentials{}, errors.New("credentials dictionary is empty")
}
var creds credentials
err := json.Unmarshal(content, &creds)
if err != nil {
return credentials{}, fmt.Errorf("reading dictionary contents: %w", err)
}
return creds, nil
}
func parseRoutes(reader io.ReadCloser) (routes, error) {
defer reader.Close()
var routes routes
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
routes = append(routes, scanner.Text())
}
return routes, scanner.Err()
}
+163
View File
@@ -0,0 +1,163 @@
package dict_test
import (
"bufio"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"github.com/Ullaakut/cameradar/v6/internal/dict"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNew_LoadsDictionaryFromPaths(t *testing.T) {
tempDir := t.TempDir()
credsPath := writeTempFile(t, tempDir, "creds.json", `{"usernames":["alice"],"passwords":["secret"]}`)
routesPath := writeTempFile(t, tempDir, "routes", "stream\nother\n")
got, err := dict.New(credsPath, routesPath)
require.NoError(t, err)
assert.Equal(t, []string{"alice"}, got.Usernames())
assert.Equal(t, []string{"secret"}, got.Passwords())
assert.Equal(t, []string{"stream", "other"}, got.Routes())
}
func TestNew_CustomAndDefaultPaths(t *testing.T) {
tempDir := t.TempDir()
customCredsPath := writeTempFile(t, tempDir, "creds.json", `{"usernames":["alice"],"passwords":["secret"]}`)
customRoutesPath := writeTempFile(t, tempDir, "routes", "stream\nother\n")
tests := []struct {
name string
credentialsPath string
routesPath string
assertFunc func(t *testing.T, got dict.Dictionary)
}{
{
name: "custom credentials and routes",
credentialsPath: customCredsPath,
routesPath: customRoutesPath,
assertFunc: func(t *testing.T, got dict.Dictionary) {
assert.Equal(t, []string{"alice"}, got.Usernames())
assert.Equal(t, []string{"secret"}, got.Passwords())
assert.Equal(t, []string{"stream", "other"}, got.Routes())
},
},
{
name: "custom credentials default routes",
credentialsPath: customCredsPath,
assertFunc: func(t *testing.T, got dict.Dictionary) {
assert.Equal(t, []string{"alice"}, got.Usernames())
assert.Equal(t, []string{"secret"}, got.Passwords())
assert.NotEmpty(t, got.Routes())
assert.Contains(t, got.Routes(), "stream")
},
},
{
name: "default credentials custom routes",
routesPath: customRoutesPath,
assertFunc: func(t *testing.T, got dict.Dictionary) {
assert.NotEmpty(t, got.Usernames())
assert.Contains(t, got.Usernames(), "admin")
assert.NotEmpty(t, got.Passwords())
assert.Contains(t, got.Passwords(), "admin")
assert.Equal(t, []string{"stream", "other"}, got.Routes())
},
},
{
name: "whitespace paths use defaults",
credentialsPath: " \t\n",
routesPath: "\n\t",
assertFunc: func(t *testing.T, got dict.Dictionary) {
assert.NotEmpty(t, got.Usernames())
assert.Contains(t, got.Usernames(), "admin")
assert.NotEmpty(t, got.Passwords())
assert.Contains(t, got.Passwords(), "admin")
assert.NotEmpty(t, got.Routes())
assert.Contains(t, got.Routes(), "stream")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := dict.New(test.credentialsPath, test.routesPath)
require.NoError(t, err)
test.assertFunc(t, got)
})
}
}
func TestNew_Errors(t *testing.T) {
tempDir := t.TempDir()
validCredsPath := writeTempFile(t, tempDir, "creds.json", `{"usernames":["alice"],"passwords":["secret"]}`)
validRoutesPath := writeTempFile(t, tempDir, "routes", "stream\n")
invalidJSONPath := writeTempFile(t, tempDir, "invalid.json", "{")
emptyCredsPath := writeTempFile(t, tempDir, "empty.json", "")
longRoute := strings.Repeat("a", bufio.MaxScanTokenSize+1)
tooLongRoutesPath := writeTempFile(t, tempDir, "routes-too-long", longRoute)
tests := []struct {
name string
credentialsPath string
routesPath string
wantErrContains string
wantErrIs error
}{
{
name: "missing credentials file",
credentialsPath: filepath.Join(tempDir, "missing.json"),
routesPath: validRoutesPath,
wantErrContains: "reading credentials dictionary",
},
{
name: "invalid credentials json",
credentialsPath: invalidJSONPath,
routesPath: validRoutesPath,
wantErrContains: "reading dictionary contents",
},
{
name: "empty credentials file",
credentialsPath: emptyCredsPath,
routesPath: validRoutesPath,
wantErrContains: "credentials dictionary is empty",
},
{
name: "missing routes file",
credentialsPath: validCredsPath,
routesPath: filepath.Join(tempDir, "missing-routes"),
wantErrContains: "opening routes dictionary",
},
{
name: "routes file too long",
credentialsPath: validCredsPath,
routesPath: tooLongRoutesPath,
wantErrIs: bufio.ErrTooLong,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := dict.New(test.credentialsPath, test.routesPath)
require.Error(t, err)
if test.wantErrContains != "" {
assert.ErrorContains(t, err, test.wantErrContains)
}
if test.wantErrIs != nil {
assert.True(t, errors.Is(err, test.wantErrIs))
}
})
}
}
func writeTempFile(t *testing.T, dir, name, content string) string {
t.Helper()
path := filepath.Join(dir, name)
require.NoError(t, os.WriteFile(path, []byte(content), 0o600))
return path
}