מנהל הקבצים (file_manager)

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

⏱ 8 דק' קריאה 1361 מילים ערוך דף זה ב-GitHub

file_manager הוא עורך קוד ודפדפן קבצים בתוך פאנל הניהול, שנועד למפתחים: עורכים תבנית, קובץ SCSS או קובץ הגדרות ישירות על השרת, עם בדיקת תחביר לפני שמירה, זיהוי התנגשות עם מי שערך במקביל, ושמירת גרסאות קודמות. הוא שונה מספריית המדיה ({admin}/storage, ראו אחסון קבצים), שמיועדת לתוכן של עורכי האתר.

system/ בעמוד הזה הוא תיקיית הליבה הפרוסה (api/core בריפו), ו-{admin} הוא CONFIG::$admin_url. הפאנל נמצא ב-system/admin/file_manager.php (המחלקה ADMINMODULE_file_manager), הלוגיקה ב-system/libraries/FileManager.php, בדיקות התחביר ב-system/libraries/FileLint.php, והממשק ב-assets/file_manager/app.js.

זה גישת כתיבה לקוד האתר

מי שיכול לפתוח את הפאנל יכול לכתוב קובץ PHP ולהריץ אותו. לכן הוא פתוח רק למפתחים, ולכן ההרשאה לא נסמכת על תצורת הפאנל בלבד (ראו הרשאות). אל תיתנו אותה לעורכי תוכן.

כתובות ופעולות#

כתובתמה היא
{admin}/file_managerהמסך עצמו
{admin}/file_manager/apiנקודת JSON, הפעולה נבחרת עם action (ב-body כ-JSON, ב-$_POST או ב-?action=)
{admin}/file_manager/download?path=<נתיב מלא>הורדת קובץ כ-application/octet-stream

פעולות קריאה: capabilities, list, read, lint, history, history_content, find_files, search_content.

פעולות כתיבה: write, create_file, create_dir, rename, move, copy, delete, upload, history_restore, core_unlock. הרשימה הזו (WRITE_ACTIONS) היא מה שמוגן ב-CSRF, נרשם ביומן ועובר בדיקת כתיבה.

התשובה היא תמיד JSON עם success, ובכישלון error, ולפעמים code שהממשק יודע לטפל בו: csrf, lint, conflict.

הרשאות#

הבדיקה נעשית ב-constructor של הפאנל: ADMIN::is_developer(). מי שאינו מפתח מקבל 403 (JSON עבור api ו-download, HTML עבור המסך). הבדיקה שם בכוונה ולא בשכבת ההרשאות של הפאנלים: שכבת perm_developer של הפאנלים פועלת רק כשיש לפאנל שורה בטבלה CRM_adminPanel_panels, ובלי שורה היא מדולגת וכל מנהל נכנס.

מפתח (is_developer) הוא מנהל שיש לו developer_token תקף בטבלת המנהלים. הטוקן חתום ומאומת בקוד (ADMIN::decode_developer_token), וטוקן לא תקף נמחק מהשורה. ראו התחברות וסשנים.

הסקריפט {admin}/install_file_manager (מפתחים בלבד, וחוזר על עצמו בבטחה) יוצר את טבלת היומן ורושם את הפאנל עם perm_developer = 1. אם שורת הפאנל נמצאה עם 0 הוא מתקן אותה ל-1, כי 0 חושף גישה לקבצים לכל מנהל.

ה-jail: איפה מותר לעבוד#

כל נתיב שהלקוח שולח הוא נתיב מלא, וכל גישה לדיסק עוברת דרך FileManager::resolve($abs, $mustExist = true):

  • מחרוזת ריקה או null byte נדחים.
  • realpath מנרמל .. ועוקב אחרי קישורים סימבוליים, כך שקישור שמצביע אל מחוץ לשורש נדחה לפי היעד האמיתי.
  • התוצאה חייבת להיות תחת אחד מה"שורשים". שורש 0 הוא תמיד CONFIG::$base_path.
  • ביצירת קובץ חדש ($mustExist = false) תיקיית האב חייבת להתקיים בתוך ה-jail, והשם חייב להיות שם פשוט בלי / או \.
  • קובץ רגיש (ראו להלן) נדחה.

שורשים נוספים#

הפרמטר file_manager_extra_roots בטבלה CRM_params (נתיבים מוחלטים, מופרדים בשורה חדשה או בפסיק) מוסיף שורשים. נתיב שאינו קיים או אינו תיקייה מדולג.

מצב קריאה בלבד#

file_manager_readonly = 1 הופך את כל השורשים לקריאה בלבד. כל פעולת כתיבה נכשלת, בלי תלות בהרשאות המשתמש.

קבצים רגישים#

קבצים שמסכמים סודות מוסתרים מהרשימות ונדחים בקריאה, בכתיבה ובשינוי שם. הדפוסים (SENSITIVE_PATTERNS):

  • .env ו-.env.*
  • config.php, וכל /includes/*config*.php
  • market_settings.json
  • *_key.json ו-*_keys.json
  • .git, .ssh, id_rsa*, .htpasswd

הרשימה צרה במכוון: מפתח ממילא יכול להגיע לפרטי ה-DB דרך קובץ PHP שיכתוב, ומטרת המסכה היא להקשות על שליפת סודות של מערכות אחרות בלחיצה אחת אם הסשן של מנהל נחטף. הפרמטר file_manager_show_sensitive = 1 מכבה את המסכה (המסך capabilities מציג את המצב).

תיקיית הליבה נעולה#

תיקיית CONFIG::$core_path מנוהלת מרכזית ונדרסת בכל עדכון גרסה (ראו עדכונים), ולכן כתיבה אליה נדחית עם הסבר. הפעולה core_unlock (נשמרת ב-$_SESSION["fm_core_unlocked"], לסשן הנוכחי בלבד, לא נשמרת לצמיתות) פותחת אותה למקרי חירום. שינוי שנעשה שם ייעלם בעדכון הבא. התאמות לאתר מבצעים ב-application/.

עריכה#

read#

read מחזירה את התוכן, hash של התוכן, mtime, סימון binary ו-too_large, ורמז mode לעורך (modeFor לפי סיומת). קובץ בינארי (null byte בתחילתו) וקובץ גדול מ-MAX_EDIT_BYTES (2MB) מוחזרים בלי תוכן: אפשר להוריד אותם אבל לא לערוך.

write: שתי בדיקות לפני הדיסק#

{ "action": "write", "path": "/var/www/site/application/views/x.tpl", "content": "...", "hash": "<ה-hash מה-read>", "force": false, "csrf": "<token>" }
  1. התנגשות. אם קיבלתם hash והקובץ בדיסק השתנה מאז הקריאה, התשובה היא code: "conflict" עם התוכן וה-hash הנוכחיים. כך שני אנשים (או אדם ותהליך FTP) לא דורסים זה את זה.
  2. תחביר. FileLint::check בודקת קבצי php, phtml, scss, css ו-json. ב-PHP זה token_get_all, ובשרת שמאפשר exec גם php -l; JSON עם json_decode. שגיאה מחזירה code: "lint" והקובץ לא נשמר.

force: true עוקף את שתיהן, וגם זה נרשם ביומן (FORCED over lint error).

השמירה עצמה כותבת קובץ זמני .fm_tmp_<hex> באותה תיקייה ואז rename, כך שקובץ חצי-כתוב לא יוגש אם הכתיבה נפסקת. הרשאות הקובץ הקיים נשמרות. אחרי כל שמירה afterWrite מנקה מטמונים רלוונטיים: קובצי scss ו-css מנקים את מטמון ה-SCSS המהודר, וקובצי tpl מנקים את מטמון Smarty. תשובת השמירה כוללת side_effects שמפרט מה נוקה. ראו צינור הנכסים.

היסטוריה#

לפני כל דריסה או מחיקה של קובץ שאינו גדול מ-2MB נשמר עותק דחוס (gz) ב-<cache_folder>/file_history/<sha1 של הנתיב>/, עם meta.json שמתעד זמן, מנהל וגודל. נשמרות 20 גרסאות אחרונות לכל קובץ (HISTORY_KEEP). history מחזירה את הרשימה, history_content את התוכן של גרסה אחת, ו-history_restore משחזרת: קודם נשמרת הגרסה הנוכחית, כך ששחזור הוא עצמו הפיך.

ההיסטוריה נמצאת בתיקיית המטמון

מחיקת תיקיית המטמון (CONFIG::$cache_folder) מוחקת גם את ההיסטוריה. היא לא גיבוי, ראו הקשחה לגיבויים אמיתיים.

פעולות על קבצים ותיקיות#

פעולההתנהגות
create_filepath, content (ברירת מחדל ריק). נכשלת אם הקובץ קיים
create_dirpath. תיקייה בהרשאה 0755. נכשלת אם קיימת
renamepath, name (שם פשוט בלבד). נכשלת אם היעד קיים או שם הקובץ רגיש
move, copypath, dest (תיקייה). נכשלת אם הקובץ קיים ביעד; הזזת תיקייה לתוך עצמה נחסמת. העתקת תיקייה רקורסיבית
deleteקובץ: נשמרת גרסה אחרונה ואז נמחק. תיקייה: נמחקת רקורסיבית בלי היסטוריה. מחיקת שורש נחסמת
uploaddest (תיקייה, ב-$_POST) וקובץ בשדה file. שם הקובץ הוא basename של השם שנשלח. קובץ קיים נשמר בהיסטוריה לפני הדריסה. השמירה עם move_uploaded_file

העלאה בפאנל הזה לא מסננת סיומות (זה כלי למפתחים). לא משתמשים בה להעלאת תוכן ציבורי; לשם כך אבטחת העלאות.

חיפוש:

  • find_files (root, query, limit ברירת מחדל 200): חיפוש שם קובץ מטושטש (fuzzy), עם דילוג על node_modules, .git, cache, .wz_history, vendor.
  • search_content (root, query, limit 300, ignore_case, exts): חיפוש בתוכן. כש-exec ו-grep זמינים (נבדק בפועל מול קובץ ניסיון, לא רק לפי grep --version) משתמש ב-grep, אחרת סורק ב-PHP. התשובה מציינת engine ו-truncated.

CSRF#

פעולות הכתיבה דורשות אסימון CSRF בשיטת double-submit cookie. האסימון נשלח ב-csrf ב-JSON, ב-$_POST["csrf"] או בכותרת X-WZ-CSRF, ומושווה (hash_equals) לעוגיית admin_csrf. הפעולה capabilities יוצרת את העוגייה בקריאה הראשונה ומחזירה את האסימון, ובקריאות הבאות מחזירה את הקיים. העוגייה חיה שעה, ולכן עורך שנשאר פתוח יכול לקבל code: "csrf": הממשק (app.js) מבקש אסימון חדש ומשחזר את השמירה במקום לגרום לאיבוד טיוטה. הפרטים הכלליים בCSRF, XSS ו-SQL injection.

יומן ביקורת#

כל פעולת כתיבה, וגם הורדה, נרשמת ב-CRM_file_manager_log:

עמודהמשמעות
id
admin_idהמנהל שביצע
actionwrite, create_file, create_dir, rename, move, copy, delete, upload, history_restore, core_unlock, download
pathהנתיב (נחתך ל-900 תווים)
bytesגודל
noteהערה (נחתך ל-250), למשל FORCED over lint error
ipREMOTE_ADDR
user_agentנחתך ל-250
createdזמן

הרישום לא שובר פעולה: כישלון כתיבה ליומן נבלע. לכן אם יומן ביקורת חשוב לכם, בדקו מדי פעם שיש בו שורות.

SELECT created, admin_id, action, path
FROM CRM_file_manager_log
ORDER BY id DESC
LIMIT 50;

כותרות בהורדה#

download שולחת Content-Disposition: attachment, Cache-Control: private, no-store ו-X-Content-Type-Options: nosniff. זו כותרת האבטחה היחידה שהליבה שולחת בכל המערכת; כותרות נוספות מוגדרות בשרת (ראו הקשחה).

הגדרות מהירות#

פרמטר ב-CRM_paramsמשמעות
file_manager_readonly1 = כל הפאנל לקריאה בלבד
file_manager_extra_rootsשורשים נוספים מעבר ל-base_path
file_manager_show_sensitive1 = ביטול המסכה של קבצים רגישים

ערכי הפרמטרים נערכים ב-{admin}/Params (ראו פרמטרים ושפות). להשבתה מוחלטת של הפאנל במערכת קבועה, אפשר להגדיר file_manager_readonly = 1, או להסיר את הרשאת המפתח מכל המנהלים.

ראו גם#

מצאתם טעות או חוסר? תקנו את הדף או פתחו Issue בריפו. התיעוד נכתב מתוך הקוד של ליבה 5.0.115.