I first posted a rough write-up of these vulnerabilities to r/CBSE using a throwaway reddit account, but I figured a proper write-up on my own blog would be a better home for it. The tweet (X post) where this is being discussed can be found here.
These vulnerabilities were initially discovered on 25 February 2026 and were promptly reported to CERT-In.
What is CBSE and On-Screen Marking?
The Central Board of Secondary Education (CBSE) is one of the largest national education boards in India. It operates under the Government of India and runs major examinations like the Class 10 and Class 12 board exams for millions of students every year.
CBSE is affiliated with over 28,000 schools in India and several hundred more abroad, which makes it one of the most influential educational bodies in the country. Every year, millions of answer sheets are evaluated by thousands of teachers and examiners as part of the board exam process.
To streamline all of that, CBSE has started moving to a digital On-Screen Marking (OSM) system for the Class 12 board exams (circular). Instead of checking physical answer sheets, examiners log into an online portal where scanned copies of answer scripts are assigned to them for evaluation.
Because this platform is used by huge numbers of evaluators and handles sensitive academic data, its security really matters. It seems like this platform is developed by Coempt EduTeck Pvt Ltd and this same OnMark platform is used by multiple boards & other institutions.
While poking around, I found several critical vulnerabilities in the OSM portal that could lead to full account takeover of examiner accounts. Anyone exploiting these could also tamper with or disrupt the grading process, which directly threatens the integrity of the exam evaluations.
I reported all of this to CERT-In before publishing this blog.

Finding the Vulnerabilities
Some background on me first. I'm a hobbyist cybersecurity researcher and I just finished my Class 12 exams this year. I've done bug bounty and security work for fun before, so when CBSE rolled out OSM and I noticed the portal link was completely public, my curiosity got the better of me.
I opened the On-Screen Marking portal and started playing around with the HTTP requests and everything else I could see.
The login page asks for three things: a user ID, a school code, and a password, followed by an OTP step. Nothing about that screen looks unusual. The problems only showed up once I stopped looking at the page and started looking at the code behind it.
Reading the JavaScript Bundle
Like most modern single-page apps, the portal is an Angular application that ships its entire frontend logic in one bundled, minified JavaScript file. The browser downloads this file and runs it locally to render every screen of the app.
That bundle is served publicly at:
https://cbse.onmark.co.in/cbseevalweb/main.dc17c24606b3b008.js
Anyone can request it, logged in or not. So I pretty-printed it and started reading. What I found inside was horrible.
Vulnerability 1: A Hardcoded Master Password
Sitting in plain text inside the frontend bundle was a hardcoded master password. Not a hash, not a token reference, but the literal password string, baked directly into the client-side JavaScript that gets shipped to every visitor's browser.
The logic around it was worse than the leak itself. When this master password was entered into the login form, the app automatically filled the OTP field and bypassed the normal authentication flow entirely. There was no second factor to clear and no server-side check to satisfy. Entering the magic string was enough.
To log in as a specific examiner, all an attacker needs is:
- A target's user ID and school code, both of which are publicly obtainable.
- The master password, sitting in a JS file anyone can download.
With those, I was able to log in as an examiner (bypassing the OTP/2FA flow totally) and reach the evaluation dashboard, where I could view and edit marks.
Vulnerability 2: OTP Validation Done Entirely Client-Side
The OTP step turned out to be pure theatre. When you trigger authentication, the server sends the OTP back inside the auth response, and the JavaScript running in your browser compares what you typed against that value locally before letting you through.
Think about what that means. The secret you're supposed to prove you received is handed straight to your browser, and the browser grades its own test. Anyone watching the network tab can just read the OTP out of the response. And because the comparison happens in client-side code, you can skip the form altogether and simply tell the app the check passed.
A security control that runs on the attacker's machine isn't a control at all.

Vulnerability 3: No Route Guards, So the Whole App Is Walk-In
Even setting the login flow aside, the app's routing offered no protection. The entire Angular route configuration had zero canActivate guards. Routes like /dashboard, /profile, /evalscriptsview, /heallscripts, /evaluatordetails, and /verificationdashboard were all directly navigable.
The only thing standing between an anonymous visitor and an internal page was a default redirect to /login, and that's trivial to defeat. By seeding a few values into browser storage and navigating straight to a route, you land on any page you like:
localStorage.setItem('jwtToken', 'dev-token-12345');
sessionStorage.setItem('role_id', '23');
sessionStorage.setItem('ValType', 'Regular');
sessionStorage.setItem('eval', JSON.stringify({
user_id: 'DEV001',
role_id: '23',
mobile_no: '9999999999',
email: 'dev@test.com',
jwtToken: 'dev-token-12345'
}));
// Then navigate
window.location.href = '/cbseevalweb/#/dashboard';
Paste that into the browser console and you're dropped onto the dashboard, having never authenticated against anything. The token is fake, the user is invented, and the app doesn't care.
Vulnerability 4: Changing Any Password Without Knowing the Old One
This is where the individual bugs start combining into a full account takeover.
The "change password" feature collects an old password from the user, like you'd expect. But when I inspected the actual request it sends, the ChangePassword API payload only contained:
{ "ValuatorID": "...", "pin_NewPassword": "..." }
The oldpassword variable exists in the component, it's just never included in the request that goes to the server. The current password is never verified. Whatever ValuatorID you put in the body gets its password reset to whatever you choose.
On its own that's bad. Combined with the next issue, it's catastrophic.
Vulnerability 5: Systemic IDOR Across the Entire API
Almost every API call in the app identifies the acting user by reading ValuatorID / user_id straight out of sessionStorage["eval"], the same browser-storage object I showed editing above. The server trusts whatever ID the client sends instead of deriving it from the authenticated session.
That makes this an Insecure Direct Object Reference (IDOR) vulnerability at the architectural level. It's not one broken endpoint. Practically every POST request in the service is affected. Change the ID in storage and the app acts as that user for any operation it offers.
Stitching it all together:
- IDOR lets you act as any examiner by editing one value in your browser.
- The
ChangePasswordAPI resets a password without checking the old one. - So you can set
ValuatorIDto any victim and reset their password to one you control. That's a complete account takeover, with no credentials and no insider access.
From there, an attacker can log into the victim's account legitimately, view assigned answer scripts, and alter marks. At the scale of a national board exam, the integrity implications speak for themselves.
Putting It Together
To summarize what these flaws allowed:
- Log in as any examiner using a master password leaked in the frontend.
- Bypass OTP entirely, because validation happens in the browser.
- Reach any internal page without authenticating at all.
- Reset any examiner's password without knowing their current one.
- Act as any user across the API thanks to systemic IDOR, and in doing so edit marks, change examiner details, and tamper with the evaluation process.
None of this required sophisticated exploitation. The hardest part was reading a JavaScript file and editing a couple of values in DevTools.
Responsible Disclosure
I reported all of this to CERT-In (the Indian Computer Emergency Response Team) before writing anything publicly.
My first email laid out the master-password leak and the client-side OTP validation. They replied asking for more detail and a screen recording, so I sent a full walkthrough: the master-password auth bypass on video, the browser-console login bypass, and then the extra findings I'd uncovered since, namely the missing route guards, the password-change flaw, and the systemic IDOR.
Their response was a boilerplate acknowledgement:
Dear Sir,
Thank you for reporting this incident to CERT-In. We have registered your complaint/incident under Ref: CERTIn-XXXXX. We are in process of taking appropriate action with the concerned authority.

After that, I followed up several times and never heard back. It's honestly funny that most of the vulnerabilities I reported went unpatched for a long time, when I'd have fixed them in an hour or two if they were mine to fix. The sheer incompetency of our authorities baffles me.
I held off on publishing for a while, mostly to give them a fair window to fix things. But these issues sat in a system handling the exam evaluations of millions of students, and that's exactly the kind of thing that deserves daylight once it's been responsibly reported.
Takeaways
If there's one lesson here for anyone building software like this, it's that the client cannot be trusted, ever. Every one of these vulnerabilities traces back to the same root mistake: putting secrets and security decisions in code that runs on the user's machine.
- Secrets (passwords, OTPs, anything sensitive) belong on the server, never in a JavaScript bundle.
- Authentication and authorization must be enforced server-side, on every request.
- A user's identity should come from their authenticated session, not from a value they can edit in DevTools.
- Sensitive operations like password changes must verify the requester's authority, and their current password.
These aren't advanced defenses. They're the basics. For a platform entrusted with the integrity of national board examinations, the basics are the least we should expect.
Aftermath
This section was written on May 27, about five days after this blog first went up. In that time, CBSE has publicly denied that any of these vulnerabilities ever existed. I want to lay out, in good faith, what's actually happened from my side, because the public record matters here.
To recap the timeline: every issue documented above was reported to CERT-In back in February, and they verified and acknowledged the findings. My complaint sits on file under Ref: CERTIn-16590126. I followed up multiple times after that and never got a substantive response. Three months passed, the Class 12 results were released, and the portal and its marking workflows were still in the same vulnerable state. That silence is what eventually pushed me to publish.
Once the story picked up in the press, CBSE issued a statement claiming the findings were false. There were two immediate problems with that statement. First, their initial release pointed to a domain that didn't actually exist; someone in my circle quietly registered it and pointed it at this very blog before they retracted the tweet. Second, even taken at face value, their position doesn't hold up: if what I accessed was a test environment, how was I able to pull what was unmistakably production data out of it?
The receipts for that are public:
- A proof screenshot of the access itself.
- CBSE's own official mails referencing the same URL they later tried to distance themselves from.
- A screen recording of the hardcoded master password being used to reach production data.
- An archive of the site and its source code as of 03/03/2026, preserved before anything was changed.
- Evidence that the vulnerabilities were still present in the March build of the site, well after my reports.
- The same master password sitting in the JS bundles of other
onmarkdomains, unpatched at the time of posting. - Findings from other researchers showing that every CBSE-related subdomain under
onmarkresolves to the same load balancer. Test environments don't typically need a load balancer, and they certainly shouldn't share infrastructure with the "prod" they're supposedly separated from.
On top of all that, three days ago, shortly before the site was taken down, I discovered an SQL injection vulnerability in the OSM portal and reported it to CERT-In. The response so far has been a one-line "thank you" mail.
I want to be clear about my motive: this entire body of work was done in good faith, to flag a serious problem in a system that grades the futures of millions of students. The right response from an institution of CBSE's scale is to acknowledge, investigate, and fix, not to deny and deflect. I hope this pushes things in that direction.
Media Coverage
A lot of famous personalities and organizations like Deedy Das, Satish Acharya, Internet Freedom Foundation tweeted about it & this blog has been featured in news reports by multiple media outlets:
- India Today
- NDTV
- Times of India
- The Hindu BusinessLine
- ThePrint
- News18
- Moneycontrol
- IFF Blog
- Medianama
- Free Press Journal
- Careers360
Thanks for reading.