חשיפת משתמשים דרך REST API – ניחוש שמות התחברות

נקודת הקצה /wp-json/wp/v2/users חושפת שמות משתמש לאנונימיים. ככה חוסמים אותה בלי לשבור את המערכת.

REST API של WordPress כולל את נקודת הקצה /wp-json/wp/v2/users. בברירת מחדל היא מחזירה לכל מבקר אנונימי את רשימת המשתמשים שיש להם פוסטים פורסמו – שם משתמש, slug, ID, ולעיתים גם תיאור. עבור תוקף, זה צעד ראשון מושלם בהתקפת brute-force: במקום לנחש גם שם משתמש וגם סיסמה, הוא צריך לנחש רק סיסמה.

למה זה משנה

תוקפים שלא יודעים מי האדמין באתר מתחילים מ-GET ל-/wp-json/wp/v2/users. תוך שנייה הם יודעים שמשתמש בשם geffen הוא ID 1 ולכן כמעט בוודאות אדמין. עכשיו הם מתחברים ל-wp-login.php עם השם הזה ועם רשימת 10,000 הסיסמאות הנפוצות ביותר. גם אם אתה משתמש ב-Cloudflare WAF, הוא לא חוסם את הניסיון הראשון – הוא רק מגביל קצב. הניחוש הראשון של "Password123!" כבר מגיע לשרת. ב-WordPress 4.7.1 נוספה סינון של משתמשים בלי פוסטים, אבל לא של משתמשים שכן פרסמו – ולכן כל בלוג עם שם הכותב גלוי בעמוד הבית כבר חושף לפחות אדמין אחד.

איך לזהות

גש בדפדפן (במצב גלישה פרטית, בלי להיות מחובר) לכתובת https://example.com/wp-json/wp/v2/users. אם אתה רואה JSON עם שמות משתמש – החשיפה פעילה. ניסיון נוסף: https://example.com/?rest_route=/wp/v2/users – זה עוקף תוספי אבטחה שמסננים רק לפי נתיב URL. הרבה תוספים כמו "Disable REST API" חוסמים את הצורה הראשונה אבל לא את השנייה.

איך לתקן

הדרך הנקייה: סינון rest_authentication_errors שמחזיר 401 לכל בקשה למשתמשים מצד מבקר לא מחובר. את התוסף Gutenberg ואת REST API הפנימיים זה לא שובר, כי המנהל המחובר עדיין יקבל את הנתונים.

// הוסף ל-mu-plugin או ל-functions.php של תבנית ילד
add_filter('rest_authentication_errors', function ($result) {
    if (!empty($result)) {
        return $result;
    }
    if (!is_user_logged_in()) {
        global $wp;
        $route = $wp->request ?? '';
        if (preg_match('#wp/v2/users#', $route) || (isset($_GET['rest_route']) && strpos($_GET['rest_route'], 'wp/v2/users') !== false)) {
            return new WP_Error('rest_login_required', 'Authentication required', ['status' => 401]);
        }
    }
    return $result;
});

טכניקה משלימה: שנה את user_nicename כך שיהיה שונה מ-user_login. גם אם הסינון יישבר באיזשהו עדכון עתידי, ה-slug שייחשף לא יהיה שם ההתחברות:

UPDATE wp_users SET user_nicename = 'author-x9k2' WHERE user_login = 'admin';

טעויות נפוצות

טעות ראשונה: לחסום את /wp-json/ כולו ב-.htaccess. זה שובר את עורך הבלוקים, את Yoast SEO, את Contact Form 7 ועוד עשרות תוספים שמשתמשים ב-REST API באופן לגיטימי. הסינון חייב להיות סלקטיבי לנקודת הקצה users בלבד. טעות שנייה: לבדוק רק את הצורה /wp-json/wp/v2/users ולשכוח את ?rest_route=. תוקפים מודעים לעקיפה הזו. טעות שלישית: לסמוך על Yoast/RankMath – אף אחד מהם אינו מטפל ב-enumeration ברירת מחדל.

בדיקה לאחר תיקון

במצב גלישה פרטית בדוק את שתי הצורות. שתיהן צריכות להחזיר 401 עם JSON: {"code":"rest_login_required","message":"Authentication required","data":{"status":401}}. לאחר מכן התחבר כאדמין ובדוק שעורך הבלוקים, הוספת תוסף ושינוי הגדרות עדיין עובדים. לבסוף, פתח את האודיט ב-RankPlus וודא שהבדיקה rest_users_public עוברת.

טיפ: אל תסמוך רק על חסימה אחת. שלב את הטיפול הזה עם בדיקת user_enum שחוסמת ?author=N – שתי החסימות מטפלות בשני וקטורים שונים של אותה התקפה.