Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bb0e0cabe | |||
| 83f90a9e64 | |||
| 93b853c130 | |||
| 9b2d38450c | |||
| 40512f5287 | |||
| e27dfc8fba | |||
| 0e7577ed7c | |||
| b61fe52161 | |||
| 5df1776f51 | |||
| 7f417cdd1a | |||
| bbe9705cc3 | |||
| 375080f960 | |||
| 8c2c7123aa | |||
| 7791c45304 | |||
| c92eef6cdd | |||
| c3fcc7a39c | |||
| fd88e761e2 | |||
| 1af533a1d3 | |||
| 3510b98797 | |||
| 1e25be7ca5 | |||
| 6e06346685 | |||
| 1fc21f0906 | |||
| 369728f6c3 | |||
| 6f09f99eb8 | |||
| cb7531f93f | |||
| 78c7e17816 | |||
| d98d78cd73 | |||
| e9ffb44b45 | |||
| 9f85415d89 | |||
| c0d890acad | |||
| 553524ae43 | |||
| f80af6bd58 | |||
| 6c127e4cbe | |||
| c16c8c0aaa | |||
| be74c3c814 | |||
| 2f93ddd7e5 | |||
| 108f869a43 | |||
| 6685f74a90 | |||
| 37176292d0 | |||
| a49b8ef481 | |||
| 8e26751247 | |||
| 2564943ae7 | |||
| 74b4590758 | |||
| 9e9c1ba5b6 | |||
| 9d78e84dc0 | |||
| ecd318d0c2 | |||
| 58b101ed60 | |||
| 7e6c501582 | |||
| d9a221f9c6 | |||
| c10525b50e | |||
| 67b118a82e | |||
| 1f5db9baa0 | |||
| 4ef463d8a9 | |||
| ae3329bd25 | |||
| 46e17bb0ee | |||
| 27b296c9d2 | |||
| 006c0139be | |||
| 63119d3ff3 | |||
| 5859e9c595 | |||
| d0220ceb7f | |||
| 064a6ff588 | |||
| 9a269bfe0e | |||
| c44b933a83 | |||
| 1f5e9fc502 | |||
| 08231074b9 | |||
| c6d801750e | |||
| 2cf49a8db4 | |||
| 4fba8a8594 | |||
| 76365e3a07 | |||
| e6a38af241 | |||
| a4ad49c1a7 | |||
| 0ac1046138 | |||
| eef9c6f562 | |||
| cf18d869e0 | |||
| 4017429835 | |||
| 615f14d614 | |||
| d09b7abea9 | |||
| fdb146f019 | |||
| 77446189dd | |||
| e4ba477b06 | |||
| 6ae2608f8e | |||
| 6908c7bcac | |||
| adbbe244b0 | |||
| 8d6de630a5 | |||
| 9aa86a5c2d | |||
| 201d7e31c6 |
@@ -1,28 +1,16 @@
|
|||||||
# Compiled Object files
|
# Results
|
||||||
*.slo
|
result.json
|
||||||
*.lo
|
*.xml
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
|
|
||||||
# Precompiled Headers
|
# IDE config
|
||||||
*.gch
|
.idea/
|
||||||
*.pch
|
.vscode/
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
# Deps
|
||||||
*.so
|
cpp/deps/jsoncpp/
|
||||||
*.dylib
|
cpp/deps/mysql-connector/
|
||||||
*.dll
|
cpp/deployment/cameradar_*_Release_Linux.tar.gz
|
||||||
|
|
||||||
# Fortran module files
|
# Test
|
||||||
*.mod
|
test/cameradartest.conf.json
|
||||||
|
test/cameradar_*_Debug_Linux.tar.gz
|
||||||
# Compiled Static libraries
|
|
||||||
*.lai
|
|
||||||
*.la
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
language: generic
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- echo "Testing Docker Hub credentials"
|
||||||
|
- docker login -u=$DOCKER_USERNAME -p=$DOCKER_PASSWORD
|
||||||
|
- echo "Docker Hub credentials are working"
|
||||||
|
|
||||||
|
install:
|
||||||
|
- docker build -t cameradar .
|
||||||
|
|
||||||
|
script:
|
||||||
|
- docker run cameradar
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- echo "Test Success - Branch($TRAVIS_BRANCH) Pull Request($TRAVIS_PULL_REQUEST) Tag($TRAVIS_TAG)"
|
||||||
|
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then echo -e "Push Container to Docker Hub" && docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD && docker tag cameradar $DOCKER_REPO:latest && docker push $DOCKER_REPO; fi
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
# Cameradar Changelog
|
||||||
|
|
||||||
|
This file lists all versions of the repository and precises all changes.
|
||||||
|
|
||||||
|
## v2.0.0
|
||||||
|
|
||||||
|
#### Major changes:
|
||||||
|
|
||||||
|
* Cameradar is no longer a C++ application but a Golang library
|
||||||
|
* Cameraccess is a Golang application replacing the former C++ one (the C++ Cameradar image can still be used with the tag `1.1.4`)
|
||||||
|
* The docker image for Cameraccess is lighter than the one for Cameradar
|
||||||
|
* The Cameradar golang library enables users to build their own application around camera discovery and attack. Example of applications could be an automatic camera discovery daemon with scheduled scans, a security audit tool to check if CCTV cameras are protected from attacks by being isolated and having strong passwords, etc.
|
||||||
|
|
||||||
|
## v1.1.4
|
||||||
|
|
||||||
|
#### Minor changes:
|
||||||
|
|
||||||
|
* Simplified use of Docker image
|
||||||
|
* Renamed MySQL table name to be more explicit
|
||||||
|
* Refactoring of the Golang functional tester done
|
||||||
|
* The output was made more human readable
|
||||||
|
* Added automatic code quality checks for pull requests
|
||||||
|
* Added contribution documentation
|
||||||
|
* Updated dictionaries to add user suggestions for Chinese cameras
|
||||||
|
* Enhanced `result.json` file's format
|
||||||
|
|
||||||
|
#### Bugfixes:
|
||||||
|
|
||||||
|
* Fixed a bug in the functional testing in which if the `result.json` file was not formatted correctly, the test failed but was still considered a success.
|
||||||
|
|
||||||
|
## v1.1.3
|
||||||
|
|
||||||
|
#### Minor changes:
|
||||||
|
|
||||||
|
* Added automatic pushes to DockerHub to the travis integration
|
||||||
|
* Made travis configuration file better
|
||||||
|
* Changed the package generation scripts to make them report errors
|
||||||
|
* Removed old etix_rtsp_server binary from the test folder
|
||||||
|
|
||||||
|
#### Bugfixes:
|
||||||
|
|
||||||
|
* Fixed an issue that made it mandatory to launch tests at least once so that they can work the second time
|
||||||
|
* Fixed an issue that made the golang testing tool not compile in the testing script
|
||||||
|
* Fixed an issue that made the golang testing tool sometimes ignore some tests
|
||||||
|
* The previous known issue has been investigated and we don't know where it came from. However after a night of testing I have been unable to reproduce it, so I will consider it closed
|
||||||
|
|
||||||
|
## v1.1.2
|
||||||
|
|
||||||
|
#### Minor changes:
|
||||||
|
|
||||||
|
* Added travis integration
|
||||||
|
* Added default environment value for Docker deployment
|
||||||
|
* Updated docker image description with new easy usage
|
||||||
|
* Updated README badges style (replaced flat with square-flat)
|
||||||
|
* Build last package can now also generate a debug package if given the `Debug` command-line argument
|
||||||
|
|
||||||
|
#### Known issues
|
||||||
|
|
||||||
|
* There is still the issue with Camera Emulation Server, see the [previous version's patchnote](#v1.1.1) for more information.
|
||||||
|
|
||||||
|
## v1.1.1
|
||||||
|
|
||||||
|
#### Minor changes:
|
||||||
|
|
||||||
|
* Removed unnecessary null pointer checks (thanks to https://github.com/elfring)
|
||||||
|
* Updated package description
|
||||||
|
* Removed debug message in CMake build
|
||||||
|
* Added `/ch01.264` to the URL dictionary in the deployment (Comelit default RTSP URL)
|
||||||
|
* Updated tests partially (still needs work to make the code cleaner)
|
||||||
|
* Variable names are now compliant with Golang best practices
|
||||||
|
* JSON variable names are back to normal
|
||||||
|
* Functions have been moved in more appropriate source files
|
||||||
|
* Structure definitions have been moved in more appropriate source files
|
||||||
|
* Source files have been renamed to be more relevant
|
||||||
|
* JUnit output now considers each camera as a test case
|
||||||
|
* JUnit output now contains errors which makes debugging much easier
|
||||||
|
* Added header files where it was forgotten
|
||||||
|
|
||||||
|
#### Bugfixes:
|
||||||
|
|
||||||
|
* Fixed an issue where if you loose your internet connection during thumbnail generation, FFMpeg would get stuck forever and thus Cameradar would never finish
|
||||||
|
* Fixed an issue where multithreading could cause crashes
|
||||||
|
* Fixed an issue where the routes dictionary was mistaken for the credentials dictionary
|
||||||
|
* Fixed issues with the golang testing tool
|
||||||
|
* Fixed automated camera generation
|
||||||
|
* Fixed docker IP address resolution
|
||||||
|
|
||||||
|
#### Known issues:
|
||||||
|
|
||||||
|
* There is an issue with Camera Emulation Server that makes it impossible for Cameradar to generate thumbnails, which is why right now the verification of the thumbnails presence is commented and it is assumed correct. It is probably an issue with GST-RTSP-Server but requires investigation.
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
|
||||||
|
#### Major changes:
|
||||||
|
|
||||||
|
* There are more command line options
|
||||||
|
* Port can now be overridden in the command line
|
||||||
|
* Target can now be overridden in the command line
|
||||||
|
* Bruteforce is now multithreaded and will use as many threads as there are discovered cameras
|
||||||
|
* Thumbnail generation is now multithreaded and will use as many threads as there are discovered cameras
|
||||||
|
* There are now default configuration values in order to make cameradar easier to use
|
||||||
|
|
||||||
|
#### Minor changes:
|
||||||
|
|
||||||
|
* The algorithms take external input into account (so that a 3rd party can change the DB to help Cameradar in real-time) and thus check the persistent data at each iteration
|
||||||
|
* The default log level is now DEBUG instead of INFO
|
||||||
|
* The attack logs are now INFO instead of DEBUG
|
||||||
|
* The thumbnail generation logs are now INFO instead of DEBUG
|
||||||
|
|
||||||
|
#### Bugs fixed
|
||||||
|
|
||||||
|
* Fixed a bug in which the MySQL cache manager would consider a camera with known ids as having a valid path even if it weren't
|
||||||
|
* Fixed a bug in which TCP RTSP streams would not generate thumbnails
|
||||||
|
|
||||||
|
## v1.0.5
|
||||||
|
|
||||||
|
* Fixed error in MySQL Cache Manager in which thumbnail generation on valid streams could not be done
|
||||||
|
* Fixed potential crash in the case the machine running cameradar has no memory left to allocate space for the dynamic cache manager
|
||||||
|
|
||||||
|
## v1.0.4
|
||||||
|
|
||||||
|
#### Bugs fixed:
|
||||||
|
|
||||||
|
* Fixed nmap package detection
|
||||||
|
|
||||||
|
## v1.0.3
|
||||||
|
|
||||||
|
#### Bugs fixed:
|
||||||
|
|
||||||
|
* Corrected GStreamer check
|
||||||
|
|
||||||
|
## v1.0.2
|
||||||
|
|
||||||
|
#### Bugs fixed:
|
||||||
|
|
||||||
|
* Fixed issues in MySQL Cache Manager
|
||||||
|
|
||||||
|
#### Minor changes:
|
||||||
|
|
||||||
|
* Added useful debug logs
|
||||||
|
|
||||||
|
## v1.0.1
|
||||||
|
|
||||||
|
### Ubuntu 16.04 Release
|
||||||
|
|
||||||
|
#### Major changes:
|
||||||
|
|
||||||
|
* The Docker deployment is now done using Ubuntu 16.04 instead of Ubuntu 15.10, so that it uses more recent packages.
|
||||||
|
|
||||||
|
#### Minor changes:
|
||||||
|
|
||||||
|
* Removed useless dependencies
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
### First production-ready release
|
||||||
|
|
||||||
|
#### Major changes:
|
||||||
|
|
||||||
|
* Added functional testing
|
||||||
|
|
||||||
|
## v0.2.2
|
||||||
|
|
||||||
|
After doing some testing on a weirdly configured camera network in a far away Datacenter, I discovered that some Cameras needed a few tweaks to the Cameradar attack method in order to be accessed.
|
||||||
|
|
||||||
|
#### Major changes:
|
||||||
|
|
||||||
|
* Cameradar can access Cameras that are configured to always send 400 Bad Requests responses
|
||||||
|
|
||||||
|
#### Minor changes:
|
||||||
|
|
||||||
|
* Changed iterator name from `it` to `stream` in dumb cache manager to improve code readability
|
||||||
|
|
||||||
|
#### Bugfixes:
|
||||||
|
|
||||||
|
* Cameradar no longer considers a timing out Camera as an accessible stream
|
||||||
|
|
||||||
|
## v0.2.1
|
||||||
|
|
||||||
|
This package adds fixes the Docker deployment package.
|
||||||
|
|
||||||
|
#### Minor changes
|
||||||
|
|
||||||
|
* Fixed the Docker deployment package
|
||||||
|
* Updated README
|
||||||
|
|
||||||
|
## v0.2.0
|
||||||
|
|
||||||
|
### MySQL Cache Manager Release
|
||||||
|
|
||||||
|
This package adds a new cache manager using a MySQL database, that can store the results between mutiple uses.
|
||||||
|
|
||||||
|
#### Major changes
|
||||||
|
|
||||||
|
* Added a MySQL Cache Manager
|
||||||
|
|
||||||
|
#### Minor changes
|
||||||
|
|
||||||
|
* Removed legacy code
|
||||||
|
* Removed boost dependency
|
||||||
|
* Improved debugging logs
|
||||||
|
|
||||||
|
## v0.1.1
|
||||||
|
|
||||||
|
### Docker release
|
||||||
|
|
||||||
|
This package adds a way to deploy Cameradar using Docker.
|
||||||
|
|
||||||
|
#### Major changes
|
||||||
|
|
||||||
|
* Added a quick Docker deployment process
|
||||||
|
* Added automatic dependencies downloading through CMake for the manual installation
|
||||||
|
* Added CPack packaging for the Docker deployment
|
||||||
|
|
||||||
|
#### Minor changes
|
||||||
|
|
||||||
|
* Changed recommended cloning method to HTTPS
|
||||||
|
* Added lots of informations to README.md
|
||||||
|
|
||||||
|
## v0.1.0
|
||||||
|
|
||||||
|
This package was the first OpenSource version of Cameradar. It contained only a simple cache manager and had some bugs.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Cameradar Contribution
|
||||||
|
|
||||||
|
This file will give you guidelines on how to contribute if you want to, and will list known contributors to this repo.
|
||||||
|
|
||||||
|
If you're not into software development or not into Golang, you can still help. Updating the dictionaries for example, would be a really cool contribution! Just make sure the credentials and routes you add are **default constructor credentials** and not custom credentials.
|
||||||
|
|
||||||
|
If you have other cool ideas, feel free to share them with me at [brendan.leglaunec@etixgroup.com](mailto:brendan.leglaunec@etixgroup.com) !
|
||||||
|
|
||||||
|
## Version 2.0.0
|
||||||
|
|
||||||
|
- *Cameradar* is the name of the Golang library.
|
||||||
|
- *Cameraccess* is the name of the binary that uses Cameradar to discover and access the cameras.
|
||||||
|
|
||||||
|
This quite big refactoring comes from the fact that most users who want to access cameras either want to launch it with the basic cache manager, mostly using the docker image already provided in this repository, or will not use it because it does not integrate into their software solution without sharing their database with Cameradar, which would cause issues with database migrations for example.
|
||||||
|
|
||||||
|
Transforming it into a library allows developers to use it directly in their own code exactly as they want, allowing for a greater flexibility. The Cameraccess binary also provides a simple use example as well as maintains the old simple way of using Cameradar for non-developers.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Branches & issues
|
||||||
|
|
||||||
|
When an issue is opened, a branch will be automatically created. If you want to work on this issue, this is the branch you **have** to work on and create your pull request from.
|
||||||
|
|
||||||
|
**Always make sure you're not working on the same issue as someone else, by asking on the issue to be assigned to it.**
|
||||||
|
|
||||||
|
### Commit names
|
||||||
|
|
||||||
|
The name of the commits should always be `v[next version] : [name of the fixed issue]` (ex: `v1.1.4 : Removed unnecessary null pointer checks`), and each PR should only contain one single commit.
|
||||||
|
|
||||||
|
When working on your local branch, you can do as many commits as you want, obviously. The most important is that you **squash** your commits before creating your pull request.
|
||||||
|
|
||||||
|
In case you're not familiar with squashing, here is a simple way to do it :
|
||||||
|
|
||||||
|
+ On your branch, when everything is clean and working, launch `git log` and count the number of commits your branch is ahead from compared to the `develop` branch.
|
||||||
|
+ Then launch `git rebase -i HEAD~X`, X being the number of commits you want to squash. For example if I had 12 commits on my branch, I will squash all of them by writing `git rebase -i HEAD~12`.
|
||||||
|
+ This will open a file letting you decide what to do with the commits. You want to keep the first `pick` and write `s` instead of the other ones, s meaning squash.
|
||||||
|
+ If there are conflicts, you will fix them step by step by following what git tells you, it's pretty straight-forward.
|
||||||
|
+ If there are no conflicts or if they are resolved, git will let you edit the commit names. Don't forget to comment the commit names of the commits you squashed by adding a `#` character in front of the commit message.
|
||||||
|
+ Now launch `git log`, you should see only one commit by the name you chose during the rebase.
|
||||||
|
|
||||||
|
### Pull Requests
|
||||||
|
|
||||||
|
When your pull request is created, GitHub will first check for conflicts, Codacy will check the shell and C++ code's quality and then Travis CI will try to build and launch functional tests of your versions of Cameradar.
|
||||||
|
|
||||||
|
If GitHub reports conflicts with the develop branch, you should resolve them by yourself using your git command-line interface. The easiest and cleanest way is to use `git rebase -i origin/develop` and follow git's instructions.
|
||||||
|
If Codacy reports new issues, they will be added in the comments of the PR to let you know what you should fix.
|
||||||
|
If Travis CI reports errors, you should be able to view the logs [by clicking here](https://travis-ci.org/EtixLabs/cameradar/builds) and you should fix it. No PR will be merged before all tests are passing correctly.
|
||||||
|
|
||||||
|
### Coding guidelines
|
||||||
|
|
||||||
|
This part will tell you about what are the general coding guidelines I want to keep on this project.
|
||||||
|
|
||||||
|
#### Golang
|
||||||
|
|
||||||
|
+ All Golang code has to be formated using `gofmt`
|
||||||
|
+ Make sure you follow the Golang [best practices](https://golang.org/doc/effective_go.html)
|
||||||
|
|
||||||
|
#### Shell scripting
|
||||||
|
|
||||||
|
+ Just make sure Codacy does not trigger warnings on your code.
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
+ **Brendan Le Glaunec** - [@Ullaakut](https://github.com/Ullaakut) - brendan.leglaunec@etixgroup.com : *Original developer & Maintainer*
|
||||||
|
+ **Jeremy Letang** - [@jeremyletang](https://github.com/jeremyletang) - letang.jeremy@gmail.com : *Idea of the project & Mentorship*
|
||||||
|
After Width: | Height: | Size: 220 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
FROM golang:alpine
|
||||||
|
WORKDIR /go/src/github.com/EtixLabs/cameradar/cameraccess
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/EtixLabs/cameradar
|
||||||
|
|
||||||
|
RUN apk update && \
|
||||||
|
apk upgrade && \
|
||||||
|
apk add nmap nmap-nselibs nmap-scripts \
|
||||||
|
curl-dev \
|
||||||
|
gcc \
|
||||||
|
libc-dev \
|
||||||
|
git \
|
||||||
|
pkgconfig
|
||||||
|
|
||||||
|
RUN go get github.com/andelf/go-curl
|
||||||
|
RUN go get github.com/pkg/errors
|
||||||
|
RUN go get gopkg.in/go-playground/validator.v9
|
||||||
|
RUN go get github.com/jessevdk/go-flags
|
||||||
|
RUN go get github.com/fatih/color
|
||||||
|
|
||||||
|
RUN go install
|
||||||
|
|
||||||
|
ENTRYPOINT ["/go/bin/cameraccess"]
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# Cameradar
|
||||||
|
|
||||||
|
## An RTSP stream access tool that comes with its library
|
||||||
|
|
||||||
|
[](#license)
|
||||||
|
[](https://hub.docker.com/r/ullaakut/cameradar/)
|
||||||
|
[](https://travis-ci.org/EtixLabs/cameradar)
|
||||||
|
[](https://goreportcard.com/report/github.com/EtixLabs/cameradar)
|
||||||
|
[](https://github.com/EtixLabs/cameradar/releases/latest)
|
||||||
|
|
||||||
|
#### Cameradar allows you to:
|
||||||
|
|
||||||
|
* **Detect open RTSP hosts** on any accessible target host
|
||||||
|
* Detect which device model is streaming
|
||||||
|
* Launch automated dictionary attacks to get their **stream route** (e.g.: `/live.sdp`)
|
||||||
|
* Launch automated dictionary attacks to get the **username and password** of the cameras
|
||||||
|
* Retrieve a complete and user-friendly report of the results
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/Cameradar.png" width="350"/></p>
|
||||||
|
|
||||||
|
## Table of content
|
||||||
|
|
||||||
|
- [Docker Image](#docker-image)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Output](#output)
|
||||||
|
- [Check camera access](#check-camera-access)
|
||||||
|
- [Command line options](#command-line-options)
|
||||||
|
- [Contribution](#contribution)
|
||||||
|
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Docker Image for Cameraccess
|
||||||
|
|
||||||
|
Install [docker](https://docs.docker.com/engine/installation/) on your machine, and run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run ullaakut/cameradar <command-line options>
|
||||||
|
```
|
||||||
|
|
||||||
|
[See command-line options](#command-line-options).
|
||||||
|
|
||||||
|
e.g.: `docker run ullaakut/cameradar -t 192.168.100.0/24 -l` will scan the ports 554 and 8554 of hosts on the 192.168.100.0/24 subnetwork and attack the discovered RTSP streams and will output lots of logs.
|
||||||
|
|
||||||
|
* `YOUR_TARGET` can be a subnet (e.g.: `172.16.100.0/24`) or even an IP (e.g.: `172.16.100.10`), a range of IPs (e.g.: `172.16.100.10-172.16.100.20`) or a mix of all those separated by commas (e.g.: `172.17.100.0/24,172.16.100.10-172.16.100.20,0.0.0.0`).
|
||||||
|
* If you want to get the precise results of the nmap scan in the form of an XML file, you can add `-v /your/path:/tmp/cameradar_scan.xml` to the docker run command, before `ullaakut/cameradar`.
|
||||||
|
* If you use the `-r` and `-c` options to specify your
|
||||||
|
|
||||||
|
### Library
|
||||||
|
|
||||||
|
### Dependencies of the library
|
||||||
|
|
||||||
|
- `curl-dev` / `libcurl` (depending on your OS)
|
||||||
|
- `nmap`
|
||||||
|
- `github.com/pkg/errors`
|
||||||
|
- `gopkg.in/go-playground/validator.v9`
|
||||||
|
- `github.com/andelf/go-curl`
|
||||||
|
|
||||||
|
#### Installing the library
|
||||||
|
|
||||||
|
`go get github.com/EtixLabs/cameradar`
|
||||||
|
|
||||||
|
After this command, the *cameradar* library is ready to use. Its source will be in:
|
||||||
|
|
||||||
|
$GOPATH/src/pkg/github.com/EtixLabs/cameradar
|
||||||
|
|
||||||
|
You can use `go get -u` to update the package.
|
||||||
|
|
||||||
|
Here is an overview of the exposed functions of this library:
|
||||||
|
|
||||||
|
#### Discovery
|
||||||
|
|
||||||
|
You can use the cameradar library for simple discovery purposes if you don't need to access the cameras but just to be aware of their existence.
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/Discover.png"/></p>
|
||||||
|
The Discover function calls the RunNmap function as well as the ParseNmapResults function and returns the discovered streams without attempting any attack.
|
||||||
|
It will use default values for its calls to RunNmap:
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/NmapPresets.png"/></p>
|
||||||
|
This describes the nmap time presets. You can pass a value between 1 and 5 as described in this table, to the RunNmap function.
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/RunNmap.png"/></p>
|
||||||
|
The RunNmap function will execute nmap and generate an XML file containing the results of the scan.
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/ParseNmapResults.png"/></p>
|
||||||
|
The ParseNmapResult function will open the specified XML file and return all open RTSP streams found within it.
|
||||||
|
|
||||||
|
#### Attack
|
||||||
|
|
||||||
|
If you already know which hosts and ports you want to attack, you can also skip the discovery part and use directly the attack functions. The attack functions also take a timeout value as a parameter.
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/AttackCredentials.png"/></p>
|
||||||
|
The AttackCredentials function takes valid streams as an input (with IP addresses and ports) and will attempt to guess their credentials using the provided dictionary.
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/AttackRoute.png"/></p>
|
||||||
|
The AttackRoute function takes valid streams as an input (with IP addresses and ports) and will attempt to guess their routes using the provided dictionary.
|
||||||
|
|
||||||
|
#### Data models
|
||||||
|
|
||||||
|
Here are the different data models useful to use the exposed functions of the cameradar library.
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/Models.png"/></p>
|
||||||
|
|
||||||
|
#### Dictionary loaders
|
||||||
|
|
||||||
|
The cameradar library also provides two functions that take file paths as inputs and return the appropriate data models filled.
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/LoadCredentials.png"/></p>
|
||||||
|
|
||||||
|
LoadCredentials takes a JSON file that has the same format as [this one](dictionary/credentials.json).
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/LoadRoutes.png"/></p>
|
||||||
|
|
||||||
|
LoadRoutes takes a file that has the same format as [this one](dictionary/routes). Warning: This file is not JSON.
|
||||||
|
|
||||||
|
#### Miscellaneous
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/RTSPURL.png"/></p>
|
||||||
|
|
||||||
|
RTSPURL allows you to generate the full URL of a stream.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
The **RTSP port used for most cameras is 554**, so you should probably specify 554 as one of the ports you scan. Not specifying any ports to the cameraccess application will scan the 554 and 8554 ports.
|
||||||
|
|
||||||
|
e.g.: `docker run ullaakut/cameradar -p "18554,19000-19010" -t localhost` will scan the ports 18554, and the range of ports between 19000 and 19010 on localhost.
|
||||||
|
|
||||||
|
You **can use your own files for the ids and routes dictionaries** used to attack the cameras, but the Cameradar repository already gives you a good base that works with most cameras, in the `/dictionaries` folder.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -v /my/folder/with/dictionaries:/tmp/dictionaries \
|
||||||
|
ullaakut/cameradar \
|
||||||
|
-r "/tmp/dictionaries/my_routes" \
|
||||||
|
-c "/tmp/dictionaries/my_credentials.json" \
|
||||||
|
-t 172.19.124.0/24
|
||||||
|
```
|
||||||
|
|
||||||
|
This will put the contents of your folder containing dictionaries in the docker image and will use it for the dictionary attack instead of the default dictionaries provided in the cameradar repo.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
For each camera, Cameraccess will output this:
|
||||||
|
|
||||||
|
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/images/Output.png"/></p>
|
||||||
|
|
||||||
|
|
||||||
|
## Check camera access
|
||||||
|
|
||||||
|
If you have [VLC Media Player](http://www.videolan.org/vlc/), you should be able to use the GUI or the command-line to connect to the RTSP stream using this format : `rtsp://username:password@address:port/route`
|
||||||
|
|
||||||
|
With the above result, the RTSP URL would be `rtsp://admin:12345@173.16.100.45:554/live.sdp`
|
||||||
|
|
||||||
|
## Command line options
|
||||||
|
|
||||||
|
* **"-t, --target"**: Set custom target. Required.
|
||||||
|
* **"-p, --ports"**: (Default: `554,8554`) Set custom ports.
|
||||||
|
* **"-s, --speed"**: (Default: `4`) Set custom nmap discovery presets to improve speed or accuracy. It's recommended to lower it if you are attempting to scan an unstable and slow network, or to increase it if on a very performant and reliable network. See [this for more info on the nmap timing templates](https://nmap.org/book/man-performance.html).
|
||||||
|
* **"-T, --timeout"**: (Default: `1000`) Set custom timeout value in miliseconds after which an attack attempt without an answer should give up.
|
||||||
|
* **"-r, --custom-routes"**: (Default: `dictionaries/routes`) Set custom dictionary path for routes
|
||||||
|
* **"-c, --custom-credentials"**: (Default: `dictionaries/credentials.json`) Set custom dictionary path for credentials
|
||||||
|
* **"-o, --nmap-output"**: (Default: `/tmp/cameradar_scan.xml`) Set custom nmap output path
|
||||||
|
* **"-l, --log"**: Enable debug logs (nmap requests, curl describe requests, etc.)
|
||||||
|
* **"-h"** : Display the usage information
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
Not yet implemented.
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
See [the contribution document](/CONTRIBUTION.md) to get started.
|
||||||
|
|
||||||
|
## Frequently Asked Questions
|
||||||
|
|
||||||
|
> Cameradar does not detect any camera!
|
||||||
|
|
||||||
|
That means that either your cameras are not streaming in RTSP or that they are not on the target you are scanning. In most cases, CCTV cameras will be on a private subnetwork, isolated from the internet. Use the `-t` option to specify your target.
|
||||||
|
|
||||||
|
> Cameradar detects my cameras, but does not manage to access them at all!
|
||||||
|
|
||||||
|
Maybe your cameras have been configured and the credentials / URL have been changed. Cameradar only guesses using default constructor values if a custom dictionary is not provided. You can use your own dictionaries in which you just have to add your credentials and RTSP routes. To do that, see how the [configuration](#configuration) works. Also, maybe your camera's credentials are not yet known, in which case if you find them it would be very nice to add them to the Cameradar dictionaries to help other people in the future.
|
||||||
|
|
||||||
|
> What happened to the C++ version?
|
||||||
|
|
||||||
|
You can still find it under the 1.1.4 tag on this repo, however it was less performant and stable than the current version written in Golang.
|
||||||
|
|
||||||
|
> How to use the Cameradar library for my own project?
|
||||||
|
|
||||||
|
See the cameraccess example. You just need to run `go get github.com/EtixLabs/cameradar/cameradar` and to use the `cmrdr` package in your code.
|
||||||
|
|
||||||
|
> I want to scan my own localhost for some reason and it does not work! What's going on?
|
||||||
|
|
||||||
|
Use the `--net=host` flag when launching the cameradar image, or use the binary by running `go run cameraccess/main.go`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright 2017 Etix Labs
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// 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/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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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.RTSPURL(stream)))
|
||||||
|
success++
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s\tAdmin panel URL:\t%s %s\n", red("\xE2\x96\xB6"), yellow(cmrdr.AdminPanelURL(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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
// 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",
|
||||||
|
targets,
|
||||||
|
"-p",
|
||||||
|
ports,
|
||||||
|
"-oX",
|
||||||
|
resultFilePath,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// 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 + "/"
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// 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()
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// 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
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// 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"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"usernames": [
|
||||||
|
"",
|
||||||
|
"admin",
|
||||||
|
"Admin",
|
||||||
|
"root",
|
||||||
|
"supervisor",
|
||||||
|
"ubnt"
|
||||||
|
],
|
||||||
|
"passwords" : [
|
||||||
|
"",
|
||||||
|
"admin",
|
||||||
|
"9999",
|
||||||
|
"123456",
|
||||||
|
"pass",
|
||||||
|
"camera",
|
||||||
|
"1234",
|
||||||
|
"12345",
|
||||||
|
"fliradmin",
|
||||||
|
"system",
|
||||||
|
"jvc",
|
||||||
|
"meinsm",
|
||||||
|
"root",
|
||||||
|
"4321",
|
||||||
|
"1111111",
|
||||||
|
"password",
|
||||||
|
"ikwd",
|
||||||
|
"supervisor",
|
||||||
|
"ubnt"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
1.AMP
|
||||||
|
1/stream1
|
||||||
|
CAM_ID.password.mp2
|
||||||
|
GetData.cgi
|
||||||
|
MediaInput/h264
|
||||||
|
MediaInput/mpeg4
|
||||||
|
VideoInput/1/h264/1
|
||||||
|
access_code
|
||||||
|
access_name_for_stream_1_to_5
|
||||||
|
av0_0
|
||||||
|
av2
|
||||||
|
avn=2
|
||||||
|
axis-media/media.amp
|
||||||
|
cam
|
||||||
|
cam0_0
|
||||||
|
cam0_1
|
||||||
|
cam1/h264
|
||||||
|
cam1/h264/multicast
|
||||||
|
cam1/mjpeg
|
||||||
|
cam1/mpeg4
|
||||||
|
camera.stm
|
||||||
|
ch0
|
||||||
|
ch001.sdp
|
||||||
|
ch01.264
|
||||||
|
ch0_unicast_firststream
|
||||||
|
ch0_unicast_secondstream
|
||||||
|
channel1
|
||||||
|
h264
|
||||||
|
h264/media.amp
|
||||||
|
image.mpg
|
||||||
|
img/media.sav
|
||||||
|
img/video.asf
|
||||||
|
img/video.sav
|
||||||
|
ioImage/1
|
||||||
|
ipcam.sdp
|
||||||
|
ipcam_h264.sdp
|
||||||
|
live.sdp
|
||||||
|
live/h264
|
||||||
|
live/mpeg4
|
||||||
|
live_mpeg4.sdp
|
||||||
|
livestream
|
||||||
|
livestream/
|
||||||
|
media/media.amp
|
||||||
|
media/video1
|
||||||
|
mjpeg/media.smp
|
||||||
|
mp4
|
||||||
|
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-640x480
|
||||||
|
nphMpeg4/nil-320x240
|
||||||
|
play1.sdp
|
||||||
|
play2.sdp
|
||||||
|
rtpvideo1.sdp
|
||||||
|
rtsp_live0
|
||||||
|
rtsp_live1
|
||||||
|
rtsp_live2
|
||||||
|
rtsp_tunnel
|
||||||
|
rtsph264
|
||||||
|
stream1
|
||||||
|
user.pin.mp2
|
||||||
|
user_defined
|
||||||
|
video
|
||||||
|
video.3gp
|
||||||
|
video.mp4
|
||||||
|
video1
|
||||||
|
video1+audio1
|
||||||
|
vis
|
||||||
|
wfov
|
||||||
|
video.h264
|
||||||
|
11
|
||||||
|
12
|
||||||
|
ch1-s1
|
||||||
|
live3.sdp
|
||||||
|
onvif-media/media.amp
|
||||||
|
axis-media/media.amp
|
||||||
|
axis-media/media.amp?videocodec=h264
|
||||||
|
mpeg4/media.amp
|
||||||
|
stream
|
||||||
|
cam/realmonitor
|
||||||
|
live
|
||||||
|
video.pro2
|
||||||
|
videoMain
|
||||||
|
VideoInput/1/mpeg4/1
|
||||||
|
VideoInput/1/h264/1
|
||||||
|
video.pro3
|
||||||
|
video.pro1
|
||||||
|
video.mjpg
|
||||||
|
h264_vga.sdp
|
||||||
|
media.amp
|
||||||
|
media
|
||||||
|
ONVIF/MediaInput
|
||||||
|
nphMpeg4/g726-640x48
|
||||||
|
MediaInput/mpeg4
|
||||||
|
MediaInput/h264
|
||||||
|
Streaming/Channels/1
|
||||||
|
ch0_0.h264
|
||||||
|
rtsph2641080p
|
||||||
|
live/av0
|
||||||
|
cam1/onvif-h264
|
||||||
|
ucast/11
|
||||||
|
LowResolutionVideo
|
||||||
|
1
|
||||||
|
live/ch00_0
|
||||||
|
medias2
|
||||||
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 70 KiB |