Use of Web Browser Cache Containing Sensitive Information
Description
Use of Web Browser Cache Containing Sensitive Information occurs when a web application allows sensitive data to be stored in browser cache, history, or client-side storage mechanisms. This includes caching pages with personal information, storing sensitive data in localStorage/sessionStorage, allowing autocomplete on sensitive form fields, and failing to set appropriate cache-control headers. Sensitive data persists on client devices where it can be accessed by other users or malware.
Risk
Cached sensitive information creates multiple attack vectors. On shared computers (libraries, kiosks, family devices), subsequent users can access previous users' cached data. Browser history exposes sensitive URLs. Autocomplete reveals previous form inputs including passwords and credit cards. localStorage persists even after sessions end. Cached responses may be accessed through browser developer tools or filesystem inspection. This risk is amplified for applications handling financial, medical, or personal data.
Solution
Set appropriate cache-control headers to prevent caching of sensitive pages: Cache-Control: no-store, no-cache, must-revalidate, private. Add Pragma: no-cache for HTTP/1.0 compatibility. Disable autocomplete on sensitive form fields using autocomplete="off" or specific values like autocomplete="new-password". Avoid storing sensitive data in localStorage/sessionStorage—use httpOnly cookies or server-side sessions instead. Clear sensitive data when users log out. Use memory-only storage for sensitive client-side data.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Information Disclosure Sensitive data accessible to subsequent users of shared devices. |
| Privacy | Scope: Personal Data Exposure PII, financial data, and medical records exposed through caching. |
| Authentication | Scope: Credential Exposure Cached credentials or session data enables unauthorized access. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: Flask without cache control headers
from flask import Flask, render_template, jsonify
@app.route('/account/dashboard')
def dashboard_vulnerable():
user = get_current_user()
# Sensitive page can be cached by browser!
return render_template('dashboard.html', user=user)
@app.route('/api/user/profile')
def profile_vulnerable():
user = get_current_user()
# API response with PII can be cached!
return jsonify({
'name': user.name,
'ssn': user.ssn,
'account_number': user.account_number
})
# VULNERABLE: Sensitive data in response URL
@app.route('/receipt')
def receipt_vulnerable():
# Sensitive data in URL - stored in history!
return redirect(f'/receipt/view?ssn={ssn}&amount={amount}')
// VULNERABLE: Servlet without cache headers
@WebServlet("/account/details")
public class VulnerableServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// No cache control headers!
User user = getCurrentUser(request);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<p>SSN: " + user.getSsn() + "</p>");
out.println("<p>Account: " + user.getAccountNumber() + "</p>");
out.println("</body></html>");
}
}
// VULNERABLE: Spring controller
@Controller
public class VulnerableController {
@GetMapping("/transactions")
public String transactions(Model model) {
// Response cached by default!
model.addAttribute("transactions", getTransactions());
return "transactions";
}
}
<!-- VULNERABLE: HTML form without autocomplete control -->
<form action="/login" method="POST">
<!-- Browser autocomplete enabled by default! -->
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<!-- Credit card can be saved! -->
<input type="text" name="cardNumber" placeholder="Card Number">
<input type="text" name="cvv" placeholder="CVV">
<!-- SSN might be remembered! -->
<input type="text" name="ssn" placeholder="Social Security Number">
<button type="submit">Submit</button>
</form>
// VULNERABLE: Storing sensitive data in localStorage
function loginVulnerable(username, password) {
// Credentials stored persistently!
localStorage.setItem('username', username);
localStorage.setItem('password', password); // NEVER do this!
localStorage.setItem('authToken', getToken());
}
// VULNERABLE: Sensitive data in sessionStorage
function loadUserProfile(user) {
// PII in browser storage!
sessionStorage.setItem('user', JSON.stringify({
ssn: user.ssn,
accountNumber: user.accountNumber,
dateOfBirth: user.dob
}));
}
// VULNERABLE: Not clearing data on logout
function logoutVulnerable() {
// Just clears session, leaves cached data!
fetch('/api/logout', { method: 'POST' });
window.location = '/login';
// localStorage/sessionStorage data persists!
}
// VULNERABLE: Express without cache headers
app.get('/api/sensitive-data', (req, res) => {
// No cache control!
res.json({
ssn: req.user.ssn,
accounts: req.user.accounts
});
});
Fixed Code
# SAFE: Flask with cache control headers
from flask import Flask, render_template, jsonify, make_response
def add_no_cache_headers(response):
"""Add headers to prevent caching of sensitive data."""
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, private, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response
@app.route('/account/dashboard')
def dashboard_safe():
user = get_current_user()
response = make_response(render_template('dashboard.html', user=user))
return add_no_cache_headers(response)
@app.route('/api/user/profile')
def profile_safe():
user = get_current_user()
response = jsonify({
'name': user.name,
'ssn': user.ssn,
'account_number': user.account_number
})
return add_no_cache_headers(response)
# SAFE: Apply to all sensitive routes
@app.after_request
def add_security_headers(response):
"""Add security headers to all responses."""
# For authenticated pages
if hasattr(g, 'current_user') and g.current_user:
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, private'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response
# SAFE: POST for sensitive operations (not cached)
@app.route('/receipt', methods=['POST'])
def receipt_safe():
# POST requests are not cached by browsers
# Sensitive data in POST body, not URL
ssn = request.form['ssn']
amount = request.form['amount']
return render_receipt(ssn, amount)
// SAFE: Servlet with cache headers
@WebServlet("/account/details")
public class SecureServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Set cache control headers
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, private");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
User user = getCurrentUser(request);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<p>SSN: " + user.getSsn() + "</p>");
out.println("<p>Account: " + user.getAccountNumber() + "</p>");
out.println("</body></html>");
}
}
// SAFE: Spring controller with cache control
@Controller
public class SecureController {
@GetMapping("/transactions")
public ResponseEntity<String> transactions() {
return ResponseEntity.ok()
.cacheControl(CacheControl.noStore())
.header("Pragma", "no-cache")
.header("Expires", "0")
.body(renderTransactions());
}
}
// SAFE: Spring Security configuration for cache control
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
.cacheControl(cache -> {}) // Adds no-cache, no-store, etc.
.contentTypeOptions(content -> {})
);
return http.build();
}
}
// SAFE: Filter for all sensitive endpoints
@WebFilter("/secure/*")
public class NoCacheFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, private");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
chain.doFilter(req, res);
}
}
<!-- SAFE: HTML form with autocomplete controls -->
<form action="/login" method="POST" autocomplete="off">
<!-- Specific autocomplete values -->
<input type="text" name="username" autocomplete="username">
<!-- Prevent password saving -->
<input type="password" name="password" autocomplete="new-password">
<!-- Financial fields - prevent autocomplete -->
<input type="text" name="cardNumber" autocomplete="off"
inputmode="numeric" pattern="[0-9]*">
<input type="text" name="cvv" autocomplete="off"
inputmode="numeric" maxlength="4">
<!-- Sensitive PII - no autocomplete -->
<input type="text" name="ssn" autocomplete="off"
inputmode="numeric" pattern="[0-9-]*">
<button type="submit">Submit</button>
</form>
<!-- For one-time codes -->
<input type="text" name="otp" autocomplete="one-time-code">
// SAFE: Secure client-side storage patterns
class SecureStorage {
constructor() {
// Use memory storage for sensitive data
this.sensitiveData = new Map();
}
// Store sensitive data in memory only
setSecure(key, value) {
this.sensitiveData.set(key, value);
}
getSecure(key) {
return this.sensitiveData.get(key);
}
// Clear all sensitive data
clearAll() {
this.sensitiveData.clear();
}
}
const secureStorage = new SecureStorage();
// SAFE: Proper logout with data clearing
function logoutSafe() {
// Clear all client-side data
localStorage.clear();
sessionStorage.clear();
secureStorage.clearAll();
// Clear cookies via server
fetch('/api/logout', {
method: 'POST',
credentials: 'include'
}).then(() => {
// Force page reload to clear memory
window.location.href = '/login';
});
}
// SAFE: Clear sensitive data on page unload
window.addEventListener('beforeunload', () => {
// Clear sensitive in-memory data
secureStorage.clearAll();
});
// SAFE: Express with cache control
const helmet = require('helmet');
app.use(helmet.noSniff());
// Middleware for sensitive routes
const noCacheMiddleware = (req, res, next) => {
res.set({
'Cache-Control': 'no-store, no-cache, must-revalidate, private',
'Pragma': 'no-cache',
'Expires': '0',
'Surrogate-Control': 'no-store'
});
next();
};
app.use('/api/sensitive', noCacheMiddleware);
app.use('/account', noCacheMiddleware);
app.get('/api/sensitive-data', noCacheMiddleware, (req, res) => {
res.json({
ssn: req.user.ssn,
accounts: req.user.accounts
});
});
// SAFE: Don't store sensitive data client-side
// Instead, use httpOnly cookies for session tokens
app.post('/login', (req, res) => {
const token = generateToken(req.body.username);
// Store token in httpOnly cookie - not accessible to JS
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000
});
// Don't return sensitive data to client
res.json({ success: true });
});
// SAFE: Session-based data retrieval
app.get('/api/profile', requireAuth, (req, res) => {
// Data fetched from server each time, not cached
// User ID from secure session, not client storage
const user = getUserFromSession(req);
res.set('Cache-Control', 'no-store');
res.json({
name: user.name,
// Only return what's needed
});
});
Exploited in the Wild
Shared Computer Information Leakage
Public library and internet cafe computers have exposed previous users' banking and personal information through browser cache.
Browser History Exposure
Sensitive URLs containing tokens, IDs, or parameters exposed user data through browser history.
Form Autofill Attacks
Malicious websites have exploited browser autofill to steal credit card numbers and passwords from cached form data.
Tools to test/exploit
-
Browser Developer Tools — inspect cache, localStorage, sessionStorage.
-
Burp Suite — analyze cache headers.
-
OWASP ZAP — test for caching issues.
-
SecurityHeaders.com — check header configuration.
CVE Examples
-
CVE-2019-11358 — Cache-related information exposure.
-
CVE-2020-8945 — Sensitive data caching vulnerability.
-
Numerous application-specific caching issues.
References
-
MITRE. "CWE-525: Use of Web Browser Cache Containing Sensitive Information." https://cwe.mitre.org/data/definitions/525.html
-
OWASP. "Session Management Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html