Oof, that’s certainly a title. What does it mean? Well, in short, an application was coaxed into showing admin-level data to any user via client-side role manipulation. But how did it happen?
Introduction
During a bug bounty engagement against a high-traffic social networking platform, I spent time mapping the application’s architecture before attempting any active exploitation. The platform runs a Rails backend with a Vue.js frontend, which I have some familiarity with. On this site, server-rendered flags are embedded directly in the HTML. This is a common pattern, as this leads to much better initial load performance, especially on a site with plenty of things happening under the hood. However, it’s also worth scrutinizing carefully, because it also means that the server is making decisions at render time about what data to include in the initial payload.
The scope of the testing covered the authenticated web application, focusing on session handling, authorization controls, and the various social and content management features available to regular members. I was not targeting admin functionality directly, but rather discovered these features as a byproduct of simply reading the page sources carefully.
Discovery
After authenticating with my test account, I reviewed the HTML source of the main page, focusing section by section. I skimmed through the main navigation component, not expecting much to be in there. I found a server-rendered JSON blob being parsed as an attribute (standard Vue initialization) with all of the expected, usually boring, fields: profile name and data, notification counts, etc. But one field stood out immediately:
"newReportsCount": 41
This was not rendered in my UI anywhere. I thought about what this could mean, was it was a hardcoded value that was never used? So I refreshed, and the number went up to 45. I refreshed again, it went down to 32. Whatever this was, it was a live count of something. While I thought about all the different things this could be, I kept coming back to one possible answer: these are live counts of what’s currently in the moderation queue, reported in page source to every authenticated user, regardless of admin or moderator status.
I took a deeper look at the object properties to see how it was validating if I was an admin or not. Based on the fact the count was only in the source, not displayed in the UI, I began to wonder if the admin and/or mod status was being checked client-side vs. server-side. That’s when I found the “[Platform].user” object, with the following explicit role flags:
"isModerator": false,
"isSeniorModerator": false,
"isEmployee": false
The Vue components used these flags to conditionally render moderator UI elements. With all flags false, the moderator nav and action buttons did not render. However, the flags themselves were present in the client’s DOM, writable from the browser console, and the rendering logic was entirely client-side.
Finding: Client-Side Flag Manipulation Reveals Admin UI
I tried manually changing these flags to true in the browser console and refreshing, however they instantly reverted to false before the UI reloaded. So at least there is a server-side check influencing these flag assignments. But I should still be able to manipulate them client-side as they are assigned, and hopefully see if it changes the render. That’s where Caido came in to play. Caido is a web proxy tool similar to Burp Suite, with a lot of familiar functionality. In this case, I used Caido’s search and replace rules to modify these assignments in-transit to essentially flip the role flags to true. I had to add quite a few rules, because some of the flags were HTML encoded in the data-props attribute. There were a few rules that looked like the regular "isModerator": true, but some of them had to be encoded:
Match: "isModerator":false Replace: "isModerator":true


Once these rules were active, I reloaded the page a final time, and confirmed in the console that the flags were staying set to True. By that point, checking the console was almost beside the point when I saw the rendered UI. The new navigation bar was complete with a button to access the mod queue (with the live count of pending reports in the queue). When browsing content on the site, I also had new buttons for content action and reporting controls, and could even add “moderator notes” to photos and posts.
Preventing Abuse: Server-Side Checks Held
Every attempt to interact with the rendered admin functionality hit proper server-side authorization. Requests to admin-level pages such as the moderation queue returned a 302 redirect back to the home page. This applied when clicking rendered admin buttons, directly requesting admin URLs, and manipulating request parameters. In short, the server didn’t care what the client was told it’s user role was, the backend enforced authorization independently. Throughout my testing it held its ground.
To further validate that client-side flag manipulation is limited to rendering, relationship state flags such as block status were also tested. Flipping these values produced no change in actual block status, confirming that all relationship enforcement is handled server-side. This prevents users from avoiding blocks by manipulating response flags.
So from a security engineering perspective, it did everything right. Client-side rendering should never be treated as an access control boundary, and in this case, it wasn’t.
Why This Still Matters
The server-side controls working correctly doesn’t make the client-side leakage a non-finding. This could be a partial finding. In fact, there are two issues at play here with real consequences.
First, is the Information Disclosure via newReports. The platform is exposing operational data about its moderation workload to every authenticated user. By itself, this could be a non-issue. However, it is exactly the kind of data point that could help an adversary understand elements such as: platform operations, the capacity of staff to handle moderation requests, or to time coordinated abuse activity around busier moderation periods. For a platform with user-generated content, do they have more people working moderation queues than security tickets? If so, this could be a clear indicator of resource allocation to dealing with security issues. Also, I could use these endpoints to build timing maps. What time of day are the counts the highest? What period of the week? How fast are tickets closed? This is information an adversary would take into heavy account.
Second up is the renderable admin UI. This is certainly an interesting case. An attacker who knows exactly which endpoints the moderator interface hits (which they can see through the fully rendered nav element) now has a complete map of the admin attack surface. If a future authorization regression occurs on any of those endpoints, that attacker now knows exactly where to probe. Information disclosure of endpoint structure is its own risk category, separate from whether those endpoints are currently accessible.
In summary: defense in depth worked here, but the outer layer failed. A well-layered security posture keeps server-side controls tight and avoids leaking structural information on the client side. Only one of those two things is happening in this case.
Impact Assessment
This report likely falls as an “Information Disclosure.” Chances are this report would be filed as “informational” or even “N/A.” While there’s a slight chance it could be “Low,” no formal report for bounty was submitted. Reasons being:
- No unauthorized access to moderation functionality was achieved
- No user data was exposed beyond what a member would normally see
- No server-side authorization controls were bypassed.
While these factors influenced the decision not to formally report, these kept it from being a complete non-finding:
- Real-time moderation queue depth leaks to all authenticated users
- Complete admin endpoint structure is recoverable by any member via client-side flag manipulation
Takeaways
The finding reinforces something worth keeping in mind for any engagement against a modern web application using client-side component frameworks: the page source is part of your attack surface, not just a placeholder for the DOM. Server-rendered props, bootstrapped objects, and component data all represent decisions the server made about what to hand the client. Those decisions have security implications independent of what the UI renders.
When you see role flags serialized into client-side state, the first question should be “What happens when these are changed?” The second question, which this engagement answered, is: “Does the server enforce that independently, or is it trusting the client?” In this case, the server got it right, but the information handed to the client before enforcement kicked in was still more than it needed to share.
