امنیت در PHP بخش ۱: SQL Injection

زبان PHP محبوب‌ترین زبان برنامه نویسی سمت سرور است. بر اساس داده‌های سایت W3Techs در سال ۲۰۱۹، ۷۹ درصد از وب سایت‌ها، قدرت گرفته از PHP هستند. وب سایت‌هایی مانند فیسبوک،یاهو و ویکی پدیا.از آنجایی که PHP زبان محبوبی است، امنیت در PHP امری ضروری است. متاسفانه تعداد برنامه‌های آسیب پذیر نوشته شده با PHP بسیار زیاد است. اغلب برنامه‌های نوشته شده با PHP بخشی از کد خود را با سایر برنامه‌ها به اشتراک می‌گذراند که اگه این کد‌های به اشتراک گذاشته شده دارای آسیب پذیری باشد، تمام برنامه‌هایی که از این کد استفاده کرده‌اند نیز آسیب پذیر می‌شوند.دلایل آسیب پذیریاغلب آسیب پذیری‌ها نتیجه عادت‌های غلط برنامه نویسی یا عدم آگاهی از امنیت برنامه‌های PHP است. علت این عدم آگاهی و عادت‌های بد، می‌تواند اعتماد برنامه نویس به ورودی کاربران به عنوان یک ورودی مطمئن باشد.زمانی که شما شروع به نوشتن کد می‌کنید، باید همیشه دو نکته را در ذهن داشته باشید: اعتبارسنجی و sanitization کردن داده‌های ورودی از کاربر. اگر این دو نکته را در هنگام نوشتن کد رعایت کنید، می‌توانید مطمئن شوید که آنچه را که پردازش و اجرا می‌کنید، معتبر است. همچنین باید مطمئن شوید که خروجی شما از هرگونه کدهای مخرب که ممکن است توسط مهاجمان در داده‌های شما تزریق و به کاربران آسیب برساند، مصون باشد. اگر این نکات ساده را برای هر درخواستی و پردازشی رعایت کنید، احتمال قرار گرفتن در معرض یک تهدید امنیتی را به حداقل می‌رسانید.برنامه نویسی شی گرا (OOP) نقش مهمی در اعمال امنیت در PHP دارد. کدی که قابلیت استفاده مجدد داشته باشد و به خوبی نوشته شده باشد، می‌تواند امنیت کلی یک سیستم را افزایش دهد. نوشتن یک کد مناسب و قابل فهم می‌تواند این تضمین را به ما بدهد که برنامه همیشه یک روند ثابت را برای پردازش دنبال می‌کند.تزریق کد SQL یا SQL Injection در PHPتزریق کد SQL یکی از خطرناک‌ترین آسیب پذیری‌ها در برنامه‌های تحت وب است. این آسیب پذیری هر ساله در لیست سایت OWASP در کنار مواردی دیگری مانند XSS و CSRF، جایگاه شماره یک را در بین آسیب پذیری‌های برنامه‌های تحت وب را به خود اختصاص داده است. اگر شما ورودی کاربر را بدون اعتبار سنجی و sanitize کردن در یک کوئری قرار دهید، مهاجم این توانایی را دارد که کوئری و خروجی را مطابق با خواسته خود تغییر دهد.یک حمله موفق SQL Injection می‌تواند منجر به سرقت اطلاعاتی مثل: نام کاربری، رمز عبور، اطلاعات کارت اعتباری و یا هرگونه اطلاعات مهم و حساسی شود. در برخی موارد این حملات می‌تواند منجر به آسیب دیدن کل سرور شود. نمونه‌ای از کد نا امندر مثال زیر پارامتر 'article' که از کاربر گرفته شده، به شکل نا امنی در کوئری استفاده شده است:$articleid = $_GET['article']; $query = &quotSELECT * FROM articles WHERE articleid = '$articleid'&quotیک مهاجم می‌تواند با قرار دادن مقداری خاص در ورودی ساختار پرس و جو را عوض کند. مثلا:1'+union+select+1,version(),3'که در نتیجه کوئری زیر حاصل می‌شود:$query = &quotSELECT * FROM articles WHERE articleid = '1'+union+select+1,version(),3''&quotمهاجم می‌تواند از دستورات مشابهی استفاده کند تا کلیه جداول و ستون‌های پایگاه داده را استخراج و به اطلاعات حساس دسترسی پیدا کند. راه حل این مشکل استفاده از prepared statements هاست. در صورت استفاده از این گونه کوئری‌ها (parameterized queries) مشخص می‌کنید که کدام بخش از کوئری شما داده ورودی از سمت کاربر است. این قابلیتی است که به ما کمک می‌کند تا از تداخل ورودی کاربر با دستورات SQL و تغییر در روال کوئری جلوگیری کنیم.توصیه می‌شود که برای اجرای prepared statements در کد PHP از PHP Data Objects (PDO) استفاده کنید. PDO از نسخه ۵.۱ تا نسخه نهایی (۷.۴.۲) PHP، در دسترس است.سعی کنید از افزونه‌های mysql در کدهای PHP استفاده نکنید. این افزونه‌ها معمولا بعد از مدتی منسوخ شده و دیگر قابل استفاده نیستند. شما به راحتی می‌توانید کده‌های نوشته شده با این افزونه‌ها را با PDO جایگزین کنید.نمونه‌ای از کد امندر این مثال، مقدار user_id مستقیما به کوئری ارسال نمی‌شود. به جای آن از یک placeholder استفاده می‌کنیم و زمانی که تابع execute اجرا می‌شود، پایگاه داده مقدار آنرا به صورت امن با ورودی کاربر جایگزین می‌کند.// User ID must not be empty, must be numeric and must be less than 5 characters long if((!empty($_GET['user_id'])) && (is_numeric($_GET['user_id'])) && (mb_strlen($_GET['user_id'])<5)) { $servername = &quotlocalhost&quot $username = &quotusername&quot $password = &quotpassword&quot $database = &quotdbname&quot // Establish a new connection to the SQL server using PDO try { $conn = new PDO(&quotmysql:host=$servername;dbname=$database&quot, $username, $password); // Assign user input to the $user_id variable $user_id = $_GET['user_id']; // Prepare the query and set a placeholder for user_id $sth = $conn->prepare('SELECT user_name, user_surname FROM users WHERE user_id=?'); // Execute the query by providing the user_id parameter in an array $sth->execute(array($user_id)); // Fetch all matching rows $user = $sth->fetch(); // If there is a matching user, display their info if(!empty($user)) { echo &quotWelcome &quot.$user['user_name'].&quot &quot.$user['user_surname']; } else { echo &quotNo user found&quot } // Close the connection $dbh = null; } catch(PDOException $e) { echo &quotConnection failed.&quot } } else { echo &quotUser ID not specified or invalid.&quot }منبع : acunetix