לדלג לתוכן

אבטחת קוד בפייתון - טעויות נפוצות שכל מתכנת עושה בהתחלה

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

סודות שמוטמעים ישירות בקוד

הטעות הכי נפוצה, ובד"כ הראשונה שכל מתכנת עושה - שמירת מפתחות API, סיסמאות למסד נתונים, או טוקנים ישירות בקוד המקור:

# אל תעשו ככה
API_KEY = "sk-live-abc123realkey456"
DB_PASSWORD = "MyPassword123"

הבעיה היא לא רק שמישהו שיציץ בקוד יראה את הסוד - הבעיה היא שברגע שהקוד עולה ל-git, הסוד נמצא שם לנצח, גם אם תמחקו אותו בהמשך, כי הוא נשאר בהיסטוריה. הדרך הנכונה היא לשמור סודות במשתני סביבה, מחוץ לקוד לגמרי:

import os

API_KEY = os.environ["API_KEY"]

ואת הערך עצמו שמים בקובץ .env שלא נכנס ל-git (מוסיפים אותו ל-.gitignore), או מגדירים אותו ישירות בשרת שמריץ את הקוד.

שימוש ב-eval ו-exec על קלט לא בטוח

eval() ו-exec() מריצים מחרוזת כאילו היא קוד פייתון. זה נשמע נוח - למשל, מחשבון קטן שמריץ את מה שהמשתמש הקליד:

# מסוכן מאוד אם user_input מגיע ממשתמש
result = eval(user_input)

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

הזרקת SQL - SQL injection

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

# מסוכן - הזרקת SQL אפשרית
username = user_input
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)

אם משתמש יזין כ-username משהו כמו ' OR '1'='1, השאילתה תשתנה לגמרי ותחזיר את כל המשתמשים, או גרוע מזה. הפתרון הוא שאילתות מפורמטות (parameterized queries), שבהן מעבירים את הערך בנפרד ולא כחלק מהמחרוזת:

# בטוח - הערך מועבר בנפרד מהשאילתה
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))

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

אי-אימות קלט מהמשתמש

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

def get_age(data):
    return int(data["age"])  # מה קורה אם data["age"] הוא "abc" או שלילי?

עדיף תמיד לבדוק ולתקף לפני שמשתמשים בערך:

def get_age(data):
    age = data.get("age")
    if not isinstance(age, int) or age < 0 or age > 150:
        raise ValueError("גיל לא תקין")
    return age

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

שימוש ב-pickle על קלט לא מהימן

pickle הוא מודול פייתון שהופך אובייקטים למחרוזת בייטים ובחזרה, נוח מאוד לשמירת נתונים. הבעיה - pickle.loads על נתונים שמגיעים ממקור לא מהימן יכול להריץ קוד שרירותי במחשב שלכם, לא רק לטעון נתונים:

# מסוכן אם data מגיע ממקור חיצוני לא מהימן
import pickle
obj = pickle.loads(data)

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

תלויות לא מעודכנות

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

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

לסיכום

אף אחת מהטעויות האלה לא דורשת ידע מתקדם כדי להימנע ממנה - כולן דורשות רק מודעות. סודות במשתני סביבה, לא ב-eval על קלט זר, שאילתות מפורמטות במקום הדבקת מחרוזות, אימות קלט, לא pickle על נתונים לא מהימנים, ותלויות מעודכנות. שישה כללים פשוטים שחוסכים לכם את רוב הבעיות האמיתיות.

הצטרפו לקהילה בדיסקורד