Safeguard
Application Security•

CWE-603 Client-Side Authentication: Why the Server Must Decide

When the login check lives in client code, a modified client just skips it. Why authentication must happen on the server, with real examples from MITRE.

Safeguard Security Team
3 min read

CWE-603 describes a client and server product where authentication is performed in the client code but not in the server code. A modified client that leaves out the authentication check can then walk straight in.

MITRE lists it under ICS and operational technology platforms as well as general software, and the examples below show why that matters.

How it happens

A desktop app asks for a password, compares it to a value or a rule inside the program, and only then sends requests. The server answers every request it receives, because it assumes the client already did the checking.

# Client code
if entered_password != "s3cret-baked-into-the-app":
    exit("Access denied")
send_request(server, "list_all_users")
# Server code
def handle(request):
    return run(request)      # no identity, no session, no check

The record explains the consequence plainly. Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the mechanism to reach parts of the application that would otherwise be protected.

Two observed examples:

  • CVE-2022-33139: a SCADA system only used client-side authentication, allowing adversaries to impersonate other users.
  • CVE-2006-0230: a client-side check for a password allowed access to a server using crafted XML requests from a modified client.

What an attacker gains

The recorded consequence is a bypass of the protection mechanism and the ability to gain privileges or assume another identity. In an industrial setting, that can mean issuing commands as a legitimate operator.

How to find it

Treat the client as hostile and talk to the server without it.

  1. Capture the traffic between client and server.
  2. Replay a privileged request without any login step.
  3. If the server answers, authentication lives only in the client.

Reading the client, whether it is JavaScript, a mobile app, or a compiled binary, will often show the check in plain sight. Threat modelling the trust boundary on a diagram catches the design error before code exists.

How to fix it

MITRE's mitigation is one line, and it is the whole answer: do not rely on client side data, and always perform server side authentication.

  • The server verifies credentials, issues a session or token, and checks it on every request.
  • Authorization decisions are made server side from that verified identity.
  • Secrets do not ship inside client binaries, since they can be extracted.
  • A client prompt can stay for user convenience, as long as nothing depends on it for safety.
def handle(request):
    user = verify_session(request.headers.get("Authorization"))
    if user is None:
        return 401
    return run(request, user)

Quick check

Can obfuscating the client fix this? No. An attacker who controls the client can still skip or replay the calls. The check has to be on the server.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.