فرض کنید از یک دستگاه خودپرداز پول میگیرید و دستگاه اول اسکناسها را بیرون میدهد، بعد موجودی شما را کم میکند. حالا فرض کنید درست در فاصلهی این دو کار بتوانید دوباره دکمهی «برداشت» را بزنید. دستگاه هنوز فکر میکند موجودی شما دستنخورده است و باز پول میدهد.
این تصویر ساده، هستهی یکی از مشهورترین حادثههای تاریخ قراردادهای هوشمند است: هک The DAO در ژوئن ۲۰۱۶. حادثهای که فقط یک پروژه را زمین نزد؛ بلکه جامعهی اتریوم را به تصمیمی سخت کشاند و در نهایت یک زنجیره را به دو زنجیره تبدیل کرد.
The DAO چه بود؟
The DAO یک «سازمان خودگردان غیرمتمرکز» یا DAO (Decentralized Autonomous Organization) بود؛ یعنی سازمانی که قواعدش به جای اساسنامهی کاغذی، در کد یک قرارداد هوشمند (smart contract) روی اتریوم نوشته شده بود. کد آن را شرکت آلمانی Slock.it و همبنیانگذارانش نوشته بودند. ایده این بود که دارندگان توکن DAO با رأی خود تصمیم بگیرند اتر (ETH) جمعشده صرف چه پروژههایی شود.
به گزارش کمیسیون بورس و اوراق بهادار آمریکا (SEC) در گزارش رسمی سال ۲۰۱۷:
- فروش توکن از ۳۰ آوریل تا ۲۸ مه ۲۰۱۶ (۱۱ اردیبهشت تا ۸ خرداد ۱۳۹۵) انجام شد؛
- حدود ۱٫۱۵ میلیارد توکن DAO در برابر حدود ۱۲ میلیون اتر فروخته شد؛
- ارزش این اتر در زمان پایان فروش حدود ۱۵۰ میلیون دلار بود.
یکی از قابلیتهای The DAO «جداشدن» (split) بود: هر عضوی که با تصمیم اکثریت موافق نبود، میتوانست سهم اتر خود را بردارد و به یک DAO فرزند (child DAO) ببرد. همین مسیر خروج، نقطهی ضعف ماجرا شد.
سازوکار بازگشتپذیری به زبان ساده
بازگشتپذیری (reentrancy) یعنی قرارداد A هنوز کارش تمام نشده که قرارداد B را صدا میزند، و B از فرصت استفاده میکند و دوباره وارد A میشود. مستندات رسمی سالیدیتی همین را یکی از دامهای اصلی میداند: هر فراخوانی به قرارداد دیگر، حتی یک ارسال سادهی اتر، میتواند کنترل را به کدی بدهد که شما ننوشتهاید.
در کد منتشرشدهی The DAO (نسخهی ۱٫۰ در مخزن عمومی Slock.it)، تابع جداشدن کارها را تقریباً به این ترتیب انجام میداد:
- محاسبهی سهم اتر کاربر بر اساس موجودی توکن او.
- انتقال این سهم به DAO فرزند.
- پرداخت «پاداش» انباشتهی کاربر به نشانی خود او.
- و تنها در پایان: صفر کردن موجودی توکن کاربر.
مشکل در فاصلهی گام ۳ و ۴ است. اگر نشانی کاربر یک کیفپول معمولی باشد، اتفاقی نمیافتد. اما اگر آن نشانی خودش یک قرارداد باشد، دریافت اتر باعث اجرای کد آن قرارداد میشود (تابع پیشفرض یا fallback). مهاجم قراردادی ساخت که در همین لحظه دوباره تابع جداشدن را صدا بزند. نمودار سادهی مراحل:
- مهاجم درخواست جداشدن میدهد.
- The DAO سهم او را حساب میکند و به DAO فرزند میفرستد.
- The DAO میخواهد پاداش بدهد و اتر را به قرارداد مهاجم میفرستد.
- کد قرارداد مهاجم اجرا میشود و دوباره درخواست جداشدن میدهد.
- The DAO موجودی مهاجم را بررسی میکند؛ هنوز صفر نشده است، پس همان سهم را دوباره حساب و منتقل میکند.
- این چرخه چند بار تکرار میشود و فقط در پایان، موجودی صفر میشود؛ وقتی دیگر دیر شده است.
نکته: اشکال در خود اتریوم نبود. ویتالیک بوترین در پست بنیاد اتریوم در همان روز تأکید کرد که مشکل مختص The DAO است و خود اتریوم امن است. اشکال در ترتیب کارها در یک قرارداد بود.
روز حمله و دفاعی که دیر رسید
خطر فراخوانی بازگشتی پیش از حمله هم مطرح شده بود. به گزارش CoinDesk در ۱۴ ژوئن ۲۰۱۶، چند روز قبل این نوع حمله در جامعهی توسعهدهندگان بحث شد، یکی از اعضای انجمن The DAO نشان داد که بخشی از کد آن هم آسیبپذیر است، و Slock.it اعلام کرد که در آن لحظه وجهی در خطر نیست و اصلاح را آماده میکند. بنا بر گزارش SEC، از اواخر مه هم پیشنهادهایی برای بهروزرسانی امنیتی کد و توقف موقت همهی پیشنهادها تا رفع آسیبپذیریها مطرح شده بود. اما قراردادی که روی زنجیره مستقر شده بود را نمیشد بهسادگی عوض کرد، و این دفاع ناقص ماند.
به گزارش SEC، در ۱۷ ژوئن ۲۰۱۶ (۲۸ خرداد ۱۳۹۵) فرد یا گروهی ناشناس شروع به بیرون کشیدن اتر کرد و حدود ۳٫۶ میلیون اتر، یعنی حدود یکسوم کل اتر جمعشده، به یک DAO فرزند تحت کنترل مهاجم منتقل شد.
یک جزئیات مهم وضعیت را قابلمدیریت کرد: بنا بر همان گزارش و پست بنیاد اتریوم، قواعد خود The DAO اجازه نمیداد مهاجم تا حدود ۲۷ روز اتر را از DAO فرزند خارج کند. این مهلت، فرصتی برای تصمیمگیری جامعه ساخت.
سافتفورک، هاردفورک و تولد اتریوم کلاسیک
پیشنهاد سافتفورک. در پست ۱۷ ژوئن بنیاد اتریوم، یک سافتفورک (soft fork) پیشنهاد شد؛ یعنی قاعدهای سختگیرانهتر که تراکنشهای کاهندهی موجودی The DAO و فرزندانش را نامعتبر کند، بدون برگرداندن هیچ بلوکی. اما در ۲۸ ژوئن، بنیاد اتریوم هشدار داد که پیادهسازی این سافتفورک یک آسیبپذیری حملهی از کار انداختن سرویس (DoS) دارد و توصیه کرد گرهها فعلاً بدون آن اجرا شوند. از آن پس، بحث به سمت هاردفورک رفت.
هاردفورک. گزینهی بعدی یک هاردفورک (hard fork) بود؛ تغییری در قواعد که نسخههای قدیمی نرمافزار آن را نمیپذیرند. بنا بر پست ۱۵ ژوئیهی بنیاد اتریوم، طرح این بود که در بلوک ۱٬۹۲۰٬۰۰۰ موجودی حسابهای مرتبط با The DAO به یک قرارداد بازپرداخت منتقل شود و هر دارنده بتواند در برابر هر ۱۰۰ توکن DAO، یک اتر پس بگیرد. یک نظرسنجی مبتنی بر موجودی اتر (carbonvote) هم برای تعیین تنظیم پیشفرض نرمافزار geth برگزار شد.
به گزارش بنیاد اتریوم، بلوک ۱٬۹۲۰٬۰۰۰ در ۲۰ ژوئیه ۲۰۱۶ (۳۰ تیر ۱۳۹۵) این «تغییر غیرعادی وضعیت» را اجرا کرد و حدود ۱۲ میلیون اتر به قرارداد بازپرداخت منتقل شد.
اتریوم کلاسیک. همه موافق نبودند. به گزارش SEC، اقلیتی هاردفورک را خلاف اصل تغییرناپذیری بلاکچین دانستند و همان زنجیرهی قبلی را ادامه دادند؛ زنجیرهای که امروز «اتریوم کلاسیک» (Ethereum Classic) نام دارد. دو زنجیره تا پیش از بلوک ۱٬۹۲۰٬۰۰۰ تاریخچهی مشترک دارند و از آن نقطه جدا میشوند.
پرسش پشت این جدایی هنوز زنده است: آیا «کد، قانون است»، یا جامعه در شرایط استثنایی حق تغییر قواعد را دارد؟ این نوشته قصد داوری ندارد.
درسها برای برنامهنویسها
الگوی بررسیها، اثرها، تعاملها (Checks-Effects-Interactions). مستندات سالیدیتی همین الگو را برای جلوگیری از بازگشتپذیری توصیه میکند:
- بررسیها: اول همهی شرطها را چک کنید (آیا موجودی کافی است؟ آیا مجاز است؟).
- اثرها: بعد وضعیت قرارداد را بهروز کنید (موجودی را کم یا صفر کنید).
- تعاملها: فقط در آخر با قرارداد یا نشانی دیگر تماس بگیرید (اتر بفرستید).
اگر The DAO موجودی را پیش از پرداخت صفر میکرد، ورود دوبارهی مهاجم چیزی برای برداشت پیدا نمیکرد.
ممیزی و ابزار. هر قراردادی که دارایی دیگران را نگه میدارد باید پیش از استقرار، بهدقت بازبینی و ممیزی (audit) شود و آزمونهایی داشته باشد که دقیقاً سناریوی حمله را امتحان کنند. ویتالیک در همان پست، ابزارهایی مثل درستیسنجی صوری (formal verification) و اجرای نمادین را برای توسعهی امن مهم دانست.
سقف ریسک. در همان پست به نویسندگان قرارداد توصیه شد قراردادهایشان بیش از حدود ۱۰ میلیون دلار ارزش نگه ندارند (با چند استثنا). ایدهی کلی ساده است: کد جدید هر چقدر هم بررسی شده باشد، بهتر است با سقف، مهلتهای زمانی و امکان توقف اضطراری منتشر شود. مهلت ۲۷ روزهی The DAO نشان داد که یک تأخیر زمانی چقدر میتواند فرصت واکنش بسازد.
درسها برای کاربران
- قرارداد هوشمند، کد است؛ و کد ممکن است اشکال داشته باشد، حتی اگر پروژه مشهور باشد و سرمایهی زیادی جذب کرده باشد.
- «غیرمتمرکز» به معنای «بیخطر» نیست. پیش از سپردن دارایی، بپرسید آیا کد ممیزی شده، آیا سقف و سازوکار توقف دارد، و تیم چه سابقهای در پاسخ به اشکال دارد.
- هر تصمیمی را با این فرض بگیرید که بازگشت وجوه تضمینشده نیست. هاردفورک ۲۰۱۶ یک استثنای بحثبرانگیز بود، نه یک قاعده.
- عبارت بازیابی (seed phrase) خود را هرگز در هیچ سایت، اپ، چت یا هوش مصنوعی وارد نکنید؛ هیچ پروژهی واقعی برای بازپرداخت یا جبران خسارت آن را نمیخواهد.
خلاصه
- The DAO به گزارش SEC حدود ۱۲ میلیون اتر (حدود ۱۵۰ میلیون دلار در آن زمان) جمع کرد و حدود ۳٫۶ میلیون اتر آن در ۱۷ ژوئن ۲۰۱۶ جابهجا شد.
- علت، بازگشتپذیری بود: قرارداد پیش از صفر کردن موجودی، اتر میفرستاد و مهاجم در همان فاصله دوباره وارد میشد.
- پیادهسازی سافتفورک پیشنهادی آسیبپذیری DoS داشت و هاردفورک در بلوک ۱٬۹۲۰٬۰۰۰ در ۲۰ ژوئیه ۲۰۱۶ اجرا شد.
- اقلیتی زنجیرهی قبلی را ادامه دادند که امروز اتریوم کلاسیک نام دارد.
- درس اصلی برای برنامهنویس: الگوی Checks-Effects-Interactions، ممیزی جدی و سقف ریسک.
گام بعد
اگر این ماجرا کنجکاوتان کرده که خودتان قرارداد هوشمند بنویسید و از همان ابتدا امن بنویسید، فایل رایگان «نقشهی راه برنامهنویس قرارداد هوشمند» را بگیرید؛ مسیر یادگیری را از مبانی سالیدیتی تا امنیت، گامبهگام نشان میدهد. برای تمرین الگوهایی مثل Checks-Effects-Interactions به شبکهی واقعی نیاز ندارید؛ محیط Remix VM در مرورگر کافی است. دورهی مرتبط با این نوشته، A1 «مبانی امنیت قرارداد هوشمند» است که بازگشتپذیری و دیگر الگوهای حمله را با تمرین عملی بررسی میکند؛ برای خبر شدن از انتشار، ثبتنام فهرست انتظار.
منابع
- U.S. SEC, Report of Investigation Pursuant to Section 21(a): The DAO (Release No. 81207, 2017-07-25) — https://www.sec.gov/litigation/investreport/34-81207.pdf
- Ethereum Foundation Blog, Vitalik Buterin, "CRITICAL UPDATE Re: DAO Vulnerability" (2016-06-17) — https://blog.ethereum.org/2016/06/17/critical-update-re-dao-vulnerability
- Ethereum Foundation Blog, Felix Lange, "Security Alert – DoS Vulnerability in the Soft Fork" (2016-06-28) — https://blog.ethereum.org/2016/06/28/security-alert-dos-vulnerability-in-the-soft-fork
- Ethereum Foundation Blog, "To fork or not to fork" (2016-07-15) — https://blog.ethereum.org/2016/07/15/to-fork-or-not-to-fork
- Ethereum Foundation Blog, "Hard Fork Completed" (2016-07-20) — https://blog.ethereum.org/2016/07/20/hard-fork-completed
- The DAO source code, DAO.sol at tag v1.0 (Slock.it, GitHub) — https://github.com/blockchainsllc/DAO/blob/v1.0/DAO.sol
- CoinDesk, "Leaderless DAO Put to the Test Following Ethereum Vulnerability" (2016-06-14) — https://coindesk.com/markets/2016/06/14/leaderless-dao-put-to-the-test-following-ethereum-vulnerability/
- Solidity Documentation, Security Considerations (Reentrancy; Checks-Effects-Interactions) — https://docs.soliditylang.org/en/latest/security-considerations.html
آخرین بازبینی دقت: ۱۴۰۵/۰۸/۰۶

