Clickjacking with Basic Auth
How Clickjacking Manifests in Basic Auth
Basic Access Authentication (Basic Auth) relies on the browser automatically sending an Authorization: Basic … header with every request to a protected realm once the user has entered valid credentials. The credentials are cached by the browser for the duration of the session, so any subsequent request to the same origin—including requests initiated from a framed page—will include those credentials unless the user explicitly logs out.
Clickjacking (UI redressing) exploits this behavior by loading the target API endpoint inside an invisible or decoy iframe and tricking the user into interacting with it. Because the browser will automatically attach the Basic Auth credentials, the framed request is processed as if the user had intentionally invoked the endpoint. Typical attack patterns include:
- State‑changing actions via GET or POST: An attacker frames a URL like
https://api.example.com/users/123/passwordthat accepts a POST to change a password. A seemingly harmless button on the attacker’s page is positioned over the invisible iframe; when the user clicks the button, the browser sends the POST with the cached Basic Auth header, allowing the attacker to change the victim’s password without their knowledge. - Data exfiltration through side‑effects: Some APIs return sensitive data in the response body of a GET request (e.g.,
/profile). By framing the endpoint and using CSS to make the iframe visible only under the attacker’s control (e.g., overlaying a fake UI), the attacker can lure the user into clicking a "Submit" button that actually triggers the GET, leaking the response into the attacker’s domain via techniques like cross‑origin reads throughwindow.nameoriframe.contentWindowwhen the response is HTML‑injectable. - Credential harvesting via fake auth dialogs: Although the browser’s native Basic Auth dialog cannot be directly framed, attackers can overlay a fake login form that mimics the dialog. When the user enters credentials, they are sent to the attacker’s server, while the real Basic Auth request proceeds in the background, giving the attacker both the credentials and a valid session.
These attacks are possible because Basic Auth does not tie authentication to a specific browser state like a cookie with SameSite restrictions; the credentials are sent on every request, making the endpoint vulnerable to both CSRF and clickjacking when lacking proper frame‑protection headers.
Basic Auth-Specific Detection
middleBrick’s unauthenticated black‑box scan includes two complementary checks that together reveal a clickjacking exposure in a Basic Auth‑protected API:
- Authentication header detection: The scanner issues a request without credentials and examines the response. If it receives a
401 Unauthorizedwith aWWW‑Authenticate: Basic realm="…"header, middleBrick flags the endpoint as using Basic Auth. - Frame‑protection header analysis: For the same endpoint, middleBrick checks for the presence of either
X‑Frame‑Options(with values DENY, SAMEORIGIN, or ALLOW‑FROM) or aContent‑Security‑Policyheader containing aframe‑ancestorsdirective. Absence of both headers results in a finding labeled "Missing clickjacking protection".
When both conditions are true—Basic Auth is in use and no frame‑protection header is present—middleBrick correlates them into a higher‑severity finding, noting that an attacker could frame the API and leverage the automatically supplied Basic Auth credentials to perform unauthorized actions.
The scanner also records the HTTP verb and path of the endpoint, allowing the report to highlight which specific routes (e.g., POST /orders, PUT /settings) are at risk. This information is presented in the detailed findings table, complete with severity rating, description, and remediation guidance.
Because the scan is agentless and requires only a URL, developers can run middleBrick via the CLI (middlebrick scan https://api.example.com) or through the GitHub Action to catch this issue early in the CI/CD pipeline.
Basic Auth-Specific Remediation
Mitigating clickjacking on Basic Auth endpoints involves two layers: preventing the API from being framed and ensuring that state‑changing actions cannot be triggered solely by the automatically supplied credentials.
1. Add frame‑protection headers
The simplest and most effective defense is to instruct the browser not to allow the resource to be embedded in frames. This can be done globally or per‑route.
// Express.js example
const express = require('express');
const app = express();
// Global middleware – deny framing for all responses
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
// Alternative: CSP frame‑ancestors
// res.setHeader('Content-Security-Policy', "frame-ancestors 'self'";
next();
});
app.get('/api/data', (req, res) => {
// Basic Auth is handled by express‑basic‑auth or similar middleware
res.json({ message: 'sensitive data' });
});
app.listen(3000);
In Python/Flask the same effect is achieved with a after_request hook:
from flask import Flask, request, Response
from functools import wraps
app = Flask(__name__)
def check_auth(username, password):
return username == 'admin' and password == 'secret'
def authenticate():
return Response(
'Could not verify your access level for that URL.
',
401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}
)
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
@app.after_request
def add_frame_headers(response):
response.headers['X-Frame-Options'] = 'DENY'
# Or use CSP:
# response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
return response
@app.route('/api/transfer', methods=['POST'])
@requires_auth
def transfer():
# Process money transfer
return {'status': 'ok'}
if __name__ == '__main__':
app.run(port=5000)
2. Require an additional CSRF token for state‑changing operations
Even with framing blocked, defense‑in‑depth suggests adding a CSRF token that must be supplied via a custom request header (e.g., X‑CSRF‑Token) for any endpoint that modifies state. Because Basic Auth does not rely on cookies, the token can be stored in a server‑side session or derived from a short‑lived secret known only to the legitimate client.
// Express middleware to validate CSRF token
function csrfProtection(req, res, next) {
if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
return next();
}
const token = req.get('X-CSRF-Token');
if (!token || token !== req.session.csrfSecret) {
return res.status(403).send('Invalid CSRF token');
}
next();
}
app.use(csrfProtection);
// When rendering a form, embed the token
app.get('/form', (req, res) => {
req.session.csrfSecret = Math.random().toString(36).substring(2);
res.send(`
`);
});
By combining X‑Frame‑Options (or CSP) with a CSRF token requirement, you eliminate the clickjacking vector: the browser will refuse to embed the endpoint, and even if an attacker could somehow bypass framing protection, the missing or incorrect CSRF token will cause the request to be rejected.
These fixes rely only on standard HTTP features and widely available middleware—no agents, no configuration changes beyond adding headers or token validation, and they work with any language or framework that supports Basic Auth.