Clickjacking in Adonisjs with Mongodb
Clickjacking in Adonisjs with Mongodb — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack where an invisible or misleading layer tricks a user into interacting with a different page than the one they believe they are using. When using AdonisJS with MongoDB, the risk arises not from AdonisJS itself, but from how response rendering and HTTP headers are configured in combination with data fetched from MongoDB. If an AdonisJS view embeds external or user-controlled content without proper isolation, and the application does not set anti-clickjacking headers, an attacker can embed the app’s pages inside an <iframe> and overlay interactive elements.
In AdonisJS applications, MongoDB is often used as the primary data store via the official MongoDB driver or ODMs. Consider a dashboard route that renders a view with data retrieved from a MongoDB collection:
// controllers/DashboardController.js
async index({ view }) {
const stats = await MongoClient.db('app').collection('stats').find({}).toArray()
return view.render('dashboard', { stats })
}
If the rendered page includes third-party widgets, analytics scripts, or if the view allows dynamic URLs sourced from MongoDB without strict validation, an attacker might manipulate stored data to point to malicious endpoints. The response does not include X-Frame-Options or Content-Security-Policy frame-ancestors directives, enabling an attacker to load the page in an iframe and position transparent controls over buttons or links. Because AdonisJS templates (e.g., Edge.js) can dynamically inject HTML based on MongoDB content, missing CSP headers or unsafe inline event handlers can amplify the impact. For example, if a stored setting determines which external resources to load, an attacker who can tamper with MongoDB documents may be able to influence what gets embedded, increasing the likelihood that a user’s click is hijacked.
Additionally, if the application exposes an API consumed by a frontend that renders iframes or components based on MongoDB documents, missing referrer or frame-protection headers make it easier for an attacker to craft a page that embeds the API-driven UI. AdonisJS does not automatically enforce frame restrictions; developers must explicitly configure HTTP headers or CSP directives to prevent embedding. Without these controls, an otherwise benign AdonisJS + MongoDB setup can be leveraged in a clickjacking scenario where user actions are misdirected through invisible overlays.
Mongodb-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on two areas: secure header configuration and safe handling of data sourced from MongoDB. AdonisJS provides hooks and middleware to set HTTP headers globally or per route, which is the primary defense against clickjacking. Below are concrete examples that integrate MongoDB data safely while enforcing strong framing policies.
1. Set anti-clickjacking headers in AdonisJS middleware
Create a middleware that adds X-Frame-Options and Content-Security-Policy headers. This ensures that pages rendering MongoDB data cannot be embedded in hostile frames.
// start/hooks/anti_clickjacking.js
const antiClickjackingHook = () => {
return async ({ response }) => {
response.header('X-Frame-Options', 'DENY')
response.header(
'Content-Security-Policy',
"frame-ancestors 'self'; frame-src 'none';"
)
}
}
module.exports = antiClickjackingHook
Register the hook in start/hooks.ts:
import antiClickjackingHook from './anti_clickjacking'
export const hooks = {
async ready() {},
async request({ request, response }) {
await antiClickjackingHook()({ response })
},
}
2. Validate and sanitize MongoDB-derived content in views
When rendering data stored in MongoDB, ensure that any user-influenced fields (such as URLs or scriptable attributes) are validated and escaped. Use AdonisJS built-in escaping in Edge templates:
// controllers/DashboardController.js
async index({ view, escape }) {
const stats = await MongoClient.db('app').collection('stats').find({}).toArray()
const safeStats = stats.map(item => ({
label: escape(item.label),
value: Number(item.value) || 0,
// Never directly embed untrusted HTML
description: escape(item.description || '')
}))
return view.render('dashboard', { stats: safeStats })
}
If you must include external resources, store strict allowlists in MongoDB and enforce them in CSP headers dynamically:
// middleware/DynamicCsp.js
async handle({ request, response }, next) {
const settings = await MongoClient.db('app').collection('settings').findOne({ key: 'csp' })
const frameSrc = settings?.frameSrc || "'none'"
response.header(
'Content-Security-Policy',
`frame-ancestors 'self'; frame-src ${frameSrc};`
)
await next()
}
3. Avoid unsafe inline event handlers sourced from MongoDB
If your application stores HTML snippets or event handler attributes in MongoDB, never inject them directly into templates. Instead, map stored values to a strict set of allowed actions. For example, if a widget configuration includes an onclick field, validate it against a whitelist of safe operations or reconstruct it safely in the view:
// Bad: direct injection (vulnerable)
// <button onclick="{{ widget.rawOnClick }}">Click</button>
// Good: map to known actions
const actions = {
'submitReport': () => submitReport(),
'refreshData': () => refreshData()
}
// In view
<button @click="${actions[widget.actionKey] ? 'actions[widget.actionKey]()' : 'void 0'}">Do</button>