Introduction
The app/api/cameras.js file in this application handles camera-related API operations, but a critical architectural flaw created a CSRF vulnerability: at line 4, the file instantiated its own independent Express application with const app = express(). This separate Express instance operated outside the main application's middleware chain defined in app/index.js, meaning any CSRF protections applied to the main app would never reach requests handled by this module.
This is a subtle but dangerous pattern. Even if a developer added CSRF middleware to the main application, the cameras API would remain completely unprotected because it was running on its own isolated Express instance without any such middleware configured.
The Vulnerability Explained
The Problematic Code
In the original app/api/cameras.js, the file created its own Express application:
const express = require('express');
const httpRequest = require('../lib/http');
const bodyParser = require('body-parser');
const app = express();
var SelfReloadJSON = require('self-reload-json');
const appRoot = require('app-root-path');
var settings = new SelfReloadJSON(appRoot + '/data/settings.json');
The key issue is lines 1, 3, and 4: importing express and body-parser, then creating a standalone const app = express(). This module-level Express instance would handle routes independently, completely bypassing any middleware (including CSRF protection) configured on the main application in app/index.js.
How Could This Be Exploited?
Consider this attack scenario:
- A user is authenticated to the camera management system and has an active session.
- The user visits a malicious website while still logged in.
- The malicious site contains a hidden form or JavaScript that submits a POST request to the cameras API endpoint:
<form action="https://target-app.com/api/cameras" method="POST">
<input type="hidden" name="url" value="http://attacker-controlled-feed.com/spy" />
<input type="hidden" name="name" value="Lobby Camera" />
</form>
<script>document.forms[0].submit();</script>
- The browser automatically includes the user's session cookies with the request.
- Because there's no CSRF validation, the cameras API processes the request as legitimate, potentially allowing an attacker to:
- Add malicious camera feeds pointing to attacker-controlled streams
- Delete existing camera configurations
- Modify camera settings to disable monitoring
Since the PR description confirms "This API endpoint appears to be publicly accessible" and "This is a web service - vulnerabilities in request handlers are directly exploitable by remote attackers," the attack surface is real and immediately exploitable.
The Fix
The fix addresses this vulnerability through changes in two files, each serving a distinct purpose:
Change 1: Remove the Redundant Express Instance (app/api/cameras.js)
Before:
const express = require('express');
const httpRequest = require('../lib/http');
const bodyParser = require('body-parser');
const app = express();
var SelfReloadJSON = require('self-reload-json');
After:
const httpRequest = require('../lib/http');
var SelfReloadJSON = require('self-reload-json');
The imports for express and body-parser are removed, along with the standalone const app = express() declaration. This ensures that the cameras module no longer operates as an isolated Express application. Instead, it will use routes registered on the main application, inheriting all middleware in the chain.
Change 2: Add CSRF Protection Middleware (app/index.js)
Before: No CSRF protection existed in the middleware chain.
After:
// CSRF protection: verify Origin/Referer header matches Host for state-changing requests
app.use(function csrfProtection(req, res, next) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].indexOf(req.method) !== -1) {
var origin = req.headers.origin || req.headers.referer;
var host = req.headers.host;
if (origin && host && origin.indexOf(host) === -1) {
return res.status(403).json({ error: 'CSRF check failed' });
}
}
next();
});
This middleware:
1. Targets only state-changing methods — GET and HEAD requests pass through unaffected since they should be idempotent.
2. Validates the Origin or Referer header — It checks whether the request's origin matches the application's host. Cross-origin forged requests will have a mismatched origin.
3. Returns 403 on mismatch — Requests failing validation receive a clear { error: 'CSRF check failed' } response.
4. Allows requests without Origin/Referer — This handles same-origin requests where browsers may not send these headers (e.g., direct API calls), avoiding false positives.
Why Both Changes Are Necessary
Removing the redundant Express instance ensures that cameras.js routes are served through the main app, which now has CSRF middleware. Without removing the standalone app = express(), the cameras module would continue to bypass any middleware added to app/index.js.
Prevention & Best Practices
Architectural Best Practices
-
Never create multiple Express instances unless you're explicitly running separate servers. Use
express.Router()for modular route definitions:
javascript const router = require('express').Router(); // Define routes on the router, not a new app module.exports = router; -
Apply security middleware at the application level in your main entry point, before any route handlers are registered.
-
Use defense-in-depth for CSRF protection:
- Origin/Referer validation (as implemented here)
- SameSite cookie attributes (SameSite=StrictorSameSite=Lax)
- Token-based validation with libraries likecsurf
- Custom headers (e.g.,X-Requested-With) that trigger CORS preflight
Detection Tools
- Semgrep with rule
javascript.express.security.audit.express-check-csurf-middleware-usagedetects missing CSRF middleware - ESLint security plugins can flag patterns like multiple Express instantiation
- OWASP ZAP can test for CSRF vulnerabilities in running applications
Relevant Standards
- OWASP Top 10: This falls under "A01:2021 – Broken Access Control"
- CWE-352: Cross-Site Request Forgery
- OWASP CSRF Prevention Cheat Sheet: Recommends token-based and header-based validation
Key Takeaways
- A standalone
const app = express()incameras.jscreated an isolated middleware chain, meaning CSRF protections on the main app were completely bypassed for camera API routes. - Origin/Referer header validation is an effective CSRF defense that doesn't require token management or client-side changes, making it ideal for API-first applications.
- The fix targets only state-changing methods (
POST,PUT,DELETE,PATCH), preserving backward compatibility for safe read operations. - Module-level Express instances are a common architectural anti-pattern that fragments security controls — always use
express.Router()for sub-modules. - Two-file fixes are sometimes necessary: removing the bypass path (
cameras.js) and adding the protection (index.js) together close the vulnerability completely.
How Orbis AppSec Detected This
- Source: Incoming HTTP requests to the cameras API endpoints (state-changing methods like POST/PUT/DELETE)
- Sink: Route handlers in
app/api/cameras.jsthat process requests without CSRF validation - Missing control: No CSRF middleware (like
csurf) was detected in the Express middleware chain, and the standaloneexpress()instance incameras.js:4bypassed any application-level protections - CWE: CWE-352 (Cross-Site Request Forgery)
- Fix: Removed the isolated Express instance from
cameras.jsand added Origin/Referer header validation middleware to the main application inapp/index.js
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This vulnerability demonstrates how architectural decisions — specifically creating a standalone Express instance in a sub-module — can silently undermine security controls. The redundant const app = express() in cameras.js meant that even if CSRF middleware existed on the main application, the cameras API remained completely exposed. The fix elegantly addresses both the symptom (missing CSRF protection) and the root cause (isolated Express instance) by consolidating the middleware chain and adding Origin/Referer validation for all state-changing requests.
When building Express.js applications, always use express.Router() for modular routes and apply security middleware at the application level to ensure consistent protection across all endpoints.