איך למנוע התקפות Broken Access Control ב-Solidity

המאמר נכתב על ידי

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

ההשפעה יכולה להיות נרחבת יותר בפרוטוקול כולו, מכיוון שפונקציות בעלי הרשאה לעיתים קרובות מפקחות על כספים מצטברים או קונפיגורציה קריטית לניהול. Poly Network אבדו סכום של 611.0 מיליון דולר ב-10 באוגוסט 2021 באירוע שזוהה על ידי DefiLlama כ”בקרת גישה לא תקינה”. לכן, ההרשאה אינה עניין של ממשק משתמש בלבד או פרטים מנהלתיים, אלא חלק בלתי נפרד מגבול הביטחון המרכזי של החוזה.

מהי פרצה בבקרת גישה שבורה ב-Solidity?

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

גם אם לחוזה יש משתנה owner, הוא עלול להיות פגיע. השאלה החשובה היא האם כל נתיב ביצוע בעל הרשאה מבצע בדיקה נכונה של ההרשאה, כולל פונקציות עזר, מפעילי אתחול, פונקציות ניהול תפקידים ו-hooks לשדרוג.

דוגמה לתקיפה נפוצה של בקרת גישה שבורה נראית כך:

  1. התוקף מזהה פונקציה שניתן להזמין מבחוץ שיש בה השלכות בעלות הרשאה.
  2. אין בה בדיקת קריאה, או שהיא בודקת את הכתובת הלא נכונה, או נשענת על מצב שניתן לשליטה על ידי התוקף.
  3. התוקף קורא לפונקציה ישירות, מ绕 שום הגבלה בממשק ה-front-end.
  4. הקריאה מעניקה תפקיד, משנה כתובת נתונה בטוחה, או מפעילה פעולה של העברת נכסים.
  5. התוקף משתמש ביכולת החדשה כדי למשוך כספים או לשנות את התנהגות הפרוטוקול.
  6. החוזה מקבל את העסקה כי ה-EVM מיישם רק בדיקות על הבלוק עצמו.

כפתורי ממשק, תיעוד ונהלי תפעול אינם מונעים קריאות ישירות לחוזה. כל חשבון יכול להגיש קריאת calldata לפונקציה ציבורית או חיצונית. לכן, קוד Solidity חייב לאכוף את המדיניות בדיוק בנקודה בה מתבצע שינוי מצב רגיש.

אילו תבניות ב-Solidity גורמות לתקיפות של בקרת גישה ושדרוג הרשאות?

פרצות של בקרת גישה לא תקינה מופיעות לעיתים קרובות כאשר מפתחים חושפים setters להגדרות תצורה, עוזרים ניהוליים או מסלולי אתחול מבלי לשקול שכל פונקציה ציבורית היא API שאינו בטוח בפני תוקף. התבנית המסוכנת אינה מוגבלת רק להיעדרות של מיידע onlyOwner: היא כוללת הרשאות שניתן לשנות על ידי קריאות חיצוניות, תפיסות שגויות שיורשות אוטומטית הרשאות מיותרות, או הגדרות רחבות מדי.

תבניות כשל נפוצות כוללות:

  • setters ציבוריים שמאפשרים לכל אחד להחליף קרן, oracles, יישום, מפעיל או זוכה.
  • פונקציית ניהול תפקידים בלי בדיקת מנהל.
  • אתחולונים שניתן לקרוא לאחר פריסה, או נקראים על ידי חשבון לא מתוכנן.
  • פונקציות שבודקות tx.origin במקום msg.sender.
  • פונקציה פנימית עם הרשאה גבוהה שמגיע through wrapper חיצוני בלתי מוגן.
  • תפקיד של מנהל יחיד שיכול לבצע כל פעולה תפעולית, ומגדיל את טווח הפגיעות.
  • הרשאות המבוססות על כתובת משתמש מספק, במקום מיפוי תפקידים שאינו משתנה.
  • פונקציות שדרוג שמאשרים את הקריאה אך לא את היישום החדש או את מסלול השדרוג.

תבנית פגומה זו, במיוחד כשמאוד עדינה, נראית כך:

function setOperator(address newOperator) external {
    operator = newOperator;
}

הפונקציה לחיוב תשלום אולי דורשת ש-msg.sender == operator, אך הגבול של בקרת ההרשאות כבר נפרץ מכיוון שכל אחד יכול להכניס את עצמו כ’מפעיל’. בודקים צריכים לעקוב אחורה מכל פונקציה רגישה כדי לאתר מי יכול לקבל את התפקיד הדרוש.

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

איך נראה התקפת בקרת גישה שבורה בקוד Solidity?

דוגמה זו מראה חוזה שמבצע בדיקת קריאה לפני מכירה, אך הפונקציה setPayoutOperator שלה היא פומבית. תוקף יכול להקצות את עצמו כ-operator ואז לקרוא ל-pay, מה שהופך את בדיקת ההרשאה מאוחרת חסרת תוקף.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract VulnerablePayoutVault {
    address public payoutOperator;

    constructor(address initialOperator) {
        payoutOperator = initialOperator;
    }

    receive() external payable {}

    function setPayoutOperator(address newOperator) external {
        // פגיעות: כל קריאה יכולה להעניק לעצמו הרשאה לתשלום.
        payoutOperator = newOperator;
    }

    function pay(address payable recipient, uint256 amount) external {
        require(msg.sender == payoutOperator, "לא אופרטור");
        require(address(this).balance >= amount, "אוברדראפט");
        (bool sent, ) = recipient.call{value: amount}("");
        require(sent, "כשל בתשלום");
    }
}

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

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract FixedPayoutVault {
    address public owner;
    mapping(address => bool) public payoutOperators;

    modifier onlyOwner() {
        require(msg.sender == owner, "לא בעלים");
        _;
    }

    modifier onlyPayoutOperator() {
        require(payoutOperators[msg.sender], "לא מפעיל תשלום");
        _;
    }

    constructor(address initialOwner, address initialOperator) {
        require(initialOwner != address(0), "כתובת אפס");
        require(initialOperator != address(0), "כתובת אפס");
        owner = initialOwner;
        payoutOperators[initialOperator] = true;
    }

    receive() external payable {}

    function setPayoutOperator(address operator, bool allowed) external onlyOwner {
        require(operator != address(0), "כתובת אפס");
        // תיקון: רק הבעלים יכול לשנות חברות בתפקידי ההרשאה.
        payoutOperators[operator] = allowed;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "כתובת אפס");
        owner = newOwner;
    }

    function pay(address payable recipient, uint256 amount)
        external
        onlyPayoutOperator
    {
        require(address(this).balance >= amount, "חוסר באון");
        (bool sent, ) = recipient.call{value: amount}("");
        require(sent, "כשל בתשלום");
    }
}

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

איך מונעים מפתחים ב-Solidity פרצות של בקרת גישה לא תקינה?

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

טכניקה מה היא מונעת עלות בגז או מורכבות מגבלות
onlyOwner modifier ניהול בלתי מורשה של פונקציות שרק לבעלים קריאה לקרן אחת לפני כל בדיקה בעל שהוחלש עדיין שומר על כל ההרשאות שלו
מיפויי תפקידים או AccessControl של OpenZeppelin שימוש לא מאושר בתפקידים תפעוליים נפרדים קריאות לקרן ולוגיקה של ניהול תפקידים תכנונים לא נכונים עלולים לייצר הרשאות עודפות
תפקידים עם מינימום הרשאות שמירה או מפעיל שמבצע גם פעולות ניהול בלתי קשורות יותר פונקציות והגדרות תפקידים דורש קווי גבול ברורים לתפעול
Timelocks לשינויים קריטיים שדרוגים זדוניים מיידיים או שינויים בפרמטרים המתנה לביצוע והוספת חוזים לא מונע פעולות זדוניות לאחר ההשהיה אם לא קיימת יכולת תגובה של משתמשים
ניהול רב חתימות שינוי בסטטוס בעלות או הרשאות על ידי מפתח אחד בלבד תיאום בין החותמים ועלות תפעולית חותמים משותפים או מדיניות רכות עלולים להוות סיכון
הגנות אתחול חד-פעמיות אתחול בלתי מורשה או חוזר דגל באחסון ומבנה אתחול תהליכי פריסה צריכים להבטיח הגדרה נכונה של בעלות ואחריות

שימוש במודולים של שליטה בבעלות ותפקידים שאושרו בקפידה הוא רצוי, אך אין להריץ ספרייה שלמה כמודל ביטחון יחיד. לדוגמה, Ownable של OpenZeppelin מגדיר אוטומטית מנגנון בעלות, אך הצוות צריך להחליט אם הבעלות תשלוט גם בהשהיה, שדרוג, איסוף דמי פרסום, החלפת oracle, או חירום.

פרקטיקה אסורה היא שימוש ב-tx.origin לאישור. היא יכולה להיות מושפעת דרך חוזים מתווכים ואינה מזהה את הקריאה המיידית. עדיף להשתמש ב-msg.sender ולהשוות לכתובת המוגדרת של בעלים, תפקיד או חוזה מפוקח.

אילו תקריות אמיתיות ממחישות את ההשפעה של התקפות על בקרת גישה?

מדד DefiLlama מתעד 130 תקריות חוזה חכם המוגדרות כ”פריצת בקרת גישה לא תקינה” בין 2020 ל-2026, בלי כלכלות חוץ של חורי חילופי כספים או ארנקים, בסכום כולל של 909.7 מיליון דולר. אירועים אלה מראים שהכשלות בבקרת הגישה משפיעות על גשרים, פרוטוקולי DeFi, בוטים ויישומים במספר רשתות.

פרוטוקול תאריך סכום רשת
Poly Network 2021-08-10 $611.0M Ethereum, BSC, Polygon
Mirror 2021-10-08 $90.0M Terra
Wintermute 2022-06-05 $17.6M Optimism
ValueDefi 2021-05-05 $10.0M BSC
Cork V1 2025-05-28 $12.0M Ethereum
SUPERFORTUNE AI 2026-05-27 $15.2M Ethereum
The Sandbox 2026-08-22 $675K Base, BSC, Ethereum
Radix 2026-08-31 $1.2M Radix

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

איך מזהים פרצות בבקרת גישה לפני הפריצה?

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

רשימת בדיקה לבדיקה:

  1. רשום את כל הפונקציות שמעבירות נכסים, משנות תפקידים, משדרגות קוד, עוצרות פעילות או משנות כתובות אמינות.
  2. זהה את הקריאה המיועדת ואת כלל ההרשאה לכל פונקציה רשומה.
  3. עקוב אחר כל פונקציה שיכולה להעניק, לבטל, להעביר או לאתחל את ההרשאה הזו.
  4. בדוק את כל עטיפות החוץ שמגיעות לפונקציות פנימיות עם הרשאות.
  5. תאשר שאתחול מתרחש פעם אחת בלבד ותוך תהליך הפריסה המתוכנן.
  6. ודא שפונקציות חירום לא יוכלו לבצע פעולות שגרתיות או ניהול שאינן רלוונטיות.
  7. בדוק ביטול תפקיד, העברת בעלות, וטיפול בכתובת אפס.
  8. עיין בפרוקסיז, חוזים שיורשים ומסלולי delegatecall כחלק מאותה גבול הרשאה.

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

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

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

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

המאמר נכתב על ידי

שאלות נפוצות

מהי פרצה ב-Broken Access Control ב-Solidity?

פרצת ב-Broken Access Control ב-Solidity מתרחשת כאשר חוזה לא מאשר באופן מהימן ש- msg.sender מורשה לפעולה פרטית. פגם זה מאפשר לקריאה לא מורשית לבצע שינויים במצב ולפעמים להרחיב את ההרשאות באופו לא בטוח.

איך התקפות Broken Access Control מובילות ל-privilege escalation?

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

אילו פעולות ב-Solidity צריכות להיות מוגבלות עם בקרת גישה?

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

מה אירע באירוע ה-Poly Network בנוגע לבקרת גישה?

ב-2021-08-10, Poly Network הפסידה 611 מיליון דולר באירועים שהוגדרו על ידי DefiLlama כבקרת גישה לא תקינה. זה מראה כי בדיקות ההרשאה חשובות יותר מהנהלה בלבד, כי הרשאות משותפות יכולה להשפיע על כספים וניהול.

מדוע ההרשאה היא גבול ביטחוני מרכזי ב-Solidity?

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

צ׳אט