Before we get into it: this was a responsible disclosure engagement, and the vulnerability has since been patched. Because the target is still active and I have an obligation to protect them, I will be redacting a lot of identifying information throughout this write-up. Platform names, URLs, customer IDs, and anything else that could point back to the target have been removed or redacted. What I can share is the technical detail, and honestly, that is the interesting part anyway.
Also worth mentioning: this is not a fresh zero-day. I found this one a while back while hunting on a bug bounty program and sat on writing it up for longer than I probably should have. Figured it was finally time to share it.
Step One: The Exposed .git Directory
This one actually started with a message from a friend. He had stumbled across an exposed .git directory on a web application and reached out to see if I wanted to collab and dig into it together. I said yes without hesitating, because an exposed .git is rarely just an exposed .git.
We pulled the source using git-dumper, which reconstructs an entire codebase from an exposed .git folder even without direct file access to the underlying files. Within a short time we had the full source code of the application sitting locally in front of us.
We started reading through it together. It was a ThinkPHP application, a popular PHP framework. And it was while I was going through the account controller that I stopped and stared at my screen for a second. There was a route in there that should not have existed in a production application, let alone been reachable from the public internet.
The Vulnerable Route: A Superuser Login Shortcut
The route in question was /pc/account/sulogin. From the name alone you can guess what it does. "Su" as in superuser. It is a login-as-any-user function, and it was sitting there, completely reachable from the public internet.
The function accepted two query parameters from the URL: a customer ID (cid) and a computed key (authkey). Here is what it actually did under the hood:
$authkey = $_GET['authkey'];
$cid = $_GET['cid'];
if (md5('REDACTED' . $cid . date('Y-m-d')) == $authkey) {
$db = D('Customer');
$map['customer_id'] = $cid;
$user = $db->where($map)->find();
if (!empty($user)) {
session(null);
session('user', $user);
$this->redirect("account/myaccount");
}
} else {
echo "sulogin error";
}Let me walk through exactly what this code is doing, because it is important to understand why each line is a problem.
The first two lines pull the authkey and cid values directly out of the URL query string with no sanitisation whatsoever. Whoever is making the request controls both of these values entirely.
The if statement on line three is where the authentication is supposed to happen. It takes a hardcoded string prefix, concatenates the customer ID and today's date onto it, runs the whole thing through MD5, and checks whether the result matches the authkey you supplied. If it matches, the application considers you authenticated.
If that check passes, it queries the database for a customer record matching the supplied ID, clears any existing session data, and then writes that customer's full record directly into the session. At that point the application treats you as that user for every subsequent request. You are fully logged in.
The redirect at the end drops you straight into the account dashboard.
There is no password check. No second factor. No rate limiting. No IP restriction. Just that MD5 comparison.

Why This Was Trivial to Forge
The problem is that the authkey is not actually a secret. It is a deterministic value that anyone can compute themselves if they know the formula, and the formula was sitting right there in the source code.
The three inputs to the hash were a hardcoded string prefix visible in the source, the customer ID which was a sequential integer, and today's date which is obviously not secret information. That means any attacker who has read the source code, which they could do thanks to the exposed .git directory, already has everything they need to compute a valid authkey for any customer ID they choose.
This is the core of the vulnerability. The application was using MD5 as if it were a secret token, but MD5 is just a mathematical function. There is no secret key involved, no randomness, nothing that an attacker cannot independently reproduce. The whole check collapses the moment someone reads the source.
Proof of Concept
The steps to reproduce this were embarrassingly straightforward.
Step one: get a valid customer ID. These were sequential integers so they were trivially enumerable, but I used my own account ID to keep things clean.
Step two: compute the authkey. Open a PHP interactive shell with php -a and run the following:
$cid = "REDACTED";
$authkey = md5('REDACTED' . $cid . date('Y-m-d'));
echo $authkey;This gives you a 32 character hex string. That string is your forged session key, valid for the rest of the calendar day.
Step three: hit the endpoint. Take your customer ID and the hash you just computed, drop them into the URL as query parameters, and visit it in a browser.
Step four: you are now logged in as that user. No password prompt, no MFA, no friction. The application reads your forged authkey, validates it against the same formula, finds a match, pulls the user record from the database, and writes it into your session. From the application's perspective you authenticated successfully.
What an Attacker Could Actually Do With This
Once inside an account the attacker has the same privileges as the legitimate account holder. In this application that included:
Viewing personal information: full name, contact details, vehicle information, and purchase history.
Modifying account details: changing the registered email address or phone number, which could be used to permanently lock the real owner out of their own account.
Performing actions on the user's behalf: submitting requests, interacting with platform services, and potentially making bookings or purchases depending on what the platform exposes to logged in users.
Multiply that across every account on the platform and you have a full user base compromise. Not just a data leak, but full account takeover for every single registered user.

The Root Causes
There are a few things that went wrong here simultaneously, which is usually how these situations go.
The .git directory should never be publicly accessible. If your web server is serving your source code to anyone who asks, no amount of security in the code itself will save you. This is a deployment misconfiguration that web servers and CI/CD pipelines should be explicitly configured to prevent. Without the source code exposure this vulnerability would have been significantly harder to find and exploit.
Predictable, reconstructable tokens are not authentication. If the validity of a token can be computed by someone who does not already have privileged access, it is not providing any security. Proper implementation here would involve cryptographically random tokens tied to a server-side session, or at minimum a secret key that never appears in the source code.
Internal tooling needs to be properly access-controlled. Features built for internal or support use have a habit of outliving their intended deployment context. If a shortcut login function needs to exist at all, it should be gated behind IP allowlisting, admin authentication, or removed entirely from production builds.
MD5 is not suitable for security-sensitive operations. MD5 was not designed as a secure keyed hash function and should not be used as one. For anything security-sensitive, reach for HMAC with a proper secret key and a modern hash function like SHA-256.
Responsible Disclosure
I reported this to the security team promptly with full technical details and a working proof of concept. The vulnerability has since been patched.
If you are running a web application, take five minutes to check whether your .git directory is publicly accessible. You might be surprised what you find.
I hope you found this article informative and I wish you a lovely day ❤
Happy hacking!