Session stays valid after an account is deleted
CWE-613CWE-285OWASP A01:2021Updated September 4, 20265 min read
If an account is blocked or deleted while the user is logged in, the existing session keeps working in many applications. The authorisation check looks at the session rather than at the current status of the account. A departed employee or a deactivated attacker thereby keeps access until the session happens to expire.
Blocking an account feels like a definitive act: the switch is flipped, access is withdrawn. For anyone who is not logged in at that moment, that is accurate. For anyone who does hold an active session, nothing changes at all in many applications. Here is why, and why this is an access control problem rather than a session problem.
What goes wrong when an account is deleted?
At login, the application establishes who someone is and whether they may have access. That outcome is recorded in the session. On every subsequent request only the session is consulted: if it contains a user number, the user is logged in.
When the session stays valid after an account is deleted, that reasoning has never been interrupted. On the second and every following request, the application no longer asks whether this account still exists, is still active and still holds the same rights. It trusts a decision taken at an earlier moment.
It is like a visitor badge that is checked on entry and never again. If the visitor is denied access along the way, the doors should know about it. With a system that checks only at the entrance, they simply walk on.
How is a session that survives account deletion exploited?
Vulnerable:
// The session is the only source of truth
function currentUser(req) {
if (!req.session.userId) return null;
return {
id: req.session.userId,
role: req.session.role, // recorded at login
};
}
app.delete('/admin/users/:id', async (req, res) => {
await users.remove(req.params.id);
res.send('User deleted'); // sessions left untouched
});
The administrator deletes the account and gets a confirmation. The row is gone from the user table and the name no longer appears in the overview. But that user’s session still exists, with the user number and the role in it. Their next request goes through without any obstacle:
GET /cases/export HTTP/1.1
Host: portal.example
Cookie: sid=8f42c19ade7b3f5c
HTTP/1.1 200 OK
The user no longer exists and still has access. For an employee who has left on the spot, that is exactly the window in which most damage is done.
Safe:
// The current status is fetched on every request
async function currentUser(req) {
if (!req.session.userId) return null;
const user = await users.find(req.session.userId);
if (!user || user.status !== 'active') {
await req.session.destroy();
return null;
}
return user; // including this moment's rights
}
app.delete('/admin/users/:id', async (req, res) => {
await users.block(req.params.id);
await sessions.removeForUser(req.params.id); // clean up actively
await tokens.revoke(req.params.id);
await auditlog.write('account blocked', { by: req.user.id });
res.send('User blocked and sessions ended');
});
Two things now happen that reinforce each other. The authorisation check consults the current status rather than the stored snapshot, which means rights changed in the meantime take effect immediately too. And on blocking, existing sessions and tokens are actively cleaned up, so access ends straight away instead of at the next request.
What is the impact of a session that survives account deletion?
The severity runs from medium to high, depending on who is blocked and why. For a routine cleanup of a dormant account the effect is slight. For a forced departure, a suspicion of fraud or a compromised account, this is the moment the measure has to work.
The scenario that occurs most often is a departing employee. HR reports the departure, the administrator blocks the account, and the process is regarded as finished. If the session on that employee’s laptop stays valid, they still have hours or days of access to customer data, documents and systems, in precisely the period the organisation believes access has been withdrawn.
More awkward still is the second scenario: an account is compromised, this is discovered, and the account is blocked as the first incident measure. If the attacker keeps their session, the response to the incident has in fact failed while everyone believes the problem is contained. That makes this finding not only a technical risk but an organisational one: it undermines confidence in your own measures.
How do you detect a session that survives account deletion?
The test requires two accounts. A tester logs in as the target user, leaves that session open, and blocks or deletes the account from an administrator account. Then a request is sent to a protected route with the first session. If it still works, the finding is demonstrated.
The same setup is repeated for variants that are often implemented separately: withdrawing a role, changing a group membership, disabling an account in a central directory, and the expiry of a licence or subscription. In practice it regularly turns out that blocking does work but withdrawing rights does not, or the other way around. Testers also check whether tokens for API access, mobile applications and integrations are included, because those are frequently forgotten in the cleanup. AssistSec explicitly tests the time span involved: how long does the old situation remain in force, and is that window acceptable for the kind of data behind the application.
How do you prevent a session that survives account deletion?
- Fetch the user’s status and rights on every request instead of freezing them in the session.
- Actively remove all associated sessions and tokens when an account is blocked or deleted.
- Revoke API keys, refresh tokens and mobile application connections as well.
- Always let a missing user or role lead to refusal, never to admission.
- Use a short cache for the account status if performance matters, and clear that cache on a change.
- Apply the same approach to withdrawing roles and group memberships, not only to blocking the whole account.
- With federated authentication, refresh the status periodically rather than only at login.
- Record blocks and session terminations in an audit log, so it is demonstrable afterwards when access really ended.
Sources
Frequently asked questions
Why is this access control rather than session management?
Because the core issue is not that the session lives too long, but that the authorisation decision rests on outdated data. The application trusts what was true at login instead of what is true now. That is exactly what broken access control means.
Do I have to query the database on every request?
Not necessarily in raw form. A short cache of a few seconds to a minute is usually an acceptable compromise, provided you clear that cache the moment an account is blocked. Performance stays reasonable without the status lagging for long.
Does this apply to withdrawing rights too?
Yes, and it goes wrong there even more often. If a role is read from the session or from a token instead of being looked up on every request, a user keeps their old rights until the session expires. When an administrator role is withdrawn, that is an immediate security problem.
What if accounts are managed in a central directory?
Then the application has to refresh the status there periodically or be notified of changes. A connection that only fetches the status at login leaves precisely this gap. With federated authentication, a short token lifetime is therefore extra important.
Related articles
- VulnerabilitiesCWE-284A01:2021Broken access controlBroken access control explained: horizontal and vertical privilege escalation, forced browsing, and how deny by default fixes it server-side.
- VulnerabilitiesCWE-613A07:2021JWT stays valid after logoutA JWT is valid until its expiry and takes no notice of logging out. Learn how to revoke tokens without losing the benefits.
- VulnerabilitiesCWE-269A01:2021Privilege escalationPrivilege escalation explained: how attackers gain admin rights through a client-trusted role field or an unprotected admin route, and how to stop it.
- VulnerabilitiesCWE-613A07:2021Session stays valid after logoutA logout that only clears the cookie leaves the token intact on the server. Learn how a captured session then simply keeps working.
- VulnerabilitiesCWE-613A07:2021Sessions do not expireA session without an expiry stays usable forever. Learn why that makes stolen tokens valuable and how to set a sensible timeout.