SQL היא שפת שאילתות עבור טיפול במסדי נתונים רלציוניים (מאורגנים בטבלאות).
השאילתות העיקריות מחולקות לקבוצות
SELECTINSERTUPDATE/DELETESQL היא השפה של השאילתות וניתן להתקין שרת חינמי שמממש 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;--
-- יש רווח' מאחורי הקלעים יגרום לmysql לסגור את המחרוזת אחרי השם NonExistentUser ושאר המחרוזת שכתבנו מבחינת mysql תהיה ההמשך של השאילתה.OR 1=1 - יחזיר תמיד true-- זה תו ב mysql ששם את המשך השאילתה בcomment. כאשר המנוע של mysql יפרש את השאילתה הוא יתעלם מכל בא שבא אחרי התו הזה.בצורה זאת נוכל להזריק שאילתה שתמיד תחזיר true ותיתן לנו גישה לאתר
היה אפשר לכתוב משהו חזק אף יותר למשל
user = NonExistentUser'; DROP TABLE Users; --
המחרוזת הזאת תבצע 2 שאילתות, אחת בדיקה על ה USER והשנייה DROP TABLE שתמחק את המסד נתונים.
התגוננות מאוד מקובלת נגד ההתקפה הנ״ל היא הגבלה של query אחד בכל קריאה, כלומר כאשר אני אבצע את הפעולה הנ״ל רק החלק של ה SELECT יתבצע. החלק השני לא יתבצע ויכול לזרוק שגיאה או להחזיר אזהרה. מדיניות זאת נקראת Single Query Policy והיא הגנה ברמת הקוד של mysql ולא ברמת התוכנה שלנו.
השגת מידע על הdatabase באמצעות SQLi
האופרטור UNION בשפת SQL מאחד תוצאות של טבלאות. נוכל להשתמש באופרטור זה כדי לקבל מידע על המסד נתונים שמנהל את האתר.
נשים לב שכמות העמודות צריכה להיות באותו גודל אצל שתי הטבלאות כדי להחזיר UNION. למשל כדי להשיג את הגרסה של המסד נתונים ניתן להשתמש בפקודה הבאה בכל אחד מהגרסאות

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

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

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

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

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

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

התקיפה הזאת היא מאוד נפוצה ולכן יש גם הרבה שיטות להגן בפניה
Prepared statements - שאילתות מוכנות מראש כלומר השאילתא מוכנה ללא הפרמטרים ורק לאחר מכן הפרמטרים נכנסים. מכיוון שהשאילתה כבר מקומפלת הפרמטרים לא יכולים לשנות את ההתנהגות שלה.
quoting - לקחת מחרוזת אקראית ולהפוך אותה לבטוחה כלומר מחרוזת ששרת ה SQL יתייחס אליה בוודאות בתור נתון ולא בתור קוד.

Access restriction למסד נתונים
Single Query policy
logging
encryption
הדרך הטובה והבטוחה ביותר היום להגן על שרתים באופן שורשי היא עם prepared statements ו quoting שנקראות PDO בשפת PHP.