איך למנוע התקפות אימות קלט ב-Solidity

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

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

ההשפעה היא מוחשית: Qubit אבדו 80.0 מיליון דולר על BSC ב-28-01-2022 באירוע שסווג כ-Missing Input Validation. הכשלים הללו אינם רק בעיות של טרנזקציות פגומות. ערך בלתי מבוקר יכול להפעיל קוד Solidity תקין כדי לבצע פעולה עסקית לא תקינה, כגון זיכוי בטחונות שאינם קיימים, העברת נכסים ליעד שאינו אמון או יצירת חוב מעבר למגבלת הלווים בשטח האמיתי שלו.

מהי חולשה באימות קלט ב-Solidity, כיצד מתבצע מתקפה?

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

Solidity מיישמת סוגים, לא כוונת פרוטוקול. uint256 יכול להיות כל מספר שאינו מחזיר שגיאה, ו-address יכול לזהות EOA, חוזה malicious, כתובת אפס או טוקן שאינו נתמך. על החוזה לאכוף את כללים המשמעיים החסרים.

רצף התקפה טיפוסי הוא:

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

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

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

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

דגמים נפוצים שאינם בטוחים כוללים:

  • ערך בטחון שהמשתמש מצהיר עליו: לווים מספקים את הערך המשמש לחישוב כוח ההלוואה שלהם.
  • כתובות טוקן אקראיות: פונקציות הפקדה שמקבלות כל ERC-20, כולל טוקנים עם התנהגות העברת לא תקינה.
  • כתובות מקבל שלא נבדקות: פונקציה שמקבלת address(0), החוזה עצמו או כתובת שמפרה כללי שמירה.
  • אחוזים יחסיים ושיעורים מוגבלים: עמלה, LTV, הנחה, זליגה או שיעור תגמול העולים על טווח נקודות הבסיס המיועד.
  • קשרים בין מערכים שלא אומתו: מערכים מקבילים באורך שונה, כניסות כפולות שמתקבלות או נתיב חוזר על נכס באופן בלתי צפוי.
  • מעברי מצב ללא תנאים מוקדמים: פעולות תביעה, ביטול, מימוש או היגירה שמבוצעות במצב מחזורי לא מתאים.
  • נתוני חיצוני שנאמנים על ידי הפונקציה כעובדה: פונקציה שמאמינה למחירים, מאוזנים, חתימות או ערכי החזרה שמספק המשתמש בלי וידוא.

האימות חייב להיות בהקשר. require(amount > 0) שימושי, אך אינו מראה ש-amount הוא בר-מימון, מבוטח כבטוח, בתוך הגבלה או תואם למצב הנוכחי. גם בדיקת כתובת טוקן שאינה אפס אינה מאשרת שהטוקן מאושר, מתנהג כצפוי או שייך לשוק הנכון.

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

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

דוגמה זו מאפשרת ללווה təqdim את collateralValue שלו. החוזה בודק שהתשלום המבוקש לא עולה על הערך הזה, אך לעולם לא מראה שהלווה הפקיד בטחונות ששווים לסכום זה. כך, ש caller יכול גם להגדיר את שני הערכים לרמה אקראית וללוות את הנכסים שמומנים על ידי המלווה.

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

interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);
}

contract UnsafeLender {
    IERC20 public immutable asset;
    mapping(address => uint256) public debt;

    constructor(IERC20 asset_) {
        require(address(asset_) != address(0), "zero asset");
        asset = asset_;
    }

    function borrow(
        uint256 collateralValue,
        uint256 amount
    ) external {
        require(collateralValue > 0, "no collateral");
        require(amount <= collateralValue, "insufficient collateral");

        // פגיע: הלווה מספק את הערך התומך בהלוואה.
        debt[msg.sender] += amount;

        require(asset.transfer(msg.sender, amount), "העברה נכשלה");
    }
}

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

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

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

contract ValidatedLender {
    IERC20 public immutable loanAsset;
    IERC20 public immutable collateralAsset;
    uint256 public immutable maxLtvBps;

    mapping(address => uint256) public collateral;
    mapping(address => uint256) public debt;

    constructor(IERC20 loanAsset_, IERC20 collateralAsset_, uint256 maxLtvBps_) {
        require(address(loanAsset_) != address(0), "zero loan asset");
        require(address(collateralAsset_) != address(0), "zero collateral asset");
        require(maxLtvBps_ > 0 && maxLtvBps_ <= 10_000, "ערך לא חוקי");
        loanAsset = loanAsset_;
        collateralAsset = collateralAsset_;
        maxLtvBps = maxLtvBps_;
    }

    function depositCollateral(uint256 amount) external {
        require(amount > 0, "סכום אפס");
        uint256 beforeBalance = collateralAsset.balanceOf(address(this));
        require(collateralAsset.transferFrom(msg.sender, address(this), amount), "העברה נכשלה");
        uint256 received = collateralAsset.balanceOf(address(this)) - beforeBalance;

        // תיקון: מתחשב רק ב-Bתשלום שפועל בפועל.
        require(received == amount, "התנהגות לא תומכת בטוקן");
        collateral[msg.sender] += received;
    }

    function borrow(uint256 amount) external {
        require(amount > 0, "סכום אפס");
        uint256 limit = (collateral[msg.sender] * maxLtvBps) / 10_000;
        require(debt[msg.sender] + amount <= limit, "חריגות מגבלת ההלוואה");

        debt[msg.sender] += amount;
        require(loanAsset.transfer(msg.sender, amount), "העברה נכשלה");
    }
}

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

איך מונעים מתקפות אימות קלט ב-Solidity?

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

טכניקה מה היא מונעת עלות בגז או במורכבות מגבלה
רשימות מותרות ל-token וליישומים טוקנים לא נתמכים, adapters זדוניים, קריאות חיצוניות אקראיות חיפוש בזיכרון או השוואה לכתובת קבועה טוקנים מאושרים עדיין עלולים להביא לסיכון כלכלי
בדיקות גבולות סכומים אפס, שיעורים מופרזים, נקודות בסיס לא תקינות, זמן סיום לא בטוח נמוך, בדרך כלל כמה השוואות גבולות לבד אינם מראים תקינות כלכלית
בדיקת כתובת אפס ומקבלים שריפת נכסים, בעלות בלתי שימושית, יעדי שמירה לא תקינים נמוך, השוואת כתובת אחת כתובת לא אפס עדיין יכולה להיות לא אמון
הרשאות ובדיקות מחזור חיים קריאות במצב לא נכון או על ידי שחקן לא מורשה קריאת זיכרון ועיצוב תפקידים תפקידים דורשים ניהול בטוח
אימות delta של מאזן אשראי שגוי והעברות טוקן לא נתמכות קריאות חשבון טוקן נוסף דורש טיפול זהיר באובייקטי rebase טוקנים
אימות invariants חוסן שבור, יחסי היצע, חוב ושמורות קריאות וחשבונות נוספים invariants חייבים לכסות את כל המודל הכלכלי

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

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

אילו אירועים אמיתיים סווגו כ-Missing input validation?

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

פרוטוקול תאריך סכום שאבד רשת
Qubit 2022-01-28 80.0 מיליון דולר BSC
Hedgey 2024-04-19 44.7 מיליון דולר Arbitrum, Ethereum
Transit Swap 2022-10-02 28.9 מיליון דולר Ethereum, BSC
Pickle 2020-11-22 19.7 מיליון דולר Ethereum

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

DeFiLlama מסווג 52 אירועים של חוזים חכמים מ־2020 עד 2026 כ-Missing Input Validation, בהפסדים משולבים של 330.5 מיליון דולר. היקף זה מוכיח ש-Soken רואה באימות כנושא כלכלה-אבטחה ולא כרשימת בדיקות שטחיות של require.

איך מזהים חולשות של אימות קלט בלתי תקין?

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

השתמש ברשימת בדיקה זו:

  1. רשום כל פרמטר בפונקציה external ו־public שמשפיע על ערך, סמכות או מסלול.
  2. זיהוי האם כל כתובת היא לא אפס, מאושרת ומתאימה לתפקיד שלה.
  3. בדוק כל סכום מול גבולות פרוטוקול, מאזנים, מגבלות חוב, מגבלות היצע והנחות על עשרוניות.
  4. וודא שמחירים, ערכי בטחונות ומאזנים מקורם במקורות מהימנים או מראש ונדגמו באופן עצמאי.
  5. אמת אורך מערכים, ייחודיות, סדר ויחסי אינדקס בין מערכים.
  6. ודא שכל פונקציה מתירה רק מצבי מחזור חיים תקפים ומבוטחים על ידי קוד מורשה.
  7. בדוק את חשבוניות delta של מאזן עבור עלויות בהעברה, ריבוסינג והתנהגות לא שגרתית של ERC-20.
  8. קבע ובדוק את מצב התוצאה לכל פעולה שמזיזה נכסים.

Slither מסוגל לזהות דפוסי שגיאות כמו קריאות חיצוניות מסוכנות, ערכי החזרה לא מבוקרים ושליטה גישה רופפת, שמרבים להקיף כשלי אימות. כמובן, זה לא יכול להסיק את כל invariants של הפרוטוקול ולכן מומלץ להשתמש ב-Foundry fuzzing ליצירת ערכים אפס, מקסימום, מערכים כפולים, כתובות בלתי צפויות וטוקנים שאינם נתמכים.

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

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

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

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

שאלות נפוצות

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

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

איך נמנע התקפות אימות קלט ב-Solidity?

יש לאמת כל קלט חיצוני לפני שמזיזים נכסים או מבצעים פעולות חשבונאות, באמצעות allowlists, bounds, zero-address checks, בדיקות הרשאה והבטחות מצב כדי להבטיח שהערכים תואמים לכללי הביטחון והכלכלה של החוזה.

מה קרה בהתקפת אימות הקלט של Qubit?

Qubit איבדה $80M ב-BSC ב-28.01.2022 בהתקפה שסווגה כמחסור באימות קלט. מקרה זה מראה שהעדר בדיקות יכול לגרום לחוזה לזהות נכסים לא קיימים או לבצע פעולות כלכליות לא חוקיות.

למה על חוזי Solidity לבדוק את ה-zero address?

חוזי Solidity צריכיםreject כתובת zero כאשר הכתובת שלה נדרשת לזהות יעד, נכס או סמכות אמיתיים. בדיקת zero-address מונעת קבלת ערך מש placeholder שמפר את ההנחות על הטרנספירים, ההרשאות והחשבונאות.

האם bounds לבד מספיקים למנוע נקודות תורפה באימות קלט?

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

צ׳אט