HIGH clickjackingbearer tokens

Clickjacking with Bearer Tokens

How Clickjacking Manifests in Bearer Tokens

Clickjacking attacks exploit the fact that users can be tricked into clicking on seemingly innocuous UI elements that trigger unintended actions. When Bearer Tokens are involved, clickjacking becomes particularly dangerous because it can lead to unauthorized API calls using stolen or manipulated tokens.

The most common clickjacking scenario with Bearer Tokens occurs when an attacker embeds your application in an invisible iframe and overlays deceptive UI elements. A user might believe they're clicking a "Download PDF" button, but they're actually clicking a hidden "Delete Account" button that makes an API call using their Bearer Token.

Consider this vulnerable pattern:

<!-- Victim application -->
<button onclick="makeAPICall()">Download Report</button>
<script>
function makeAPICall() {
  fetch('/api/reports/download', {
    method: 'GET',
    headers: {
      'Authorization': 'Bearer ' + localStorage.getItem('authToken')
    }
  });
}
</script>

An attacker could create a malicious page that loads this application in an iframe with zero opacity:

<!-- Malicious clickjacking page -->
<style>
  iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    z-index: 2;
  }
  .fake-button {
    position: absolute;
    top: 200px;
    left: 100px;
    width: 150px;
    height: 50px;
    background: blue;
    color: white;
    z-index: 1;
  }
</style>
<iframe src="https://victim-app.com"></iframe>
<div class="fake-button" onclick="clickThrough()">Click Here for Free Gift</div>
<script>
function clickThrough() {
  // Coordinates aligned with victim app's button
  const iframe = document.querySelector('iframe');
  const clickX = 120;
  const clickY = 220;
  iframe.contentWindow.document.elementFromPoint(clickX, clickY).click();
}
</script>

When the user clicks the "Free Gift" button, they're actually clicking the victim application's button through the iframe. The API call executes with the user's Bearer Token, performing actions they never intended.

Another variant involves cursorjacking, where the attacker manipulates the cursor position to mislead users about where they're actually clicking. This is particularly effective when combined with Bearer Tokens because the token is automatically included in the request headers.

Bearer Tokens-Specific Detection

Detecting clickjacking vulnerabilities in Bearer Token implementations requires both manual testing and automated scanning. Here's how to identify these issues:

Manual Testing Steps:

1. Test frame embedding:
   curl -I https://your-api.com
   # Look for X-Frame-Options header

2. Check CSP headers:
   curl -I https://your-api.com | grep "Content-Security-Policy"
   # Verify frame-ancestors directive

3. Test with iframe:
   <iframe src="https://your-api.com"></iframe>
   # Try to interact with elements

4. Verify same-origin policy:
   # Attempt to access localStorage from different origin

Automated Scanning with middleBrick:

middleBrick's clickjacking detection specifically tests Bearer Token endpoints by:

  • Checking for X-Frame-Options header presence and correct values (DENY, SAMEORIGIN)
  • Analyzing Content-Security-Policy for frame-ancestors restrictions
  • Testing whether sensitive endpoints can be embedded in iframes
  • Verifying that state-changing operations require additional verification beyond just Bearer Token presence
  • Checking for missing anti-CSRF tokens in conjunction with Bearer Token usage

Run middleBrick to scan your Bearer Token endpoints:

npx middlebrick scan https://api.yourservice.com/user/profile

# Or integrate into CI/CD
middlebrick scan --fail-below B --output json

The scan results will show clickjacking risk alongside other Bearer Token-specific vulnerabilities like BOLA (Broken Object Level Authorization) and missing rate limiting.

Bearer Tokens-Specific Remediation

Securing Bearer Token endpoints against clickjacking requires multiple defensive layers. Here are Bearer Tokens-specific implementations:

1. Frame-Busting JavaScript:

// Implement in all pages handling Bearer Token operations
if (self !== top) {
  try {
    top.location = self.location;
  } catch (e) {
    // Fallback for sandboxed iframes
    document.body.style.display = 'none';
    document.write('<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:red;color:white;text-align:center;padding-top:200px;font-size:24px">Security Alert: This page cannot be embedded</div>');
  }
}

// Enhanced version with token validation
window.addEventListener('load', function() {
  if (window.location !== window.parent.location) {
    // Destroy any sensitive Bearer Token operations
    const sensitiveElements = document.querySelectorAll('[data-sensitive="true"]');
    sensitiveElements.forEach(el => el.remove());
    
    // Clear localStorage for security-critical tokens
    if (localStorage.getItem('authToken')) {
      localStorage.removeItem('authToken');
      console.warn('Bearer token cleared due to clickjacking attempt');
    }
  }
});

2. HTTP Header Protection:

// Express.js middleware for Bearer Token endpoints
app.use('/api/*', (req, res, next) => {
  // X-Frame-Options: DENY prevents all framing
  res.setHeader('X-Frame-Options', 'DENY');
  
  // Content-Security-Policy with frame-ancestors
  res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
  
  // Additional CSP for modern browsers
  res.setHeader('X-Content-Security-Policy', "allow 'none'");
  
  next();
});

// For endpoints that must support framing from specific domains
app.use('/public/*', (req, res, next) => {
  res.setHeader('X-Frame-Options', 'ALLOW-FROM https://trusted.com');
  res.setHeader('Content-Security-Policy', "frame-ancestors https://trusted.com");
  next();
});

3. Double-Submit Cookie with Bearer Token:

// Generate anti-CSRF token and bind to Bearer Token session
function generateCSRFToken() {
  return crypto.randomBytes(32).toString('hex');
}

// Middleware to set CSRF cookie and validate
app.use((req, res, next) => {
  if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {
    return next();
  }
  
  const csrfToken = req.cookies._csrf || generateCSRFToken();
  res.cookie('_csrf', csrfToken, { httpOnly: true, secure: true });
  
  // Store mapping between Bearer Token and CSRF token
  const bearerToken = req.headers.authorization.substring(7);
  storeTokenMapping(bearerToken, csrfToken);
  
  next();
});

// Validate CSRF for state-changing operations
app.put('/api/profile', (req, res) => {
  const bearerToken = req.headers.authorization.substring(7);
  const csrfToken = req.cookies._csrf;
  
  if (!validateTokenMapping(bearerToken, csrfToken)) {
    return res.status(403).json({ error: 'CSRF validation failed' });
  }
  
  // Process request
});

4. Origin Verification:

// Verify request origin for Bearer Token operations
app.use((req, res, next) => {
  const allowedOrigins = [
    'https://yourdomain.com',
    'https://app.yourdomain.com'
  ];
  
  const origin = req.headers.origin || req.headers.referer;
  
  if (origin && !allowedOrigins.includes(origin)) {
    // For sensitive Bearer Token operations, reject requests from unexpected origins
    if (req.path.startsWith('/api/sensitive')) {
      return res.status(403).json({ error: 'Invalid origin' });
    }
  }
  next();
});

5. User Interaction Confirmation:

// Require additional confirmation for critical operations
function confirmCriticalAction(action, bearerToken) {
  return new Promise((resolve, reject) => {
    // Generate a one-time confirmation token
    const confirmationToken = crypto.randomBytes(16).toString('hex');
    
    // Store token temporarily (server-side)
    storeConfirmationToken(bearerToken, confirmationToken, action);
    
    // Show confirmation dialog (client-side)
    const confirmed = window.confirm(`Confirm ${action}? This cannot be undone.`);
    
    if (confirmed) {
      resolve(confirmationToken);
    } else {
      reject(new Error('Action cancelled'));
    }
  });
}

// API endpoint using confirmation
app.delete('/api/user', async (req, res) => {
  try {
    const confirmationToken = await confirmCriticalAction('account deletion', req.headers.authorization.substring(7));
    
    // Verify confirmation token server-side
    if (!verifyConfirmationToken(confirmationToken)) {
      return res.status(403).json({ error: 'Invalid confirmation' });
    }
    
    // Proceed with deletion
    await deleteUser(req.user.id);
    res.json({ success: true });
    
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Frequently Asked Questions

Can clickjacking steal my Bearer Tokens?
Clickjacking itself doesn't directly steal Bearer Tokens from localStorage or cookies. However, it can trick users into performing actions that use their existing Bearer Tokens in unintended ways. The real danger is that clickjacking can cause users to unknowingly make API calls that delete data, transfer funds, or change account settings—all while authenticated with their Bearer Token.
Why do I need both X-Frame-Options and CSP headers?
X-Frame-Options is supported by older browsers and provides basic frame protection, while Content-Security-Policy's frame-ancestors directive is the modern standard. Using both ensures compatibility across different browsers and versions. Some attacks might bypass one header but not the other, so defense in depth is recommended.