پرش به محتوا
سرور و .htaccess

محتوای ترکیبی در wordpress پس از انتقال به https

هشدارهای محتوای ترکیبی وردپرس را پس از مهاجرت به https پاک کنید: نشانی‌های http باقی‌مانده در پایگاه‌داده را پیدا کنید، امن جایگزینشان کنید و https را در htaccess اجباری کنید.

منتشرشده

گواهی‌تان نصب شده، سایت روی https بالا می‌آید و قفل هنوز حاضر نمی‌شود ظاهر شود. این محتوای ترکیبی است: خود صفحه روی https آمده، اما چیزی درونش — یک تصویر، یک شیوه‌نامه، یک اسکریپت — هنوز روی http:// ساده درخواست می‌شود. این راهنما شما را از پیدا کردن آن نشانی‌ها، جایگزینی‌شان بدون خراب کردن دادهٔ سریالایزشده، اصلاح siteurl و home، تا اجباری کردن https در سطح سرور عبور می‌دهد تا مشکل نتواند برگردد.

محتوای ترکیبی دقیقاً چیست

نصب گواهی نحوهٔ رمزگذاری اتصال را عوض می‌کند. چیزی را که صفحه‌هایتان درخواست می‌کنند عوض نمی‌کند. هر http://yoursite.com/wp-content/uploads/logo.png که پیش از مهاجرت در یک نوشته، یک ابزارک یا یک تنظیم پوسته نوشته شده هنوز در پایگاه‌داده نشسته، و مرورگر مطیعانه آن را روی اتصالی ناامن درخواست می‌کند.

مرورگرها با دو دسته رفتار متفاوتی دارند، و این تمایز وقتی می‌خواهید فوریت کار را بسنجید مهم است:

  • محتوای ترکیبی فعال — اسکریپت‌ها، شیوه‌نامه‌ها، آی‌فریم‌ها، XHR. یکسره مسدود می‌شوند. به همین دلیل است که یک سایت می‌تواند بعد از مهاجرت به https خراب به نظر برسد بی‌آنکه هیچ‌جا پیام خطایی باشد: یک شیوه‌نامه در سکوت رد شده است.
  • محتوای ترکیبی منفعل — تصویر، صدا، ویدیو. معمولاً هنوز بارگذاری می‌شود، اما قفل تنزل می‌یابد و برخی مرورگرها نشانگر «ناامن» نشان می‌دهند.

هر دو ارزش رفع کردن دارند. فقط اولی چیزها را می‌شکند.

گام ۱: بفهمید چه چیزی هنوز ناامن است

از مرورگر شروع کنید. صفحه را باز کنید، ابزارهای توسعه‌دهنده را باز کنید و کنسول را بخوانید. هر درخواست مسدود یا تنزل‌یافته آنجا با نشانی کاملش نام برده می‌شود. این به شما می‌گوید چه چیزی ناامن است؛ نمی‌گوید کجا ذخیره شده.

برای آن، مستقیم به پایگاه‌داده نگاه کنید. برون‌ریزی کنید و در فایل خروجی جست‌وجو کنید:

wp db export dump.sql
grep -o "http://example\.com[^\"']*" dump.sql | sort -u | head -50

اگر WP-CLI در دسترس نیست، mysqldump همان فایل را می‌سازد:

mysqldump -u USER -p DBNAME > dump.sql

نشانی‌های یکتایی را که برمی‌گردند بخوانید. مسیرهای uploads به محتوای نوشته‌ها و متا اشاره می‌کنند. مسیرهای دارایی پوسته معمولاً به گزینه‌ها اشاره می‌کنند. هر چیزی با دامنه‌ای که نمی‌شناسید یک جاسازی بیرونی است که به راه‌حل دیگری نیاز دارد (پایین‌تر پوشش داده شده).

گام ۲: اول siteurl و home را درست کنید

siteurl و home دو گزینه‌ای هستند که وردپرس تقریباً هر نشانی داخلی‌ای را که تولید می‌کند با آن‌ها می‌سازد. اگر هر کدام هنوز http:// بگوید، وردپرس هر قدر هم بقیهٔ پایگاه‌داده تمیز باشد باز هم نشانی ناامن بیرون می‌دهد.

بررسی‌شان کنید:

wp option get siteurl
wp option get home

یا در sql:

SELECT option_name, option_value
FROM wp_options
WHERE option_name IN ('siteurl', 'home');

این دو رشته‌های سادهٔ متنی‌اند، نه آرایهٔ سریالایزشده، پس یک به‌روزرسانی مستقیم sql اینجا واقعاً امن است:

UPDATE wp_options
SET option_value = REPLACE(option_value, 'http://example.com', 'https://example.com')
WHERE option_name IN ('siteurl', 'home');

پیش از آنکه وقت بگذارید، یک تله. اگر wp-config.php ثابت‌های WP_HOME یا WP_SITEURL را تعریف کرده باشد، آن ثابت‌ها کاملاً بر پایگاه‌داده اولویت دارند و به‌روزرسانی شما انگار هیچ کاری نکرده:

define( 'WP_HOME', 'https://example.com' );
define( 'WP_SITEURL', 'https://example.com' );

آن‌ها را همان‌جا به‌روز کنید، یا حذفشان کنید و بگذارید پایگاه‌داده تصمیم بگیرد. این را اول بررسی کنید — بسیاری از موارد «عوضش کردم و هیچ اتفاقی نیفتاد» را همین توضیح می‌دهد.

گام ۳: جایگزینی باید نسبت به سریالایز امن باشد

بقیهٔ پایگاه‌داده جایی است که خطر واقعی نشسته، و آن خطری نیست که بیشتر مردم انتظارش را دارند. خطر این نیست که جایگزینی نشانی‌هایی را جا بیندازد. خطر این است که در سطح متن موفق شود و دادهٔ پیرامونش را نابود کند.

وردپرس آرایه‌ها و اشیا را به شکل رشته‌های سریالایزشدهٔ php ذخیره می‌کند، و آن قالب طول بایتی هر رشته‌ای را که در بر دارد ثبت می‌کند:

a:1:{s:4:"logo";s:38:"http://example.com/img/logo.png";}

با یک REPLACE خام در sql مقدار http:// را به https:// تغییر دهید، متن یک بایت بلندتر می‌شود در حالی که طول اعلام‌شده روی مقدار قدیمی می‌ماند. php طول را می‌خواند، همان تعداد بایت جلو می‌رود، پایان‌بخش را جایی که انتظار دارد پیدا نمی‌کند و از واسریالایز کردن کل آرایه سر باز می‌زند. آنگاه وردپرس به پوسته یا افزونه مقداری می‌دهد که طوری رفتار می‌کند که انگار آن تنظیم هرگز ذخیره نشده است.

نشانه‌اش خطا نیست. ناپدید شدن ابزارک‌ها، بازگشت تنظیمات سفارشی‌ساز به پیش‌فرض و خالی رندر شدن چیدمان‌های صفحه‌ساز است — بدون آنکه چیزی در پیشخوان نشان دهد اشکالی پیش آمده. بازیابی از آن بدون پشتیبان واقعاً دردناک است، پس یکی بگیرید:

wp db export backup-before-https-replace.sql

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

wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid

گزارش را بخوانید. اگر جدولی که نمی‌شناسید هزاران نتیجه نشان داد، پیش از اجرا بایستید و نگاه کنید. بعد واقعی اجرایش کنید:

wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only

پارامتر --skip-columns=guid اهمیت دارد: مقدار guid یک شناسهٔ دائمی برای خوانندگان خوراک است، نه یک نشانی زنده، و بازنویسی‌اش می‌تواند باعث شود مشترکان کل بایگانی شما را نوشتهٔ تازه ببینند. اگر پیش از اجرای هر چیزی می‌خواهید ببینید کدام ستون‌ها برای sql ساده امن‌اند و کدام‌ها به مدیریت آگاه از سریالایز نیاز دارند، دستورها را با ابزار جست‌وجو و جایگزینی sql وردپرس بسازید.

اگر نمی‌توانید از خط فرمان استفاده کنید، افزونهٔ مهاجرتی که صریحاً می‌گوید دادهٔ سریالایزشده را مدیریت می‌کند همان کار رمزگشایی را از صفحهٔ پیشخوان انجام می‌دهد. اگر ابزاری این را نگفته، فرض کنید انجامش نمی‌دهد.

گام ۴: https را در سطح سرور اجباری کنید

تمیز کردن پایگاه‌داده جلوی درخواست منابع ناامن از سوی صفحه‌هایتان را می‌گیرد. جلوی رسیدن بازدیدکننده روی http:// را از اساس نمی‌گیرد. آن یک ریدایرکت در سطح سرور است، و در Apache جایش در .htaccess است:

# BEGIN Force HTTPS
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
# END Force HTTPS

دو نکته دربارهٔ جای قرارگیری. این را بالای نشانگرهای # BEGIN WordPress بگذارید — هر چه داخل آن‌ها باشد هر بار که کسی پیوندهای یکتا را ذخیره کند دور ریخته می‌شود. و این فقط وقتی کار می‌کند که mod_rewrite فعال باشد و میزبان مجازی بازنویسی را اجازه دهد (AllowOverride All، یا دست‌کم FileInfo). روی nginx فایل .htaccess کاملاً نادیده گرفته می‌شود و این باید در یک بلوک سرور برود.

یک RedirectMatch این کار را انجام نمی‌دهد. فقط با مسیر درخواست تطبیق می‌یابد و هیچ راهی ندارد بسنجد که درخواست فعلی از پیش امن هست یا نه، پس درخواست‌های https را به خودشان ریدایرکت می‌کند. RewriteRule شرطی بالا ابزار درست است.

حلقهٔ ریدایرکت، و چرا رخ می‌دهد

اگر سایت همان لحظه که آن قانون را می‌افزایید شروع به ریدایرکت بی‌پایان کرد، TLS شما جایی بالادست خاتمه می‌یابد — یک متعادل‌کنندهٔ بار، یک پراکسی معکوس، یا یک شبکهٔ توزیع محتوا — که بعد http ساده را به Apache می‌فرستد. Apache یک درخواست ناامن می‌بیند، به https ریدایرکت می‌کند، پراکسی دوباره http می‌فرستد و همین‌طور می‌چرخد.

به جای آن سرایند فورواردشده را بسنجید:

RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

خود وردپرس هم در آن آرایش همین نقطهٔ کور را دارد — تابع is_ssl() مقدار $_SERVER['HTTPS'] را می‌خواند که پراکسی هرگز تنظیمش نمی‌کند، پس نشانی‌های پیشخوان به شکل http:// بیرون می‌آیند. این را به wp-config.php بیفزایید، بالای خط «ویرایش را متوقف کنید»:

if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
	$_SERVER['HTTPS'] = 'on';
}

دربارهٔ ریسکش باید رک بود: X-Forwarded-Proto سرایندی است که کلاینت می‌فرستد. اعتماد به آن فقط وقتی درست است که پراکسی‌ای تحت کنترل شما همیشه بازنویسی‌اش کند. روی سروری که مستقیم از اینترنت در دسترس است، می‌شود جعلش کرد.

چیزهایی که جایگزینی پایگاه‌داده درست نمی‌کند

  • نشانی‌های سخت‌کدشده در فایل‌ها. هر چیزی که در functions.php، در قالب یک پوستهٔ فرزند یا به شکل مسیر دارایی درون‌خطی نوشته شده در پایگاه‌داده نیست. پوشهٔ پوسته را جداگانه grep کنید.
  • JSON اسکیپ‌شده. بعضی صفحه‌سازها نشانی‌ها را به شکل https:\/\/example.com ذخیره می‌کنند. جست‌وجوی شکل عادی آن‌ها را کاملاً جا می‌اندازد، پس شاید به یک پاس دوم روی رشتهٔ اسکیپ‌شده نیاز باشد.
  • منابع بیرونی. اسکریپت یا جاسازی طرف‌سومی که فقط روی http در دسترس است از سمت شما قابل رفع نیست. نسخهٔ https پیدا کنید یا رهایش کنید.
  • کش‌ها. کش صفحه، کش شیء و نسخه‌های شبکهٔ توزیع محتوا بعد از یک جایگزینی درست هم همان نشانه‌گذاری قدیمی را ارائه می‌دهند. پیش از آنکه نتیجه بگیرید جایگزینی شکست خورده، هر سه را خالی کنید.

یک چیز که ارزش رد کردن دارد: سرایند سیاست امنیت محتوای upgrade-insecure-requests با بازنویسی درخواست‌های ناامن در مرورگر هشدارها را ساکت می‌کند. اما نشانه را درمان می‌کند و نشانی‌های غلط را در پایگاه‌دادهٔ شما باقی می‌گذارد، جایی که برون‌ریزی، مهاجرت یا خوراک بعدی آن‌ها را با خود می‌برد. داده را درست کنید، بعد اگر خواستید از آن به عنوان تور ایمنی استفاده کنید.

هنوز گیر کرده‌اید؟

بعد از هر گام siteurl و home را دوباره بررسی کنید — افزونه‌ها و ابزارهای مهاجرت گاهی پشت سر شما بازنویسی‌شان می‌کنند. اگر کنسول هنوز نشانی ناامنی را نام می‌برد که در فایل خروجی پیدایش نمی‌کنید، منبع صفحه را ببینید و آنجا دنبالش بگردید: اگر در نشانه‌گذاری تولیدشده هست ولی در پایگاه‌داده نیست، در php ساخته می‌شود، و پوسته یا افزونه‌ای که تولیدش می‌کند همان چیزی است که باید درست شود.

FAQ

پرسش‌ها

چرا سایت وردپرسی من بعد از نصب گواهی SSL هنوز هشدار محتوای ترکیبی نشان می‌دهد؟

گواهی فقط نحوهٔ رمزگذاری اتصال را عوض می‌کند، نه چیزی را که صفحه‌هایتان درخواست می‌کنند. نشانی‌های قدیمی http در پایگاه‌داده باقی می‌مانند، درون محتوای نوشته‌ها، گزینه‌های ابزارک‌ها و تنظیمات پوسته. مرورگر صفحه را روی https بارگذاری می‌کند، می‌بیند تصویری یا اسکریپتی روی http ساده درخواست می‌شود، و روی صفحه‌ای که از هر جهت دیگر امن است محتوای ترکیبی گزارش می‌دهد.

چطور نشانی‌های http باقی‌مانده در پایگاه‌دادهٔ وردپرس را پیدا کنم؟

صفحه‌ای را در مرورگر باز کنید و کنسول توسعه‌دهنده را بخوانید، که هر درخواست ناامن را با نشانی‌اش نام می‌برد. سپس پایگاه‌داده را برون‌ریزی کنید و در فایل خروجی دامنه‌تان را با پیشوند http جست‌وجو کنید. شمردن نتایج در هر جدول به شما می‌گوید مشکل در محتوای نوشته‌هاست، در wp_options، یا در جدول‌های افزونه‌ای که انتظارش را نداشتید.

آیا می‌توانم محتوای ترکیبی را با یک جست‌وجو و جایگزینی sql ساده درست کنم؟

فقط برای مقادیر ساده مثل siteurl و home. هر چیزی که وردپرس به صورت آرایهٔ سریالایزشده ذخیره می‌کند، که بیشتر مقادیر گزینه‌ها و متا را در بر می‌گیرد، طول بایتی هر رشتهٔ درونش را ثبت می‌کند. جایگزینی خام متن را عوض می‌کند اما طول قدیمی را باقی می‌گذارد، آنگاه مقدار در واسریالایز شکست می‌خورد و تنظیم بی‌صدا به پیش‌فرضش برمی‌گردد.

کدام قانون htaccess در وردپرس https را اجباری می‌کند؟

یک RewriteRule که با شرطی روی متغیر https محافظت شده و بالای نشانگرهای BEGIN WordPress قرار گرفته تا ذخیرهٔ پیوندهای یکتا پاکش نکند. پشت یک پراکسی یا شبکهٔ توزیع محتوا، متغیر https حتی در درخواست‌های امن هم خاموش خوانده می‌شود، پس به جای آن سرایند X-Forwarded-Proto را بسنجید وگرنه قانون تا ابد ریدایرکت می‌کند.

چرا بعد از اجباری کردن https سایتم وارد حلقهٔ ریدایرکت شد؟

تقریباً به یقین TLS شما روی یک پراکسی یا شبکهٔ توزیع محتوا خاتمه می‌یابد که بعد http ساده را به Apache می‌فرستد. بازنویسی یک درخواست ناامن می‌بیند، به https ریدایرکت می‌کند، پراکسی دوباره http می‌فرستد و حلقه تکرار می‌شود. شرط را به سرایند X-Forwarded-Proto تغییر دهید و متغیر سرور https را در wp-config تنظیم کنید.

آیا محتوای ترکیبی کل صفحه را خراب می‌کند یا فقط قفل را؟

به منبع بستگی دارد. مرورگرها محتوای ترکیبی فعال مثل اسکریپت‌ها، شیوه‌نامه‌ها و آی‌فریم‌ها را یکسره مسدود می‌کنند، که می‌تواند بدون هیچ توضیح آشکاری چیدمان یا کارکرد را بشکند. محتوای ترکیبی منفعل مثل تصویر و ویدیو معمولاً هنوز بارگذاری می‌شود، اما قفل تنزل می‌یابد و بازدیدکننده ممکن است نشانگر ناامن ببیند. هر دو ارزش رفع کردن دارند.