Session Fixation with Hmac Signatures
How Session Fixation Manifests in Hmac Signatures
Session fixation occurs when an attacker forces a user to accept a known session identifier, then later uses that identifier to hijack the user’s authenticated session. When HMAC‑based signatures are used to protect session tokens (for example, signing a cookie value with a secret key), the vulnerability can appear if the server accepts a client‑provided token, verifies its HMAC, and then treats the token as valid without issuing a new identifier after login.
Consider a typical pattern in a Node.js API that signs a session ID:
const crypto = require('crypto');
function signSessionId(id, secret) {
return crypto.createHmac('sha256', secret).update(id).digest('hex');
}
// Middleware that trusts any cookie with a valid HMAC
function sessionMiddleware(req, res, next) {
const cookie = req.headers.cookie;
const match = cookie && cookie.match(/session=([^;]+)/);
if (match) {
const [providedId, providedSig] = match[1].split('.');
const expectedSig = signSessionId(providedId, process.env.SESSION_SECRET);
if (providedSig === expectedSig) {
req.sessionId = providedId; // <-- fixation point
return next();
}
}
res.status(401).send('Invalid session');
}
If an attacker can set the session cookie before the victim logs in (e.g., via a cross‑site scripting payload or a crafted link), the victim’s browser will send that known identifier. After login, the server still sees the same req.sessionId because it never replaces the identifier. The attacker, who knows the original identifier, can now reuse the valid HMAC‑signed token and gain access to the victim’s account.
This pattern maps to OWASP API Security Top 10 A2: Broken Authentication, and has been observed in real‑world flaws such as CVE‑2020-13943 (Apache Tomcat session fixation) where the server accepted a user‑supplied JSESSIONID without regeneration.
Hmac Signatures-Specific Detection
Detecting session fixation in HMAC‑protected sessions requires checking whether the server ever issues a new session identifier (and consequently a new HMAC) after a successful authentication event. middleBrick’s unauthenticated black‑box scan looks for the following indicators:
- The API accepts a session token presented in a cookie, header, or query parameter and verifies its HMAC without requiring any additional proof of possession (e.g., a password or OAuth token).
- After a login endpoint (
/auth/login) returns a success status, the response does not contain aSet‑Cookieheader with a new session value, nor does it return a freshly signed token in the body. - The same token value that was sent in the request continues to be accepted in subsequent authenticated calls.
For example, middleBrick will send a request to GET /profile with a cookie session=attacker123.abcdef.... If the API responds with 200 OK and the same cookie is still valid after a POST to /login with valid credentials, the scanner flags a potential session fixation.
The finding includes:
| Property | Value |
|---|---|
| Category | Broken Authentication |
| Severity | High |
| Evidence | Pre‑login token accepted post‑login without regeneration |
| Remediation Guidance | Generate a new session identifier and HMAC after successful authentication. |
Because middleBrick performs the check without needing any credentials or agents, it can be run from the CLI, a GitHub Action, or directly inside an IDE via the MCP Server.
Hmac Signatures-Specific Remediation
The fix is to ensure that, after a user proves their identity, the server discards any client‑provided session identifier and creates a fresh one. The new identifier must be signed with the HMAC secret and transmitted to the client (usually via a Set‑Cookie header or response body).
Below is a corrected Node.js example that regenerates the session identifier after login:
const crypto = require('crypto');
function signSessionId(id, secret) {
return crypto.createHmac('sha256', secret).update(id).digest('hex');
}
function generateSessionId() {
return crypto.randomBytes(16).toString('hex');
}
// Login handler
app.post('/login', (req, res) => {
const { username, password } = req.body;
// … verify credentials …
if (valid) {
const newId = generateSessionId();
const sig = signSessionId(newId, process.env.SESSION_SECRET);
const token = `${newId}.${sig}`;
res.setHeader('Set‑Cookie', `session=${token}; HttpOnly; SameSite=Strict`);
return res.json({ msg: 'logged in' });
}
res.status(401).send('Invalid credentials');
});
// Session verification middleware (unchanged)
function sessionMiddleware(req, res, next) {
const cookie = req.headers.cookie;
const match = cookie && cookie.match(/session=([^;]+)/);
if (match) {
const [id, sig] = match[1].split('.');
if (sig === signSessionId(id, process.env.SESSION_SECRET)) {
req.sessionId = id;
return next();
}
}
res.status(401).send('Invalid session');
}
Key points:
generateSessionId()creates a cryptographically random identifier that the attacker cannot predict.- The login endpoint always issues a
Set‑Cookieheader with the new HMAC‑signed token, overriding any old value. - The verification middleware remains the same; it will reject the old token because its signature no longer matches the newly issued secret‑derived value.
Similar patterns apply in other languages:
import hmac, hashlib, os, secrets
SECRET = os.environ['SESSION_SECRET'].encode()
def sign_session_id(session_id):
return hmac.new(SECRET, session_id.encode(), hashlib.sha256).hexdigest()
def generate_session_id():
return secrets.token_urlsafe(16)
@app.route('/login', methods=['POST'])
def login():
# validate username/password …
new_id = generate_session_id()
token = f'{new_id}.{sign_session_id(new_id)}'
resp = make_response(jsonify({'msg': 'logged in'}))
resp.set_cookie('session', token, httponly=True, samesite='Strict')
return resp
@app.before_request
def verify_session():
token = request.cookies.get('session')
if token:
try:
uid, sig = token.split('.')
if hmac.compare_digest(sig, sign_session_id(uid)):
request.session_id = uid
return
except ValueError:
pass
return jsonify({'error': 'Invalid session'}), 401
Deploying the fixed code and rescanning with middleBrick (via middlebrick scan https://api.example.com, the GitHub Action, or the MCP Server) will show the session fixation finding resolved and the security score improved.