SQL Injection

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

SQL היא השפה של השאילתות וניתן להתקין שרת חינמי שמממש SQL בעצמו למשל MySQL.

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

כיצד אתרי אינטרנט משתמשים ב SQL? ניקח דוגמה של אתר קניות שיש לו בקשת GET מהצורה

../article.php?id=?

שמביאה כתבה לפי מזהה id. אם נסתכל על קוד php שממש את השאילתה זה יראה מהצורה

<?php

$id =  $_GET["id"];
$result = mysqli_query($sqlObject, "SELECT * FROM articles WHERE ArticleId=" . $id);

נניח לרגע שיש לנו טבלת משתמשים ונניח שאימות לאתר נראה ככה:

$password = $_POST["password"];
$user = $_POST["user"];
//..

$query = "SELECT * FROM Users WHERE UserName = '%s' AND Password = '%s'";
mysqli_query($sql, sprintf($query, $user, $password));

בגלל שזה קלט שמגיע מהשתמש היוזר יוכל לנצל את זה ולתת שם מהצורה

user = NonExistentUser' OR 1=1;-- 

בצורה זאת נוכל להזריק שאילתה שתמיד תחזיר true ותיתן לנו גישה לאתר

היה אפשר לכתוב משהו חזק אף יותר למשל

user = NonExistentUser'; DROP TABLE Users; -- 

המחרוזת הזאת תבצע 2 שאילתות, אחת בדיקה על ה USER והשנייה DROP TABLE שתמחק את המסד נתונים.

Single Query Policy

התגוננות מאוד מקובלת נגד ההתקפה הנ״ל היא הגבלה של query אחד בכל קריאה, כלומר כאשר אני אבצע את הפעולה הנ״ל רק החלק של ה SELECT יתבצע. החלק השני לא יתבצע ויכול לזרוק שגיאה או להחזיר אזהרה. מדיניות זאת נקראת Single Query Policy והיא הגנה ברמת הקוד של mysql ולא ברמת התוכנה שלנו.

השגת מידע על הdatabase באמצעות SQLi
האופרטור UNION בשפת SQL מאחד תוצאות של טבלאות. נוכל להשתמש באופרטור זה כדי לקבל מידע על המסד נתונים שמנהל את האתר.

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

Pasted image 20240510234026.png

ב MySQL נוכל ללמוד על שמות הטבלאות ושמות העמודות על ידי ההזרקה לשאילתה המקורית

Screenshot 2024-05-10 at 13.27.34.png

ואז, בהנחה שההזרקה הקודמת הביאה לנו נתונים על שמות הטבלאות והעמודות שלהם נוכל להזריק:
Screenshot 2024-05-10 at 13.27.48.png

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

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

נוכל לצמצם את כמות השאילתות להיות מליניארית ללוגריתמית על ידי שימוש בפקודה ORDER BY . כיוון שפקודה זו מאפשרת למיין לפי אינדקסים של שדות נוכל להריץ ORDER BY בקפיצות של 2 על שנקבל שגיאה וככה באמצעות אלגוריתמים חיפוש בינארי נוכל למצוא כמה עמודות יש בטבלה כדי לבצע UNION.

Screenshot 2024-05-10 at 23.38.58.png

Database passwords

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

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

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

דוגמה נפוצה ל hash היום היא SHA256.
Screenshot 2024-05-10 at 23.50.27.png

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

blind sql

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

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

Screenshot 2024-05-11 at 0.09.11.png

השאילתה תכשל אם התנאי יתקיים כלומר הצלחנו לגלות שהסיסמא של המשתמש מתחילה באות a.

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

למשל התנאי הבא ימתין אך ורק אם התו הראשון של הסיסמה גדול או שווה ל a ואחרת יחזור מיד

Screenshot 2024-05-11 at 12.25.47.png

הגנות על שרתים

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

הדרך הטובה והבטוחה ביותר היום להגן על שרתים באופן שורשי היא עם prepared statements ו quoting שנקראות PDO בשפת PHP.