Move cameradar to root folder

This commit is contained in:
Brendan LE GLAUNEC
2018-03-12 14:59:59 +01:00
committed by Brendan Le Glaunec
parent 2555a86f5f
commit ecdac00145
8 changed files with 1 additions and 1 deletions
-232
View File
@@ -1,232 +0,0 @@
// 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 cmrdr
import (
"fmt"
"time"
curl "github.com/andelf/go-curl"
"github.com/pkg/errors"
v "gopkg.in/go-playground/validator.v9"
)
// HACK: See https://stackoverflow.com/questions/3572397/lib-curl-in-c-disable-printing
func doNotWrite([]uint8, interface{}) bool {
return true
}
func routeAttack(camera Stream, route string, timeout time.Duration, enableLogs bool) bool {
easy := curl.EasyInit()
defer easy.Cleanup()
if easy != nil {
attackURL := fmt.Sprintf(
"rtsp://%s:%s@%s:%d/%s",
camera.Username,
camera.Password,
camera.Address,
camera.Port,
route,
)
if enableLogs {
// Debug logs when logs are enabled
easy.Setopt(curl.OPT_VERBOSE, 1)
} else {
// Do not write sdp in stdout
easy.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
}
// Do not send a body in the describe request
easy.Setopt(curl.OPT_NOBODY, 1)
// Send a request to the URL of the camera we want to attack
easy.Setopt(curl.OPT_URL, attackURL)
// Set the RTSP STREAM URI as the camera URL
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
easy.Perform()
// Get return code for the request
rc, err := easy.Getinfo(curl.INFO_RESPONSE_CODE)
if err != nil {
return false
}
// If it's a 404, it means that the route was not valid
if rc == 404 {
return false
}
return true
}
return false
}
func credAttack(camera Stream, username string, password string, timeout time.Duration, enableLogs bool) bool {
easy := curl.EasyInit()
defer easy.Cleanup()
if easy != nil {
attackURL := fmt.Sprintf(
"rtsp://%s:%s@%s:%d/%s",
username,
password,
camera.Address,
camera.Port,
camera.Route,
)
if enableLogs {
// Debug logs when logs are enabled
easy.Setopt(curl.OPT_VERBOSE, 1)
} else {
// Do not write sdp in stdout
easy.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
}
// Do not send a body in the describe request
easy.Setopt(curl.OPT_NOBODY, 1)
// Send a request to the URL of the camera we want to attack
easy.Setopt(curl.OPT_URL, attackURL)
// Set the RTSP STREAM URI as the camera URL
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
easy.Perform()
// Get return code for the request
rc, err := easy.Getinfo(curl.INFO_RESPONSE_CODE)
if err != nil {
return false
}
// If it's a 403 or a 401, it means that the credentials are not correct
if rc == 403 || rc == 401 {
return false
}
return true
}
return false
}
func attackCameraCredentials(target Stream, credentials Credentials, resultsChan chan<- Stream, timeout time.Duration, log bool) {
for _, username := range credentials.Usernames {
for _, password := range credentials.Passwords {
ok := credAttack(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(target Stream, routes Routes, resultsChan chan<- Stream, timeout time.Duration, log bool) {
for _, route := range routes {
ok := routeAttack(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(targets []Stream, credentials Credentials, timeout time.Duration, log bool) (results []Stream, err 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 streams")
}
go attackCameraCredentials(target, credentials, attacks, timeout, log)
}
attackResults := []Stream{}
for _ = range targets {
attackResults = append(attackResults, <-attacks)
}
found := 0
for _, result := range attackResults {
if result.CredentialsFound == true {
targets = replace(targets, result)
found++
}
}
if found == 0 {
return targets, errors.New("No credentials found")
}
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(targets []Stream, routes Routes, timeout time.Duration, log bool) (results []Stream, err 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 streams")
}
go attackCameraRoute(target, routes, attacks, timeout, log)
}
attackResults := []Stream{}
for _ = range targets {
attackResults = append(attackResults, <-attacks)
}
found := 0
for _, result := range attackResults {
if result.RouteFound == true {
targets = replace(targets, result)
found++
}
}
if found == 0 {
return targets, errors.New("No routes found")
}
return targets, nil
}
-152
View File
@@ -1,152 +0,0 @@
// 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 cmrdr
import (
"bufio"
"encoding/xml"
"fmt"
"io/ioutil"
"log"
"os/exec"
"github.com/pkg/errors"
v "gopkg.in/go-playground/validator.v9"
)
// These constants detail the different level of nmap speed presets
// that determine the timeout values and wether or not nmap makes use of parallelism
const (
// PARANOID NO PARALLELISM | 5min timeout | 100ms to 10s round-trip time timeout | 5mn scan delay
PARANOIAC = 0
// SNEAKY NO PARALLELISM | 15sec timeout | 100ms to 10s round-trip time timeout | 15s scan delay
SNEAKY = 1
// POLITE NO PARALLELISM | 1sec timeout | 100ms to 10s round-trip time timeout | 400ms scan delay
POLITE = 2
// NORMAL PARALLELISM | 1sec timeout | 100ms to 10s round-trip time timeout | 0s scan delay
NORMAL = 3
// AGGRESSIVE PARALLELISM | 500ms timeout | 100ms to 1250ms round-trip time timeout | 0s scan delay
AGGRESSIVE = 4
// INSANE PARALLELISM | 250ms timeout | 50ms to 300ms round-trip time timeout | 0s scan delay
INSANE = 5
)
// RunNmap runs nmap on the specified targets's specified ports, using the given nmap speed
func RunNmap(targets, ports string, resultFilePath string, nmapSpeed int, enableLogs bool) error {
// Prepare nmap command
cmd := exec.Command(
"nmap",
fmt.Sprintf("-T%d", nmapSpeed),
"-A",
"-p",
ports,
"-oX",
resultFilePath,
targets,
)
// Pipe stdout to be able to write the logs in realtime
stdout, err := cmd.StdoutPipe()
if err != nil {
return errors.Wrap(err, "Couldn't get stdout pipe")
}
// Execute the nmap command
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "Coudln't run nmap command")
}
// Scan the pipe until an end of file or an error occurs
in := bufio.NewScanner(stdout)
for in.Scan() {
if enableLogs {
log.Printf(in.Text())
}
}
if err := in.Err(); err != nil {
if enableLogs {
log.Printf("error: %s", err)
}
}
return nil
}
// ParseNmapResult returns a slice of streams from an NMap XML result file
// To generate one yourself, use the -X option when running NMap
func ParseNmapResult(nmapResultFilePath string) ([]Stream, error) {
var streams []Stream
// Open & Read XML file
content, err := ioutil.ReadFile(nmapResultFilePath)
if err != nil {
return streams, errors.Wrap(err, "Could not read nmap result file at "+nmapResultFilePath+":")
}
// Unmarshal content of XML file into data structure
result := &NmapResult{}
err = xml.Unmarshal(content, &result)
if err != nil {
return streams, err
}
// Iterate on hosts to try to find hosts with ports that
// - serve RTSP
// - are open
validate := v.New()
for _, host := range result.Hosts {
if host.Ports.Ports == nil {
continue
}
for _, port := range host.Ports.Ports {
err = validate.Struct(port)
if err != nil {
continue
}
streams = append(streams, Stream{
Device: port.Service.Product,
Address: host.Address.Addr,
Port: port.PortID,
})
}
}
return streams, nil
}
// Discover scans the target networks and tries to find RTSP streams within them
// targets - string: The addresses
// - a subnet (e.g.: 172.16.100.0/24)
// - an IP (e.g.: 172.16.100.10)
// - a hostname (e.g.: localhost)
// - a range of IPs (e.g.: 172.16.100.10-172.16.100.20)
// - a mix of all those separated by commas (e.g.: localhost,172.17.100.0/24,172.16.100.10-172.16.100.20,0.0.0.0).
// ports - string :
// - one or multiple ports and port ranges separated by commas (e.g.: 554,8554-8560,18554-28554)
func Discover(targets string, ports string, nmapResultPath string, speed int, log bool) ([]Stream, error) {
var streams []Stream
// Run nmap command to discover open ports on the specified targets & ports
err := RunNmap(targets, ports, nmapResultPath, speed, log)
if err != nil {
return streams, err
}
// Get found streams from nmap results
streams, err = ParseNmapResult(nmapResultPath)
if err != nil {
return streams, err
}
return streams, nil
}
-38
View File
@@ -1,38 +0,0 @@
// 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 cmrdr
import "fmt"
func replace(streams []Stream, new Stream) []Stream {
updatedSlice := streams[:0]
for _, old := range streams {
if old.Address == new.Address && old.Port == new.Port {
updatedSlice = append(updatedSlice, new)
} else {
updatedSlice = append(updatedSlice, old)
}
}
return updatedSlice
}
// RTSPURL generates a stream's RTSP URL
func RTSPURL(stream Stream) string {
return "rtsp://" + stream.Username + ":" + stream.Password + "@" + stream.Address + ":" + fmt.Sprint(stream.Port) + "/" + stream.Route
}
// AdminPanelURL returns the URL to the camera's admin panel
func AdminPanelURL(stream Stream) string {
return "http://" + stream.Address + "/"
}
-58
View File
@@ -1,58 +0,0 @@
// 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 cmrdr
import (
"bufio"
"encoding/json"
"io/ioutil"
"os"
"github.com/pkg/errors"
)
// LoadCredentials opens a dictionary file and returns its contents as a Credentials structure
func LoadCredentials(path string) (Credentials, error) {
var creds Credentials
// Open & Read XML file
content, err := ioutil.ReadFile(path)
if err != nil {
return creds, errors.Wrap(err, "Could not read credentials dictionary file at "+path+":")
}
// Unmarshal content of JSON file into data structure
err = json.Unmarshal(content, &creds)
if err != nil {
return creds, err
}
return creds, nil
}
// LoadRoutes opens a dictionary file and returns its contents as a Routes structure
func LoadRoutes(path string) (Routes, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var routes Routes
scanner := bufio.NewScanner(file)
for scanner.Scan() {
routes = append(routes, scanner.Text())
}
return routes, scanner.Err()
}
-38
View File
@@ -1,38 +0,0 @@
// 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 cmrdr
// Stream represents a camera's RTSP stream
type Stream struct {
Device string
Username string
Password string
Route string
Address string `validate:"required"`
Port uint `validate:"required"`
CredentialsFound bool
RouteFound bool
}
// Credentials is a map of credentials
// usernames are keys and passwords are values
// creds['admin'] -> 'secure_password'
type Credentials struct {
Usernames []string `json:"usernames"`
Passwords []string `json:"passwords"`
}
// Routes is a slice of Routes
// ['/live.sdp', '/media.amp', ...]
type Routes []string
-62
View File
@@ -1,62 +0,0 @@
// 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 cmrdr
import "encoding/xml"
// NmapResult is the structure that holds all the information from an NMap scan
type NmapResult struct {
XMLName xml.Name `xml:"nmaprun"`
Hosts []Host `xml:"host" validate:"required"`
}
// Host represents a host discovered during a scan
type Host struct {
XMLName xml.Name `xml:"host"`
Address Address `xml:"address"`
Ports Ports `xml:"ports"`
}
// Address is a host's address discovered during a scan
type Address struct {
XMLName xml.Name `xml:"address"`
Addr string `xml:"addr,attr"`
AddrType string `xml:"addrType,attr"`
}
// Ports is the list of openned ports on a host
type Ports struct {
XMLName xml.Name `xml:"ports"`
Ports []Port `xml:"port"`
}
// Port is a port found on a host during a scan
type Port struct {
XMLName xml.Name `xml:"port"`
PortID uint `xml:"portid,attr"`
State State `xml:"state"`
Service Service `xml:"service"`
}
// State is the state of a port
type State struct {
XMLName xml.Name `xml:"state"`
State string `xml:"state,attr" validate:"required,eq=open"`
}
// Service represents the service that a port provides
type Service struct {
XMLName xml.Name `xml:"service"`
Name string `xml:"name,attr" validate:"required,eq=rtsp"`
Product string `xml:"product,attr"`
}