Execution After Redirect (EAR)
Description
Execution After Redirect (EAR) is a control flow vulnerability where a web application sends a redirect response to another location but instead of terminating execution, continues to run additional code. When a redirect header is sent to the browser, the server-side code may still execute subsequent statements unless explicitly terminated. This allows attackers to access functionality that should have been blocked by the redirect, such as viewing protected content, executing privileged operations, or accessing administrative features even when the redirect was meant to deny access.
Risk
EAR vulnerabilities create serious security risks because they allow bypass of authentication and authorization controls. When code continues executing after a redirect intended to deny access, attackers can view sensitive information, perform unauthorized actions, or gain administrative access. The vulnerability is particularly dangerous because it often goes undetected in testing—browsers follow redirects before showing any content, so testers don't see the leaked information. However, attackers using tools that don't follow redirects can capture the full response containing sensitive data. This can lead to exposure of configuration details, session information, or complete authentication bypass.
Solution
Always terminate script execution immediately after sending a redirect. In PHP, call exit() or die() after header("Location: ..."). In Java servlets, call return after sendRedirect(). In ASP.NET, use Response.Redirect() with the second parameter set to true, or call Response.End(). Structure code so that protected content only executes in explicit success paths, not as fallthrough after redirect logic. Use framework-provided redirect mechanisms that automatically terminate execution. Implement security testing that captures responses before redirects are followed.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Sensitive information may be exposed in the response body after the redirect header. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Authentication and authorization checks that rely on redirects can be bypassed. |
| Integrity | Scope: Integrity Execute Unauthorized Code or Commands - Actions that should be blocked may still execute. |
Example Code
Vulnerable Code
<?php
// Vulnerable: Code executes after redirect
$requestingIP = $_SERVER['REMOTE_ADDR'];
$allowedIPs = array('192.168.1.100', '192.168.1.101');
if (!in_array($requestingIP, $allowedIPs)) {
echo "You are not authorized to view this page";
header("Location: /error.php");
// Vulnerable: No exit! Code continues executing
}
// Sensitive operations continue despite redirect
$serverStatus = getServerStatus();
$databaseInfo = getDatabaseConnectionDetails();
// Vulnerable: All this output is sent before browser follows redirect
echo "<h1>Server Administration</h1>";
echo "<p>Server Status: " . $serverStatus . "</p>";
echo "<p>DB Connection: " . $databaseInfo . "</p>";
// Attacker can capture this even though browser would redirect
?>
<!-- Another vulnerable pattern -->
<?php
function checkAuthentication() {
if (!isset($_SESSION['user'])) {
header("Location: /login.php");
// Vulnerable: Function returns, but caller continues
return false;
}
return true;
}
// Vulnerable: Return value not checked properly
checkAuthentication(); // Redirect sent but...
// ...execution continues!
processAdminAction($_POST['action']);
displayAdminPanel();
?>
// Vulnerable: Java servlet continues after redirect
import javax.servlet.http.*;
import java.io.*;
public class VulnerableAdminServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("admin") == null) {
response.sendRedirect("/login.jsp");
// Vulnerable: No return! Execution continues
}
// All this code runs even after sendRedirect
PrintWriter out = response.getWriter();
out.println("<h1>Admin Panel</h1>");
out.println("Secret admin key: " + getAdminKey());
out.println("User list: " + getAllUsers());
// Sensitive operation also executes
processAdminTasks();
}
}
// Vulnerable: Exception-based flow doesn't stop execution
public class VulnerableAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
if (!isAuthenticated((HttpServletRequest) req)) {
response.sendRedirect("/unauthorized.jsp");
// Vulnerable: Filter chain continues
}
// Request still processed by downstream servlets
chain.doFilter(req, res);
}
}
# Vulnerable: Flask redirect without return
from flask import Flask, redirect, request, session
app = Flask(__name__)
@app.route('/admin')
def vulnerable_admin():
if 'user' not in session:
redirect('/login') # Vulnerable: Not returned!
# Execution continues
# Sensitive data exposed
config = get_app_configuration()
users = get_all_users()
return f"""
<h1>Admin Panel</h1>
<p>Config: {config}</p>
<p>Users: {users}</p>
"""
@app.route('/delete/<int:item_id>')
def vulnerable_delete(item_id):
if not is_authorized(session.get('user')):
redirect('/unauthorized')
# Vulnerable: Item still gets deleted!
delete_item(item_id) # Executes regardless of authorization
return "Item deleted"
<!-- Vulnerable: ASP.NET without Response.End -->
<%@ Page Language="C#" %>
<%
if (!User.Identity.IsAuthenticated)
{
Response.Redirect("Login.aspx");
// Vulnerable: Page continues rendering
}
// Sensitive content exposed
Response.Write("<h1>Secret Administration</h1>");
Response.Write("Admin Password: " + GetAdminPassword());
Response.Write("System Keys: " + GetSystemKeys());
%>
// Vulnerable: Node.js/Express without return
const express = require('express');
const app = express();
app.get('/admin', (req, res) => {
if (!req.session.isAdmin) {
res.redirect('/login');
// Vulnerable: No return - handler continues
}
// Sensitive data returned after redirect header
const secrets = getSecrets();
res.send(`<h1>Admin</h1><p>${secrets}</p>`);
// Both redirect and content are sent!
});
Fixed Code
<?php
// Fixed: Exit immediately after redirect
$requestingIP = $_SERVER['REMOTE_ADDR'];
$allowedIPs = array('192.168.1.100', '192.168.1.101');
if (!in_array($requestingIP, $allowedIPs)) {
header("Location: /error.php");
exit(); // Fixed: Terminate execution immediately
}
// Only executes if IP is allowed
$serverStatus = getServerStatus();
$databaseInfo = getDatabaseConnectionDetails();
echo "<h1>Server Administration</h1>";
echo "<p>Server Status: " . $serverStatus . "</p>";
?>
<?php
// Fixed: Function terminates execution
function requireAuthentication() {
if (!isset($_SESSION['user'])) {
header("Location: /login.php");
exit(); // Fixed: Terminate in function
}
}
// Fixed: Call properly terminates if not authenticated
requireAuthentication();
// Only reaches here if authenticated
processAdminAction($_POST['action']);
displayAdminPanel();
?>
<?php
// Fixed: Alternative - structure code properly
if (isset($_SESSION['user'])) {
// Protected content only in authenticated block
processAdminAction($_POST['action']);
displayAdminPanel();
} else {
header("Location: /login.php");
exit();
}
?>
// Fixed: Return after redirect
import javax.servlet.http.*;
import java.io.*;
public class SecureAdminServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("admin") == null) {
response.sendRedirect("/login.jsp");
return; // Fixed: Return immediately after redirect
}
// Only executes if authenticated
PrintWriter out = response.getWriter();
out.println("<h1>Admin Panel</h1>");
out.println("Secret admin key: " + getAdminKey());
processAdminTasks();
}
}
// Fixed: Filter properly stops chain
public class SecureAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
if (!isAuthenticated((HttpServletRequest) req)) {
response.sendRedirect("/unauthorized.jsp");
return; // Fixed: Don't continue filter chain
}
// Only continue if authenticated
chain.doFilter(req, res);
}
}
# Fixed: Return redirect response
from flask import Flask, redirect, request, session
app = Flask(__name__)
@app.route('/admin')
def secure_admin():
if 'user' not in session:
return redirect('/login') # Fixed: Return the redirect
# Only executes if authenticated
config = get_app_configuration()
users = get_all_users()
return f"""
<h1>Admin Panel</h1>
<p>Config: {config}</p>
<p>Users: {users}</p>
"""
@app.route('/delete/<int:item_id>')
def secure_delete(item_id):
if not is_authorized(session.get('user')):
return redirect('/unauthorized') # Fixed: Return redirect
# Only executes if authorized
delete_item(item_id)
return "Item deleted"
# Alternative: Use decorator pattern
from functools import wraps
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'user' not in session:
return redirect('/login')
return f(*args, **kwargs)
return decorated
@app.route('/admin')
@require_auth
def admin_with_decorator():
# This only runs if authenticated
return render_admin_panel()
<%@ Page Language="C#" %>
<%
// Fixed: Use Response.Redirect with endResponse=true
if (!User.Identity.IsAuthenticated)
{
Response.Redirect("Login.aspx", true);
// Second parameter ends response
// Alternatively: Response.Redirect("Login.aspx"); Response.End();
}
// Only executes if authenticated
Response.Write("<h1>Secret Administration</h1>");
%>
// Fixed: Node.js/Express with return
const express = require('express');
const app = express();
app.get('/admin', (req, res) => {
if (!req.session.isAdmin) {
return res.redirect('/login'); // Fixed: Return after redirect
}
// Only executes if admin
const secrets = getSecrets();
res.send(`<h1>Admin</h1><p>${secrets}</p>`);
});
// Fixed: Use middleware for auth
function requireAdmin(req, res, next) {
if (!req.session.isAdmin) {
return res.redirect('/login'); // Fixed: Return
}
next();
}
app.get('/admin', requireAdmin, (req, res) => {
// Only reaches here if admin check passed
res.send(getAdminPanel());
});
CVE Examples
- CVE-2013-1402: Application exposed configuration details after redirect.
- CVE-2009-1936: EAR combined with file inclusion and path traversal.
- CVE-2007-2713: Unauthorized administrator access due to execution after redirect.
- CVE-2007-5578: Authentication bypass through EAR.
- CVE-2007-6652: Non-admin static code injection via EAR.
References
- MITRE Corporation. "CWE-698: Execution After Redirect (EAR)." https://cwe.mitre.org/data/definitions/698.html
- OWASP. "Execution After Redirect."
- CWE-670: Always-Incorrect Control Flow Implementation.