Phishing Api Keys with Bearer Tokens
How Phishing Api Keys Manifests in Bearer Tokens
Attackers often target bearer tokens because they are the primary credential used to authorize API calls. When a token is leaked, the attacker can replay it until it expires or is revoked. Phishing techniques that lead to bearer‑token theft include:
- Fake OAuth consent screens: A phishing site mimics the legitimate authorization server, tricks the user into granting an OAuth client access, and captures the resulting access token returned in the redirect URI.
- Token‑in‑URL leakage: Some implementations place the bearer token in the query string (e.g.,
?access_token=…) for legacy reasons. A phishing page that logs referrer headers or uses JavaScript to readwindow.locationcan harvest the token. - Man‑in‑the‑middle via TLS stripping: If an API endpoint is reachable over HTTP (or misconfigured HSTS), an attacker can downgrade the connection and sniff the
Authorization: Bearer …header. - Malicious browser extensions: Extensions with access to all pages can read
localStorageorsessionStoragewhere SPA applications store bearer tokens and exfiltrate them to an attacker‑controlled server. - Credential stuffing leading to token reuse: After stealing username/password via a phishing login page, the attacker exchanges those credentials for a fresh bearer token via the token endpoint, effectively phishing the token indirectly.
These patterns appear in code paths that:
- Read the token from
request.headers.authorizationwithout validating its origin. - Store the token in client‑side storage accessible to any JavaScript.
- Include the token in URLs, logs, or error messages.
- Fail to enforce short lifetimes or refresh‑token rotation.
Bearer Tokens-Specific Detection
middleBrick’s black‑box scanner can surface bearer‑token phishing risks through several of its 12 parallel checks:
- Data Exposure: Scans HTTP responses for strings that match common token patterns (e.g., JWT
eyJ…, opaque base64 strings, or strings longer than 30 chars that appear in JSON bodies, headers, or error messages). If a token appears in a response body, header, or URL query parameter, the check flags it as a potential leakage vector. - Authentication: Verifies that endpoints requiring a bearer token actually validate the token’s signature, expiration, audience (
aud), and issuer (iss) claims. Missing or weak validation raises a finding. - Input Validation: Checks for reflected token values in query parameters or form fields that could be harvested via cross‑site scripting.
- Encryption: Confirms that the API is only reachable over TLS; any HTTP endpoint triggers a finding because tokens could be stripped.
- Rate Limiting: Detects absent or weak rate limits on token endpoints, which facilitates credential‑stuffing attacks that lead to token phishing.
Example of a finding middleBrick might report:
{
"check": "Data Exposure",
"severity": "high",
"description": "Bearer token found in JSON response body at /api/user/profile",
"evidence": "{\"access_token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…\"}",
"remediation": "Do not return access tokens in API responses. Use short‑lived tokens and transmit them only via the Authorization header over TLS."
}
To test locally with the CLI:
middlebrick scan https://api.example.com --output json
The CLI will return a JSON report that includes the above findings, enabling CI/CD pipelines to fail builds when a high‑severity token‑exposure issue is detected.
Bearer Tokens-Specific Remediation
Remediation focuses on reducing the attack surface for token phishing and ensuring that even if a token is intercepted, its usefulness is limited.
1. Issue short‑lived, signed tokens
Use JWTs with a short exp (e.g., 5‑15 minutes) and rely on a refresh‑token flow for longer sessions. Verify the signature, exp, nbf, aud, and iss on every request.
const jwt = require('jsonwebtoken');
function verifyBearer(req, res, next) {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return res.status(401).send('Missing token');
}
const token = auth.slice(7);
try {
const payload = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, {
audience: 'my-api',
issuer: 'https://auth.example.com'
});
req.user = payload;
next();
} catch (err) {
return res.status(401).send('Invalid token');
}
}
module.exports = verifyBearer;
2. Never place tokens in URLs, logs, or error messages
Strip tokens from outgoing logs and ensure they are not echoed back in error responses.
// Express middleware to sanitize logs
function sanitizeLogs(req, res, next) {
const originalWrite = res.write;
res.write = function(chunk, encoding) {
if (Buffer.isBuffer(chunk)) {
chunk = chunk.toString();
}
// Remove Authorization header from log output
chunk = chunk.replace(/Authorization:\s*Bearer\s+[^\s\r\n]+/gi, 'Authorization: Bearer [REDACTED]');
originalWrite.call(this, chunk, encoding);
};
next();
}
app.use(sanitizeLogs);
3. Enforce TLS and HSTS
Redirect all HTTP to HTTPs and include a strict HSTS header.
const helmet = require('helmet');
app.use(helmet({ hsts: { maxAge: 31536000, includeSubDomains: true, preload: true } }));
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
return res.redirect(301, `https://${req.header('host')}${req.url}`);
}
next();
});
4. Bind tokens to the client (optional)
If using OAuth 2.0, enable proof‑key for code exchange (PKCE) for public clients and consider token binding or mutual TLS for highly sensitive APIs.
5. Rotate and revoke refresh tokens
Store refresh tokens hashed in the database, issue a new refresh token on each use, and invalidate the previous one. This limits the window an attacker can use a stolen refresh token.
By applying these fixes, the risk of a phishing‑derived bearer token being reused is dramatically reduced, and any leaked token becomes short‑lived and useless after a brief period.