השתמש בפרמטרי SQL דינמיים בערכות רשומות רגילות ב- Dreamweaver 8.0.2 וב- CS3

מה כלול

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

כדי לשפר את האבטחה ב- Dreamweaver 8.0.2 וב- CS3 ביטלנו את היכולת להשתמש בפרמטרי SQL דינמיים בערכות רשומות רגילות. הקוד המקורי חשף מסדי נתונים ל חדירות SQL. השימוש במשפטים מוכנים עבור שרת מדגמי ASP_VBS ו- ColdFusion טיפל בפגיעוּת האבטחה. היה צורך להוסיף פונקציה לדף כאשר ערכת הרשומות הראשונה מיושמת, ולאחר מכן לקרוא לפונקציה זו מכל ערכות הרשומות בדף עבור PHP_MySQL. טכניקה זו דומה למשפטים המוכנים של ASP_VBS ו- ColdFusion.

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

החדרות SQL ושיטות מניעה

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

דוגמה לשאילתת SQL רגילה:

SELECT * FROM company_com WHERE name_com = '$companyName'

בדוגמה שלמעלה, המשתנה ‎$companyName נקרא משדה טופס הזנה, כך שלמשתמש יש את שליטה מלאה על הערך שיישלח. בתרחיש הטוב ביותר, כאשר משתמש מזין "Adobe" (ללא מרכאות) בקלט המתאים לשם החברה, שאילתת ה-SQL המתאימה תיארה כך:

SELECT * FROM company_com WHERE name_com = 'Adobe'

אך ישנם מקרים שבהם המשתמשים יהיו מעוניינים לפרוץ לאתר האינטרנט ולגנוב מידע רגיש ו/או להרוס את מסד הנתונים. לאור הדוגמה לעיל, ניתן בקלות ליצור חדירת SQL על ידי הזנת המחרוזת הבאה "blabla' OR '1'='1" (ללא המרכאות הכפולות). על ידי הזנת רצף תווים זה, למשתמש תהיה גישה לכל החברות במקום לחברה יחידה. משפט ה-SQL שמבוצע בפועל הוא:

SELECT * FROM company_com WHERE name_com = 'blabla' OR '1'='1'

יתר על כן, אם המשתמש רוצה למחוק את הטבלה company_com כולה, הוא יכול לעשות זאת פשוט על ידי העברת המחרוזת הבאה "x'; DROP TABLE company_com; --‎" (ללא המרכאות הכפולות). להלן שאילתת ה-SQL המלאה:

SELECT * FROM company_com WHERE name_com = 'x'; DROP TABLE company_com; --'

השתמש באחד מהאמצעים הבאים כדי להגן על אתר האינטרנט שלך מפני החדרות SQL:

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

אנו ממליצים על משפטים מוכנים המתוארים ביתר פירוט ב שימוש במשפטים מוכנים. Dreamweaver 8.0.2 ו- CS3 משתמשים בגישת המשפטים המוכנים עבור שרת מדגמי ASP_VBS, ‏ASP_JavaScript, ‏Cold Fusion ו- JSP, ובשיטת הוספת תווי escape לקלט של המשתמש עבור השרת מדגם PHP_MySQL. למידע נוסף על התקפות מסוג החדרת SQL, עיין בסעיף דוגמאות להתקפות מסוג החדרת SQL.

השתמש במשפטים מוכנים

Dreamweaver 8.0.2 ו- CS משתמשים במשפטים מוכנים מכיוון שפתרון זה מבטיח לנו שההחלפה בפועל של פרמטרי ה-SQL בערכים נתונים (שעשויים להישלח דרך כתובת URL) תבוצע בשרת ולא בדף. משפטים מוכנים גם מבטיחים כי לערך המועבר ל- SQL עצמו יש את סוג הנתונים המתאים וכי הוא משתמש בתווי ה-escape המתאימים (אם נאמר כי פרמטר הוא מסוג int, אזי המשתמש לא יוכל לשלוח אות, או אם פרמטר הוא מסוג טקסט, אזי כל המחרוזת שהועברה תוקף כראוי בתווי escape ולמשתמש לא תהיה אפשרות לבצע החדרת SQL).

משפטים מוכנים עבור השרתים מדגמי ASP_VBS ו- ASP_JS

לגבי ASP_VBS ו- ASP_JS, אנו מסתמכים על שכבת מסד הנתונים של ADODB. הדוגמה הבאה מציגה מערך רשומות פשוט של ASP_VBS המשתמש בפרמטר SQL דינמי כדי לסנן תוצאות:

... <% Dim Recordset1__MMColParam Recordset1__MMColParam = "1" If (Request.QueryString("id_com") <> "") Then Recordset1__MMColParam = Request.QueryString("id_com") End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com WHERE id_com = ?" Recordset1_cmd.Prepared = true Recordset1_cmd.Parameters.Append Recordset1_cmd.CreateParameter("param1", 5, 1, -1, Recordset1__MMColParam) ' adDouble Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...

משפטים מוכנים עבור שרת מדגם ColdFusion

ColdFusion מציע תמיכה מובנית במשפטים מוכנים. הדוגמה הבאה מציגה מערך רשומות פשוט של ColdFusion המשתמש בפרמטר SQL דינמי כדי לסנן תוצאות:

... <cfparam name="URL.id_com" default="1"> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com WHERE id_com = <cfqueryparam value="#URL.id_com#" cfsqltype="cf_sql_numeric"> </cfquery> ...

הדמייה של משפטים מוכנים עבור שרת מדגם PHP_MySQL

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

... <?php if (!function_exists("GetSQLValueString")) { function GetSQLValueString($theValue, $theType, $theDefinedValue = "", $theNotDefinedValue = "") { $theValue = get_magic_quotes_gpc() ? stripslashes($theValue) : $theValue; $theValue = function_exists("mysql_real_escape_string") ? mysql_real_escape_string($theValue) : mysql_escape_string($theValue); switch ($theType) { case "text": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "long": case "int": $theValue = ($theValue != "") ? intval($theValue) : "NULL"; break; case "double": $theValue = ($theValue != "") ? "'" . doubleval($theValue) . "'" : "NULL"; break; case "date": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "defined": $theValue = ($theValue != "") ? $theDefinedValue : $theNotDefinedValue; break; } return $theValue; } } $colname_Recordset1 = "-1"; if (isset($_GET['id_com'])) { $colname_Recordset1 = $_GET['id_com']; } mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = sprintf("SELECT * FROM company_com WHERE id_com = %s", GetSQLValueString($colname_Recordset1, "int")); $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...

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

אז איך נוכל להגן מפני החדרות SQL ועדיין לאפשר פרמטרים דינמיים ולדאוג לכך שהקוד הנוצר ניתן לעריכה דרך לוח Server Behaviors ב- Dreamweaver? הפיתרון הוא להכניס את הפרמטרים הדינמיים לשאילתת ה-SQL עצמה, במקום להעביר אותם כפרמטרי SQL קלאסיים (אשר ייעטפו בתווי escape או יצורפו ל- SQL באמצעות משפטים מוכנים).

חשוב: משתמשים אינם יכולים לייצר קוד כזה באמצעות אובייקטים ו-Server Behaviors הנשלחים ב-Dreamweaver 8.0.2 או CS3 (לדוגמה: הכנסת ערכת רשומות דרך לוח Server Behaviors או סרגל Insert). פתרון זה מחייב את המשתמש לערוך את הקוד באופן ידני, או להתקין הרחב של צד שלישי שמייצרת קוד כזה. האחריות לכתיבת קוד מאובטח נופלת על המשתמש או מפתח התוכנה של צד שלישי.

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

אלה המקרים העיקריים שזיהינו ככאלה שפתרון זה ישמש אותם:

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

שני מקרי השימוש יפורטו בפסקאות הבאות.

יצירה/עדכון ידניים של שאילתת SQL לקבלת פרמטרים דינמיים תוך שמירה על ערכת רשומות הניתנת לעריכה מתוך Dreamweaver

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

http://www.mydomain.com/index.php?sortCol=name_com&sortDir=asc

יש צורך בקידוד ידני כדי לבצע משימה זו ב- Dreamweaver באמצעות Server Behaviors. דגימות הקוד שלהלן מציגות את השינויים שיש לבצע עבור כל דגם שרת.

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

ASP_VBS

לפני:

... <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...

אחרי:

... <% Dim orderBy: orderBy = "" If (Request.QueryString("sortCol") <> "") Then orderBy = "ORDER BY " & Request.QueryString("id_com") If (Request.QueryString("sortDir") <> "") Then orderBy = orderBy & " " & Request.QueryString("sortDir") End If End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com " & orderBy & "" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...

ColdFusion

לפני:

... <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com </cfquery> ...

אחרי:

... <cfparam name="URL.sortCol" default=""> <cfparam name="URL.sortDir" default="ASC"> <cfset orderBy=""> <cfif (#URL.sortCol# NEQ "")> <cfset orderBy="ORDER BY #URL.sortCol# #URL.sortDir#"> </cfif> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com #orderBy# </cfquery> ...

PHP_MySQL

לפני:

... <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com"; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...

אחרי:

... <?php $orderBy = ""; if (isset($_GET['sortCol'])) { $orderBy = "ORDER BY " . $_GET['sortCol']; if (isset($_GET['sortDir'])) { $orderBy .= " " . $_GET['sortDir']; } } ?> <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com " . $orderBy . ""; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...

צור/שדרג הרחבות קיימות כדי ליצור פרמטרים דינמיים תוך שמירה על ערכת רשומות הניתנת לעריכה מתוך Dreamweaver

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

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

אנו מספקים הרחבת הדגמה שמקבלת חיבור וטבלה כקלט ומייצרת טבלה דינמית המציגה את כל הרשומות. ההרחבה גם מעדכנת את ה- SQL שנוצר כך שיכיל את המשתנה orderBy כמוצג בדוגמאות שלעיל. ההרחבה תוכננה עבור Dreamweaver 8.0.2 ו- CS3. הקוד שנוצר אינו בטוח לחלוטין, שכן המטרה היא ליצור ערכות רשומות המכילות פרמטרים דינמיים שעדיין ניתנות לעריכה באמצעות ממשקי Dreamweaver רגילים. קוד המקור של ההרחבה נמצא ב תיקיית תצורת המשתמש.

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

ASP_VBS

הקוד שמייצר את שאילתת ה-SQL המתאימה עבור השרת מדגם ASP_VBS נמצא בקובץ "‎[USER_CONFIGURATION_FOLDER]/Commands/My Dynamic Table.js" שב תיקיית תצורת המשתמש. השינוי הבולט נמצא בשורה 130:

... 130: paramObj.encodedSQL = "SELECT * FROM " + paramObj.table + " \" & orderBy & \""; ...

קובץ המשתתפים עבור התנהגות השרת MyDynamicTable ASP_VBS כולל משתתף נוסף שמוסיף את הגדרת המשתנה "orderBy" לדף:

<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="connectionref_statement" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="recordset_main" /> <groupParticipant name="repeatedRegion_init2" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="recordset_close" /> </groupParticipants> </group>

ColdFusion

אותו קובץ "‎[USER_CONFIGURATION_FOLDER]/Commands/My Dynamic Table.js" ב תיקיית הגדרת המשתמש גם מכיל את הקוד עבור השרת מדגם ColdFusion; השינוי הרלוונטי במקרה זה נמצא בשורה 138:

... 138: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table + " #orderBy#"; ...

קובץ המשתתפים עבור התנהגות השרת MyDynamicTable ColdFusion כולל משתתף נוסף שמוסיף את הגדרת המשתנה "orderBy" לדף:

<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="RepeatedRegion_pageNum" /> <groupParticipant name="RepeatedRegion_maxRows" /> <groupParticipant name="RepeatedRegion_startRow" /> <groupParticipant name="RepeatedRegion_endRow" /> <groupParticipant name="RepeatedRegion_totalPages" /> </groupParticipants> </group>

PHP_MySQL

דגם השרת האחרון בהרחבה הוא PHP_MySQL. השינוי הרלוונטי בקובץ "‎[USER_CONFIGURATION_FOLDER]/Commands/My Dynamic Table.js" ב תיקיית הגדרת המשתמש נמצא בשורה 122:

... 122: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table+ " \"‎. $orderBy . \""; ...

קובץ המשתתפים עבור התנהגות השרת MyDynamicTable PHP_MySQL כולל משתתף נוסף שמוסיף את הגדרת המשתנה "orderBy" לדף:

<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="Connection_include" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="EditOps_SQLValueString" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="Recordset_close" /> </groupParticipants> </group>

יתרונות

לשיטה שתוארה לעיל מספר יתרונות, כולל:

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

חסרונות

מכיוון ש- Dreamweaver לא תוכנן כברירת מחדל לתמוך בשיטה זו, יש לה גם כמה חסרונות, כולל:

  • הקוד שנוצר בשיטה המתוארת במסמך זה פגיע להחדרות SQL ולכן יש להגן עליו בדרכים אחרות. שיטות ההגנה מסוג זה כוללות, בין היתר, את השיטות הבאות:
    • ודא שמשתמש הקצה לא יכול להשתמש בשיטה של החדרת SQL כדי לבנות את דף האינטרנט ו/או את מסד הנתונים;
    • הוסף תווי Escape לארגומנטים לפני שהם מגיעים לשאילתת ה- SQL עצמה;
    • ודא שגם אם המשתמש מצליח לפרוץ לקוד שלך, הוא לא יגנוב מידע רגיש ו/או לא יבנה את מסד הנתונים.
  • שתי בעיות ידועות שלא ניתן לתקן עד שתצא הגרסה הבאה של Dreamweaver:
    • ColdFusion: ממשק משתמש פשוט של ערכת רשומות מסיר את קודי ה-hash סביב שמות המשתנים משאילתת ה-SQL בעת העריכה ולאחר מכן מחיל אותם מחדש
    • ColdFusion, ‏ASP_VBS, ‏ASP_JS: לחצן הבדיקה מממשק המשתמש המתקדם של ערכת הרשומות אינו פועל כאשר שאילתת SQL מכילה שמות משתנים

איתור תיקיית תצורת המשתמש

  • Windows XP

    C:\Documents and Settings\[username]\Application Data\Adobe\Dreamweaver 9\Configuration
  • Windows Vista

    C:\Users\[username]\AppData\Roaming\Adobe\Dreamweaver 9\Configuration
  • Mac OS 10.4.x

    ‏/Users/[username]/Library/Application\ Support/Adobe/Dreamweaver\ 9/Configuration

קבל עזרה במהירות ובקלות

משתמש חדש?