HIGH open redirectnestjs

Open Redirect in Nestjs

How Open Redirect Manifests in Nestjs

Open redirect vulnerabilities in Nestjs applications typically occur when user-controlled input is used to construct redirect URLs without proper validation. These vulnerabilities allow attackers to craft URLs that redirect users to malicious sites, enabling phishing attacks and credential harvesting.

The most common patterns in Nestjs include:

  • Using @Query() parameters directly in res.redirect() calls
  • Passing user input to res.redirect() without validation
  • Using @Param() values to construct redirect URLs
  • Accepting redirect URLs from request bodies or headers

Here's a vulnerable Nestjs controller pattern:

import { Controller, Get, Query, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('auth')
export class AuthController {
  @Get('login')
  login(@Query('redirect') redirectUrl: string, @Res() res: Response) {
    // VULNERABLE: No validation of redirectUrl
    res.redirect(redirectUrl);
  }
}

An attacker could exploit this by visiting:

https://example.com/auth/login?redirect=https://evil-site.com

Another common pattern involves using @Param() for redirects:

import { Controller, Get, Param, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('redirect')
export class RedirectController {
  @Get(':url')
  redirectTo(@Param('url') url: string, @Res() res: Response) {
    // VULNERABLE: URL from path parameter used directly
    res.redirect(`https://example.com/${url}`);
  }
}

This would allow /redirect//evil.com to redirect to evil.com.

Dynamic route generation based on user input is another attack vector:

import { Controller, Get, Query } from '@nestjs/common';

@Controller('dynamic')
export class DynamicController {
  @Get()
  getDynamic(@Query('path') path: string) {
    // VULNERABLE: Path concatenation without validation
    return `https://example.com/${path}`;
  }
}

Even seemingly safe redirects can be vulnerable when combined with other features. For example, using res.redirect() with user-controlled status codes:

import { Controller, Get, Query, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express';

@Controller('status-redirect')
export class StatusRedirectController {
  @Get()
  statusRedirect(@Query('url') url: string, @Query('status') status: number, @Res() res: Response) {
    // VULNERABLE: User controls both URL and status code
    res.redirect(status, url);
  }
}

The combination of user-controlled URLs and status codes creates multiple attack vectors.

Nestjs-Specific Detection

Detecting open redirect vulnerabilities in Nestjs requires both static code analysis and runtime scanning. For static analysis, look for patterns where user input flows to redirect functions without validation.

middleBrick's black-box scanning approach is particularly effective for Nestjs applications because it tests the actual runtime behavior without requiring source code access. The scanner automatically identifies endpoints that might be vulnerable to open redirects by:

  • Analyzing route patterns and parameter usage
  • Testing common redirect parameters like redirect, next, returnUrl, and url
  • Checking for unsafe redirect destinations
  • Validating response headers for Location redirects

For Nestjs applications, middleBrick specifically tests against these common patterns:

GET /auth/login?redirect=[TEST_URL]
GET /api/v1/redirect/[TEST_URL]
POST /api/v1/redirect { "url": "[TEST_URL]" }
GET /dynamic?path=[TEST_PATH]

The scanner validates whether the application redirects to arbitrary URLs or enforces a whitelist of allowed destinations. It also checks for mixed-content issues where HTTP redirects occur from HTTPS contexts.

Manual detection in Nestjs code involves searching for these patterns:

// Search for these imports and usage patterns
import { Res } from '@nestjs/common';
import { Response } from 'express';

// Look for these redirect patterns
res.redirect(url);
res.redirect(status, url);
res.location(url);

Pay special attention to controllers handling authentication flows, as these often implement post-login redirects:

import { Controller, Get, Query, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('auth')
export class AuthController {
  @Get('callback')
  callback(@Query('returnUrl') returnUrl: string, @Res() res: Response) {
    // HIGH RISK: Common pattern for open redirects
    res.redirect(returnUrl);
  }
}

middleBrick's OpenAPI analysis can also detect potential issues by examining the API specification for redirect-related endpoints and parameters, then validating them against runtime behavior.

Nestjs-Specific Remediation

Securing Nestjs applications against open redirects requires implementing URL validation and using Nestjs's built-in features effectively. The primary defense is validating redirect URLs against a whitelist of allowed domains.

Here's a secure implementation using a whitelist approach:

import { Controller, Get, Query, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express';

const ALLOWED_DOMAINS = ['https://example.com', 'https://app.example.com'];

function validateRedirectUrl(url: string): boolean {
  try {
    const parsedUrl = new URL(url);
    return ALLOWED_DOMAINS.some(domain => {
      try {
        const allowed = new URL(domain);
        return parsedUrl.hostname === allowed.hostname && 
               parsedUrl.protocol === allowed.protocol;
      } catch {
        return false;
      }
    });
  } catch {
    return false;
  }
}

@Controller('auth')
export class AuthController {
  @Get('login')
  login(@Query('redirect') redirectUrl: string, @Res() res: Response) {
    if (redirectUrl && validateRedirectUrl(redirectUrl)) {
      res.redirect(redirectUrl);
    } else {
      // Default to safe location
      res.redirect('/dashboard');
    }
  }
}

For relative path redirects within your application, use Nestjs's join utility to prevent path traversal:

import { Controller, Get, Query, Res, Join } from '@nestjs/common';
import { Response } from 'express';

@Controller('relative-redirect')
export class RelativeRedirectController {
  @Get()
  relativeRedirect(@Query('path') path: string, @Res() res: Response) {
    const safePath = Join('/dashboard', path);
    // Validate that safePath is within allowed paths
    if (safePath.startsWith('/dashboard')) {
      res.redirect(safePath);
    } else {
      res.redirect('/dashboard');
    }
  }
}

Nestjs's ParseIntPipe and other validation pipes can be used to sanitize numeric redirect parameters:

import { Controller, Get, Query, Res, ParseIntPipe } from '@nestjs/common';
import { Response } from 'express';

@Controller('safe-redirect')
export class SafeRedirectController {
  @Get()
  safeRedirect(
    @Query('status', ParseIntPipe) status: number,
    @Query('url') url: string,
    @Res() res: Response
  ) {
    const safeStatus = [301, 302, 303, 307, 308].includes(status) ? status : 302;
    
    if (validateRedirectUrl(url)) {
      res.redirect(safeStatus, url);
    } else {
      res.redirect(safeStatus, '/default');
    }
  }
}

For applications using Passport.js with Nestjs, implement secure post-login redirects:

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  validate(username: string, password: string) {
    // Authentication logic
    return { username };
  }
}

@Controller('auth')
export class AuthController {
  @Get('login-success')
  loginSuccess(@Query('redirect') redirect: string) {
    // Always validate redirect URLs
    const safeRedirect = validateRedirectUrl(redirect) ? redirect : '/home';
    return { redirect: safeRedirect };
  }
}

Consider implementing a centralized redirect service for complex applications:

import { Injectable } from '@nestjs/common';

@Injectable()
export class RedirectService {
  private readonly allowedDomains = new Set([
    'https://example.com',
    'https://app.example.com'
  ]);

  validateAndRedirect(url: string, defaultUrl: string = '/home'): string {
    if (this.isValidRedirect(url)) {
      return url;
    }
    return defaultUrl;
  }

  private isValidRedirect(url: string): boolean {
    try {
      const parsedUrl = new URL(url);
      return this.allowedDomains.has(
        `${parsedUrl.protocol}//${parsedUrl.hostname}`
      );
    } catch {
      return false;
    }
  }
}

This service can be injected into any controller that needs redirect functionality, ensuring consistent validation across your application.

Frequently Asked Questions

How can I test if my Nestjs application has open redirect vulnerabilities?
Use middleBrick's black-box scanning by submitting your API URL. The scanner automatically tests common redirect patterns and validates whether your application redirects to arbitrary URLs. You can also manually test by appending redirect parameters like ?redirect=https://evil.com to your endpoints and checking if the application redirects to that URL.
What's the difference between open redirect and other redirect vulnerabilities in Nestjs?
Open redirect specifically allows redirecting to any external domain, while other redirect issues might be limited to path traversal or internal route manipulation. In Nestjs, open redirects are particularly dangerous when combined with authentication flows, as they can be used to phish user credentials by redirecting to attacker-controlled login pages that mimic your legitimate site.