Open Redirect in Laravel with Mongodb
Open Redirect in Laravel with Mongodb — how this specific combination creates or exposes the vulnerability
An open redirect in a Laravel application using MongoDB as the primary data store occurs when user-supplied input is used to construct a redirect response without strict validation or canonicalization. In this stack, the risk typically arises in controller methods that accept a url or redirect_to parameter and then use that value directly with redirect()->to() or return redirect($url). MongoDB does not introduce the redirect mechanism itself, but it often stores configuration or content that influences which URLs are considered valid targets. If those stored values are trusted without verification, or if the application builds redirect destinations by concatenating user input with values retrieved from MongoDB, the attack surface expands.
Attackers may probe endpoints that accept an id or slug parameter, query MongoDB (e.g., via Eloquent-like ODMs such as Laravel MongoDB), and then redirect to a URI stored in a document or derived from fields like external_url. If the application does not enforce an allowlist of acceptable domains and does not validate that the final resolved URL is within the application’s control, an open redirect can be triggered. This is particularly relevant when combined with social engineering or phishing, because the host and path may appear legitimate due to the trusted origin. The presence of MongoDB can create an illusion of safety if developers assume that data stored in their own database is inherently safe to use for redirects.
Common patterns that lead to open redirects in this combination include:
- Using a route parameter to look up a document in MongoDB and then redirecting to the document’s stored URL field without validating the scheme and host.
- Building absolute URLs using user input (e.g.,
$request->get('next')) and appending it to a base URL retrieved from a MongoDB collection. - Relying on configuration documents stored in MongoDB to define redirect mappings without verifying that the configured URLs are not pointing to external malicious domains.
Because the scan is unauthenticated, an attacker does not need credentials to identify endpoints that accept a target parameter and subsequently perform a redirect. The scanner observes the HTTP response codes and headers (primarily Location) to detect whether a 3xx response leads to an external domain, indicating a potential open redirect.
Mongodb-Specific Remediation in Laravel — concrete code fixes
To mitigate open redirects in a Laravel application that uses MongoDB, treat any URL stored in or retrieved from MongoDB as untrusted input. Apply strict allowlisting and normalization before using the value in a redirect. Below are concrete, MongoDB-focused code examples and practices.
1. Validate against an allowlist of allowed hosts
When a document in MongoDB contains an external_url field, parse the URL and ensure its host is within an approved set. Do not trust the stored protocol-relative or full URLs implicitly.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use MongoDB\Laravel\Eloquent\Model; // Example ODM model
use Illuminate\Support\Str;
class CampaignController extends Controller
{
public function redirectToExternal(Request $request, $id)
{
// Retrieve document from MongoDB via an ODM model
$campaign = Campaign::find($id);
if (! $campaign) {
return response('Not found', 404);
}
$rawUrl = $campaign->external_url ?? $request->get('url');
if (! $rawUrl) {
return response('Missing target', 400);
}
// Normalize and validate
$url = url($rawUrl); // Ensure absolute URL
$parsed = parse_url($url);
$allowedHosts = ['app.example.com', 'static.example.com'];
if (! isset($parsed['host']) || ! in_array($parsed['host'], $allowedHosts, true)) {
return response('Invalid redirect target', 400);
}
// Ensure scheme is HTTPS where possible
if (isset($parsed['scheme']) && strtolower($parsed['scheme']) !== 'https') {
return response('Insecure scheme', 400);
}
return redirect()->to($url);
}
}
2. Use route binding with constrained patterns instead of raw MongoDB IDs for redirects
Avoid using an arbitrary MongoDB _id to look up a redirect target without further validation. Prefer route model binding with explicit constraints and avoid directly exposing database identifiers for redirect selection.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use MongoDB\Laravel\Eloquent\Model;
class PageController extends Controller
{
public function show($slug)
{
// Assume slug is validated against an indexed field in MongoDB
$page = Page::where('slug', $slug)->firstOrFail();
// If the page defines a redirect_url, validate it strictly
if ($page->redirect_url) {
$parsed = parse_url($page->redirect_url);
$allowedHosts = ['app.example.com'];
if (! isset($parsed['host']) || ! in_array($parsed['host'], $allowedHosts, true)) {
abort(400, 'Invalid redirect URL');
}
return redirect()->to($page->redirect_url);
}
return view('page.show', ['page' => $page]);
}
}
3. Sanitize and normalize before storing in MongoDB
When ingesting URLs into MongoDB, normalize them (e.g., ensure a consistent scheme, remove credentials, strip unnecessary fragments) and store the parsed components if you need to perform runtime checks. This makes validation efficient and reduces the likelihood of storing a malicious-looking but technically valid URL.
<?php
namespace App\Services;
use MongoDB\Laravel\Eloquent\Model;
class UrlNormalizer
{
public static function normalizeForStorage(string $url): array
{
$parsed = parse_url($url);
if (! $parsed || ! isset($parsed['host'])) {
throw new \InvalidArgumentException('Invalid URL');
}
$scheme = isset($parsed['scheme']) && in_array(strtolower($parsed['scheme']), ['http', 'https']) ? strtolower($parsed['scheme']) : 'https';
$host = strtolower($parsed['host']);
$port = $parsed['port'] ?? null;
$path = $parsed['path'] ?? '/';
return [
'raw' => $scheme . '://' . $host . ($port ? ':' . $port : '') . $path,
'scheme' => $scheme,
'host' => $host,
'port' => $port,
'path' => $path,
];
}
}
// Usage when saving a document
$data = UrlNormalizer::normalizeForStorage($inputUrl);
$document = new Campaign();
$document->external_url = $data['raw'];
$document->redirect_host = $data['host'];
$document->save();
4. Reject or escape open redirect parameters in forms and APIs
If your application accepts a redirect target via query parameters or request body (e.g., OAuth-like flows), reject the request if the target is external or use a short-lived token that maps to a validated server-side destination instead of passing raw URLs through MongoDB fields.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AuthController extends Controller
{
public function login(Request $request)
{
$request->validate([
'redirect' => 'sometimes|url',
]);
$redirect = $request->get('redirect');
if ($redirect) {
$parsed = parse_url($redirect);
$allowedHosts = ['myapp.example.com'];
if (! isset($parsed['host']) || ! in_array($parsed['host'], $allowedHosts, true)) {
// Log and discard; do not store or use the redirect
$redirect = null;
}
}
// Proceed with login; if $redirect is valid, use it after login
return redirect()->route('home');
}
}