قفل سبز نوار آدرس رفته، جای آن نوشته «Not Secure» یا یک آیکون هشدار نشسته، و در کنسول مرورگر خطی شبیه این تکرار میشود: Mixed Content: The page at 'https://example.com/' was loaded over HTTPS, but requested an insecure resource 'http://example.com/img/logo.png'. This request has been blocked. اگر همین حالا این را میبینید، مشکل تقریباً همیشه یک منبع http:// است که داخل صفحهای با https:// بارگذاری میشود. مرورگر آن را بلاک میکند یا فقط هشدار میدهد، و در هر دو حالت اعتماد کاربر خدشه میخورد.
نکتهای که خیلیها دیر متوجه میشوند: mixed content همیشه یعنی سایت شما هک نشده. یعنی یک تصویر، فونت، اسکریپت یا iframe قدیمی هنوز با پروتکل ناامن صدا زده میشود. تفاوت این دو را جدی بگیرید، چون مسیر رفع کاملاً فرق میکند.
چرا قفل سبز میرود و مرورگر چه چیزی را بلاک میکند
مرورگرها دو دسته mixed content را جدا میکنند. دسته اول passive یا نمایشی است: تصویر، ویدیو، فایل صوتی. اینها معمولاً فقط هشدار میگیرند و بارگذاری میشوند، ولی قفل سبز را از بین میبرند. دسته دوم active یا فعال است: جاوااسکریپت، CSS، XHR/fetch، iframe، WebSocket. اینها بیرحمانه بلاک میشوند. یعنی ممکن است ظاهر سایت سالم باشد اما یک اسکریپت آنالیتیکس یا ویجت چت اصلاً اجرا نشود و شما فقط بابت «کار نکردن فرم» تیکت بگیرید.
یک حالت سوم هم هست که کمتر دیده میشود: redirect از https به http. مثلاً لینکی در سایت با http:// شروع میشود و سرور شما آن را به نسخه امن هدایت نمیکند. نتیجهاش یک حلقه یا یک صفحه ناامن است. برای اینکه مطمئن شوید مشکل از DNS یا رکوردهای دامنه نیست، از ابزارهای بررسی DNS و شبکه استفاده کنید و رکوردهای A و CNAME را قبل از هر تغییر چک کنید.
پیدا کردن منبع دقیق با کنسول و یک دستور ساده
اولین کاری که میکنم این است: صفحه را در Chrome باز میکنم، F12 میزنم، تب Console را میگیرم و فیلتر mixed را تایپ میکنم. آدرس دقیق فایل مقصر همانجا نوشته شده. اگر تعدادشان زیاد بود، در تب Network ستون Protocol را اضافه میکنم و روی هدر آن کلیک میکنم تا همه درخواستهای http بالا بیایند. این روش سریعتر از گشتن در سورس است.
وقتی سایت روی سرور خودتان است و به شل دسترسی دارید، یک grep ساده کل دامنه را جارو میکند:
grep -rIn --include="*.php" --include="*.html" --include="*.css" --include="*.js" \
-e 'http://' /var/www/html | grep -v 'https://' | head -50
فلگ -I فایلهای باینری را رد میکند و -n شماره خط میدهد. اگر وردپرس دارید، این دستور در پوشه wp-content معمولاً چند مورد در فایلهای CSS افزونهها و فونتهای آیکون پیدا میکند. همانجا هم بمانید، چون بیشترین مقصر همین دو دستهاند.
وقتی منبع داخل دیتابیس است، نه در فایل
اگر grep چیزی پیدا نکرد ولی کنسول هنوز خطا میدهد، منبع داخل دیتابیس است. در وردپرس، آدرسهای http:// در جدول wp_options (کلیدهای siteurl و home)، در wp_posts داخل محتوای نوشتهها، و در متادیتای wp_postmeta پنهان میشوند. اول با یک کوئری فقط بشمارید، بعد تغییر بدهید:
SELECT COUNT(*) FROM wp_posts WHERE post_content LIKE '%http://example.com%';
اگر عدد کوچک بود، دستی اصلاح کنید. اگر صدها رکورد بود، قبل از هر کاری بکاپ بگیرید و بعد با wp search-replace جلو بروید:
wp search-replace 'http://example.com' 'https://example.com' --all-tables --precise --dry-run
فلگ --dry-run را حذف نکنید. اول خروجی را ببینید، بعد اجرا کنید. --precise هم جلوی جایگزینیهای سریالیشده و خرابشدن دادههای serialized را میگیرد. بدون این فلگ، ویجتهای ذخیرهشده در wp_options بعد از جایگزینی خالی میشوند و شما فکر میکنید افزونه خراب شده.
اینجا اشتباه میکنند: خیلیها مستقیم در phpMyAdmin میروند و با یک UPDATE ... REPLACE() همهچیز را عوض میکنند. نتیجهاش این است که سایت بالا میآید ولی صفحهساز (page builder) محتوای قدیمی را نشان نمیدهد، یا تنظیمات قالب به حالت پیشفرض برمیگردد. علامتش هم این است که در پیشخوان همهچیز هست، ولی در فرانتاند بخشهایی خالی است. علت دقیقاً همان داده serialized است که طول رشتهاش دیگر با عدد ذخیرهشده نمیخواند.
رفع دستهجمعی و جلوگیری از برگشت
بعد از پاکسازی، دو کار مانده. اول، هدر Content-Security-Policy را جدی بگیرید. با یک دستور ساده میتوانید ببینید مرورگر چه چیزی را مجاز میداند:
curl -sI https://example.com | grep -i -e 'content-security-policy' -e 'strict-transport-security'
اگر Strict-Transport-Security با مقدار max-age=31536000 برنگشت، یعنی HSTS فعال نیست و مرورگر هنوز اجازه دارد نسخه http را امتحان کند. این هدر را در Nginx اینطور اضافه میکنم:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
دوم، ریدایرکت سراسری. در Nginx یک بلوک جدا برای پورت 80 بگذارید که همهچیز را به https بفرستد. اگر این کار را نکنید، هر لینک قدیمی که در انجمنها یا ایمیلهای قدیمی مانده، دوباره کاربر را به صفحه ناامن میآورد و شما فکر میکنید مشکل حل شده.
هزینهای که اینجا باید بپذیرید: HSTS یکطرفه است. وقتی مرورگر آن را دید، تا انقضای max-age دیگر اجازه ندارد سایت شما را روی http باز کند. اگر بعداً گواهی SSL منقضی شود یا سرور را جابهجا کنید و گواهی آماده نباشد، کاربران با خطای سخت گیر میکنند و راه فراری جز پاک کردن کش مرورگر ندارند. پس max-age را اول با یک عدد کوچک مثل 300 ثانیه تست کنید، بعد به یک سال برسانید.
ابزارهایی که کار را کوتاه میکنند
افزونههایی مثل Really Simple SSL یا Better Search Replace کار را سریع میکنند، ولی هر کدام یک هزینه دارند. اولی گاهی با .htaccess شما درگیر میشود و ریدایرکتهای دستیتان را بیاثر میکند؛ دومی روی دیتابیسهای بزرگ ممکن است تایماوت بخورد. اگر سایت شما بیش از چند صد هزار رکورد دارد، بهجای افزونه از WP-CLI روی SSH استفاده کنید. سریعتر است و وسط کار قطع نمیشود.
اگر بعد از همه این کارها هنوز خطا میبینید، احتمالاً منبع از سرویس بیرونی میآید: یک اسکریپت CDN، یک فونت گوگل، یا یک iframe نقشه. اینها را باید در سورس قالب یا تنظیمات افزونه پیدا و به نسخه https عوض کنید. برای اینکه مطمئن شوید مشکل از سمت سرور نیست و سرویسها بالا هستند، وضعیت لحظهای سرویسها را چک کنید. اگر سایت شما هدف حملات مکرر است و این تغییرات مدام برمیگردند، احتمال دستکاری فایلها را جدی بگیرید؛ در آن صورت امنیت وردپرس؛ از wp-config تا افزونههایی که خودشان حفرهاند نقطه شروع درستی است.
یک نکته عملی: بعد از هر تغییر، کش مرورگر و کش CDN را پاک کنید. نصف تیکتهایی که میبینم بابت همین است که توسعهدهنده تغییر را داده ولی مرورگر نسخه قدیمی را نشان میدهد و او فکر میکند رفع نشده.
پرسشهای پرتکرار
آیا Mixed Content روی امنیت واقعی سایت تأثیر دارد یا فقط ظاهری است؟
هر دو. بخش passive بیشتر ظاهری است و قفل سبز را از بین میبرد، ولی بخش active واقعاً خطرناک است. اگر یک اسکریپت از مسیر http بارگذاری شود، هر کسی در مسیر شبکه میتواند آن را دستکاری کند و کد دلخواه به مرورگر کاربر برساند. یعنی همان صفحه امن شما میتواند به ابزار سرقت اطلاعات تبدیل شود.
چرا بعد از نصب SSL هنوز هشدار Mixed Content میگیرم؟
چون نصب گواهی فقط ارتباط سرور با مرورگر را رمزنگاری میکند و آدرسهای داخل محتوای شما را تغییر نمیدهد. تا وقتی در دیتابیس یا فایلهای قالب آدرس http:// باقی مانده باشد، مرورگر آن را ناامن میبیند. باید آدرسها را هم اصلاح کنید، نه فقط گواهی را نصب.
آیا با تغییر آدرس سایت در تنظیمات وردپرس مشکل حل میشود؟
فقط بخشی از آن. تغییر siteurl و home آدرسهای تولیدشده توسط وردپرس را درست میکند، ولی آدرسهای داخل محتوای نوشتهها، متادیتا و فایلهای CSS افزونهها دستنخورده میمانند. برای پوشش کامل باید جستوجو و جایگزینی روی کل دیتابیس انجام دهید.
چطور بفهمم کدام افزونه مسئول است؟
افزونهها را یکییکی غیرفعال کنید و بعد از هر بار، کنسول را با فیلتر mixed نگاه کنید. اگر خطا با غیرفعال کردن یک افزونه رفت، مقصر همان است. راه دقیقتر این است که در تب Network ستون Protocol را ببینید و دامنهای که درخواست ناامن میفرستد را پیدا کنید؛ معمولاً نام دامنه همان افزونه را لو میدهد.
اگر میخواهید این کار یکبار برای همیشه تمام شود، بعد از پاکسازی، یک اسکن خودکار هفتگی روی دیتابیس و فایلها بگذارید تا اولین آدرس http:// که اضافه شد، قبل از دیدهشدن توسط کاربر پیدا شود. برای سایتهایی که ترافیک جدی دارند و هر دقیقه قطعی هزینه دارد، خدمات امنیت سرورنت همین لایه پایش و سختسازی را پوشش میدهد.
دیدگاهها ۰
هنوز دیدگاهی ثبت نشده؛ اولین نفر باشید!