به عنوان یک توسعه دهنده که برنامه ایجاد می کند، قدرت بالایی در نوک انگشتان خود دارید. با این حال، مسئولیت ایمن نگه داشتن داده هایی که برنامه شما ذخیره می کند نیز بر عهده شماست. زبان کوئری ساختاریافته (SQL) یک سیستم مدیریت پایگاه داده رابطه ای برای توسعه نرم افزار است. تزریق SQL یا SQL Injection یک مشکل امنیتی قدیمی و بسیار رایج در SQL است.
در این پست، نحوه انجام تزریق SQL را در برنامه لاراول توضیح خواهیم داد و چند تکنیک پیشگیری را پیشنهاد می کنیم. ابتدا نگاهی به تزریق SQL خواهیم داشت. پس از آن، نمونههایی از تزریق SQL در لاراول و راههای جلوگیری از آنها را خواهیم دید.
ممکن است در هنگام استفاده از یک چارچوب یا ابزار با این فرض که نگهبانان ابزار همه چیز مربوط به امنیت را پوشش داده اند، احساس ایمنی کاذب داشته باشید. لاراول راه های ایمن بسیاری برای کار با SQL ارائه می دهد. با این حال، هنوز ارزش یادگیری در مورد مواردی را دارد که پوشش داده نشده است وجود دارد. به خواندن ادامه دهید تا در مورد برخی از موارد استفاده و ویژگی های لاراول که ممکن است برنامه شما را در معرض تزریق SQL قرار دهد آشنا شوید.
قبل از اینکه به نمونه هایی از تزریق SQL لاراول بپردازیم، بیایید نگاهی به تزریق SQL بیندازیم.
تزریق SQL چیست؟
تزریق SQL یا SQL injection نوعی حمله سایبری است که در آن کد SQL مخرب در فیلدهای ورودی یک برنامه وب قرار می گیرد و از آسیب پذیری ها در لایه پایگاه داده برنامه سوء استفاده می کند. مهاجم می تواند داده ها را دستکاری یا بازیابی کند، ورودی های پایگاه داده را اصلاح کند و حتی عملیات های اداری را اجرا کند.
به زبان خیلی ساده بخواهیم این نوع حمله را توضیح دهیم:
یک هکر می تواند کدهای مخرب را تزریق کند و حتی عملیات جدی تری را در پایگاه داده شما انجام دهد.
حملات تزریق SQL بر سازمان های اصلی نیز تأثیر می گذارد.
در سال 2011، شبکه سونی مورد حمله شدید SQL Injection قرار گرفت که زیرساخت دیجیتال آن را به خطر انداخت. حدود 77 میلیون اکانت شبکه پلی استیشن تحت تأثیر قرار گرفتند و هزینه ای بالغ بر 170 میلیون دلار برای سونی به همراه داشت.
در اکتبر 2023، هکرهایی مشاهده شدند که سعی داشتند از طریق سرورهای مایکروسافت SQL آسیب پذیر در برابر تزریق SQL به محیط های ابری نفوذ کنند. این تکنیک حرکت جانبی قبلاً در حملات به سرویسهای دیگر مانند VMs و خوشههای Kubernetes دیده شده است.
ابتدا، تزریق SQL را با استفاده از یک مثال عملی توضیح خواهیم داد. فرض کنید یک برنامه وب دارید که نمایه کاربر را از طریق URL زیر نمایش می دهد:
https://api.anophel.com/users/?user=john.doe
برای واکشی دادهها برای کاربر فعلی john.doe
، کوئری زیر اجرا میشود:
SELECT * FROM users WHERE username = 'john.doe'
در تلاش برای انجام تزریق SQL، یک هکر می تواند مقدار پارامتر کاربر را به چیزی شبیه به این تنظیم کند:
--;https://example.com/profile.php/?user=john.doe’ OR 2=2
برای درخواست HTTP فوق، کوئری SQL زیر اجرا خواهد شد:
SELECT * FROM users WHERE username = 'john.doe' OR 2=2;--'
کوئریی SQL بالا همیشه true خواهد بود زیرا 2=2 همیشه درست است و با کلمه کلیدی OR، اگر یک طرف true باشد، کل عبارت true خواهد بود. بنابراین، کوئریی بالا تمام سطرهای جدول «کاربران» را برمی گرداند. این یک مثال معمولی از تزریق SQL است.
با آسیبپذیری مانند آنچه در بالا نشان داده شد، یک هکر میتواند کدهای مخرب را تزریق کند و حتی عملیات جدیتری را بر روی پایگاه داده شما انجام دهد. در ادامه مفصل میتوانید انواع مختلف تزریق SQL و بهترین روشهای توصیه شده بیشتر بدانید.
چه نوع تزریق SQL وجود دارد؟
سه نوع اصلی تزریق SQL وجود دارد: SQLi درون باند، SQLi استنتاجی و SQLi خارج از باند.
SQLi درون باند
هنگامی که یک مهاجم یک تزریق SQL را از همان مکانی که برای جمع آوری خروجی تزریق استفاده می شود، آغاز می کند، به آن SQLi درون باندی می گویند. این یکی از رایج ترین انواع حملات SQLi است و اغلب به SQLi مبتنی بر خطا و SQLi مبتنی بر UNION تقسیم می شود.
SQLi مبتنی بر خطا
SQLi مبتنی بر خطا نوعی تزریق SQL است که پیام های خطای پرتاب شده توسط پایگاه داده را خروجی می دهد. این نوع تزریق می تواند برای دادن اطلاعات ارزشمند در مورد پایگاه داده مانند اندازه و عناصر آن به مهاجمان استفاده شود. در بسیاری از مواقع، مهاجمان از یک SQLi مبتنی بر خطا برای انجام شناسایی روی پایگاه داده استفاده میکنند تا در نهایت تزریق SQL را برای انجام کارهای پیچیدهتر مانند خروجی دادهها اجرا کنند.
نمونه ای از یک SQLi مبتنی بر خطا
وضعیتی را در نظر بگیرید که در آن با برنامه وب خود که اکنون در حال تولید است، خطا ایجاد کرده اید. به منظور جمع آوری اطلاعات در مورد پایگاه داده شما، مهاجم ورودی کاربر را با چیزی تغییر می دهد که می داند یک خطا را نشان می دهد.
این منجر به کوئری SQL می شود:
SELECT * FROM users WHERE id = '42''
که به دلیل علامت تیک خارجی در انتها خطا ایجاد می کند. اگر ورود خطا روشن باشد، خطا به مهاجم ارائه می شود (البته خطا ترجمه شده):
خطا: شما یک خطا در سینتکس SQL خود دارید. کتابچه راهنمای مربوط به نسخه سرور MySQL خود را برای سینتکس مناسب برای استفاده در نزدیکی… بررسی کنید.
مهاجم اکنون می داند که برنامه وب در برابر SQLi مبتنی بر خطا آسیب پذیر است و می تواند با فراخوانی پیام های خطای آموزنده تری که اطلاعاتی در مورد پایگاه داده ارائه می دهد از این مزیت استفاده کند.
برای جلوگیری از این امر، ثبت خطا باید در یک برنامه وب زنده غیرفعال شود یا باید به یک فایل محدود خروجی داده شود.
SQLi مبتنی بر UNION
یک SQLi مبتنی بر UNION از عملگر UNION برای خروجی داده های اضافی در یک نتیجه واحد، معمولاً به جدولی که قبلاً قابل مشاهده است در برنامه وب استفاده می کند. به منظور اجرای موفقیت آمیز یک SQLi مبتنی بر UNION، مهاجم باید اطلاعاتی در مورد پایگاه داده مانند نام جدول، تعداد ستون ها در کوئری و نوع داده داشته باشد. دلیل این امر این است که برای موفقیت یک UNION، کوئری های SELECT در حال UNION باید:
تعداد ستون های یکسانی داشته باشید.
انواع داده های سازگار داشته باشید.
یکی از راههایی که میتوان این دادههای شناسایی را جمعآوری کرد، از طریق یک SQLi مبتنی بر خطا در زمانی که ثبت خطا فعال است، است. اطلاعات جمعآوریشده از لاگهای خطا ممکن است اطلاعات کافی را در اختیار مهاجم قرار دهد تا اندازه جدول و انواع دادههای مورد استفاده را درک کند.
نمونه ای از یک SQLi مبتنی بر UNION
موقعیتی را در نظر بگیرید که در آن مهاجم موفق به جمع آوری اطلاعات در مورد اندازه جدول، نوع داده در حال استفاده، و نام جدول دوم، نام ها شده است.
http://mycoolapp.com/allusers.php?id=' UNION SELECT * FROM names --
این منجر به کوئری SQL می شود:
SELECT * FROM users WHERE id ='' UNION SELECT * FROM names -- ' and password = 'abcd'
--
یک کامنت در SQL است، بنابراین هر چیزی بعد از آن --
به طور خودکار کامنت داده می شود. دستور SELECT اولیه یک مجموعه تهی را برمی گرداند، زیرا هیچ کاربری با id ” در کاربران وجود ندارد، در حالی که دستور SELECT دوم تمام اطلاعات را در نام ها برمی گرداند.
Blind SQLi
Blind SQLi بسیار شبیه به In-Band SQLi است، با یک تفاوت: پاسخ های برنامه وب نتایج کوئری یا خطاهای پایگاه داده را خروجی نمی دهند. اساساً، توسعهدهنده برنامههای وب، پیامهای خطا را از پایگاه داده سرکوب میکند و استفاده از تزریق SQL را برای مهاجمان دشوارتر میکند. با این حال، این مشکل تزریق SQL را حل نمی کند. مهاجمان هنوز هم می توانند از Blind SQLi استفاده کنند که به دو شکل وجود دارد: SBlind SQLi مبتنی بر محتوا و Blind SQLi مبتنی بر زمان.
Blind SQLi مبتنی بر محتوا
Blind SQLi مبتنی بر محتوا از کوئریها برای پاسخ های شرطی به جای خروجی داده ها استفاده می کند. این کوئریهای SQL از پایگاه داده سوالات درست یا نادرست می پرسند تا مهاجم بتواند خروجی را ارزیابی کند و تشخیص دهد که آیا یک برنامه وب آسیب پذیر است یا خیر. این می تواند بسیار خسته کننده باشد، بنابراین مهاجمان گاهی اوقات این حملات را خودکار می کنند.
نمونه ای از یک SQLi کور مبتنی بر محتوا
بازگشت به مثال برنامه جالب ما. وضعیتی را در نظر بگیرید که در آن اقدامات احتیاطی انجام داده اید و خروجی های نتایج کوئری یا خطاهای پایگاه داده را نشان نمی دهید. به منظور جمع آوری اطلاعات شناسایی در مورد پایگاه داده شما، مهاجم کوئریهایی را به امید بازگشت غلط به سیستم تزریق می کند.
این منجر به کوئری SQL می شود:
SELECT * FROM users WHERE id = 42 and 4=1
البته چهار برابر یک نیست. اگر برنامه در برابر یک Blind SQLi مبتنی بر محتوا آسیب پذیر باشد، چیزی از این کوئری برگردانده نمی شود یا صفحه به نحوی با عملکرد عادی متفاوت است. اگرچه این لزوماً آسیب پذیر بودن برنامه را تأیید نمی کند، ممکن است در واقع نشانه خوبی برای مهاجم باشد. برای تأیید آسیبپذیری برنامه، مهاجم یک کوئری تزریق میکند که باید true باشد و خروجی را مشاهده کند.
این منجر به کوئری SQL می شود:
SELECT * FROM users WHERE id = 42 and 4=4
این مقدار true را برمی گرداند و داده ها را برای کاربر با شناسه 42 خروجی می دهد.
اگر برنامه وب پاسخ متفاوتی به بازگرداندن صحیح پایگاه داده نسبت به پاسخ غلط داشته باشد، مهاجم می داند که برنامه در برابر تزریق SQL آسیب پذیر است. با ادامه استفاده از تست های درست/نادرست در برابر پایگاه داده، مهاجم می تواند اطلاعات اضافی در مورد آن و حتی محتویات خود پایگاه داده را بیابد.
چرا لاراول ممکن است در برابر تزریق SQL آسیب پذیر باشد؟
در اینجا دلایلی وجود دارد که برنامه های لاراول ممکن است مستعد تزریق SQL باشند:
کوئریهای خام: استفاده از کوئریهای خام SQL به جای کوئری ساز لاراول یا Eloquent ORM می تواند خطر تزریق SQL را در صورت عدم پاکسازی ورودی به درستی افزایش دهد. برای آشنایی با 20 ترفند الکونت ها در لاراول این مقاله را بررسی کنید.
اعتبار سنجی ورودی ناکافی: عدم اعتبارسنجی مناسب ورودی های کاربر می تواند برنامه ها را در معرض حملات تزریق قرار دهد. همیشه ورودی های کاربر را قبل از استفاده در جستارهای پایگاه داده اعتبارسنجی و پاکسازی کنید. برای آشنایی با هنر اعتبارسنجی در لاراول این مقاله را بررسی کنید.
ساخت کوئری ناامن: استفاده نادرست از سازنده کوئری لاراول یا ORM Eloquent، مانند الحاق ورودی های کاربر به طور مستقیم به کوئریها بدون استفاده از اتصال های پارامتری، می تواند منجر به آسیب پذیری شود.
عدم استفاده از دستورات پارامتری شده: عدم استفاده از دستورات پارامتری شده در کوئری های آماده شده می تواند برنامه ها را در معرض تزریق SQL قرار دهد. کوئریهای پارامتری به طور خودکار پاکسازی ورودی را کنترل می کنند و خطر حملات تزریق را کاهش می دهند.
کتابخانههای قدیمی یا نسخههای چارچوب: استفاده از نسخههای قدیمی لاراول یا کتابخانههای مرتبط ممکن است برنامهها را در معرض آسیبپذیریهای امنیتی شناختهشده قرار دهد. برای بهره مندی از وصله های امنیتی، مرتباً به آخرین نسخه ها به روز رسانی کنید. برای آشنایی با ویژگی های جدید لاراول 11 این مقاله را بررسی کنید.
لاراول و SQL Injection
لاراول یک فریم ورک PHP منبع باز رایگان است. از دیزاین پترن MVC پیروی می کند و دارای ابزارهای داخلی برای انجام وظایفی مانند احراز هویت کاربر، مسیریابی و عملیات پایگاه داده است.
با کمک ORM Eloquent، می توانید یک برنامه کوچک لاراول بسازید که بدون نوشتن یک کوئریی خام SQL، داده ها را در پایگاه داده SQL می خواند و می نویسد. به این معنی که برای خواندن داده ها از SQL نیازی به نوشتن کوئریهایی مانند "SELECT * FROM"
ندارید. با این حال، لاراول از Query SQL خام پشتیبانی می کند، زیرا کار مورد نظر شما ممکن است در برخی موارد به کوئریهای خام نیاز داشته باشد.
اکنون به چند نمونه از تزریق SQL لاراول و راه های ممکن برای جلوگیری از حملات نگاه می کنیم.
مثال 1: استفاده از RawMethods
RawMethods متد ساده لاراول است که به توسعه دهندگان اجازه می دهد از کوئریهای خام فقط در بخش های خاصی از کوئری پایگاه داده استفاده کنند. برخی از نمونههای RawMethods لاراول عبارتند از selectRaw
، whereRaw
و orderByRaw
. با این حال، RawMethods
در برابر تزریق SQL آسیبپذیر هستند، که اسناد رسمی در جمله زیر بیان میکنند: «به یاد داشته باشید، لاراول نمیتواند تضمین کند که هر درخواستی که از عبارات خام استفاده میکند در برابر آسیبپذیریهای تزریق SQL محافظت میشود.»
برای نشان دادن تزریق SQL در WhereRaw
RawMethod
، اجازه دهید نگاهی به کد زیر بیاندازیم:
DB::table('posts')
->select('postTitle', 'postBody')
->whereRaw('id =' . $id)->first();
نمونه کد بالا باید یک ردیف از جدول پست ها را برگرداند. یا اگر هیچ پستی با id
مشخص شده وجود نداشته باشد هیچ. با این حال، این کد یک رفتار ناخواسته سوم دارد.
مقدار id با ورودی کاربر تعریف می شود. بیایید ببینیم وقتی یک کاربر مقدار زیر را وارد می کند چه اتفاقی می افتد:
https://example.com/post/11 AND 1=1
درخواست HTTP بالا منجر به اجرای کوئریی SQL زیر می شود:
SELECT postTitle, postBody FROM posts WHERE id = 11 AND 1=1
برنامه طبق انتظار، ردیف را با id 11 برمی گرداند. این به این دلیل است که 1=1
همیشه درست است. با این حال، بگویید یک هکر 1=1
را به چیزی که همیشه نادرست است تغییر می دهد، به عنوان مثال:
SELECT postTitle, postBody FROM posts WHERE id = 11 AND 1=2
این باعث می شود برنامه صفر ردیف را برگرداند یا از کار بیفتد. این رفتار وجود آسیبپذیری تزریق SQL را در قسمت WhereRaw
کوئری اولیه ما نشان میدهد.
جلوگیری
ما به هر کسی که از لاراول استفاده می کند توصیه می کنیم تا جایی که می تواند از درخواست های خام خودداری کند.
با انجام این کار، آنها می توانند از برخی از ویژگی های امنیتی که قبلاً در چارچوب تعبیه شده است لذت ببرند. اما اگر باید از کوئریهای خام استفاده کنید، باید مطمئن شوید که ورودی های کاربر را از سمت سرور تأیید می کنید.
یکی از راههای رفع آسیبپذیری در مثال ما این است که تأیید کنیم که مقدار id یک عدد صحیح است. با کد زیر می توانید این کار را انجام دهید:
$validator = Validator::make(['id' => $id], [
'id' => 'required|numeric'
]);
if ($validator->fails()) {
abort(404);
}else {
//Run query
}
راه حل دیگر این است که کوئری اولیه را با استفاده از یک کوئریی پارامتری بازنویسی کنید.
DB::table('posts')
->select('postTitle', 'postBody')
->whereRaw('id = ?', $id)->first();
ما یک ?
به عنوان یک مکان نگهدار برای مقدار id و مقدار واقعی id را به عنوان پارامتر دوم برای WhereRaw
ارائه کرد.
مثال 2: استفاده از DB::statement
اگر تنها کاری که می خواهید انجام دهید این است که یک کوئری را اجرا کنید، لاراول متد DB::statement
را برای آن دارد. کوئریی خام SQL را به عنوان یک پارامتر می پذیرد، اما به طور کامل توسط ویژگی های امنیتی داخلی لاراول پوشش داده نمی شود. برای نشان دادن نحوه عملکرد دستور DB اجازه دهید نگاهی به کد زیر بیاندازیم:
DB::statement("UPDATE users SET password=".$newPassword. " WHERE username =" . $username);
کد بالا باید رمز عبور یک کاربر خاص را با به روز رسانی یک ردیف صحیح در جدول کاربران تغییر دهد. با این حال، در شرایطی که کاربر «Anybody OR 1=1» را بهعنوان نام کاربری و «123456» را بهعنوان رمز عبور وارد میکند، نتیجه متفاوتی ایجاد میشود.
کوئری زیر اجرا خواهد شد:
UPDATE users SET password="123456 " WHERE username ="Anyone" OR 1=1
کوئریی بالا رمز عبور همه کاربران را روی "123456" تنظیم می کند. با این نوع سوء استفاده، یک هکر می تواند به چندین حساب کاربری در وب سایت شما دسترسی پیدا کند.
جلوگیری
اعتبار سنجی ورودی کاربر می تواند در این مورد نیز کمک کند. علاوه بر این، شما می توانید استفاده کنید؟ به عنوان یک مکان نگهدار برای ورودی های کاربر، سپس مقادیر واقعی را به عنوان پارامتر دوم متد DB::statement
مطابق زیر ارائه کنید:
DB::statement("UPDATE users SET password=? WHERE username =?", [$password, $username]);
مثال 3: پیام های خطا
برنامه های ساخته شده با لاراول ممکن است اطلاعات حساسی مانند کوئریهای پایگاه داده را در طول استثناهای کنترل نشده نمایش دهند.
DB::statement("SELECT * FROM users WHERE id=".$id );
با وارد کردن id نامعتبر (مقدار غیر صحیح) کد بالا پیغام خطا را در مرورگر کاربر نمایش می دهد.
صفحه خطا جزئیات حساس مربوط به کوئریی SQL را نشان می دهد. این نوع اطلاعات به هکر کمک می کند تا منطق زیر برنامه شما را درک کند و شما را در معرض حملات احتمالی قرار دهد.
جلوگیری
قبل از شروع به production، برنامه خود را برای این نوع استثناهای کنترل نشده آزمایش کنید. همچنین راهی برای خاموش کردن کامل این نوع گزارش خطا در لاراول وجود دارد. توصیه می کنیم گزارش خطا را در production خاموش کنید. همچنان میتوانید جزئیات مفیدی درباره خرابیها و خطاهای برنامهتان در فایلهای لاگ لاراول پیدا کنید.
برای غیرفعال کردن گزارش خطای درون صفحه، فایل env.
را باز کنید و مقدار APP_DEBUG
را از true
به false
تغییر دهید.
به روز رسانی و وصله های منظم
به روز رسانی و وصله های منظم در جلوگیری از حملات تزریق SQL به برنامه های لاراول بسیار مهم است. توسعهدهندگان لاراول دائماً بهروزرسانیها و وصلههایی را منتشر میکنند تا آسیبپذیریهای موجود در این چارچوب را برطرف کنند. انجمن لاراول در زمینه گزارش و رفع مشکلات امنیتی فعال است.
هنگامی که یک آسیب پذیری کشف می شود، تیم لاراول به سرعت یک وصله برای رفع مشکل منتشر می کند. به عنوان یک توسعه دهنده، مهم است که با این نسخه ها به روز بمانید و به سرعت وصله ها را در برنامه خود اعمال کنید. در اینجا مثالی وجود دارد که اهمیت به روز رسانی منظم را نشان می دهد:
// Vulnerable code without updates
$id = $_GET['id'];
$query = "SELECT * FROM users WHERE id = $id";
$result = DB::select($query);
// Fixed code with updates
$id = $_GET['id'];
$query = "SELECT * FROM users WHERE id = ?";
$result = DB::select($query, [$id]);
در این مثال، کد آسیبپذیر مستقیماً از ورودی کاربر در کوئری SQL استفاده میکند و آن را مستعد حملات تزریق SQL میکند. با این حال، با بهروزرسانی منظم لاراول و اعمال وصلهها، میتوانید از ویژگی اتصالات کوئری داخلی لاراول، همانطور که در کد ثابت نشان داده شده است، استفاده کنید.
نتیجه
به طور خلاصه، تزریق SQL، متاسفانه، چیزی در لاراول است. اما اعتبارسنجی ورودی های کاربر و کوئریهای پارامتری شده می تواند به کاهش خطر تزریق SQL کمک کند.
امنیت برنامه لاراول شما یک فرآیند مداوم است. و ما نمیتوانیم تمام آسیبپذیریها و راهحلهای ممکن را در یک پست از بین ببریم. بنابراین مطمئن شوید که همیشه بهترین روش ها را دنبال کنید و کد و ابزار خود را به روز نگه دارید.