8531c006d4
* enhancement: supporting http tunneled rtsp * refactor: simplify HTTP tunnel support per review feedback - Extract streamCandidate() for nmap port classification - Add isCommonHTTPPort() for masscan and nmap fallback - Move URL building to Stream.String() and Stream.URL() - Pass Stream directly to attack methods instead of individual args - Add TLS config for HTTPS tunnel support - Make auth detection non-fatal for tunneled streams - Rename HTTPTunnel to UseHTTPTunnel * - Testing the auth workflow for the tunneled streams is not blocking the rest of the pipeline since I changed the return values to Auth unknown and nil - added extra ports in the test according to suggestions * fixing some lint errors * removing the unused buildrtspurl * delayed the urlstream call for clarity removed error messages refactored the test that used the deprecated buildTRSPurl to use stream.URL and stream.String() methods * extracting iscommonHTTP port to pkg/ports (package ports) switching on u.scheme to create proper schemes for http and https * refactor: replace HTTP tunnel bool with scheme-based detection; enable TLS only for HTTPS-tunneled streams * chore: simnplify InferTunnelScheme and newRTSPClient * fix: remove rendundant check in streamCandidate * fix: typo in parseScheme * tests: coverage for new schemes * fix: use RTSP and not RTSPS for HTTPS URLs * fix: tunneled RTSP scheme handling and auth detection fallback * ui: render empty credentials as none in summary and TUI * chore: ignore duplicate-string warning for none literal * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: rtsps probe headers --------- Co-authored-by: Brendan Le Glaunec <brendan@glaulabs.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
125 lines
3.0 KiB
Go
125 lines
3.0 KiB
Go
package nmap
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/netip"
|
|
"strings"
|
|
|
|
"github.com/Ullaakut/cameradar/v6"
|
|
"github.com/Ullaakut/cameradar/v6/pkg/ports"
|
|
nmaplib "github.com/Ullaakut/nmap/v4"
|
|
)
|
|
|
|
// Reporter reports scan progress and debug information.
|
|
type Reporter interface {
|
|
Debug(step cameradar.Step, message string)
|
|
Progress(step cameradar.Step, message string)
|
|
}
|
|
|
|
// Runner is something that can run an nmap scan.
|
|
type Runner interface {
|
|
Run(ctx context.Context) (*nmaplib.Run, error)
|
|
}
|
|
|
|
// Scanner scans targets and ports for RTSP streams.
|
|
type Scanner struct {
|
|
runner Runner
|
|
reporter Reporter
|
|
}
|
|
|
|
// New returns a Scanner configured with the provided terminal and scan speed.
|
|
func New(scanSpeed int16, targets, ports []string, reporter Reporter) (*Scanner, error) {
|
|
runner, err := nmaplib.NewScanner(
|
|
nmaplib.WithTargets(targets...),
|
|
nmaplib.WithPorts(ports...),
|
|
nmaplib.WithServiceInfo(),
|
|
nmaplib.WithTimingTemplate(nmaplib.Timing(scanSpeed)),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating nmap scanner: %w", err)
|
|
}
|
|
|
|
return &Scanner{
|
|
runner: runner,
|
|
reporter: reporter,
|
|
}, nil
|
|
}
|
|
|
|
// Scan discovers RTSP streams on the configured targets and ports.
|
|
func (s *Scanner) Scan(ctx context.Context) ([]cameradar.Stream, error) {
|
|
return runScan(ctx, s.runner, s.reporter)
|
|
}
|
|
|
|
func runScan(ctx context.Context, nmap Runner, reporter Reporter) ([]cameradar.Stream, error) {
|
|
results, err := nmap.Run(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scanning network: %w", err)
|
|
}
|
|
|
|
for _, warning := range results.Warnings() {
|
|
reporter.Debug(cameradar.StepScan, "nmap warning: "+warning)
|
|
}
|
|
|
|
var streams []cameradar.Stream
|
|
for _, host := range results.Hosts {
|
|
for _, port := range host.Ports {
|
|
if port.Status() != "open" {
|
|
continue
|
|
}
|
|
|
|
isCandidate := streamCandidate(port.Service.Name, port.ID)
|
|
if !isCandidate {
|
|
continue
|
|
}
|
|
|
|
for _, address := range host.Addresses {
|
|
addr, err := netip.ParseAddr(address.Addr)
|
|
if err != nil {
|
|
reporter.Progress(cameradar.StepScan, fmt.Sprintf("Skipping invalid address %q: %v", address.Addr, err))
|
|
continue
|
|
}
|
|
|
|
scheme := ports.InferTunnelScheme(port.ID, port.Service.Name)
|
|
streams = append(streams, cameradar.Stream{
|
|
Device: port.Service.Product,
|
|
Address: addr,
|
|
Port: port.ID,
|
|
Scheme: scheme,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
reporter.Progress(cameradar.StepScan, fmt.Sprintf("Found %d RTSP streams", len(streams)))
|
|
updateSummary(reporter, streams)
|
|
|
|
return streams, nil
|
|
}
|
|
|
|
type summaryUpdater interface {
|
|
UpdateSummary(streams []cameradar.Stream)
|
|
}
|
|
|
|
func updateSummary(reporter Reporter, streams []cameradar.Stream) {
|
|
updater, ok := reporter.(summaryUpdater)
|
|
if !ok {
|
|
return
|
|
}
|
|
updater.UpdateSummary(streams)
|
|
}
|
|
|
|
// Extracting the classifying logic to an external function to avoid nesting if loops.
|
|
func streamCandidate(serviceName string, port uint16) bool {
|
|
serviceName = strings.ToLower(strings.TrimSpace(serviceName))
|
|
if strings.Contains(serviceName, "rtsp") {
|
|
return true
|
|
}
|
|
|
|
if ports.InferTunnelScheme(port, serviceName) != "" {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|