1. דף הבית
  2. קורסים אונליין
  3. קורס PHP אונליין
  4. עבודה עם קובצי עוגיות ב-PHP

עבודה עם קובצי עוגיות ב-PHP

בפרק זה, נבחן קובצי קוקיז (עוגיות) ב-PHP. נראה איך יוצרים ומנהלים אותם, נסקור שימוש מתקדם בהם, וגם נציג שיקולי אבטחה ושיטות עבודה מומלצות.
עוגיות ב-PHP

מהן עוגיות?

קובצי עוגיות (Cookie Files) הם פיסות מידע קטנות המאוחסנות בדפדפן שאיתו גולש המשתמש באינטרנט.

פרוטוקול HTTP באינטרנט אינו שומר מצב, כלומר, כל פניה לשרת היא אנונימית ואינה תלויה בפעילות קודמת שנעשתה. קובצי העוגיות באים לפתור את הבעיה הזו ולשמר את המצב של האתר. לדוגמה: שמירה של המוצרים שהוכנסו לעגלת הקניות, שמירת פרטי המשתמש המחובר, שמירת המאמרים שהגולש סימן כמועדפים וכן הלאה. מרגע שהנתונים נשמרו בעוגיות, ניתן להשתמש בהם לאורך זמן רב מבלי לאבד את המידע.

כל עוגיה כוללת מספר מאפיינים שמייחדים אותה. אלו הם:

  • משך חיים - זמן הפעילות של העוגיה, אשר יכול להיות מבוסס הפעלה (נמחק כשהדפדפן נסגר) או קבוע (מאוחסן למשך זמן מוגדר).
  • מגבלת גודל - גודל עוגיה מקסימלי הוא בדרך כלל 4K.
  • דומיין ספציפי - עוגיה נגישה רק לדומיין שיצר אותה (מדיניות המכונה "same-origin").

סוגי עוגיות:

  • קובצי עוגיות של צד ראשון (First party cookies) - עוגיות הנוצרות ומנוהלות על ידי האתר שבו המשתמש מבקר. לדוגמה: נתונים של עגלת הקניות בחנות וירטואלית.
  • קובצי עוגיות של צד שלישי (Third party cookies) - עוגיות הנוצרות ומנוהלות על ידי אתרים אחרים מזה שבו המשתמש מבקר. לדוגמה: סטטיסטיקות של פעילות הגולש שמועברות לגוגל או לפייסבוק.

יצירת עוגיות ב-PHP

יצירת עוגיות נעשית באמצעות הפונקציה setcookie (שהיא גם הנפוצה והשימושית יותר) או setrawcookie. שתי הפונקציות פועלות בצורה זהה, למעט אופן שמירת הנתונים: הפונקציה setcookie מבצעת urlencoding לנתונים הנשמרים, פעולה שמקודדת את המידע בצורה מותאמת להעברה באינטרנט. הפונקציה setrawcookie אינה מקודדת את הנתונים הנשמרים, ולכן, שימוש בה מצריך קידוד ידני של הנתונים לפני שליחת העוגיה ברשת.

לפונקציות setcookie ול-setrawcookie קיימים 2 תחבירים. נסקור את שניהם להלן.

התחביר הישן:

bool setcookie(
    string $name,
    string $value = "",
    int $expires_or_options = 0,
    string $path = "",
    string $domain = "",
    bool $secure = false,
    bool $httponly = false
);

הסבר על הפרמטרים של הפונקציה:

name - שם העוגיה.

value - ערך העוגיה (המידע שנשמר).

expires_or_options - זמן פקיעת העוגיה, בערך מסוג Unix Timestamp (כמה שניות עברו מאז 1/1/1970).

path - המסלול (החלק מהדומיין) שבו העוגיה תקפה. יש לשים את הערך לוכסן (התו /), כדי לאפשר גישה מכל חלקי הדומיין.

domain - הדומיין שבו העוגיה תקפה. אם מועברת מחרוזת ריקה, העוגיה תקפה רק לדומיין הנוכחי (אינה תקפה לסאב-דומיינים שלו). אם מועבר דומיין שמתחיל בנקודה (לדוגמה: example.com.), העוגיה תקפה לדומיין ולסאב-דומיינים שלו. אם מועבר דומיין שאינו מתחיל בנקודה (לדוגמה: example.com), העוגיה תקפה לדומיין ולא לסאב דומיינים שלו.

secure - האם להעביר את העוגיה רק בפרוטוקול מאובטח עם SSL (כתובות המתחילות ב-https).

httponly - האם להעביר את העוגיה רק בתשתית HTTP/S בלבד (ולא באמצעות קוד JS למשל).

 

דוגמה:

<?php
setcookie('movie', 'Star Trek', time() + 3600, '/', 'mgweb.co.il', true, true);
echo "Cookie 'movie' has been set.";
?>

בדוגמה זו, הגדרנו עוגיה בשם movie שמכילה את הערך Star Trek. התוקף שלה הוא שעה אחת מרגע זה (הפונקציה time שמחזירה את חתימת הזמן הנוכחי + 3600 שניות). העוגיה תקפה לכל מסלול הדומיין ורק לדומיין mgweb.co.il. העוגיה יכולה לעבור רק בצורה מאובטחת ורק בפרוטוקול HTTP/S.

 

התחביר החדש:

setcookie(string $name, string $value = "", array $options = []);

לפי תחביר זה, מועברים בפרמטרים הראשון והשני שם העוגיה (name) וערך העוגיה (value) כמו קודם, כאשר הפרמטר השלישי (options) הוא מערך אסוציאטיבי עם יתר ההגדרות של העוגיה.

מפתחות המערך options הם אלו:

expires - זמן פקיעת העוגיה.

path - המסלול שבו העוגיה תקפה.

domain - הדומיין שבו העוגיה תקפה.

secure - האם להעביר את העוגיה רק בפרוטוקול מאובטח עם SSL.

httponly - האם להעביר את העוגיה רק בתשתית HTTP/S בלבד.

samesite - שליטה במאפיין SameSite שמגדיר באילו מצבים העוגיה מועברת. ערכים אפשריים: Strict, Lax, None. הערך Strict הוא המחמיר ביותר מבחינה אבטחתית, והוא מאפשר שימוש בעוגיה בסקריפט הנוכחי בלבד ומגביל שימוש שלה באמצעות תוכנות צד ג'. הערך Lax (פחות בטוח מאשר Strict) מאפשר העברה של העוגיות באמצעות שליחה ישירה בפרוטוקול HTTP/S בלבד, גם לתוכנות צד ג'. הערך None (הכי פחות בטוח) מאפשר העברה של העוגיות בכל צורה, לרבות באמצעות טפסים או קוד JavaScript.

ניתן לראות כי המפתחות של המערך תואמים לפרמטרים המועברים בתחביר הישן, בתוספת המפתח החדש samesite.

 

דוגמה:

<?php
setcookie('username', 'Alexander', [
    'expires' => time() + 3600,   // 1 hour from now
    'path' => '/',
    'domain' => '',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);

echo 'Cookie "username" has been set using the alternative syntax.';
?>

בדוגמה זו, אנו יוצרים עוגיה בשם username עם הערך Alexander. העוגיה תקפה לשעה, ניתן לגשת אליה מכל מקום בדומיין, תקפה רק בדומיין הנוכחי (ללא הסאב דומיינים שלו), פעילה רק על פרוטוקול מאובטח על גבי תשתית HTTPS. הערך Strict שמועבר למפתח samesite אומר שלא ניתן לגשת לעוגיה בפניות מדומיינים אחרים.

 

אנו ממליצים לעבוד תמיד עם התחביר החדש ולא עם התחביר הישן. התחביר החדש קריא יותר מאשר התחביר הישן. כמו כן, הוא מאפשר שדרוגים עתידיים לפונקציות setcookie ו-setrawcookie, בכך שאולי יתווספו בעתיד אפשרויות נוספות למערך options.

חשוב לשים לב: אם משתמשים בתחביר החדש, אין חשיבות לסדר המפתחות שבמערך options (לדוגמה: נוכל להעביר קודם את httponly ואחריו את expires). עם זאת, מטעמי סטנדרטיזציה ושמירה על אחידות וקריאות, מוטב לשמור על הסדר שהגדרנו לעיל.

אחזור עוגיות

מרגע שנוצרה העוגיה, ניתן לאחזר אותה (כלומר, לגשת אליה) באמצעות המערך הסופר-גלובלי COOKIE.

דוגמה:

<?php
if (isset($_COOKIE['username'])) {
    echo 'Hello, ' . htmlspecialchars($_COOKIE['username']) . '!';
} else {
    echo 'No cookie named "username" found.';
}
?>

בדוגמה זו, אנו בודקים אם קיימת העוגיה username, ומדפיסים הודעה בהתאם.

עדכון עוגיות

עדכון עוגיות נעשה באמצעות יצירה של עוגיה חדשה, עם שם של עוגיה קיימת. עוגיה זו מחליפה את העוגיה הקודמת.

דוגמה:

<?php
setcookie('username', 'Alex', [
    'expires' => time() + 3600,   // 1 hour from now
    'path' => '/',
    'domain' => '',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);

echo 'Cookie "username" has been redefined with a new value.';
?>

בדוגמה זו, יצרנו מחדש את העוגיה username, הפעם עם הערך Alex. עוגיה זו מחליפה את העוגיה עם השם username שיצרנו קודם. נשים לב שתוקף העוגיה הוא שעה מרגע עידכונה (ולא מרגע היצירה הראשונית שלה במקור), כך שלמעשה הארכנו את תוקף העוגיה המקורית.

מחיקת עוגיות

מחיקה של עוגיות נעשית באמצעות קביעת ערך ריק לעוגיה קיימת או באמצעות קביעת תוקף העוגיה לזמן שעבר. נמליץ להשתמש ב-2 אלו יחד.

דוגמה:

<?php
setcookie('username', '', time() - 3600);
echo 'Cookie "username" has been deleted.';
?>

בדוגמה זו, קבענו את ערך העוגיה username למחרוזת ריקה, ואת תוקף העוגיה לשעה אחורה. בכך, למעשה, העוגיה כבר אינה פעילה יותר.

המלצות בטיחות לעבודה עם עוגיות

עוגיות נשמרות כקבצים במחשב של הגולש והן ניתנות לצפיה, עריכה והפצה דיי בקלות, גם על ידי הגולש עצמו וגם באמצעות אחרים שעלולים לנצל פרצות אבטחה שונות שיש על המחשב של הגולש או באתר שמנהל את העוגיות.

תארו לעצמכם מצב שבו הגדרתם קופון הנחה של 5% לרכישה בחנות וירטואלית שפיתחתם והוא נשמר לגולש בעוגיה. ללא אבטחת העוגיה, הוא יוכל בקלות לשנות את הקופון ל-20% הנחה ולהשתמש בו באתר.

תארו מצב נוסף שבו פרטי ההתחברות של האתר שלכם נשמרים בעוגיה והם עכשיו מופצים לכל דורש באמצעות פירצת אבטחה מסוג XSS שיש לכם באתר. אחרים יוכלו להתחבר לאתר באמצעות פרטי ההתחברות שהופצו.

לכן, חשוב למזער ככל כשניתן שימוש בעוגיות לשמירת מידע באשר הוא, ובפרט, להמנע לחלוטין משמירת מידע רגיש בעוגיות, כגון, פרטי התחברות, סיסמאות, כרטיסי אשראי וכיו"ב. את המידע שנשמר בעוגיה יש להגביל מבחינת אפשרויות השימוש שלו ולאבטח אותו ככל שניתן.

להלן כמה טיפים חשובים שיסייעו לכם בהעלאת רמת האבטחה של קוד העושה שימוש בעוגיות ב-PHP:

  • יש לקבוע את הערך של secure ל-true. זאת על מנת שקובצי העוגיות יעברו רק באמצעות חיבור מאובטח.
  • יש לקבוע את הערך של httponly ל-true. זאת על מנת למזער גישה לעוגיות באמצעות קוד JavaScript. באופן הזה, קטנה היכולת לבצע תקיפות מסוג XSS.
  • יש לקבוע את הערך של samesite ל-Strict (רמת האבטחה המחמירה ביותר), על מנת להגביל שימוש בעוגיה לסקריפט הנוכחי בלבד.

 

נוסף על האמור, מומלץ להצפין את המידע השמור בעוגיות באמצעות מפתח פרטי או טוקן (אסימון) לאבטחת העוגיות, ולבדוק את תקינותן לפני כל שימוש בהן. באופן הזה, חשיפת תוכן קובצי העוגיות לא תחשוף את המידע הרשום בהן, ויתרה מכך, ניסיון שינוי ידני של העוגיה ישבית אותה לחלוטין.

 

דוגמה:

<?php
session_start();

// Define a private key (Keep this secret and never expose it in the code)
define('PRIVATE_KEY', 'your-very-secret-key-123!@#');

// Function to generate a secure cookie value
function generateSecureCookie($data) {
    $hash = hash_hmac('sha256', $data, PRIVATE_KEY); // Create HMAC hash
    return base64_encode($data . '::' . $hash); // Concatenate data and hash
}

// Sample user ID to store in the cookie
$userId = 12345;
$secureValue = generateSecureCookie($userId);

// Store the hashed cookie securely
setcookie("secure_user", $secureValue, [
    'expires' => time() + 3600, // 1-hour expiry
    'path' => '/',
    'secure' => true, // Only send over HTTPS
    'httponly' => true, // Prevent JavaScript access
    'samesite' => 'Strict' // Mitigate CSRF attacks
]);

echo "Secure cookie set!";
?>

בדוגמה זו, הגדרנו מפתח פרטי PRIVATE_KEY שהוא למעשה סיסמה שרק אנחנו יודעים ובשימוש הקוד שלנו בלבד. הפונקציה session_start מפעילה את ה-session ומאפשרת שמירת מידע בצד השרת (דבר הנחוץ פעמים רבות בעבודה עם טוקנים או מפתחות פרטיים הנשמרים על קבצים בצד השרת). הפונקציה generateSecureCookie מקבלת מחרוזת מידע כלשהי ומצפינה אותו: היא משתמש בפונקציה hash_hmac ליצירת hash (דגימה מייצגת של מחרוזת) המבוסס על המידע ועל המפתח הפרטי, ואז משתמשת בפונקציה base64_encode כדי לקודד מחרוזת בבסיס 64 שמשרשרת את הנתון data המקורי עם ה-hash שיצרנו (השתמשנו במחרוזת :: כמפריד בין 2 הערכים). אנו יוצרים עוגיה עם השם secure_user שהערך שלה הוא הצפנה של userId.

פענוח המידע השמור בעוגיה יעשה באופן הבא:

<?php
session_start();

// Function to verify secure cookie value
function verifySecureCookie($cookieValue) {
    if (!$cookieValue) return false;

    $decoded = base64_decode($cookieValue);
    list($data, $hash) = explode('::', $decoded, 2);

    // Recalculate the hash
    $expectedHash = hash_hmac('sha256', $data, PRIVATE_KEY);

    // Use hash_equals() to prevent timing attacks
    return hash_equals($expectedHash, $hash) ? $data : false;
}

// Verify the stored cookie
if (isset($_COOKIE['secure_user'])) {
    $userId = verifySecureCookie($_COOKIE['secure_user']);

    if ($userId !== false) {
        echo "Valid cookie! User ID: " . htmlspecialchars($userId);
    } else {
        echo "Invalid cookie! Possible tampering detected.";
        setcookie("secure_user", "", time() - 3600, "/"); // Delete invalid cookie
    }
} else {
    echo "No secure cookie found.";
}
?>

הפונקציה verifySecureCookie מקבלת ערך של עוגיה ובודקת אותו. אם הערך לא קיים, היא יוצאת ומחזירה false. אם הערך קיים, היא פותחת את הקידוד שלו מבסיס 64 באמצעות הפונקציה base64_decode ומפצלת את הערך למידע המקורי (data) ולדגימת ההצפנה שלו (hash). נרצה עכשיו לוודא ש-hash הוא אכן הצפנה של data. נשים לב ש-hash הוא רק דגימה של ההצפנה (כלומר, אי אפשר להגיע ממנו חזרה לערך ההצפנה המלא), ולכן, אנו יוצרים דגימה חדשה של ההצפנה ומשווים אותה עם הדגימה הקיימת: אנו מפעילים שוב hash_hmac על הנתון המקורי (data) עם המפתח הפרטי PRIVATE_KEY, כדי ליצור דגימה חדשה expectedHash ומשווים את דגימת ההצפנה המקורית hash מול דגימת ההצפנה החדשה expectedHash. את ההשוואה אנו לא עושים באמצעות בדיקת שיוויון רגילה, כי אם באמצעות הפונקציה היעודית hash_equals שמבצעת את ההשוואה באופן שמגן מפני מתקפות זמן (נסיונות פריצה חוזרים ורבים בפרק זמן קצר). זמן הביצוע של hash_equals ארוך משמעותית מהשוואה רגילה, ובכך אנו מגינים מפני נסיונות חוזרים שיארכו זמן ארוך הרבה יותר.

הרעיון באבטחת המידע באמצעות המפתח הפרטי, הוא שאם מישהו יערוך את קובץ העוגיה שעל המחשב של המשתמש, הוא למעשה ישבית את העוגיה. זאת מכיוון שעל מנת להכניס ערך חוקי לעוגיה יש צורך לדעת את המפתח הפרטי, אשר שמור בקוד בצד השרת ואינו חשוף למשתמש.

הוספת תגובה
אנו משתמשים בעוגיות על מנת לשפר את חווית המשתמש באתר. מדיניות הפרטיותאני מסכים