118 lines
5.1 KiB
Go
118 lines
5.1 KiB
Go
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/EtixLabs/cameradar"
|
|
"github.com/fatih/color"
|
|
"github.com/jessevdk/go-flags"
|
|
)
|
|
|
|
type options struct {
|
|
Target string `short:"t" long:"target" description:"The target on which to scan for open RTSP streams - required" required:"true"`
|
|
Ports string `short:"p" long:"ports" description:"The ports on which to search for RTSP streams" default:"554,8554"`
|
|
OutputFile string `short:"o" long:"nmap-output" description:"The path where nmap will create its XML result file" default:"/tmp/cameradar_scan.xml"`
|
|
Routes string `short:"r" long:"custom-routes" description:"The path on which to load a custom routes dictionary" default:"../dictionaries/routes"`
|
|
Credentials string `short:"c" long:"custom-credentials" description:"The path on which to load a custom credentials JSON dictionary" default:"../dictionaries/credentials.json"`
|
|
Speed int `short:"s" long:"speed" description:"The nmap speed preset to use" default:"4"`
|
|
Timeout int `short:"T" long:"timeout" description:"The timeout in miliseconds to use for attack attempts" default:"1000"`
|
|
EnableLogs bool `short:"l" long:"log" description:"Enable the logs for nmap's output to stdout"`
|
|
}
|
|
|
|
func main() {
|
|
var options options
|
|
_, err := flags.ParseArgs(&options, os.Args[1:])
|
|
if err != nil {
|
|
os.Exit(0)
|
|
}
|
|
|
|
credentials, err := cmrdr.LoadCredentials(options.Credentials)
|
|
if err != nil {
|
|
color.Red("Invalid credentials dictionary: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
routes, err := cmrdr.LoadRoutes(options.Routes)
|
|
if err != nil {
|
|
color.Red("Invalid routes dictionary: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
streams, _ := cmrdr.Discover(options.Target, options.Ports, options.OutputFile, options.Speed, options.EnableLogs)
|
|
|
|
// Most cameras will be accessed successfully with these two attacks
|
|
streams, _ = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
|
streams, _ = cmrdr.AttackCredentials(streams, credentials, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
|
|
|
// But some cameras run GST RTSP Server which prioritizes 401 over 404 contrary to most cameras.
|
|
// For these cameras, running another route attack will solve the problem.
|
|
for _, stream := range streams {
|
|
if stream.RouteFound == false || stream.CredentialsFound == false {
|
|
streams, _ = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
|
break
|
|
}
|
|
}
|
|
|
|
prettyPrint(streams)
|
|
}
|
|
|
|
func prettyPrint(streams []cmrdr.Stream) {
|
|
yellow := color.New(color.FgYellow, color.Bold, color.Underline).SprintFunc()
|
|
blue := color.New(color.FgBlue, color.Underline).SprintFunc()
|
|
green := color.New(color.FgGreen, color.Bold).SprintFunc()
|
|
red := color.New(color.FgRed, color.Bold).SprintFunc()
|
|
white := color.New(color.Italic).SprintFunc()
|
|
|
|
success := 0
|
|
|
|
if len(streams) > 0 {
|
|
for _, stream := range streams {
|
|
if stream.CredentialsFound && stream.RouteFound {
|
|
fmt.Printf("%s\tDevice RTSP URL:\t%s\n", green("\xE2\x96\xB6"), blue(cmrdr.GetCameraRTSPURL(stream)))
|
|
success++
|
|
} else {
|
|
fmt.Printf("%s\tAdmin panel URL:\t%s %s\n", red("\xE2\x96\xB6"), yellow(cmrdr.GetCameraAdminPanelURL(stream)), white("You can use this URL to try attacking the camera's admin panel instead."))
|
|
}
|
|
|
|
fmt.Printf("\tDevice model:\t\t%s\n\n", stream.Device)
|
|
fmt.Printf("\tIP address:\t\t%s\n", stream.Address)
|
|
fmt.Printf("\tRTSP port:\t\t%d\n", stream.Port)
|
|
if stream.CredentialsFound {
|
|
fmt.Printf("\tUsername:\t\t%s\n", green(stream.Username))
|
|
fmt.Printf("\tPassword:\t\t%s\n", green(stream.Password))
|
|
} else {
|
|
fmt.Printf("\tUsername:\t\t%s\n", red("not found"))
|
|
fmt.Printf("\tPassword:\t\t%s\n", red("not found"))
|
|
}
|
|
if stream.RouteFound {
|
|
fmt.Printf("\tRTSP route:\t\t%s\n\n\n", green("/"+stream.Route))
|
|
} else {
|
|
fmt.Printf("\tRTSP route:\t\t%s\n\n\n", red("not found"))
|
|
}
|
|
}
|
|
if success > 1 {
|
|
fmt.Printf("%s Successful attack: %s devices were accessed", green("\xE2\x9C\x94"), green(len(streams)))
|
|
} else if success == 1 {
|
|
fmt.Printf("%s Successful attack: %s device was accessed", green("\xE2\x9C\x94"), green(len(streams)))
|
|
} else {
|
|
fmt.Printf("%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.", red("\xE2\x9C\x96"))
|
|
}
|
|
} else {
|
|
fmt.Printf("%s No streams were found. Please make sure that your target is on an accessible network.", red("\xE2\x9C\x96"))
|
|
}
|
|
}
|