مبارزه با SQL Injection: قوانین قالب بندی

09 اسفند 1397
درسنامه درس 2 از سری مقابله با SQL Injection
SQL-Injection-formatting-rules

با سلام خدمت شما خوانندگان گرامی، در این مقاله قصد داریم در رابط به قوانین مربوط به قالب بندی (formatting) کوئری ها صحبت کنیم. این قوانین به ما کمک می کنند تا از شر SQL Injection در امان باشیم اما باید توجه داشت که چرا اعمال این قوانین به صورت دستی صحیح نیست و ممکن است باعث مشکلاتی برای برنامه ی ما شود.

قوانین قالب بندی در MySQL

حقیقت این است که قوانین قالب بندی آن قدر ها هم ساده نیستند و نمی شود تمام آن ها را در یک لیست جمع کرد چرا که بر اساس موقعیت های مختلف تغییر می کنند. نکته ی جالب این جاست که معمولا برنامه نویسان با SQL طوری برخورد می کنند که انگار با یک رشته ی literal طرف هستند در حالی که SQL مانند یک برنامه است، مانند یک اسکرپت PHP است و مانند هر برنامه ی دیگری، ساختار و اجزای خاص خود را دارد. بنابراین تک تک این قسمت ها نیاز به قالب بندی خاصی دارد که مخصوص خودش است و برای دیگر اجزا کار نمی کند. این قوانین در MySQL از این قرار هستند:

  • رشته ها:
    • یا باید از طریق prepared statement ها اضافه شوند و یا درون Quotation (علامت های ' ' یا " ") باشند.
    • کاراکتر های خاص (این لیست را مشاهده کنید) باید escape شوند.
    • باید encoding از قبل به صورت واضح مشخص شود و یا بر اساس hex کار کنید.
  • اعداد:
    • باید از طریق prepared statement اضافه شوند.
    • یا فیلتر شوند تا تنها مقادیر عددی اضافه شوند.
  • Identifier ها:
    • باید بین دو علامت backtick قرار بگیرند (علامت ` - کلید بالای کلید تب، سمت چپ و بالا کیبورد)
    • کاراکتر های خاص (این لیست را مشاهده کنید) باید escape شوند.
  • اپراتور ها و کلمات کلیدی:
    • قوانین خاصی برای اپراتور ها و کلمات کلیدی وجود ندارد.

Identifier ها (به معنی «مشخص کننده») در واقع همان نام پایگاه داده، نام جدول، نام ردیف ها و نام ستون ها هستند.

همانطور که میبینید این قوانین در قسمت های مختلف تفاوت دارند بنابراین نمی شود گفت تمام داده هایتان را escape کنید یا همه جا از prepared statement ها استفاده کنید. در مورد prepared statement ها می توانید دو مقاله ی زیر را مطالعه کنید:

اجرای کوئری ها و SQL Injection (قسمت اول)

اجرای کوئری ها و SQL Injection (قسمت دوم)

حتما بسیاری از شما می گویید، من که همیشه این کار ها را انجام می دادم. آیا برنامه ی من امن است؟ خیر! مشکل این است که شما این کارها را به صورت دستی انجام می دهید. شما هیچ گاه نباید به صورت دستی و تک تک این موارد را پیاده سازی کنید بلکه باید مکانیسمی داشته باشید که به طور طور خودکار این کار را برایتان انجام دهد. چرا؟

قالب بندی به صورت دستی

به نظر شما چرا قالب بندی به شکل دستی مشکل دارد؟ به این دلیل که به صورت دستی انجام می شود و هر چیزی که به صورت دستی انجام شود احتمال خطا را ده ها بار افزایش می دهد. قالب بندی به شکل دستی به سطح سواد برنامه نویس، به حال او، به خستگی یا شادی یا غمگین بودن او بستگی دارد و الی آخر.

در واقع متکی بودن یک سیستم به احوال برنامه نویس از مشکلات بزرگ آن محسوب می شود و می توان گفت قالب بندی دستی بزرگترین و شاید تنها دلیل حملات SQL Injection در دنیا است.

اما چرا؟

  • قالب بندی به شکل دستی می تواند ناقص باشد: مثال Bobby Tables را از جلسه ی قبل به یاد دارید؟ این مثال یکی از بهترین نمونه های قالب بندی ناقص بوده که به صورت دستی انجام شده است به این دلیل که رشته ی ما تنها داخل quotation قرار داده شده بود اما escape نشده بود. اگر نگاهی به قوانین بالا بیندازید گفته ایم که در رشته ها علاوه بر استفاده از quotation ها باید از escaping و encoding مناسب نیز استفاده شود. تصور کنید سایت شما هزاران خط کد دارد و رعایت تک تک این موارد واقعا سخت و زمان گیر خواهد بود.
  • ممکن است literal اشتباهی را قالب بندی کنیم: البته تا زمانی که کاملا پیرو قوانین قالب بندی باشیم خطر آنچنانی ندارد چرا که باعث توقف برنامه می شود و در مراحل برنامه نویسی سایت متوجه آن خواهیم شد و آن را تصحیح خواهیم کرد اما اگر قالب بندی به شکل ناقص انجام شده باشد عملا نسخه ی خود را پیچیده ایم! به طور مثال، بسیاری از جواب ها در Stack Overflow می گویند بهتر است Identifier ها را نیز escape کنیم. این حرف کاملا غلط است و ممکن است باعث SQL Injection شود.
  • قالب بندی به صورت دستی کاملا اختیاری است: جدا از اینکه ممکن است حواس ما پرت شود و قالب بندی را به شکل کامل اجرا نکنیم، نکته ی جالب دیگری بین برنامه نویسان PHP دیده می شود. برخی از آن ها اصلا برخی از داده ها را قالب بندی نمی کنند چرا که تصور می کنند داده ها به دسته هایی مانند clean (غیر مضر) و unclean (مضر) یا user input (ورودی کاربر) و non-user input (غیر ورودی کاربر) تقسیم می شوند. تصور اشتباه آنجاست که فکر می کنند داده های امن نیازی به قالب بندی ندارند؛ نام Sarah O'Hara را از جلسه ی قبل به یاد دارید؟ این نام کد مضر نیست بلکه نام یک انسان است اما به خاطر وجود  در فامیل این دختر برنامه ی ما به مشکل می خورد. حواس شما به عنوان برنامه نویس باید به نوع داده ی literal باشد نه منبع داده (داده از کجا آمده است). اگر نوع داده رشته است، بنابراین باید به شکل رشته ها قالب بندی شود. این اختیاری بودن قالب بندی دستی و اینکه می توانیم هر جا خواستیم از آن استفاده نکنیم باعث مشکلات زیادی می شود.
  • قالب بندی دستی می تواند فاصله ی بسیار زیادی با اجرای کوئری داشته باشد: این مشکل تقریبا نادیده گرفته می شود اما مهم ترین مشکل قالب بندی دستی است. در این مورد مفصلا صحبت می کنیم.
  • قالب بندی دستی باعث می شود شلوغی کد و در هم ریختگی آن بیشتر شود در حالی که لازم نبود خودمان را این چنین سر در گم کنیم.

مشکل فاصله ی قالب بندی دستی تا اجرای کوئری بحث بسیار مهمی است. تقریبا همه ی ما وسوسه می شویم که به جای انجام پاک سازی (sanitization) داده در نقاط مختلف برنامه، آن ها را یکجا پاکسازی کنیم. زمانی که این کار را انجام دهیم ممکن است داده ای را پاک سازی کرده باشیم که تا زمان اجرا شدنش فاصله ی بسیار زیاده باقی مانده است. به نظر شما مشکل آن چیست؟

اول از همه باید توجه کنیم که اگر فاصله ی پاک سازی تا اجرا زیاد باشد یعنی کوئری مربوطه جلوی چشممان نیست و در این صورت نمی توانیم مطمئن شویم که این literal چه نوع داده ای است. بنابراین قوانین اول و دوم قالب بندی درجا نقض می شوند.

همچنین اگر بخواهیم از این مشکل دوری کنیم و در چند جای مختلف داده ها را پاک سازی کنیم باز هم با مشکل بزرگی روبرو می شویم؛ ممکن است به اشتباه و به دلیل شباهت برخی داده ها به هم تصور کنیم فلان داده را قبلا پاک سازی کرده ایم و آن را بدون پاکسازی وارد سیستم کنیم. تصور کنید در کار های گروهی که بین چند توسعه دهنده انجام می شود چنین مشکلی چه تبعاتی می تواند داشته باشد!

از طرفی ممکن است یک داده را دو بار پاک سازی کنیم؛ مثلا داده را در هنگام دریافت پاکسازی کرده باشیم اما قبل از اجرا نیز دوباره پاک سازی کنیم. این مورد خطری ایجاد نمی کند اما برای یک توسعه دهنده ی حرفه ای ننگ به حساب می آید.

مشکل آخر نیز از این قرار است که پاک سازی یا قالب بندی قبل از اجرا باعث می شود متغیر ما تغییر کند و دیگر در هیچ جایی به غیر از کوئری قابل استفاده نباشد. بسیاری از اوقات می خواهیم از داده ی ورودی استفاده های دیگری نیز بکنیم چرا که داده های ورودی فقط به درد پایگاه داده نمی خورند اما اگر از همان ابتدا قالب بندی شوند دیگر قابل استفاده نیستند.

راه حل چیست؟

با این تفاسیر راه حل چیست؟ از چه مکانیسمی استفاده کنیم تا از این مشکلات خلاص شویم؟ در جلسه ی بعد به پاسخ این سوال خواهیم رسید ...

امیدوارم از این قسمت لذت برده باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری مقابله با SQL Injection توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.