CORS Security
CORS (Cross-Origin Resource Sharing) הוא מנגנון אבטחה המיושם על ידי דפדפנים מודרניים השולט באופן שבו יישומי אינטרנט יכולים לבצע בקשות HTTP לדומיינים השונים מזה ששירת את הדף המקורי, תוך הרפיה מבוקרת של ה-Same-Origin Policy (SOP) - מדיניות אבטחה בסיסית המגבילה סקריפטים ממקור אחד מלגשת למשאבים של מקור אחר. אף שה-CORS חיוני לארכיטקטורות מודרניות של יישומי אינטרנט, שבהן צד-לקוח (front-end) נדרש לעיתים קרובות לצרוך ממשקי API המתארחים בדומיינים שונים, תצורתו הלקויה מהווה אחת מהפגיעויות הנפוצות והמסוכנות ביותר ביישומי אינטרנט עכשוויים. שגיאות תצורת CORS עלולות לחשוף נתונים רגישים לדומיינים לא מורשים, לאפשר התקפות Cross-Site Request Forgery (CSRF) אפילו בנוכחות אסימוני אנטי-CSRF, להקל על גניבת אישורי גישה, ובמקרים קיצוניים, לאפשר לתוקפים לבצע פעולות בעלות הרשאות בשם משתמשים מאומתים. הבעיה מחמירה בשל העובדה שמפתחים רבים, בנתקלם בשגיאות CORS במהלך הפיתוח, בוחרים בפתרונות מתירניים מדי (כגון שימוש בתו הכללי "*" או שיקוף אוטומטי של מקור הבקשה) בלי להבין במלואן את ההשלכות האבטחתיות. מאמר זה בוחן לעומק את יסודות ה-CORS, פגיעויות תצורה נפוצות, ומבסס שיטות עבודה איתנות ליישום מאובטח בפלטפורמות ובמסגרות עבודה שונות, תוך איזון בין פונקציונליות לבין עמדה הגנתית הולמת.
Same-Origin Policy (SOP)
דפדפנים מיישמים SOP: סקריפטים יכולים לגשת רק למשאבים מאותו מקור (פרוטוקול + דומיין + פורט). CORS מרפה את ה-SOP באופן מבוקר.
# אותו מקור
https://example.com/api ← https://example.com/app [OK]
# מקורות שונים (חסום על ידי SOP)
https://example.com ← http://example.com (פרוטוקול)
https://example.com ← https://api.example.com (תת-דומיין)
https://example.com ← https://example.com:8080 (פורט)
CORS Headers
Access-Control-Allow-Origin
# התרת מקור מסוים (מומלץ)
Access-Control-Allow-Origin: https://trusted.com
# התרת כל מקור (מסוכן!)
Access-Control-Allow-Origin: *
# דינמי המבוסס על whitelist (נכון)
const allowedOrigins = ['https://app1.com', 'https://app2.com'];
const origin = request.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
Headers חשובים נוספים
# התרת אישורי גישה (cookies, auth headers)
Access-Control-Allow-Credentials: true
# שיטות HTTP מותרות
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Headers מותרים בבקשות
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
# Headers החשופים ל-JavaScript בצד הלקוח
Access-Control-Expose-Headers: X-Custom-Header, X-Request-Id
# זמן המטמון של ה-preflight (שניות)
Access-Control-Max-Age: 86400
Preflight Requests
דפדפנים שולחים בקשת OPTIONS לפני בקשות "לא פשוטות" כדי לאמת הרשאות.
# הלקוח שולח preflight
OPTIONS /api/resource HTTP/1.1
Origin: https://app.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization
# השרת מגיב עם הרשאות
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization
Access-Control-Max-Age: 86400
פגיעויות CORS נפוצות
1. תו כללי עם אישורי גישה
# [שגיאה] פגיע - אינו עובד ומסוכן
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
# דפדפנים חוסמים שילוב זה
# [OK] נכון - מקור מסוים עם אישורי גישה
Access-Control-Allow-Origin: https://trusted.com
Access-Control-Allow-Credentials: true
2. Reflection Attack
# [שגיאה] פגיע - משקף כל origin
const origin = request.headers.origin;
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
# [OK] נכון - אימות whitelist
const allowedOrigins = ['https://app.com', 'https://admin.com'];
const origin = request.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
3. Subdomain Wildcard
# [שגיאה] פגיע - regex מיושם בצורה לקויה
const origin = request.headers.origin;
if (/https:\/\/.*\.example\.com/.test(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
// מקבל https://evil.example.com.attacker.com
# [OK] נכון - אימות קפדני
const origin = request.headers.origin;
if (/^https:\/\/[a-z0-9-]+\.example\.com$/.test(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
תצורה מאובטחת לפי טכנולוגיה
Node.js/Express (CORS middleware)
const cors = require('cors');
// תצורה מאובטחת
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com'
];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400
};
app.use(cors(corsOptions));
Nginx
# תצורה מותנית
map $http_origin $cors_origin {
default "";
"~^https://app\\.example\\.com$" $http_origin;
"~^https://admin\\.example\\.com$" $http_origin;
}
server {
location /api {
if ($cors_origin != "") {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
}
if ($request_method = OPTIONS) {
return 204;
}
}
}
Apache
# .htaccess
SetEnvIf Origin "^https://(app|admin)\\.example\\.com$" CORS_ORIGIN=$0
Header always set Access-Control-Allow-Origin "%e" env=CORS_ORIGIN
Header always set Access-Control-Allow-Credentials "true" env=CORS_ORIGIN
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE" env=CORS_ORIGIN
Header always set Access-Control-Allow-Headers "Authorization, Content-Type" env=CORS_ORIGIN
# מענה ל-preflight OPTIONS
RewriteEngine On
RewriteCond % OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]
Testing CORS
# בדיקה עם curl
curl -H "Origin: https://evil.com" \\
-H "Access-Control-Request-Method: DELETE" \\
-H "Access-Control-Request-Headers: Authorization" \\
-X OPTIONS \\
https://api.example.com/resource
# בדיקת JavaScript
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
}
}).then(response => console.log(response));
Best Practices
- לעולם אל תשתמשו בתו הכללי (*) בממשקי API עם נתונים רגישים
- Whitelist מפורשת של מקורות מותרים
- אימות קפדני של המקור עם regex מאובטח
- צמצמו credentials: הפעילו רק אם זה באמת נחוץ
- Least privilege: התירו רק את השיטות וה-headers הנחוצים
- Cache preflight: השתמשו ב-Max-Age כדי לצמצם את התקורה
- Monitoring: תעדו דחיות CORS חשודות
רשימת תיוג ל-CORS מאובטח
- [OK] המקור אומת מול whitelist מפורשת
- [OK] ה-regex של האימות אינו מאפשר עקיפות
- [OK] Credentials מופעלים רק בעת הצורך
- [OK] השיטות וה-headers הוגבלו למינימום
- [OK] ה-preflight הוגדר כראוי
- [OK] נבדק מול מקורות זדוניים
- [OK] לוגים של בקשות שנדחו מנוטרים
