با سلام و احترام خدمت شما همراهان گرامی روکسو، در این قسمت از سری آموزشی مقابله با SQL Injection می خواهیم در رابطه با کوئری هایی صحبت کنیم که به صورت پویا (dynamic) ساخته می شوند.
یکی از مباحثی که لازم در این دوره مورد بررسی قرار بگیرد بحث ساخت کوئری های پیچیده است. یکی از مثال های خوب در این زمینه، قسمت جست و جوی پیشرفته سایت ها است. به طور مثال برخی از فروشگاه های اینترنتی به شما اجازه می دهند با فاکتور های مختلف جست و جو را انجام دهید (رنگ محصول، قیمت، نام شرکت سازنده و ...). در این حالت کوئری ما به صورت پویا و طبق خواسته ی کاربر ساخته می شود. به عبارت دیگر، هر جایی که طبق خواسته ی کاربر قسمت هایی از کد SQL به کوئری اضافه شود یا قسمت هایی از آن حذف شود در این دسته قرار می گیرند.
در چنین حالت هایی امکان استفاده از placeholder ها وجود ندارد بنابراین به مکانیسم دیگری نیاز داریم. اگر قسمت مورد نظر سایت شما (مثلا جست و جوی پیشرفته) پیچیدگی های زیادی نداشته باشد می توانید از query builder (به معنی سازنده ی کوئری) ها استفاده کنید. مکانیسم عمل این query builder ها به شکل زیر است:
$query = $users = DB::table('users')->select('*'); if ($fname = input::get('first_name')) { $query->where('first_name = ?', $fname); } if ($lname = input::get('last_name')) { $query->where('last_name = ?', $lname); } // و همینطور الی آخر $results = $query->get();
اما اکثر اوقات کوئری هایی داریم که پیچیدگی بسیار بیشتری دارند و استفاده از query builder ها آنچنان سخت می شود که دیگر توجیه عقلانی ندارد. ما همیشه میدانیم که تمام قسمت های پویای یک کوئری با placeholder ها وارد کوئری اصلی می شوند اما برای این حالت یک حقه ی بسیار خوب سراغ داریم:
در وب سایت stackoverflow.com سوالی در این رابطه پرسیده شده است و جواب آن (با آنکه ساده است) از جواب های زیرکانه و بسیار خوبی می باشد که در این چند سال دیده ام. فرد سوال کننده گفته است قصد طراحی یک سیستم جست و جو در وب سایتش را دارد که فاکتور های مختلف آن را کاربران تعیین می کنند. از طرفی ممکن است کاربر برخی از این فاکتور ها را رها کند (به طور مثال سن و سال برایش مهم نباشد). پرسشگر می خواهد بداند چطور ممکن است در این شرایط کوئری ای بسازد که تمام حالات مختلف (مانند رها کردن سن و سال توسط کاربر) را داشته باشد و قابل اجرا نیز باشد. مثالی که خود پرسشگر ذکر کرده است از این قرار است:
$sql = 'SELECT * FROM people WHERE first_name = :first_name AND last_name = :last_name AND age = :age AND sex = :sex'; $query = $db->prepare($sql); $query->execute(array(':first_name' => 'John', ':age' => '27');
جوابی که دریافت کرده است کد زیر می باشد:
SELECT * FROM people WHERE (first_name = :first_name or :first_name is null) AND (last_name = :last_name or :last_name is null) AND (age = :age or :age is null) AND (sex = :sex or :sex is null)
اگر پارامتر null
را به موارد رها شده بدهیم دیگر جای نگرانی نیست. البته در این حالت اگر از PDO استفاده می کنید باید emulation mode
روی حالت ON
باشد. در واقع با نگاه به کد بالا متوجه می شوید که تنها کافی است متغیر هایمان را به placeholder ها bind کنیم (یا مقداری دارند و یا null
می شوند). اگر چنین کاری انجام دهیم، آن هایی که مقادیر null
دارند دور انداخته می شوند و فقط آن هایی که مقدار مشخصی دارند درون کوئری قرار می گیرند.
به هر حال همیشه به یاد داشته باشید که کوئری نهایی باید تنها از طریق دو منبع ساخته شود: قسمت های ثابت (constant) و یا placeholder ها. بنابراین می توان به شکل خلاصه گفت هر کوئری SQL تنها می تواند از دو نوع داده ساخته شود:
اگر از این قانون پیروی کنید در مقابل تزریق SQL امن خواهید بود.
چند مورد از اشتباهات رایج برنامه نویسان در این زمینه از قرار زیر اند:
magic quotes
: هیچ گاه از این ویژگی استفاده نکنید. این ویژگی دقیقا پیاده سازی اشتباهات بالا (escape کردن داده های کاربر) بود که خوشبختانه در حال حاضر از زبان SQL حذف شده است. اگر هم نمی دانید این ویژگی چیست، چه بهتر! وقت خود را تلف نکنید!()filter_var
و ()strip_tags
): از اسم این مورد پیداست! اسمش HTML است یعنی مربوط به SQL نیست و شما نباید آن را به تزریق SQL ربط دهید. تمام این مواردی که من ذکر می کنم کارایی های خاص خودشان را دارند و اینطور نیست که بگویم بی استفاده هستند، بلکه می گویم در زمینه ی محافظت در برابر تزریق SQL بی اهمیت هستند. قالب بندی SQL هیچ گاه نباید داده ها را تغییر دهد! مثلا زمانی که جواهرات خود را داخل گاوصندوق می گذارید تا از آن ها محافظت کنید انتظار دارید بعدا همان جواهرات را دست نخورده بردارید نه اینکه قسمتی از آن تغییر کرده باشد! در زبان SQL نیز همین مسئله برقرار است؛ کار پایگاه داده ذخیره سازی داده است نه محافظت کردن و تغییر دادن آن.اما قبل از پایان این دوره به برخی از سوالاتی که حس می کنم ممکن است در ذهنتان خطور کند پاسخ می دهم:
- این دوره بسیار کوتاه بود. چرا؟
پاسخ: این دوره مکمل دوره ی آموزشی PDO بود به همین دلیل کوتاه و مختصر از آب در آمد. اکثر موضوعات اصلی در آن دوره گفته شد و بازتوضیح آن ها در این دوره فقط تکرار مکررات محسوب می شد بنابراین به شما پیشنهاد می کنم حتما آن دوره را نیز چک کنید.
- چرا به طور خاص از PDO یا MYSQL استفاده نشد؟
پاسخ: در این دوره سعی کردیم توجه خود را روی مبحث SQL Injection (به عنوان یک مبحث فارغ از وسیله های مختلف) متمرکز کنیم تا مباحث کلی و قابل تعمیم باشند و شما بتوانید آن را در MySQL یا PDO یا هر روشی که دارید، پیاده کنید.
از شما تشکر می کنم که همراه بنده بودید و امیدوارم این دوره به شما و امنیت وب سایتتان کمک کرده باشد.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.