۳ اشتباه امنیتی رایج در PHP و راههای جلوگیری ازشون
امنیت توی پروژههای PHP بیشتر از اون چیزیه که فقط با یه افزونه یا یه خط کد حل بشه. خیلی وقتها مشکل از چند اشتباه ساده شروع میشه که به مرور تبدیل به درهای باز برای هکرها میشه. توی این مقاله میخوام ۳ اشتباه امنیتی پرتکرار رو با مثالهای واقعی مرور کنم و بگم چطور میتونیم جلوی هرکدوم ازینا رو باهم بگیریم. آخرش هم یه بخش جمعوجور چکلیست و تنظیمات کاربردی اضافه کردم که دستتون برای عملیکردن راهحلها باز باشه.
اشتباه ۱: تزریق SQL بهخاطر کوئریهای رشتهای
بزرگترین گاف امنیتی که هنوز هم توی پروژهها می بینیم، ساختن کوئری با چسبوندن متغیرهاست. هکر با یه ورودی دستکاری شده میتونه کوئری شما رو عوض کنه، جدول بخونه یا حتی پاک کنه.
نمونه آسیبپذیر
// آسیبپذیر: کوئری رشتهای
$username = $_GET['username'];
$sql = "SELECT * FROM users WHERE username = '" . $username . "'";
$rows = $pdo->query($sql)->fetchAll();
راهحل امن با Prepared Statements
// امن: استفاده از prepared statement
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :u');
$stmt->execute([':u' => $username]);
$rows = $stmt->fetchAll();
- همیشه از prepared statement و bind parameter استفاده کن.
- برای جستوجوهای LIKE، کاراکترهای % و _ رو امنسازی کن و از ESCAPE کمک بگیر.
- به دیتابیس فقط حداقل دسترسی لازم رو بده (اصل least privilege).
- ورودیها رو Validate کن: مثلا شناسه عددی باید فقط عدد باشه؛ از
filter_varوctype_digitاستفاده کن.
اشتباه ۲: XSS و خروجیدادن بدون اسکیپ
XSS یعنی هکر بتونه اسکریپت خودش رو به خروجی صفحه شما تزریق کنه. نتیجه؟ سرقت کوکی، تغییر DOM، یا حتی اجرای درخواستهای ناخواسته از طرف کاربر.
دو نوع رایج
- Reflected XSS: کد مخرب همون لحظه از ورودی به خروجی برمیگرده.
- Stored XSS: کد مخرب ذخیره میشه (مثلا توی کامنت) و بعدها به همه نمایش داده میشه.
قانون طلایی: اسکیپ متناسب با «بافت خروجی»
هرجا قراره چیزی رو چاپ کنی، باید متناسب با جایی که چاپ میشه escape کنی. اگر از موتورهای قالب مثل Twig یا Blade استفاده میکنی، auto-escape رو روشن نگهدار.
| بافت خروجی | روش امنسازی | نمونه |
|---|---|---|
| متن HTML | htmlspecialchars($v, ENT_QUOTES, 'UTF-8') |
<div>...value...</div> |
| خصیصه HTML | مثل بالا + ممنوعیت جاوااسکریپت در href/src | <img src="..."> |
| پارامتر URL | rawurlencode() |
?q=...value... |
| داده در JS | json_encode($data, JSON_HEX_TAG|...) |
const data = ...; |
نکات تکمیلی
- اگه باید HTML کاربر رو قبول کنی، با کتابخونهای مثل HTMLPurifier روی allowlist تمیزش کن.
- CSP پیادهسازی کن:
Content-Security-Policyباdefault-src 'self'و منع inline script. - نام فایلها، تیترها و هرچیزی که به خروجی میاد رو خام چاپ نکن حتی اگر از دیتابیس خودته.
اشتباه ۳: احراز هویت و نشستهای ناایمن
نشست و کوکیها قلب امنیت لاگین هستن. یه پیکربندی اشتباه یا فراموشی یه فلگ، میتونه کل داستان رو بههم بزنه.
گافهای پرتکرار
- عدم regenerate کردن session ID بعد از لاگین (مشکل session fixation).
- کوکی بدون HttpOnly یا Secure؛ یا نبود SameSite.
- ذخیرهکردن پسورد با hash ضعیف یا بدتر، plaintext.
- نبود محافظت CSRF روی فرمهای حساس.
الگوی امن پیشنهادی
// بعد از احراز هویت موفق:
session_regenerate_id(true); // جلوگیری از fixation
// تنظیم کوکیها از طریق ini یا هنگام setcookie:
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1'); // فقط روی HTTPS
ini_set('session.cookie_samesite', 'Lax'); // یا 'Strict' برای حساسها
// هش پسورد
$hash = password_hash($password, PASSWORD_ARGON2ID /* یا PASSWORD_BCRYPT */);
if (password_verify($input, $hash)) {
// ورود موفق
}
- برای CSRF از توکن تصادفی per-session و per-form استفاده کن؛ توی فرم hidden بذار و سمت سرور اعتبارسنجی کن.
- زمان انقضای معقول برای نشست و refresh token درنظر بگیر؛ نشستهای بلااستفاده رو invalid کن.
- محدودیت تلاش لاگین (rate limit) و قفل موقت حساب در حملات brute force.
بخش ۴ (یک نکته اضافی): آپلود فایل و پیکربندی های ناامن
آپلود فایل اگر درست کنترل نشه، خیلی سریع تبدیل به اجرای کد یا افشای دیتا میشه. از اون طرف، چند تا تنظیم ساده در php.ini و وبسرور، ریسک شما رو بهشدت پایین میاره.
آپلود فایل، امن و حسابشده
- فایل رو خارج از webroot ذخیره کن؛ برای دانلود از اسکریپت امن استفاده کن.
- هم پسوند و هم MIME رو چک کن؛ بهتره با
finfo_fileتشخیص بدی، نه فقط پسوند. - نام فایل رو تصادفی کن و allowlist از فرمتهای مجاز داشته باش.
move_uploaded_fileرو بهدرستی استفاده کن و اندازه فایل رو محدود کن.
// نمونه ولیدیشن MIME
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
$allowed = ['image/png', 'image/jpeg'];
if (!in_array($mime, $allowed, true)) {
die('Invalid file');
}
تنظیمات و هاردنینگ
- در محیط production،
display_errors=Offوerror_logفعال باشه؛ اطلاعات خطا نباید به کاربر نمایش داده بشه. - ورژن PHP رو لو نده:
expose_php = Off. - بهروزرسانی منظم هسته PHP و وابستگیها؛ از Composer Audit استفاده کن:
composer audit. - سرتیترهای امنیتی:
X-Content-Type-Options: nosniff،Referrer-Policyمناسب،Permissions-Policy، وframe-ancestorsدر CSP. - دسترسی فایلها رو محدود کن؛ اگر لازم نیست،
allow_url_fopenرو غیرفعال کن و ورودیهای URL رو هرگز به include/require نده. - اسرار و کلیدها داخل کد نباشن؛ از env و مدیریت امن secret استفاده کن.
چکلیست سریع
- Prepared statement همه جا؟
- اسکیپ خروجی بر اساس بافت؟ CSP فعاله؟
- Session ID بعد از لاگین regenerate میشه؟ کوکیها Secure/HttpOnly/SameSite دارن؟
- CSRF روی همه فرمهای حساس پیاده شده؟
- آپلودها خارج از webroot و با MIME check ذخیره میشن؟
- خطاها لاگ میشن و به کاربر نمایش داده نمیشن؟
جمعبندی
سه خطای پرتکرار امنیتی "تزریق SQL، XSS، و احراز هویت ناایمن" ریشه خیلی از نفوذهاست. با چند الگوی ثابت مثل prepared statement، اسکیپ خروجی متناسب با بافت، Hash امن پسورد، تنظیم درست کوکیها و اجرای CSP میتونی سطح حمله رو حسابی پایین بیاری. بخش آپلود و پیکربندی هم جای شوخی نداره؛ همون چند تنظیم ساده میتونه جلوی دردسرهای بزرگ رو بگیره.
اگر تجربهای از این خطاها داشتی یا راهکاری داری که به کارت اومده، توی کامنت ها بگو تا بقیه هم استفاده کنن. سوالی هم داشتی با جزئیات بپرس تا با هم بررسی کنیم.
کلمات کلیدی :
ثبت دیدگاه
اگه در مورد این مطلب نظری داری یا در همین موضوع سوالی داری، همینجا مطرح کن تا از دیدگاه ارزشمندت استفاده کنیم و انرژی بگیریم، یا سوالت رو جواب بدیم
در ضمن، شماره موبایلت تو سایت نمایش داده نمیشه و پیش ما به صورت محرمانه میمونه
کد تایید پیامک شده به شماره را وارد نمایید

دیدگاه شما