Out Of Bounds Read with Bearer Tokens
How Out Of Bounds Read Manifests in Bearer Tokens
Out Of Bounds Read (OOB Read) vulnerabilities in Bearer Token implementations occur when applications access memory beyond the allocated boundaries of token data structures. In Bearer Token contexts, this typically manifests when parsing JWTs (JSON Web Tokens) or similar token formats without proper bounds checking.
Consider a common Bearer Token validation scenario where a server extracts claims from a JWT. If the implementation uses unsafe string operations or buffer handling without validating token length, an attacker can craft a token that causes the parser to read memory it shouldn't access:
func validateBearerToken(tokenString string) (*Claims, error) {
// Vulnerable: no length validation before parsing
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, keyFunc)
// If tokenString is malformed or too short,
// the parser may read beyond allocated memory
if err != nil {
return nil, err
}
return token.Claims.(*Claims), nil
}
The vulnerability becomes critical when libraries perform unsafe memory operations. For example, a JWT library might use C-based implementations where string copying without bounds checking can lead to OOB reads:
// Vulnerable C implementation in a JWT library
int extract_header(const char *token, char *header, size_t header_size) {
const char *dot = strchr(token, '.');
if (!dot) return -1;
size_t header_len = dot - token;
// OOB Read: no validation that header_len <= header_size
strncpy(header, token, header_len);
return 0;
}
Another Bearer Token-specific manifestation occurs in Base64 URL decoding. JWTs use URL-safe Base64 encoding for header and payload segments. If a library doesn't properly validate padding and length before decoding, malformed tokens can trigger OOB reads:
// Vulnerable Node.js JWT verification
function verifyJWT(token) {
const parts = token.split('.');
if (parts.length !== 3) return false;
// OOB Read potential: no validation of Base64 string length
const header = Buffer.from(parts[0], 'base64').toString();
const payload = Buffer.from(parts[1], 'base64').toString();
// Malformed Base64 with insufficient padding can cause buffer over-read
return verifySignature(parts[2], header, payload);
}
Time-based attacks also exploit OOB reads in Bearer Token implementations. When comparing timestamps or expiration claims, naive implementations might read beyond token boundaries:
# Vulnerable Python JWT validation
import jwt
def validate_expiration(token_bytes):
token = jwt.decode(token_bytes, verify=False)
# OOB Read: assuming 'exp' claim exists without validation
exp = token['exp'] # If token is truncated, this may read beyond bounds
if time.time() > exp:
return False
return True
Bearer Tokens-Specific Detection
Detecting Out Of Bounds Read vulnerabilities in Bearer Token implementations requires both static analysis and dynamic testing. The most effective approach combines automated scanning with manual code review of token parsing logic.
middleBrick's black-box scanning approach is particularly effective for Bearer Token OOB Read detection. The scanner sends crafted malformed tokens to your API endpoints and monitors for memory access violations or crashes:
# Using middleBrick CLI to scan Bearer Token endpoints
npm install -g middlebrick
middlebrick scan https://api.example.com/auth/validate \
--token-type jwt \
--max-length 2048 \
--malformed-tokens true
middleBrick tests specifically for Bearer Token OOB Read patterns by:
- Sending tokens with truncated Base64 segments to trigger decoding errors
- Crafting JWTs with missing claims to test boundary conditions
- Submitting tokens with excessive length to test buffer overflows
- Testing malformed JSON in token claims
Manual detection should focus on these Bearer Token-specific patterns:
// Code review checklist for Bearer Token OOB Read
function reviewBearerTokenCode(code) {
const issues = [];
// Check for unsafe string operations
if (code.includes('strncpy') || code.includes('memcpy')) {
issues.push('Unsafe memory copy operations');
}
// Check for missing length validation
if (code.includes('jwt.decode(') && !code.includes('length validation')) {
issues.push('Missing token length validation');
}
// Check for unsafe Base64 decoding
if (code.includes('Buffer.from(') && code.includes('base64')) {
issues.push('Potential Base64 decoding vulnerabilities');
}
return issues;
}
Dynamic testing should include fuzzing Bearer Token inputs with tools like burp intruder or custom scripts:
import requests
import jwt
import string
import random
def fuzz_bearer_token(endpoint, valid_token):
headers = {'Authorization': f'Bearer {valid_token}'}
# Test truncated tokens
for i in range(1, len(valid_token)):
truncated = valid_token[:i]
headers['Authorization'] = f'Bearer {truncated}'
response = requests.get(endpoint, headers=headers)
if response.status_code == 500:
print(f'Potential OOB Read: truncated to {i} chars')
# Test Base64 padding variations
for padding in ['', '=', '==', '===']:
modified = valid_token + padding
headers['Authorization'] = f'Bearer {modified}'
response = requests.get(endpoint, headers=headers)
if response.status_code == 500:
print(f'Potential OOB Read: padding {padding}')
Bearer Tokens-Specific Remediation
Remediating Out Of Bounds Read vulnerabilities in Bearer Token implementations requires a defense-in-depth approach with proper input validation, safe parsing libraries, and secure coding practices.
First, implement strict length validation before any token processing:
// Secure Bearer Token validation with bounds checking
import (
"errors"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
)
const (
maxTokenLength = 2048
minTokenLength = 50
)
type Claims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
func ValidateBearerToken(tokenString string) (*Claims, error) {
// Length validation first
if len(tokenString) < minTokenLength || len(tokenString) > maxTokenLength {
return nil, errors.New("token length invalid")
}
// Validate Bearer prefix
if !strings.HasPrefix(tokenString, "Bearer ") {
return nil, errors.New("invalid bearer prefix")
}
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
// Use safe JWT parsing with explicit claims
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("your-secret-key"), nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, errors.New("invalid token claims")
}
// Validate required claims exist
if claims.Username == "" || claims.ExpiresAt == nil {
return nil, errors.New("missing required claims")
}
// Check expiration safely
if claims.ExpiresAt != nil && claims.ExpiresAt.Time.Before(time.Now()) {
return nil, errors.New("token expired")
}
return claims, nil
}
For Node.js applications, use well-maintained libraries with built-in bounds checking:
// Secure Bearer Token middleware using jsonwebtoken
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const MAX_TOKEN_LENGTH = 2048;
const MIN_TOKEN_LENGTH = 64;
function bearerTokenValidator(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'Missing Authorization header' });
}
const token = authHeader.replace('Bearer ', '');
// Length validation
if (token.length < MIN_TOKEN_LENGTH || token.length > MAX_TOKEN_LENGTH) {
return res.status(400).json({ error: 'Invalid token length' });
}
// Safe JWT verification with claim validation
jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
maxAge: '24h'
}, (err, decoded) => {
if (err) {
return res.status(401).json({ error: 'Invalid token' });
}
// Validate required claims
if (!decoded.sub || !decoded.exp) {
return res.status(401).json({ error: 'Missing required claims' });
}
req.user = decoded;
next();
});
}
For Python applications, implement safe Base64 decoding with bounds checking:
import jwt
import base64
import json
from datetime import datetime, timezone
def safe_base64_decode(s: str) -> bytes:
"""Safe Base64 URL decoding with bounds checking"""
try:
# Validate length first
if len(s) > 2048:
raise ValueError("Base64 string too long")
# Add padding if needed
missing_padding = len(s) % 4
if missing_padding:
s += '=' * (4 - missing_padding)
decoded = base64.urlsafe_b64decode(s)
# Additional validation: check if decoded data looks reasonable
if len(decoded) > 2048:
raise ValueError("Decoded data too large")
return decoded
except (base64.binascii.Error, ValueError) as e:
raise ValueError(f"Invalid Base64 data: {e}")
def validate_bearer_token(token: str) -> dict:
"""Validate Bearer Token with OOB Read protection"""
if not token or len(token) < 64 or len(token) > 2048:
raise ValueError("Invalid token length")
parts = token.split('.')
if len(parts) != 3:
raise ValueError("Malformed token")
# Safe decoding of header and payload
try:
header_bytes = safe_base64_decode(parts[0])
payload_bytes = safe_base64_decode(parts[1])
header = json.loads(header_bytes.decode('utf-8'))
payload = json.loads(payload_bytes.decode('utf-8'))
# Validate required claims
if 'exp' not in payload:
raise ValueError("Missing expiration claim")
# Check expiration safely
exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
if exp < datetime.now(timezone.utc):
raise ValueError("Token expired")
return payload
except (json.JSONDecodeError, ValueError) as e:
raise ValueError(f"Token validation failed: {e}")