ما به تازگی دیدیم که چگونه یک حفره کوچک منجر به زیان مالی (در اندازه های متفاوت) می شود، به همین ترتیب قراردادهای هوشمند توسعه یافته بر روی Solidity مستعد حملات مختلف شناخته شده و ناشناخته هستند. بهرهبرداران از باگها و حفرهها برای بررسی قراردادهای هوشمند و دستکاری آنها برای انجام حملات استفاده میکنند. در اینجا ما یک لیست جامع از 5 خطای رایج در زبان برنامه نویسی Solidity را ارائه می دهیم.
ما در QuillAudits از روش تطبیقی پیروی می کنیم تا به اصل هر هک پی ببریم و آموخته های آن را در قراردادهای هوشمند آینده پیاده سازی کنیم تا از هرگونه تهدید بالقوه جلوگیری کنیم.
خطا در زبان برنامه نویسی solidity
1. علامت تماس خارجی را بردارید
ما در وهله اول این موضوع را حل می کنیم زیرا یکی از رایج ترین مشکلات Solidity است. به طور کلی، ارسال اتر به هر حساب خارجی از طریق انجام می شود منتقل کردن() عملکرد. جدای از این، دو تابع پرکاربرد برای برقراری تماس خارجی عبارتند از: زنگ زدن()و ارسال()، در اینجا به طور عمده زنگ زدن() تابع به طور گسترده ای برای انجام تماس های خارجی همه کاره توسط توسعه دهندگان استفاده می شود.
اگرچه زنگ زدن() و ارسال() توابع یک مقدار بولی را برمی گرداند که مشخص می کند آیا فراخوان موفقیت آمیز بوده است یا خیر. بنابراین در این مورد، اگر هر یک از توابع زنگ زدن() or ارسال() نتواند کار را انجام دهد، آنها با a بر می گردند نادرست بنابراین، اگر توسعهدهنده مقدار بازگشتی را بررسی نکند، به یک دام تبدیل میشود.
آسیب پذیری
به مثال زیر توجه کنید:
قرارداد لوتو{
boolpublic payedOut =false;
آدرس برنده عمومی؛
unintpublic winAmount;
// ... عملکرد اضافی در اینجا
تابع sendToWinner()public{
require(!payedOut);
winner.send(winAmount);
payedOut = درست است.
}
تابع removeLeftOver()public{
نیاز (payedout)؛
msg.sender.send(this.balance);
}
}
در قرارداد لوتو مانند بالا، می توان مشاهده کرد که الف برنده دریافت winAmount اتر مقدار کمی باقیمانده را از هر عامل خارجی خارج می کند.
در اینجا، دام برای قرارداد در خط [11] وجود دارد، جایی که a ارسال بدون اعتبارسنجی متقابل پاسخ استفاده می شود. در مثال بالا، الف برنده که تراکنش با شکست مواجه می شود (یا به دلیل کمبود گاز یا اگر قراردادی است که عمداً عملکرد بازگشتی را وارد می کند)، مجوز می دهد پرداخت شده است تنظیم شود درست صرف نظر از اینکه تراکنش اتر موفقیت آمیز بود یا خیر. در این صورت، هر بهرهبردار میتواند آن را پس بگیرد برنده برد از طریق خارج کردن LeftOver تابع.
رویکرد QuillAudit
تیم توسعه دهندگان داخلی ما با استفاده از این باگ مقابله می کنند [منتقل کردن] عملکرد به جای [ارسال] تابع، به عنوان [انتقال] اگر تراکنش خارجی برگردانده شود، برمی گردد. و اگر از [send] استفاده می کنید، همیشه مقدار بازگشتی را بررسی کنید.
یکی از رویکردهای قوی که ما دنبال می کنیم، استفاده از [الگوی برداشت] است. در اینجا، ما به طور منطقی عملکرد ارسال خارجی را از بقیه پایگاه کد جدا می کنیم و فشار تراکنش های بالقوه شکست خورده را بر روی کاربر نهایی قرار می دهیم، زیرا او کسی است که تابع برداشت را فراخوانی می کند.
2. ورود مجدد
قراردادهای هوشمند اتریوم از کدهای سایر قراردادهای خارجی استفاده می کنند و برای انجام این کار، قراردادها ملزم به ارسال تماس های خارجی هستند. این تماس های خارجی آسیب پذیر و مستعد حملات هستند، یکی از این حملات اخیراً در مورد هک DAO رخ داده است.
آسیب پذیری
مهاجمان زمانی چنین حملاتی را انجام می دهند که یک قرارداد اتر را به آدرسی نامعلوم می فرستد. در این حالت، مهاجم میتواند در یک آدرس خارجی که دارای کد مخرب در تابع بازگشتی است، قرارداد بسازد و زمانی که قرارداد اتر را به این آدرس ارسال میکند، این کد مخرب فراخوانی میشود.
واقعیت: اصطلاح "Reentrancy" از این واقعیت ابداع شده است که وقتی یک قرارداد مخرب خارجی تابعی را روی قرارداد آسیبپذیر فراخوانی میکند و سپس مسیر اجرای کد دوباره وارد آن میشود.
مثال زیر را در نظر بگیرید، این یک صندوق اتریوم است که به سپرده گذاران اجازه می دهد فقط 1 اتر در هفته برداشت کنند.
قرارداد EtherStore {
uint256 حذف عمومی محدودیت = 1 اتر;
mapping(آدرس => uint256) public lastWithdrawTime;
نگاشت (آدرس => uint256) موجودی عمومی;
تابع depozitFunds() قابل پرداخت خارجی {
balances[msg.sender] += msg.value;
}
تابع removeFunds (uint256 _weiToWithdraw) public {
require(balances[msg.sender] >= _weiToWithdraw);
// برداشت را محدود کنید
require(_weiToWithdraw <= drawallLimit);
// زمان مجاز برای برداشت را محدود کنید
نیاز (اکنون >= lastWithdrawTime[msg.sender] + 1 هفته)؛
require(msg.sender.call.value(_weiToWithdraw)());
balances[msg.sender] -= _weiToWithdraw;
lastWithdrawTime[msg.sender] = اکنون;
}
}
در قرارداد فوق، ما دو عملکرد عمومی، [depositFunds] و [withdrawFunds] داریم. [depositFunds] برای افزایش موجودی فرستنده استفاده می شود، در حالی که [withdrawFunds] مبلغی را که باید برداشت شود مشخص می کند. در این صورت، اگر مقدار برداشت کمتر از 1 اتر باشد، موفقیت آمیز خواهد بود.
دام در اینجا در خط [17] است که در آن انتقال اتر اتفاق می افتد. مهاجم می تواند یک قرارداد مخرب با آدرس قرارداد [EtherStores] به عنوان تنها پارامتر سازنده ایجاد کند. این امر [etherStore] را به یک متغیر عمومی تبدیل میکند، بنابراین بیشتر در معرض حمله قرار میگیرد.
رویکرد QuilllAudit
ما تکنیکهای مختلفی را برای جلوگیری از آسیبپذیریهای احتمالی ورود مجدد در قراردادهای هوشمند دنبال میکنیم. اولین و بهترین راه ممکن استفاده از تابع داخلی [انتقال] هنگام انتقال اتر به هر قرارداد خارجی است.
در مرحله دوم، مهم است که اطمینان حاصل شود که تمام تغییرات منطقی در متغیرهای حالت باید قبل از ارسال اتر به خارج از قرارداد انجام شود. در مثال [EtherStore]، خطوط [18] و [19] باید قبل از خط [17] قرار گیرند.
روش سوم نیز می تواند برای جلوگیری از تماس های مجدد استفاده شود. از طریق معرفی یک mutex. این یک متغیر حالت اضافه شده است که قرارداد را در حین اجرای کد قفل می کند.
3. مشاهده پیش فرض
برای توابعی که در Solidity از آنها استفاده می کنیم، مشخص کننده های دید وجود دارد و روشی را که می توان آنها را فراخوانی کرد، تعیین می کند. این قابلیت مشاهده است که فراخوانی توابع را تعیین می کند. به صورت خارجی توسط کاربران، توسط سایر قراردادهای مشتق شده، فقط به صورت داخلی یا فقط خارجی. بیایید ببینیم که چگونه استفاده اشتباه از مشخصکنندههای دید میتواند باعث آسیبپذیری بزرگ در قراردادهای هوشمند شود.
آسیب پذیری
به طور پیشفرض، قابلیت مشاهده تابع [عمومی] است، از این رو کاربران خارجی میتوانند توابع را بدون دید خاصی فراخوانی کنند. این اشکال زمانی ایجاد میشود که توسعهدهندگان فراموش میکنند قابلیت مشاهده را در توابعی که باید خصوصی باشند (یا میتوانند در خود قرارداد فراخوانی شوند) مشخص کنند. مثلا؛
قرارداد HashForEther {
تابع removeWinnings() {
// برنده اگر 8 کاراکتر هگز آخر آدرس 0 باشد
require(uint32(msg.sender) == 0);
_sendWinnings();
}
تابع _sendWinnings() {
msg.sender.transfer(this.balance);
}
}
قرارداد فوق یک بازی انعام ساده برای حدس زدن آدرس است. در این، میتوان دید که قابلیت مشاهده توابع مشخص نشده است، به ویژه تابع [_sendWinnings] [عمومی] است (بهطور پیشفرض)، بنابراین میتوان آن را از طریق هر آدرسی برای سرقت جایزه فراخوانی کرد.
رویکرد QuillAudit
تیم داخلی ما متشکل از توسعه دهندگان باتجربه است که همیشه بهترین شیوه های حسابرسی را دنبال می کنند، در اینجا باید دید عملکردها به صراحت مشخص شود، حتی اگر قرار است عمومی نگه داشته شوند، باید ذکر شود.
4. حفاظت از استفاده از سازندگان
به طور کلی سازنده ها به توابع ویژه ای گفته می شود که برای انجام وظایف حیاتی و ممتاز در حین تنظیم اولیه قراردادها استفاده می شوند. قبل از Solidity [v0.4.22]، سازندهها همان نامی را داشتند که در قراردادی که حاوی آنها بود استفاده میشد. حال، موردی را در نظر بگیرید که در آن نام قرارداد در مرحله توسعه تغییر میکند اما نام سازنده ثابت میماند، این حفره همچنین میتواند ورود آسانی به قرارداد هوشمند شما برای مهاجمان فراهم کند.
آسیب پذیری
اگر نام قرارداد اصلاح شود اما نام سازنده تغییر نکند، می تواند منجر به عواقب شدید شود. مثلا:
قرارداد مالک کیف پول {
آدرس مالک عمومی؛
// سازنده
تابع مالکWallet(آدرس _owner) public {
مالک = _owner;
}
// بازگشت به عقب. اتر را جمع آوری کنید.
تابع () قابل پرداخت {}
تابع remove() public {
require(msg.sender == مالک);
msg.sender.transfer(this.balance);
}
}
در قرارداد فوق، می بینیم که فقط مالک می تواند از طریق فراخوانی تابع [خروج] اتر را خارج کند. در اینجا، آسیبپذیری زمانی رخ میدهد که سازنده متفاوت از قرارداد نامگذاری شده است (حرف اول متفاوت است!). بنابراین بهرهبردار میتواند تابع [ownerWallet] را فراخوانی کند و خود را به عنوان مالک مجوز دهد و سپس با فراخوانی [خروج] تمام اتر موجود در قرارداد را خارج کند.
رویکرد QuillAudit
ما با نسخه [0.4.22] کامپایلر Solidity مطابقت داریم. این نسخه یک کلمه کلیدی را معرفی کرده است. [سازنده] که به نام تابع نیاز دارد تا با نام قرارداد مطابقت داشته باشد.
5. Tx.Origin Authentication
در اینجا، [Tx.Origin] متغیر جهانی Solidity است، که حاوی آدرس حسابی است که در ابتدا تماس یا تراکنش را انجام داده است. این متغیر نمی تواند برای احراز هویت استفاده شود، زیرا انجام این کار قرارداد را در برابر حملات فیشینگ آسیب پذیر می کند.
آسیب پذیری
قراردادهایی که به کاربران از طریق متغیر [tx.origin] مجوز می دهند در معرض حملات خارجی قرار می گیرند که کاربران را به انجام اقدامات تأیید شده در قرارداد اشتباه هدایت می کند. مثال زیر را در نظر بگیرید:
قرارداد فیش پذیر {
آدرس مالک عمومی؛
سازنده (آدرس _owner) {
مالک = _owner;
}
تابع () قابل پرداخت خارجی {} // جمع آوری اتر
تابع removeAll(address _recipient) public {
require(tx.origin == مالک);
_recipient.transfer(this.balance);
}
}
در اینجا در خط [11]، قرارداد تابع [withdrawAll] را با کمک [tx.origin] مجاز میکند.
رویکرد QuillAudit
ما معمولاً از استفاده از [tx.origin] برای مجوز در قراردادهای هوشمند اجتناب می کنیم. اگرچه، استفاده از [tx.origin] اکیداً ممنوع نیست، اما موارد استفاده خاصی دارد. میتوانیم از [tx.origin] برای انکار قراردادهای خارجی از فراخوانی قرارداد فعلی استفاده کنیم، میتوان آن را با [require] از فرم [require(tx.origin == msg.sender)] اجرا کرد. این کار برای جلوگیری از فراخوانی قراردادهای میانی برای فراخوانی قرارداد فعلی انجام می شود که قرارداد را به آدرس های معمولی بدون کد محدود می کند.
جمع بندی نهایی
ما به طور جامع به پنج دام رایج در زبان Solidity پرداخته ایم. هنگام توسعه قراردادهای هوشمند، نباید فراموش کنیم که آنها از نظر طراحی تغییر ناپذیر هستند، به این معنی که وقتی آنها را ایجاد می کنیم، هیچ راهی برای وصله کد منبع وجود ندارد.
این چالش بزرگی را برای توسعه دهندگان ایجاد می کند تا از مزیت ابزارهای تست امنیتی و ممیزی موجود قبل از استقرار استفاده کنند.
کشف تهدیدات مخرب احتمالی برای قراردادهای هوشمند، و خطراتی که برخی از آنها در بالا ذکر کردیم، توسط تیم کارشناسان حسابرسی داخلی ما به روشی بسیار منحصر به فرد و قوی انجام می شود. ما در QuillAudits بهترین تلاش خود را برای تحقیقات امنیتی انجام می دهیم تا قرارداد شما را با تمام شیوه های امنیتی نرم افزار به روز نگه داریم تا قرارداد شما ایمن و ایمن باشد.
با QuillHash تماس بگیرید
با حضور در صنعت سالها، QuillHash راه حل های سازمانی را در سراسر جهان ارائه کرده است. QuillHash با تیمی از متخصصان یک شرکت پیشرو در توسعه بلاک چین است که راهحلهای صنعتی مختلف از جمله DeFi را ارائه میکند، اگر در ممیزی قراردادهای هوشمند به کمک نیاز دارید، با کارشناسان ما تماس بگیرید اینجا!
برای به روز رسانی های بیشتر QuillHash را دنبال کنید
منبع: https://blog.quillhash.com/2021/06/04/top-5-common-errors-in-solidity-programming-language/
- 11
- حساب
- مزیت - فایده - سود - منفعت
- معرفی
- حسابرسی
- تصدیق
- مجوز
- بهترین
- بلاکچین
- اشکال
- اشکالات
- صدا
- موارد
- علت
- به چالش
- رمز
- مشترک
- شرکت
- قرارداد
- قرارداد
- جاری
- دائو
- DEFI
- طرح
- توسعه دهنده
- توسعه دهندگان
- پروژه
- سرمایه گذاری
- اتر
- ethereum
- واقعه
- کارشناسان
- فیس بوک
- مالی
- نام خانوادگی
- به دنبال
- فرم
- رایگان
- تابع
- آینده
- بازی
- GAS
- جهانی
- بزرگ
- هک
- اینجا کلیک نمایید
- چگونه
- HTTPS
- بزرگ
- از جمله
- صنعت
- IT
- زبان
- رهبری
- برجسته
- لاین
- لینک
- فهرست
- مسابقه
- دیگر
- مالک
- وصله
- الگو
- فیشینگ
- حملات فیشینگ
- تجویز
- در حال حاضر
- خصوصی
- برنامه نويسي
- عمومی
- کشیدن
- تحقیق
- پاسخ
- REST
- امن
- تیم امنیت لاتاری
- خدمات
- تنظیم
- ساده
- کوچک
- هوشمند
- قرارداد هوشمند
- قراردادهای هوشمند
- So
- نرم افزار
- استحکام
- مزایا
- دولت
- موفقیت
- تست
- منبع
- تهدید
- زمان
- بالا
- بالا 5
- معامله
- معاملات
- us
- کاربران
- ارزش
- طاق
- دید
- آسیب پذیری ها
- آسیب پذیری
- آسیب پذیر
- هفته
- WHO
- در داخل
- سال