Skip to content
DevMeme

EU Age Verification App Hacked in Under 2 Minutes — Meme Explained

EU Age Verification App Hacked in Under 2 Minutes
View this meme on DevMeme →

Level 1: A Lock With the Key Taped to the Door

Imagine a house with a fancy keypad lock on the front door. It looks secure — there's a PIN, there's even a little "fingerprint required" sticker. But the keypad keeps the correct code, the try-counter, and the "fingerprint on/off" switch all written on a sticky note taped to the outside of the door. Anyone walking up can read the note, flip the fingerprint switch to "off," erase the code, type their own, and walk in — and the house still insists they're the rightful owner. That's the joke: the security looked real, but every lock in it could be reset by the person it was supposed to keep out.

Level 2: What shared_prefs Actually Is

A few terms make the joke land. shared_prefs is Android's lightweight storage for app settings: small XML files under the app's private data directory. "Private" only means other apps can't read it on a non-rooted device — the app itself, the OS owner, anyone with a device backup, and any rooted/emulated environment can read and edit it freely. So putting a security secret there and assuming nobody can touch it is the client_side_security trap.

Encrypt vs. hash: encryption is a two-way door (you can lock and unlock with a key), while hashing is a one-way door (you can check a match but never recover the original). For verifying a PIN, you want the one-way door. The PinEnc/PinIV names (Enc = ciphertext, IV = initialization vector, the random starter value a cipher needs) reveal the app chose the two-way door.

Rate limiting normally means "after N wrong tries, slow down or lock out." That decision has to be enforced somewhere the attacker can't edit — a server, or at minimum tamper-resistant storage. Here it's a counter in the same editable file, so the attacker resets it to 0 and brute-forces forever. This is the kind of thing you only internalize after your first "wait, the client can just lie to us?" production incident.

Level 3: The Client Is Not Your Friend

The post is a screenshot of an X thread from Paul Moore - Security Consultant (@Paul_Reviews), opening with the line:

Hacking the #EU #AgeVerification app in under 2 minutes.

What follows is a checklist of the oldest mistake in application security: trusting the client. The app, by his account, takes the PIN you create during setup, encrypts it, and drops it into Android's shared_prefs directory — a per-app folder of XML key/value files that the app owner (and anyone with root, a backup extraction, or a rooted/emulated device) can read and rewrite at will. That single design choice is the whole vulnerability, and everything else is just consequences.

The first sin he names is conceptual:

  1. It shouldn't be encrypted at all - that's a really poor design.

He's right, and the distinction matters. A PIN you need to verify should never be recoverable — you store a salted hash (ideally via a slow KDF like scrypt, bcrypt, or Argon2), then hash the user's input and compare. Encryption is reversible by definition; it implies the system can get the plaintext PIN back, which is exactly what you don't want for an authenticator. Choosing encrypt over hash here is the hash_vs_encrypt confusion that haunts junior and government-contractor code alike.

The second sin is architectural:

  1. It's not cryptographically tied to the vault which contains the identity data.

This is the killer. The PIN and the identity vault are stored as independent blobs. Because nothing binds them — no key derived from the PIN that actually decrypts the vault — the PIN is decorative. The attack he describes is brutally simple: delete the PinEnc/PinIV values from shared_prefs, restart, set a brand-new PIN, and the app cheerfully re-attaches the old profile's verified credentials to your new PIN and presents them as valid. The proof of "I am a verified adult" is now portable to anyone holding the file.

Then come the Other issues:, which read like a satire of security_theater:

  1. Rate limiting is an incrementing number in the same config file. Just reset it to 0 and keep trying.
  2. "UseBiometricAuth" is a boolean, also in the same file. Set it to false and it just skips that step.

A rate limiter that lives in a file the attacker controls isn't a rate limiter; it's a polite suggestion. A UseBiometricAuth boolean in attacker-writable storage means the "biometric protection" is one false away from oblivion. The systemic pattern is client-side-only enforcement of trust decisions — the canonical anti-pattern. Any control that protects the server's trust in the client must be validated server-side, because the client device is, in threat-model terms, fully hostile. Government identity software gets a pass on this far too often: procurement rewards shipping over review, the people who write the spec rarely threat-model, and "it has encryption" satisfies a compliance checkbox without anyone asking what the encryption is bound to. The brutal irony is that this is an age verification app — a system whose entire reason to exist is gatekeeping — built so that the gate swings open if you delete two strings.

Comments (35)

  1. Anonymous

    Storing the PIN, the rate limiter, and the 'UseBiometricAuth' flag in one client-side file isn't defense in depth - it's a settings menu for the attacker

  2. Anonymous

    Nothing says defense in depth like a vault guarded by `UseBiometricAuth=false`.

  3. dev_meme

    *they actually name it to be just a demo and every member state must do their own but cmon

  4. @deimossos

    To be fair, its hard to do mass surveillance, without having some security vulnerabilities maybe some information just shouldn't be collected...

  5. @ZmEYkA_3310

    W age verification 🤯🤯

  6. @soreiut

    So, an attacker can simply remove the PinEnc/PinIV values from the shared_prefs file and restart the app. You need root to access shared pref

  7. @RiedleroD

    there's an age verification app?

  8. @akiyiwen

    Nothing is secure if others can have your device physically...

  9. @SamsonovAnton

    If an attacker is that smart, he surely passes the age verification.

  10. @azizhakberdiev

    cmon, didn't lucky patcher teach us not to trust edge device side data?

  11. @azizhakberdiev

    also while we are at it, kids are very smart

  12. @azizhakberdiev

    even if it gets them +0.001 robux they will hack it

  13. @Sun_Serega

    I'm actually excited for more ability to verify the age, especially from EU side it can't possibly be worse than for-profit american options like persona and being able to exclude kids from my vrchat lobbies is a big + not excited for it being mandated in basic communication apps tho...

  14. @Sun_Serega

    and yeah, there is no age verification, but there are all the tools to implement your own for your own lobbies

  15. @nyxiereal

    What the fuck shared_preferences sure is easier to use than secure_storage, but it's not that hard

  16. @YaroST12

    Is the CN still not up on that post? He's showing off the vulnerability with a rooted device, you cant "simply remove" something from shared_prefs. If you can do that - you've blown open most, if not all, of Android's security measures.

  17. @NaNmber

    A post from Durov on the matter 🙂 https://t.me/durov/491

  18. @sandor73

    ok but you still need physical access to the device

Join the discussion →

Related deep dives