וורדפרס רוצה להיות הבעלים של כל האתר שלכם, וה-.htaccess שלכם צריך לומר לא

ה-.htaccess שוורדפרס כותבת לעצמה מניח שהיא הדבר היחיד בדומיין. כל בקשה שאינה תואמת קובץ קיים מועברת ל-index.php, ווורדפרס מחליטה מה פירושה. אם האתר שלכם הוא גם גלריית תמונות, כמה עמודים סטטיים, כמה סקריפטים שכתבתם בעצמכם ובלוג, ההנחה הזו שגויה, והתסמינים מוזרים: שגיאת הקלדה בכתובת לגלריה שלכם מפיקה עמוד לא נמצא של וורדפרס בעיצוב של הבלוג, וספרייה שכוונתם להגן עליה עונה בפוסט. הנה הקובץ שאני מריץ במקום, בלוק אחר בלוק.

הפריסה שהוא מניח

/var/www/site/            document root
    .htaccess             this file
    index.html            the front page, static
    gallery/              a photo gallery, its own application
    scripts/              my own PHP scripts
    blog/                 WordPress, installed in a subdirectory
    blog/.htaccess        WordPress's own file, left alone

הנקודה בפריסה היא שוורדפרס חיה ב-blog/ והכללים שלה חיים ב-blog/.htaccess, שם הם יכולים להשפיע רק על כתובות תחת /blog/. קובץ השורש מטפל בכל השאר.

קובץ השורש

# Nothing in this file mentions WordPress. That is deliberate.

Options -Indexes
ServerSignature Off

# Deny access to files that should never be served.
<FilesMatch "^(\.htaccess|\.htpasswd|\.git.*|.*\.(bak|old|sql|log))$">
    Order allow,deny
    Deny from all
</FilesMatch>

# The blog moved from the root to /blog/ in 2010; keep the old links alive.
RewriteEngine On
RewriteRule ^([0-9]{4})/([0-9]{2})/(.*)$ /blog/$1/$2/$3 [R=301,L]
RewriteRule ^feed/?$ /blog/feed/ [R=301,L]

# Protect the scripts directory with a password.
<IfModule mod_auth.c>
</IfModule>

# Sensible defaults for static content.
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpeg "access plus 1 month"
    ExpiresByType image/png "access plus 1 month"
    ExpiresByType text/css "access plus 1 week"
</IfModule>

AddDefaultCharset UTF-8

בלוק אחר בלוק

בלי רשימות ספריות, בלי כרזת גרסה. Options -Indexes מונע מאפאצ'י להציג את תוכנה של כל ספרייה שאין בה קובץ אינדקס, מה שבאתר מעורב הוא רובן. ServerSignature Off מסיר את גרסת אפאצ'י מעמודי השגיאה. לשניהם אין שום קשר לוורדפרס, וזו הנקודה: אלה חלים על האתר כולו, ולכן מקומם בשורש.

קבצים שלעולם אין להגיש. עורכי טקסט משאירים אחריהם קובצי .bak, יצואי בסיס נתונים נשכחים בשורש הרשת, וספריית .git בשורש אתר היא עותק מלא של המקור ושל ההיסטוריה שלו לכל מי שמבקש. בלוק ה-FilesMatch דוחה את כולם בכל מקום שהם. הקובץ של וורדפרס אינו עושה זאת לספריות שמחוץ ל-blog/, מפני שאינו יודע שהן קיימות.

הפניות לכתובות ישנות. כשהבלוג היה בשורש, הפוסטים שלו חיו ב-/2009/07/some-title/. הקישורים האלה נמצאים בעמודים של אנשים אחרים ובמנועי חיפוש, וכתובת שפעם עבדה צריכה להמשיך לעבוד. שני כללי השכתוב שולחים את הצורות הישנות לביתן החדש תחת /blog/ בהפניה קבועה. זהו המקום היחיד שבו קובץ השורש יודע שהבלוג קיים, והוא יודע רק לאן הוא עבר.

ספריית הסקריפטים. בלוק ה-mod_auth הריק הוא מציין מקום: הגנת הסיסמה בפועל חיה ב-scripts/.htaccess, שם מקומם של Require valid-user ושל AuthUserFile מחוץ לשורש הרשת. אני משאיר את הגדם כאן כדי שכל מי שקורא את קובץ השורש יראה שהספרייה מוגנת איפשהו.

מטמון וקידוד. תפוגה ארוכה לתמונות, שבוע לגיליונות סגנון, וקידוד ברירת מחדל כדי שקובץ טקסט פשוט עם עברית בתוכו לא יוצג כג'יבריש. שוב, כלל-אתרי, ולכן בשורש.

מה חסר, בכוונה

אין כאן RewriteRule . /index.php. השורה הזו היא מה שעושה את וורדפרס לבעלים של כל כתובת לא מוכרת, והיא נשארת ב-blog/.htaccess, שם וורדפרס כתבה אותה ושם היא תופסת רק כתובות תחת /blog/. כתובת גלריה עם שגיאת הקלדה מקבלת עכשיו את ה-404 של אפאצ'י עצמו, כפי שצריך, ובקשה ל-/scripts/ מקבלת בקשת סיסמה במקום פוסט.

אין כאן גם דבר שנוגע בהגדרות PHP. אלה הן לכל יישום בנפרד ומקומן לצד היישום; הגדלת מגבלת ההעלאה לבלוג היא תרגיל נפרד עם קובץ משלו.

מדוע זה חשוב מעבר לסדר

כלל שכתוב שתופס הכול הוא החלטת אבטחה לא פחות מהחלטת ניתוב. כל בקשה שהיישומים האחרים שלכם אינם עונים עליה הופכת לבקשה שוורדפרס עונה עליה, עם שטח התקיפה של וורדפרס, כשהיא מריצה את הקוד של וורדפרס, בספרייה שאולי אבטחתם מתוך ההנחה שרק כתובות של הבלוג מגיעות אליה. צמצום הכללים לתת-הספרייה פירושו שהחשיפה של כל יישום היא החשיפה של הכתובות שלו עצמו, ולא יותר. כתבתי על נעילת ספריית וורדפרס עצמה; זו השכבה החיצונית של אותו רעיון.