Code Auditing Guidelines: Detecting and Fixing access token leakages via unvalidated application redirections in Your Shopify Monolith
Understanding the Vulnerability: Unvalidated Redirects and Access Token Leakage
In monolithic Shopify applications, particularly those with complex authentication flows or third-party integrations, unvalidated application redirects pose a significant security risk. When an application redirects a user to a URL that is not properly validated against a trusted allowlist, an attacker can craft a malicious URL. If this malicious URL is presented to the user and subsequently processed by the application, it can lead to the leakage of sensitive information, most critically, OAuth access tokens. This often occurs after a user has authorized the application, and the authorization server redirects back to the application with the token in the URL parameters.
The core of the problem lies in how the application handles the redirect URI provided by the authorization server. If the application blindly trusts and redirects to any URI specified in the query string of an incoming request (e.g., `?redirect_uri=http://malicious.com/steal_token`), it opens the door for token exfiltration.
Identifying Potential Leakage Points in Shopify Monoliths
Monolithic architectures, while offering development simplicity initially, can obscure critical data flow paths. For access token leakage via unvalidated redirects, we need to scrutinize areas where:
- OAuth flows are implemented (e.g., for Shopify API access, third-party app integrations).
- User sessions are managed and redirected after login, logout, or authorization.
- External services are integrated, and callbacks are handled.
- Any part of the application constructs redirect URLs based on user-supplied or external input without strict validation.
A common pattern to look for is code that takes a URL parameter and uses it directly in a redirect function. For instance, in a PHP-based monolith, this might look like:
Example PHP Vulnerability Pattern
Consider a hypothetical authentication callback handler:
// Hypothetical auth callback controller
public function handleAuthCallback(Request $request) {
$code = $request->get('code');
// ... exchange code for access token ...
$accessToken = $this->exchangeCodeForToken($code);
// VULNERABLE REDIRECT: No validation on $request->get('return_to')
$returnUrl = $request->get('return_to', '/dashboard'); // Default to dashboard
// This is the critical point of failure
return redirect($returnUrl . '?token=' . $accessToken);
}
In this snippet, if an attacker can control the `return_to` parameter, they can redirect the user to a domain they control, appending the stolen access token.
Implementing Robust Validation Strategies
The most effective defense against unvalidated redirects is to implement a strict allowlist of trusted redirect URIs. This means that any URI used for redirection must be pre-approved and stored securely. When a redirect is requested, the application must verify that the target URI matches one of the allowed destinations.
Allowlist-Based Validation in PHP
Let’s refactor the previous example to include validation. We’ll assume a configuration setting or a database table holds the allowed redirect URIs.
// Hypothetical auth callback controller with validation
public function handleAuthCallback(Request $request) {
$code = $request->get('code');
// ... exchange code for access token ...
$accessToken = $this->exchangeCodeForToken($code);
$requestedReturnUrl = $request->get('return_to');
$defaultReturnUrl = '/dashboard';
// Load allowed redirect URIs from configuration or database
$allowedRedirectUris = config('app.allowed_redirect_uris', []); // Example: ['https://your-shopify-domain.com/callback', 'https://your-shopify-domain.com/settings']
$finalRedirectUrl = $defaultReturnUrl; // Start with default
if (!empty($requestedReturnUrl)) {
// Basic URL parsing to compare host and path
$parsedUrl = parse_url($requestedReturnUrl);
if ($parsedUrl && isset($parsedUrl['host']) && isset($parsedUrl['path'])) {
// Construct a canonical URL for comparison
$canonicalRequestedUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . $parsedUrl['path'];
// Check if the canonical requested URL is in our allowlist
if (in_array($canonicalRequestedUrl, $allowedRedirectUris)) {
$finalRedirectUrl = $requestedReturnUrl;
} else {
// Log this attempt for security monitoring
Log::warning("Unvalidated redirect attempt blocked: " . $requestedReturnUrl);
// Optionally, redirect to a safe error page or default
$finalRedirectUrl = '/access-denied'; // Or $defaultReturnUrl
}
} else {
// Invalid URL format, fall back to default
Log::warning("Invalid redirect URL format: " . $requestedReturnUrl);
$finalRedirectUrl = $defaultReturnUrl;
}
}
// Append token ONLY if the redirect URL is deemed safe
// Note: It's generally better to avoid passing tokens in URL parameters.
// Consider using session storage or secure HTTP-only cookies for tokens.
return redirect($finalRedirectUrl . '?token=' . $accessToken);
}
In this improved version:
- We retrieve a list of explicitly allowed redirect URIs.
- We parse the requested redirect URL to extract its components (scheme, host, path).
- We compare the canonical form of the requested URL against the allowlist.
- If it’s not in the allowlist, we log the attempt and redirect to a safe fallback URL.
Configuration Example (PHP/Laravel)
The allowed URIs can be stored in your application’s configuration file (e.g., config/app.php or a dedicated config/security.php).
// config/app.php (or a new config file)
'allowed_redirect_uris' => [
'https://your-shopify-store.myshopify.com/auth/callback',
'https://your-app-subdomain.your-shopify-store.com/oauth/handler',
'https://your-shopify-store.myshopify.com/admin/oauth/authorize', // Example for Shopify's own redirects
],
For dynamic allowlists (e.g., per-client configurations), a database lookup would be more appropriate.
Beyond URL Parameters: Token Handling Best Practices
While validating redirect URIs is crucial, passing access tokens directly in URL parameters is inherently insecure. Tokens can be logged in browser history, server logs, or intercepted by network sniffers. A more secure approach involves:
- Server-Side Session Storage: After obtaining the access token, store it securely in the server-side session associated with the authenticated user. The redirect can then be to a safe internal URL, and the application can retrieve the token from the session when needed to make API calls.
- HTTP-Only Cookies: For web applications, store the access token (or a session ID that grants access to the token) in an HTTP-Only, Secure cookie. This prevents client-side JavaScript from accessing the token, mitigating XSS-based token theft.
- Token Exchange: If the access token is short-lived, consider implementing a token exchange mechanism where the client requests a temporary, scoped token from your backend using a secure, authenticated channel.
Example: Storing Token in Session (PHP/Laravel)
Instead of appending the token to the redirect URL:
// Hypothetical auth callback controller with session storage
public function handleAuthCallback(Request $request) {
$code = $request->get('code');
$accessToken = $this->exchangeCodeForToken($code);
// Store the token securely in the session
$request->session()->put('shopify_access_token', $accessToken);
$request->session()->save(); // Ensure session is saved
// Redirect to a safe internal URL without the token in the query string
$returnUrl = $request->get('return_to', '/dashboard');
// Perform validation on $returnUrl as shown previously
$validatedReturnUrl = $this->validateRedirectUrl($returnUrl); // Assume this method exists
return redirect($validatedReturnUrl);
}
// In other parts of your application, retrieve the token from the session:
public function someApiCall() {
$accessToken = session('shopify_access_token');
if (!$accessToken) {
// Handle token expiration or missing token scenario
return redirect('/login');
}
// Use $accessToken for API calls
}
Auditing and Code Review Checklist
When auditing your Shopify monolith for this vulnerability, consider the following:
By systematically applying these guidelines, you can significantly reduce the risk of access token leakage through unvalidated application redirects in your Shopify monolith, bolstering your application’s overall security posture.