Backdoor‌ های مخرب در پروکسی‌‌های اتریوم

به‌تازگی نسخه‌‌ اولیه‌‌ ZeppelinOS حسابرسی و در الگوی پروکسی آن نقطه‌‌ی آسیب‌‌پذیری پیدا شده که برای اجرای مجازی همه‌‌ قراردادهای هوشمند قابل بروزرسانی مورداستفاده قرار گرفته‌‌ است. این آسیب‌‌پذیری به مهاجم اجازه می‌‌دهد تا کدهای مخربی را که شناسایی آنان بدون شناخت دقیق روش کار الگوهای سالیدیتی‌‌ و پروکسی بسیار دشوار است، مخفی کند. این مشکل در ZeppelinOS پس از حسابرسی حل شد.

0 140

ویژگی‌‌های Function Call در سالیدیتی‌‌

simpleContract

simpleContract

یک قرارداد هوشمند ساده نوشته شده به زبان سالیدیتی‌‌

اگر یک توسعه‌‌دهنده هستید که برای اتریوم می‌‌نویسید، احتمالاً قرارداد هوشمند را برحسب سالیدیتی‌‌ کدگذاری می‌‌کنید، اما این روش کار شبکه با آنان نیست.

از دید شبکه‌‌ای، یک قرارداد هوشمند حسابی است که دسته‌‌ای از کدهای مربوط به خود را داراست. اگر هر حساب دیگری پیامی برای قرارداد بفرستد، کد آن روی EVM اجرا خواهد شد.

پس اگر قرارداد تنها یک قطعه کد ساختاری دارد چطور می‌‌توان تابع‌‌های مختلفی را به کار وارد کرد؟

اتریوم روش استانداردی برای برقراری ارتباط بین بخش‌‌های آن تعریف می‌‌کند که رابط دوسویه اپلیکیشن (Application Binary Interface) یا ABI نام دارد. می‌‌توانید آن را یک API با سطح پایین در نظر بگیرید که نه‌تنها ویژگی‌‌های موجود درون سیستم را تعیین می‌‌کند بلکه تعداد چیزهایی که دست‌کم گرفته‌‌ایم را هم مشخص می‌‌سازد. برخی از این چیزها روش وارد کردن تابع‌‌ها، روش استدلال در آنان و چگونگی برگشت ارزش هستند.

ABI اتریوم به ما دیکته می‌‌کند که پارامتر داده‌‌مان باید با یک انتخابگر تابع شروع شود که روش فراخواندن تابع را تعیین خواهد کرد. با استفاده از انتخابگر، کد قرارداد شما به آن بخشی حرکت می‌‌کند که تابع موردنظرتان را اجرا می‌‌کند.

انتخابگر تابع تنها 4 بایت اول هش thesha3 امضای تابع را تشکیل می‌‌دهند. مثلاً انتخابگر GET به‌عنوان [sha3(“get()”)[0:4 محاسبه می‌‌شود که 0x6d4ce63c را به ما می‌‌دهد. کد مجموعه‌‌ هم نتیجه‌‌ی [sha3(“set(unit256)”)[0:4 است.

تنها یک استثنا برای انتخابگران تابع وجود دارد و آن برای تابع برگشت موجود در هر قرارداد هوشمند است که انتخابگری ندارند. وقتی هیچ پارامتر دیگری وجود ندارد یا وقتی‌که انتخابگر با هیچ‌‌کدام از روش‌های قرارداد مطابقت ندارد، می‌‌توان آن را فراخواند.

بازبینی الگوی پروکسی

مطالب زیادی درباره الگوی پروکسی، انواع مختلف و بده‌‌بستان‌‌های آن نوشته شده است. فارغ از الگوی پروکسی انتخابی‌‌تان، کاربرد اصلی آن یکسان خواهد بود: آن همه‌‌ی پیام‌‌هایی که دریافت می‌‌کند را به نسخه اجرایی کنونی قرارداد می‌‌فرستد.

بیایید به نحوه کار آن نگاهی بیاندازیم.

Proxy

اجرای قرارداد پروکسی

نگران نباشید، نیازی به درک روش کار آن مونتاژ ترسناک بلوک ندارید. او پیام‌‌های کنونی را به نسخه کنونی می‌‌فرستد و مقدار دقیق پارامترهایی را که دریافت کرده، ارسال خواهد کرد.

قرار دادن منطق ارسالی در تابع برگشتی به ما اجازه می‌‌دهد تا هر فراخوانی را در تابع ارسال کنیم. البته این همیشه رخ نمی‌‌دهد.

پروکسی به متاتابع‌‌های خود نیاز دارد، همان‌طور که به قابل به‌روزرسانی بودن هم احتیاج دارد. پس تابع‌‌هایی مثل ()implementation و() proxyOwnerچون وجود دارند و تابع برگشتی هنوز اجرایی نشده است، قابلیت ارسال ندارند.

درگیری انتخابگر پروکسی

اگر یک توسعه‌‌دهنده باهوش اتریوم باشید، شاید فهمیده باشید که هر تابعی در قرارداد پروکسی که انتخابگر آن با انتخابگری که در قرارداد اجرایی وجود دارد یکی باشد، به‌طور مستقیم و با رد کردن کد اجرایی فراخوانده می‌‌شود.

به این دلیل که انتخابگران تابع از یک مقدار ثابت بایت استفاده می‌‌کنند، همیشه امکان به وجود آمدن clashing وجود دارد. با وجود این مسئله که کامپایلر سالیدیتی‌‌ clashing‌‌های موجود در یک قرارداد را تشخیص می‌‌دهد، این یک مسئله‌‌ روزانه نخواهد بود اما وقتی انتخابگران برای تعامل بین قراردادها به کار گرفته شوند، دیگران می‌‌توانند از این مسئله بهره خود را ببرند. clashing‌‌ها می‌‌توانند برای ساخت یک قرارداد خوب که در واقع bachdoor را مخفی می‌‌کند، مورد سوءاستفاده قرار بگیرند.

ما فهمیدیم که()clash550254402  که از کدی قدیمی هم استفاده می‌‌کرد، انتخاب‌‌کننده‌‌ی یکسانی با ()ProxyOwner دارد. پیدا کردن آن در یک لپ‌تاپ Macbook Pro تنها 15 دقیقه طول کشید. یک هکر باانگیزه می‌‌تواند این فرایند را بهینه کند و منابع بیشتری برای پیدا کردن نام‌‌های مناسب برای تابع اختصاص دهد.

قابلیت بهره‌‌برداری

الگوی پروکسی، رویکرد کنونی استفاده در اکوسیستم اتریوم برای قابل بروزرسانی کردن قراردادهای هوشمند است و حمله clashing انتخابگر هم به هر پروژه‌‌ای اجازه می‌‌دهد که آن را برای استفاده از کدی که تابع مخرب را مخفی می‌‌کند به کار بگیرد.

مثلاً بیشتر اجراهایی که قابلیت به‌‌روزرسانی مفاهیمی از وضعیت را دارند، توابعی هستند که فضای ذخیره‌‌سازی تابع را بروز می‌‌کنند. با وجود اینکه رشته‌‌های تولیدشده مانند اعداد خاص، می‌‌توانند نام‌‌های قابل‌‌قبولی برای آن توابع باشند و پوشاندن حمله clashing انتخابگر را آسان می‌‌کنند، این‌‌ها به طور ویژه برای مخفی کردن clashing انتخابگر مفید است.

در حسابرسی امنیتی که روی ZeppelinOS انجام دادیم، فهمیدیم که این مورد نه‌تنها توسط دارنده پروکسی بلکه توسط هر فردی قابل بهره‌‌برداری است، چون هدف آن‌ها این است کهدارند به همه کاربران اجازه دهند کاربران دیگر از نسخه اجرایی آنان استفاده کنند. در مثالی دیگر، فراخوان تابع که به نظر می‌‌رسد دست به نقل‌وانتقال اموال می‌‌زند، شاید اصلاً فراخوانده نشود و پول فرد دیگری را به سرقت ببرد.

راه‌‌حل پیشنهادی

قبل از اینکه ما این آسیب‌‌پذیری را پیدا کنیم، فرانسیسکو جیوردانو (Francisco Giordano) از تیم Zeppelin روی پروکسی‌‌های شفاف (Transparent Proxies) کار می‌‌کرد. این یک تکنیک بهبود یافته است که هدف آن اجازه داده به قراردادها برای استفاده از نام‌‌های یکسان برای توابع است تا پروکسی‌‌ها بدون احتمال clashing انتخابگر باشند. این باعث حذف حمله می‌‌شود.

این پروکسی‌‌های جدید توسط ارسال فراخوان‌‌ها، در صورتی که از طرف دارنده پروکسی نیامده باشند کار می‌‌کند. clashing‌‌ها باقی خواهند ماند، اما اگر ارسال کننده فراخوان فردی غیر از دارنده پروکسی باشد، فراخوان ارسال خواهد شد. این باعث می‌‌شود دارنده پروکسی تنها حسابی باشد که به clashing بیافتد و کاربران در معرض مخفی شدن قرار نخواهند گرفت.

تنها نکته منفی آن این است که کاربران دیگر نخواهند توانست با استفاده از ABI پروکسی، وضعیت آن را بخوانند. آنان باید در عوض از() web3.eth.getStorageAt استفاده کنند. پرداخت این هزینه برای حصول اطمینان از اینکه قراردادها قابل به‌روزرسانی دقیقاً همان کاری را انجام می‌‌دهند که کد منبع آنان نشان می‌‌دهد، غیرمنطقی نیست.

تمرینی برای خواننده

برای کسانی که دوست دارند نگاه عمیق‌‌تری به نحوه بهره‌‌گیری از این نقطه آسیب‌‌پذیر داشته باشند، تمرین کوچکی را در اینجا آورده‌‌ایم. کار شما این است که سعی کنید اتریوم ارسال شده در این قرارداد را به سرقت ببرید و بفهمید که چه اتفاقی در حال رخ دادن است. یادتان باشد که این یک قرارداد پروکسی است، پس باید نگاهی هم به پیاده‌‌سازی آن داشته باشید.

هر کاری که بخواهید می‌‌توانید با این قراردادها بکنید، فقط موجودی آن را کاملاً خالی نکنید تا بقیه هم بتوانند از آن استفاده کنند.

نکات

1. پیام‌‌ها روش برقراری ارتباط حساب‌‌ها با یکدیگر هستند. وقتی تراکنشی ارسال می‌‌کنید، در حال ارسال پیامی به حسابی دیگر هستید. وقتی فرستنده یک قرارداد باشد به این تراکنش‌‌ها، تراکنش‌‌های درونی می‌‌گویند.

2. ارسال پیام در اینجا شبیه به پروکسی‌‌های سنتی نیست. آنچه رخ می‌‌دهد این است که ما کد پیاده‌‌سازی را طوری اجرا می‌‌کنیم که گویی کد پروکسی از طریق فراخوان نمایندگی بوده است.

شاید از این مطالب هم خوشتان بیاید.

ارسال پاسخ

آدرس ایمیل شما منتشر نخواهد شد.