יישום של nonce על מנת להגן מפני התקפות injection

בפוסט הקודם הסברתי על hash עם CSP על משאבי inline – שזה נחמד ומעולה אבל פחות ישים בעולם האמיתי שבו בדרך כלל התוכן ה-inline (בין אם מדובר בג׳אווהסקריפט או בין אם מדובר ב-CSS) מיוצר כל הזמן על ידי ריאקט, CSS-in-JS וחבריהם.

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

על מנת להמחיש את ההתקפה, בואו ונניח אתר עם חולשת XSS. חולשה די פשוטה ומוכרת – באתר אפשר לחפש ומילות החיפוש נשלחות כ: http://example.com/search?q=QUERY ומודפסות באופן הזה:

<!DOCTYPE html>
<html>
<head>
    <title>Search Page</title>
</head>
<body>
    <!-- Example of a valid script with the nonce -->
    <script>
        // Valid script here
    </script>

    <div>Your search results for: <?= $_GET['q'] ?></div>
</body>
</html>

תוקף מרושע יכול לעשות משהו כזה:

http://example.com/search?q=<script>alert('some xss shit')</script>

אז איך אני מונע כזה דבר? אני יכול לאסור ב-CSP על inline. אבל זה ידפוק את ה-valid script שיכול להכיל בתוכו מן הגורן ומן היקב. אני יכול לעשות hash כפי שהסברתי בפוסט הקודם אבל יש גם אפשרות אחרת – לשים לסקריפטים שאני בוחר nonce. את אותו nonce אני מבקש מהשרת לשלוח ב-CSP ואותו אני מצמיד רק לסקריפטים שאני מציב. באופן הבא:

<!DOCTYPE html>
<html>
<head>
    <title>Search Page</title>
</head>
<body>
    <!-- Example of a valid script with the nonce -->
    <script nonce="rAnd0m">
        // Valid script here
    </script>

    <div>Your search results for: <?= $_GET['q'] ?></div>
</body>
</html>

אם ב-CSP יהיה את ה-nonce, שבמקרה הזה הוא rAnd0m, אז הסקריפט התקין ייטען אבל שום דבר אחר לא.

בואו ונדגים. הנה דוגמה ל-CSP שאני מכניס בתגית מטא:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <meta
      http-equiv="Content-Security-Policy"
      content="script-src 'self' 'nonce-rAnd0m'"
    />
  </head>
  <body>
    <h1 id="header">Hello, World!</h1>
    <script nonce="rAnd0m">
      document.getElementById("header").textContent = "DOM Manipulated!";
    </script>
  </body>
</html>

אם אני אכניס סקריפט אחר – הוא לא ייטען ואני אקבל שגיאת CSP.

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

כמובן ש-nonce אפשר להציב אך ורק באתרים דינמיים. כזה שיש שרת מאחוריהם ולא קבצי ג׳אווהסקריפט שזרוקים באיזה S3 מאחורי קלאודפרונט זה או אחר. ובגלל שמדובר בטכניקת הגנה פשוטה, יש לא מעט דוגמאות. למשל הדוגמה הזו בדוקומנטציה של next שקל מאד להפעיל וזה עובד הישר מהקופסה. יוצרים middleware.ts ומציבים אותו בתיקיה הראשית – src במקרה של next 14.

import { NextRequest, NextResponse } from 'next/server'
 
export function middleware(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const cspHeader = `
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
`
  // Replace newline characters and spaces
  const contentSecurityPolicyHeaderValue = cspHeader
    .replace(/\s{2,}/g, ' ')
    .trim()
 
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-nonce', nonce)
 
  requestHeaders.set(
    'Content-Security-Policy',
    contentSecurityPolicyHeaderValue
  )
 
  const response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  })
  response.headers.set(
    'Content-Security-Policy',
    contentSecurityPolicyHeaderValue
  )
 
  return response
}

ומה עם caching? אין בעיה להחזיק פה cache, כי הדפדפן לא מוודא שה-nonce הזה הוא ייחודי ולא סופק בעבר/לאחר. זה כבר כאב הראש שלכם לוודא שה-nonce הוא ייחודי או לפחות לא משתכפל לכמה אנשים שונים ושתוקף לא יוכל לנחש אותו.

לסיכום – כדאי בהחלט להכיר, במיוחד אם דורשים מכם ליישם CSP.

פוסטים נוספים שכדאי לקרוא

פתרונות ומאמרים על פיתוח אינטרנט

המנעו מהעלאת source control לשרת פומבי

לא תאמינו כמה אתרים מעלים את ה-source control שלהם לשרת. ככה תמצאו אותם וגם הסבר למה זה רעיון רע.

בינה מלאכותית

להריץ ממשק של open-webui על הרספברי פיי

להפעיל ממשק של צ׳אט ג׳יפיטי שאפשר לגשת אליו מכל מחשב ברשת הביתית על רספברי פיי עם מודל בשם tinydolphin שרץ על רספברי פיי.

צילום מסך של סוואגר
יסודות בתכנות

openAPI

שימוש בתשתית הפופולרית למיפוי ותיעוד של API וגם הסבר בסיסי על מה זה API

גלילה לראש העמוד