feat: support http tunneled rtsp (#419)
* 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>
This commit is contained in:
committed by
GitHub
parent
14dcb74e89
commit
8531c006d4
+31
-39
@@ -291,33 +291,26 @@ func (a Attacker) attackRoutesForStream(ctx context.Context, target cameradar.St
|
||||
}
|
||||
|
||||
func (a Attacker) routeAttack(stream cameradar.Stream, route string) (bool, error) {
|
||||
u, urlStr, err := buildRTSPURL(stream, route, stream.Username, stream.Password)
|
||||
stream.Routes = []string{route}
|
||||
code, err := a.describeStatus(stream)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("building rtsp url: %w", err)
|
||||
return false, fmt.Errorf("performing describe request at %q: %w", stream, err)
|
||||
}
|
||||
|
||||
code, err := a.describeStatus(u)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
||||
}
|
||||
|
||||
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, code))
|
||||
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", stream, code))
|
||||
access := code == base.StatusOK || code == base.StatusUnauthorized || code == base.StatusForbidden
|
||||
return access, nil
|
||||
}
|
||||
|
||||
func (a Attacker) credAttack(stream cameradar.Stream, username, password string) (bool, error) {
|
||||
u, urlStr, err := buildRTSPURL(stream, stream.Route(), username, password)
|
||||
stream.Username = username
|
||||
stream.Password = password
|
||||
code, err := a.describeStatus(stream)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("building rtsp url: %w", err)
|
||||
return false, fmt.Errorf("performing describe request at %q: %w", stream, err)
|
||||
}
|
||||
|
||||
code, err := a.describeStatus(u)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
||||
}
|
||||
|
||||
a.reporter.Debug(cameradar.StepAttackCredentials, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, code))
|
||||
a.reporter.Debug(cameradar.StepAttackCredentials, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", stream, code))
|
||||
return code == base.StatusOK || code == base.StatusNotFound, nil
|
||||
}
|
||||
|
||||
@@ -330,32 +323,27 @@ func (a Attacker) validateStream(ctx context.Context, stream cameradar.Stream, e
|
||||
return stream, ctx.Err()
|
||||
}
|
||||
|
||||
u, urlStr, err := buildRTSPURL(stream, stream.Route(), stream.Username, stream.Password)
|
||||
if err != nil {
|
||||
return stream, fmt.Errorf("building rtsp url: %w", err)
|
||||
}
|
||||
|
||||
client, err := a.newRTSPClient(u)
|
||||
client, err := a.newRTSPClient(stream)
|
||||
if err != nil {
|
||||
return stream, fmt.Errorf("starting rtsp client: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
desc, res, err := a.describeWithRetry(ctx, client, u, urlStr)
|
||||
desc, res, err := a.describeWithRetry(ctx, client, stream)
|
||||
if err != nil {
|
||||
return a.handleDescribeError(stream, urlStr, err)
|
||||
return a.handleDescribeError(stream, err)
|
||||
}
|
||||
a.logDescribeResponse(urlStr, res)
|
||||
a.logDescribeResponse(stream.String(), res)
|
||||
|
||||
if desc == nil || len(desc.Medias) == 0 {
|
||||
return stream, fmt.Errorf("no media tracks found for %q", urlStr)
|
||||
return stream, fmt.Errorf("no media tracks found for %q", stream)
|
||||
}
|
||||
|
||||
res, err = client.Setup(desc.BaseURL, desc.Medias[0], 0, 0)
|
||||
if err != nil {
|
||||
return a.handleSetupError(stream, urlStr, err)
|
||||
return a.handleSetupError(stream, err)
|
||||
}
|
||||
a.logSetupResponse(urlStr, res)
|
||||
a.logSetupResponse(stream.String(), res)
|
||||
|
||||
stream.Available = res != nil && res.StatusCode == base.StatusOK
|
||||
if stream.Available {
|
||||
@@ -365,11 +353,15 @@ func (a Attacker) validateStream(ctx context.Context, stream cameradar.Stream, e
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (a Attacker) describeWithRetry(ctx context.Context, client *gortsplib.Client, u *base.URL, urlStr string) (*description.Session, *base.Response, error) {
|
||||
func (a Attacker) describeWithRetry(ctx context.Context, client *gortsplib.Client, stream cameradar.Stream) (*description.Session, *base.Response, error) {
|
||||
u, err := stream.URL()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("building rtsp url: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
desc *description.Session
|
||||
res *base.Response
|
||||
err error
|
||||
)
|
||||
for range 5 {
|
||||
desc, res, err = client.Describe(u)
|
||||
@@ -379,7 +371,7 @@ func (a Attacker) describeWithRetry(ctx context.Context, client *gortsplib.Clien
|
||||
|
||||
var badStatus liberrors.ErrClientBadStatusCode
|
||||
if errors.As(err, &badStatus) && badStatus.Code == base.StatusServiceUnavailable {
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d (retrying)", urlStr, badStatus.Code))
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d (retrying)", stream, badStatus.Code))
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, nil, ctx.Err()
|
||||
@@ -391,13 +383,13 @@ func (a Attacker) describeWithRetry(ctx context.Context, client *gortsplib.Clien
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("describe retries exhausted for %q: %w", urlStr, err)
|
||||
return nil, nil, fmt.Errorf("describe retries exhausted for %q: %w", stream, err)
|
||||
}
|
||||
|
||||
func (a Attacker) handleDescribeError(stream cameradar.Stream, urlStr string, err error) (cameradar.Stream, error) {
|
||||
func (a Attacker) handleDescribeError(stream cameradar.Stream, err error) (cameradar.Stream, error) {
|
||||
var badStatus liberrors.ErrClientBadStatusCode
|
||||
if errors.As(err, &badStatus) && badStatus.Code == base.StatusServiceUnavailable {
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, badStatus.Code))
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", stream, badStatus.Code))
|
||||
a.reporter.Progress(cameradar.StepValidateStreams, fmt.Sprintf("Stream unavailable for %s:%d (RTSP %d)",
|
||||
stream.Address.String(),
|
||||
stream.Port,
|
||||
@@ -407,20 +399,20 @@ func (a Attacker) handleDescribeError(stream cameradar.Stream, urlStr string, er
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > error: %v", urlStr, err))
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > error: %v", stream, err))
|
||||
|
||||
return stream, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
||||
return stream, fmt.Errorf("performing describe request at %q: %w", stream, err)
|
||||
}
|
||||
|
||||
func (a Attacker) handleSetupError(stream cameradar.Stream, urlStr string, err error) (cameradar.Stream, error) {
|
||||
func (a Attacker) handleSetupError(stream cameradar.Stream, err error) (cameradar.Stream, error) {
|
||||
var badStatus liberrors.ErrClientBadStatusCode
|
||||
if errors.As(err, &badStatus) {
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("SETUP %s RTSP/1.0 > %d", urlStr, badStatus.Code))
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("SETUP %s RTSP/1.0 > %d", stream, badStatus.Code))
|
||||
stream.Available = badStatus.Code == base.StatusOK
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
return stream, fmt.Errorf("performing setup request at %q: %w", urlStr, err)
|
||||
return stream, fmt.Errorf("performing setup request at %q: %w", stream, err)
|
||||
}
|
||||
|
||||
func (a Attacker) logDescribeResponse(urlStr string, res *base.Response) {
|
||||
|
||||
Reference in New Issue
Block a user