Reliance on Cookies without Validation and Integrity Checking
Description
Reliance on Cookies without Validation and Integrity Checking is a vulnerability where a web application depends on the existence, format, or values of cookies for security-critical operations without properly verifying that these settings are valid and have not been tampered with. Cookies are stored on the client side and can be easily viewed, modified, or forged by attackers using browser developer tools, proxy interceptors, or custom HTTP clients. When applications trust cookie values for authentication status, user roles, permissions, or other security decisions without server-side validation or integrity verification, attackers can manipulate these values to bypass security controls.
Risk
Trusting unvalidated cookies creates severe security vulnerabilities. Attackers can modify authentication cookies to bypass login requirements. Role or permission cookies can be altered to escalate privileges from regular user to administrator. Session data stored in cookies without integrity protection can be manipulated to impersonate other users. Shopping cart or pricing information in cookies can be modified for financial fraud. Cookie-based tokens without cryptographic validation can be forged. The ease of cookie manipulation means these attacks require minimal skill, making them highly exploitable. Combined with other vulnerabilities, cookie manipulation can enable injection attacks by inserting malicious values.
Solution
Avoid storing security-critical information directly in cookies. When cookie data must be used, implement cryptographic integrity checks using HMAC or authenticated encryption to detect tampering. Store sensitive state on the server side, using only a session identifier in the cookie. Validate all cookie data against server-side records before making security decisions. Implement replay protection using unpredictable, time-limited tokens. Use the Secure and HttpOnly cookie flags. Set appropriate SameSite attributes to prevent CSRF. Never trust client-side data for authorization decisions—always verify against server-side state.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - Attackers can manipulate cookie values to alter application state, modify transactions, or inject malicious content. |
| Access Control | Scope: Access Control Gain Privileges or Assume Identity - Role or permission information stored in cookies can be modified to escalate privileges to administrative levels. |
| Availability | Scope: Availability Bypass Protection Mechanism - Authentication and authorization mechanisms that rely on cookie values can be completely bypassed through cookie manipulation. |
Example Code
Vulnerable Code
// Vulnerable: Trusting cookie values for authorization
import javax.servlet.http.*;
public class VulnerableAuthServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Vulnerable: Reading role directly from cookie
Cookie[] cookies = request.getCookies();
String userRole = "guest";
if (cookies != null) {
for (Cookie c : cookies) {
if (c.getName().equals("role")) {
// Vulnerable: Trusting cookie value without validation
userRole = c.getValue();
}
if (c.getName().equals("isAuthenticated")) {
// Vulnerable: Authentication status from cookie
if (c.getValue().equals("true")) {
// User considered authenticated based on cookie alone!
}
}
}
}
// Vulnerable: Authorization based on unvalidated cookie
if (userRole.equals("admin")) {
// Admin access granted based on easily-forged cookie!
displayAdminPanel(response);
} else {
displayUserPanel(response);
}
}
// Vulnerable: Setting role in cookie
protected void doLogin(HttpServletRequest request, HttpServletResponse response,
String username, String password) {
if (authenticate(username, password)) {
// Vulnerable: Storing role in client-side cookie
Cookie roleCookie = new Cookie("role", getUserRole(username));
Cookie authCookie = new Cookie("isAuthenticated", "true");
Cookie userIdCookie = new Cookie("userId", getUserId(username));
response.addCookie(roleCookie);
response.addCookie(authCookie);
response.addCookie(userIdCookie);
// Attacker can modify these cookies to escalate privileges
}
}
}
<?php
// Vulnerable: PHP authentication using cookies without validation
// Vulnerable: Login sets unprotected cookies
function vulnerable_login($username, $password) {
if (authenticate($username, $password)) {
$user = get_user($username);
// Vulnerable: Storing sensitive data in cookies
setcookie('user_id', $user['id'], time() + 3600);
setcookie('username', $user['username'], time() + 3600);
setcookie('role', $user['role'], time() + 3600);
setcookie('is_admin', $user['is_admin'] ? '1' : '0', time() + 3600);
// Attacker can modify: role=admin, is_admin=1
return true;
}
return false;
}
// Vulnerable: Authorization based on cookie values
function check_admin_access() {
// Vulnerable: Trusting cookie directly
if (isset($_COOKIE['is_admin']) && $_COOKIE['is_admin'] === '1') {
return true; // Admin access granted based on forged cookie!
}
return false;
}
// Vulnerable: Getting user from cookie
function get_current_user_id() {
// Vulnerable: User ID from cookie can be modified
return isset($_COOKIE['user_id']) ? $_COOKIE['user_id'] : null;
// Attacker can access other users' data by changing user_id cookie
}
// Vulnerable: Price calculation trusting cookie
function calculate_total() {
// Vulnerable: Price stored in cookie
$price = isset($_COOKIE['item_price']) ? $_COOKIE['item_price'] : 0;
$quantity = isset($_COOKIE['quantity']) ? $_COOKIE['quantity'] : 1;
// Attacker can set item_price=0.01 in cookie!
return $price * $quantity;
}
?>
// Vulnerable: Node.js Express with unvalidated cookies
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
// Vulnerable: Login sets unprotected cookies
app.post('/login', async (req, res) => {
const { username, password } = req.body;
if (await authenticate(username, password)) {
const user = await getUser(username);
// Vulnerable: Storing sensitive data in plain cookies
res.cookie('userId', user.id);
res.cookie('role', user.role);
res.cookie('permissions', JSON.stringify(user.permissions));
res.cookie('isAuthenticated', 'true');
res.redirect('/dashboard');
} else {
res.status(401).send('Invalid credentials');
}
});
// Vulnerable: Authorization middleware trusting cookies
function requireAdmin(req, res, next) {
// Vulnerable: Role from cookie without validation
if (req.cookies.role === 'admin') {
next(); // Admin access based on forged cookie!
} else {
res.status(403).send('Access denied');
}
}
// Vulnerable: Getting user data from cookie
app.get('/profile', (req, res) => {
// Vulnerable: User ID from cookie can be manipulated
const userId = req.cookies.userId;
// Attacker can access any user's profile by changing userId cookie
getUserProfile(userId).then(profile => res.json(profile));
});
// Vulnerable: Discount based on cookie
app.post('/checkout', (req, res) => {
// Vulnerable: Discount percentage from cookie
const discount = parseFloat(req.cookies.discount) || 0;
// Attacker can set discount=100 in cookie for free items!
const total = calculateTotal(req.body.items) * (1 - discount / 100);
processPayment(total);
});
Fixed Code
// Fixed: Server-side session with signed cookies
import javax.servlet.http.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class SecureAuthServlet extends HttpServlet {
private static final String HMAC_SECRET = System.getenv("COOKIE_SECRET");
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Fixed: Get session from server-side storage
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("userId") == null) {
response.sendRedirect("/login");
return;
}
// Fixed: Role from server-side session, not cookie
String userRole = (String) session.getAttribute("role");
if ("admin".equals(userRole)) {
displayAdminPanel(response);
} else {
displayUserPanel(response);
}
}
protected void doLogin(HttpServletRequest request, HttpServletResponse response,
String username, String password) throws IOException {
if (authenticate(username, password)) {
// Fixed: Store data in server-side session
HttpSession session = request.getSession(true);
session.setAttribute("userId", getUserId(username));
session.setAttribute("username", username);
session.setAttribute("role", getUserRole(username));
// Fixed: Only session ID in cookie (managed by container)
// Session ID is HttpOnly by default in modern containers
response.sendRedirect("/dashboard");
}
}
// Fixed: Signed cookie for stateless data
private void setSignedCookie(HttpServletResponse response, String name, String value) {
String signature = computeHmac(value);
String signedValue = value + "." + signature;
Cookie cookie = new Cookie(name, signedValue);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
response.addCookie(cookie);
}
private String getVerifiedCookieValue(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies == null) return null;
for (Cookie c : cookies) {
if (c.getName().equals(name)) {
String[] parts = c.getValue().split("\\.");
if (parts.length != 2) return null;
String value = parts[0];
String signature = parts[1];
// Fixed: Verify signature
if (computeHmac(value).equals(signature)) {
return value;
}
}
}
return null;
}
private String computeHmac(String data) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(HMAC_SECRET.getBytes(), "HmacSHA256"));
return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes()));
} catch (Exception e) {
throw new RuntimeException("HMAC computation failed", e);
}
}
}
<?php
// Fixed: PHP with server-side sessions and signed cookies
session_start();
// Configuration
define('COOKIE_SECRET', getenv('COOKIE_SECRET'));
// Fixed: Login using server-side sessions
function secure_login($username, $password) {
if (authenticate($username, $password)) {
$user = get_user($username);
// Fixed: Store sensitive data in server-side session
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
$_SESSION['is_admin'] = $user['is_admin'];
$_SESSION['login_time'] = time();
// Fixed: Regenerate session ID to prevent fixation
session_regenerate_id(true);
return true;
}
return false;
}
// Fixed: Authorization based on server-side session
function check_admin_access() {
// Fixed: Check session, not cookie
return isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
}
// Fixed: Getting user from session
function get_current_user_id() {
// Fixed: User ID from server-side session
return isset($_SESSION['user_id']) ? $_SESSION['user_id'] : null;
}
// Fixed: Signed cookie for stateless data (if needed)
function set_signed_cookie($name, $value, $expiry) {
$signature = hash_hmac('sha256', $value, COOKIE_SECRET);
$signed_value = base64_encode($value) . '.' . $signature;
setcookie($name, $signed_value, [
'expires' => $expiry,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
}
function get_verified_cookie($name) {
if (!isset($_COOKIE[$name])) {
return null;
}
$parts = explode('.', $_COOKIE[$name]);
if (count($parts) !== 2) {
return null;
}
$value = base64_decode($parts[0]);
$signature = $parts[1];
// Fixed: Verify signature using constant-time comparison
$expected = hash_hmac('sha256', $value, COOKIE_SECRET);
if (hash_equals($expected, $signature)) {
return $value;
}
return null; // Tampered cookie
}
// Fixed: Cart with server-side storage
function add_to_cart($product_id, $quantity) {
// Fixed: Get price from server, not client
$product = get_product($product_id);
if (!$product) return false;
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
$_SESSION['cart'][$product_id] = [
'quantity' => $quantity,
'price' => $product['price'] // Server-side price
];
return true;
}
function calculate_total() {
// Fixed: Calculate from server-side session data
$total = 0;
if (isset($_SESSION['cart'])) {
foreach ($_SESSION['cart'] as $item) {
$total += $item['price'] * $item['quantity'];
}
}
return $total;
}
?>
// Fixed: Node.js Express with signed sessions
const express = require('express');
const session = require('express-session');
const crypto = require('crypto');
const app = express();
// Fixed: Use signed sessions
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
sameSite: 'strict', // CSRF protection
maxAge: 3600000 // 1 hour
}
}));
// Fixed: Login stores data in server-side session
app.post('/login', async (req, res) => {
const { username, password } = req.body;
if (await authenticate(username, password)) {
const user = await getUser(username);
// Fixed: Store in server-side session
req.session.userId = user.id;
req.session.username = user.username;
req.session.role = user.role;
req.session.permissions = user.permissions;
req.session.loginTime = Date.now();
// Fixed: Regenerate session ID
req.session.regenerate((err) => {
if (err) {
return res.status(500).send('Session error');
}
res.redirect('/dashboard');
});
} else {
res.status(401).send('Invalid credentials');
}
});
// Fixed: Authorization middleware using session
function requireAdmin(req, res, next) {
// Fixed: Role from server-side session
if (req.session && req.session.role === 'admin') {
next();
} else {
res.status(403).send('Access denied');
}
}
// Fixed: User identification from session
app.get('/profile', (req, res) => {
if (!req.session.userId) {
return res.status(401).send('Not authenticated');
}
// Fixed: User ID from server-side session
getUserProfile(req.session.userId)
.then(profile => res.json(profile));
});
// Fixed: Discount applied server-side
app.post('/checkout', async (req, res) => {
if (!req.session.userId) {
return res.status(401).send('Not authenticated');
}
// Fixed: Get discount from server-side user data
const user = await getUser(req.session.userId);
const discount = user.discountPercentage || 0;
const total = calculateTotal(req.body.items) * (1 - discount / 100);
await processPayment(req.session.userId, total);
res.json({ total });
});
// Fixed: Signed cookie helper for stateless data
function signedCookie(name, value, secret) {
const signature = crypto
.createHmac('sha256', secret)
.update(value)
.digest('base64');
return `${Buffer.from(value).toString('base64')}.${signature}`;
}
function verifySignedCookie(cookieValue, secret) {
const [encodedValue, signature] = cookieValue.split('.');
if (!encodedValue || !signature) return null;
const value = Buffer.from(encodedValue, 'base64').toString();
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(value)
.digest('base64');
// Fixed: Constant-time comparison
if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return value;
}
return null;
}
module.exports = app;
CVE Examples
- CVE-2016-4437: Apache Shiro cookie-based RememberMe vulnerability allowed attackers to bypass authentication.
- CVE-2015-8562: Joomla session cookie vulnerability enabled remote code execution.
- CVE-2012-3137: Oracle HTTP Server cookie manipulation vulnerability.
References
- MITRE Corporation. "CWE-565: Reliance on Cookies without Validation and Integrity Checking." https://cwe.mitre.org/data/definitions/565.html
- OWASP. "Session Management Cheat Sheet."
- OWASP. "Testing for Cookies Attributes."