קלפים על שולחן

BadReveal - ניצול NFT

משפחה חדשה של פגיעויות

ניצול חדש שנמצא על ידי חוקרי סייפר ו @rugpullfinder הצוות מאפשר לתוקפים לדעת מהו ה-NFT הנדיר ביותר לפני חשיפת הפרויקט. זה מאפשר לתוקף יתרון לא אחיד בקרב משקיעים לקנות את היצירה הנדירה והיקרה ביותר. בפרויקטים מסוימים, ניתן לתמחר את הנדיר ביותר פי 50 ממחיר החתיכה הסטנדרטי.

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

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

משפיע על פרויקטים פעילים מרובים

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

CatBloxPUMACapsule (זמן ניצול: 2 ימים 18 שעות, שווי שוק: 628.19 ETH)

Everai Heroes: Duo (זמן ניצול: 4 ימים ושעתיים, שווי שוק: 2 ETH )

דגן טונץ (זמן ניצול: 3 ימים ושעתיים, שווי שוק: 13 ETH )

פרטים נוספים כמו זמני עסקה ספציפיים נמצאים ב- נִספָּח.

פרטים טכניים של הפגיעות

רקע 

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

אבל מה קורה אם תוקף יצליח איכשהו להשיג את המטא-נתונים לפני שהם נחשפה ומשתמש בנתונים האלה כדי לקנות את האגדה? התוקף מקבל הטבה לא הוגנת ומרוויח יותר מכל שאר הקונים על ידי ניצול הנתונים הללו לרכישת האגדה.

מהו URI אסימון?

URI האסימון של NFT הוא מזהה ייחודי של איך האסימון "נראה". URI יכול להיות כתובת HTTP, גיבוב IPFS, או אפילו כתם JSON שנשמר בשרשרת. זה מצביע על קובץ JSON שמכיל את המטא נתונים של האסימון.

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

הפגיעות 

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

דוגמה לניצול

כאן יש לנו דוגמה לפונקציה שמחזירה את URI האסימון אם reveal הדגל הוא אמת (הוא שקר כברירת מחדל) או URI שמצביע על תמונה נסתרת המשותפת לכל אסימון אם הדגל הוא שקר.

function tokenURI(uint256 tokenId) public view virtual override
       returns (string memory)
   {
       require(_exists(tokenId), "Nonexistent token");
       if (reveal == false) {
           return hideURI;
       }
       string memory URI = baseURI;
       uint256 randomId = ((randomNumber + tokenId) % supplyMax) + 1;
       return
           bytes(URI).length > 0
               ? string(abi.encodePacked(URI, randomId.toString(), ".json"))
               : "";
   }

ה-URI נוצר עם ארבעה אלמנטים: baseURI, tokenId, supplyMax, ו randomNumber. ארבעת האלמנטים האלה פומביים בקוד שלנו, ואנחנו יודעים בקלות את שלושת האלמנטים הראשונים אבל אנחנו לא יודעים מתי randomNumber הוא באתחול.

עכשיו בואו נראה את הפונקציה איפה reveal הופך לאמת.

function revealNFT() external onlyOwner {
    require(randomNumber != 0);
       reveal = true;
   }

אנו מבינים שכל URI נגיש לאחר חשיפת ה-NFTs. לפני כן, הייתה לנו רק תמונה גנרית אחת לכל NFT. אבל לפני שמתקשרים revealNFT(), על הבעלים לוודא זאת randomNumber אותחל.

אז בואו נראה לאן הקוד מקצה ערך randomNumber.

function requestRandomNumbers() external onlyOwner {
       requestId = COORDINATOR.requestRandomWords(
           keyHash,
           s_subscriptionId,
           requestConfirmations,
           callbackGasLimit,
           numWords
       );
   }
 
   function fulfillRandomWords(
       uint256, 
       uint256[] memory randomNumbers
   ) internal override {
       randomNumber = randomNumbers[0];
   }

בדוגמה זו, הם משתמשים VRF של Chainlink, מחולל מספרים אקראיים מחוץ לשרשרת, ליצירת קיזוז ב-URI איתו randomNumber. שתי הפונקציות שלמעלה מראות איך זה עובד. הבעלים יצטרך להתקשר requestRandomNumbers() אשר יקצה מספר אקראי ל randomNumber עם קריאה פנימית של fulfillRandomWords() מאת Chainlink.

מה קורה בחיים האמיתיים כשהבעלים רוצה לחשוף את ה-NFTs?

הבעלים יתחיל בהתקשרות requestRandomNumbers() להקצות לו ערך אקראי randomNumber. כי randomNumber יש כעת ערך שאינו אפס, הבעלים יכול להתקשר revealNFT() להפוך את reveal הדגל ל-true, ואז כל ה-URIs פומביים tokenURI().

אבל זכרו שכדי לחזות את ה-URI, אנחנו צריכים רק ארבעה אלמנטים כולל שלושה ידועים ו-כבר randomNumber שהוא גם ציבורי והערך שלו מוקצה מתי requestRandomNumbers() נקרא.

אז בינתיים בין לבין requestRandomNumbers() ו revealNFT() נקראים, URIs אינם פומביים עדיין, אך יש לנו את כל האלמנטים לחזות אותם.

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

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

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

הקלות

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

מאחר שמהות הפגיעות של BadReveal מסתמכת על היתרון שיש לתוקף בין הגדרת עסקת ה-URI האסימון לעסקת הגילוי (או המנטה), ההקלה תהיה לשלב את השניים לעסקה אחת.

אם אתה מתכנן לחשוף את ה-NFTs, הגדר את URI האסימון הבסיסי בו-זמנית:

 function reveal(string memory baseURI) public onlyOwner {
     revealed = true;
     baseTokenURI = baseURI;
 }

בדוגמה שראינו קודם לכן הפתרון יכול להיות גם להעביר את הגילוי כך שיוגדר אוטומטית על ידי העסקה של Chianlink:

function fulfillRandomness(bytes32, uint256 _randomness) internal override {
     revealed = true;
     randomNumber = randomNumbers[0];
}

מינוף גישה זו, אולי כדאי לשקול למחוק את revealed boolean מהקוד שלך והחלפתו באימות תוך כדי תנועה של ה-Chainlink randomNumber מְשִׁימָה:

function someMintingFunction(uint256 _quantity) internal {
     require(!randomNumber, "Not revealed yet");
     //…
}

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

<br> סיכום

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

חשוב להגדיר דרישות לפרויקטי NFT להשקיע בביקורות באיכות גבוהה ולהשתמש בשיטות אבטחה טובות. 

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

ברצוננו להודות מיכל שטיחים @rugpullfinder את העזרה בחקירה ואימות הפגיעות איתנו. השתמשנו במומחיות ובידע שלהם בתחום ה-NFT כדי להעמיק את חור הארנב של BarReveal

נספח - פרויקט מושפע

הפרטים של CatBloxPUMACapsule

https://etherscan.io/address/0x8b8D1225bB21CA07812FF3c2ee9358f7b5d90EcA

Everai Heroes: הפרטים של Duo

https://etherscan.io/address/0x9a38dec0590abc8c883d72e52391090e948ddf12

פרטים של דגן טוונז

https://etherscan.io/address/0x19b86299c21505cdf59ce63740b240a9c822b5e4

נכתב על ידי
אביגדור ששון כהן

אביגדור הוא חוקר אבטחה web3 ב-Sayfer. הוא נלהב מטכנולוגיות בלוקצ'יין חדשות וכיצד נוכל לוודא שאנו מפתחים אותן בצורה מאובטחת.

 

עבור לתוכן