CWE-347, Improper Verification of Cryptographic Signature, covers a product that does not verify, or incorrectly verifies, the cryptographic signature for data. A signature is only a guarantee if something checks it, and checks it correctly.
It affects update systems, token handling, package managers, document signing, and any code that says "this is signed, so trust it."
How it happens
There are a few ways to get here, all of them common.
The first is not verifying at all. The code reads a field that says "signature" and moves on, or the verification step was disabled for testing and shipped that way.
The second is verifying the wrong thing. The signature covers part of the message, and the code uses the rest. The third is accepting what the attacker chooses. In token formats, for example, the data itself can announce which algorithm to use, including "none." A verifier that obeys the token has handed the decision to the attacker.
The fourth is trusting the wrong signer. A signature is valid, but the key was never confirmed to belong to the party the code thinks it does.
MITRE's record includes older examples of the same family. CVE-2002-1796 describes a product that did not properly verify signatures for "trusted" entities. CVE-2005-2181 and CVE-2005-2182 describe insufficient verification that allowed spoofing.
What an attacker gains
MITRE's consequence statement is direct: an attacker could gain access to sensitive data and possibly execute unauthorized code. The listed impacts include gaining privileges or assuming identity, modifying application data, and executing unauthorized code or commands. If the data is a login token, the attacker becomes a user. If it is an update, the attacker's code runs.
How to find it
MITRE notes that automated static analysis can find some instances of this weakness by analyzing source code. Do these checks by hand as well.
- Search for the verification call. Is it present on every path that accepts signed data?
- Look for flags that turn verification off, in code and in configuration, and check they are off in production builds.
- Check that the algorithm and key are fixed by your code, not selected by the incoming data.
- Test with data that has been tampered with after signing. Verification must fail.
- Test with data signed by the wrong key. Verification must fail.
Tamper tests are the single most valuable check. A verification routine that has never been seen to reject anything may not work.
How to fix it
MITRE lists no mitigations in the record. The following steps are standard practice.
- Use a maintained library for verification. Do not write your own signature code.
- Pass the expected algorithm and key into the verifier. Never take them from the data.
- Verify before parsing or acting, and use only the bytes that were covered by the signature.
- Fail closed. Any error, missing signature or unknown key means reject.
- Control which keys are trusted, and handle rotation and revocation.
A Python example with the PyJWT library. The vulnerable version reads the payload without checking the signature:
import jwt
# Vulnerable: signature is not checked at all
claims = jwt.decode(token, options={"verify_signature": False})
if claims.get("admin"):
grant_admin()
The fixed version pins the algorithm and supplies the key:
import jwt
# Fixed: algorithm and key come from our code
claims = jwt.decode(token, PUBLIC_KEY, algorithms=["RS256"])
if claims.get("admin"):
grant_admin()
Because the algorithm list is fixed in code, a token that claims some other algorithm is rejected. A bad signature raises an exception, so wrap the call and treat any exception as a failed login.
Where this meets your dependencies
Signature checks also protect the software you pull in. Packages and updates should be verified, and a library with a known signature bug in its own verification code is an easy target. The free scan at gold.safeguard.sh/scan checks a dependency file in your browser with no login. Your file stays on your machine, up to 100 dependencies.
Quick check
Why should the algorithm never be taken from the token?
Because the attacker writes the token. If the verifier follows the algorithm named inside it, the attacker can choose a weaker one or none. Fix the algorithm in code.
What is the best test for a signature check?
Alter signed data after signing, and sign data with a wrong key. Both must be rejected. If they are not, the check is not working.
What can an attacker do if signature checks fail, according to MITRE?
Gain access to sensitive data and possibly execute unauthorized code, including gaining privileges and modifying application data.
Sources
- MITRE CWE-347: https://cwe.mitre.org/data/definitions/347.html