كيفية منع هجمات Broken Access Control في Solidity

بقلم

هجمات السيطرة غير الصحيحة على الوصول تحدث عندما يسمح عقد Solidity للمتصل غير الموثوق بالوصول إلى قدرة تغييرية في الحالة يجب أن تكون مقيدة، مثل نقل الأصول، تغيير التنفيذ، تعيين الأدوار، أو تكوين عنوان موثوق. لا يحتاج المهاجم إلى كسر التشفير: فهو يستغل نقص أو خطأ في التحقق من الصلاحية، أو صلاحية مفرطة، أو فحص أذونات واسع جدًا للحصول على تصعيد فعلي للامتيازات.

يمكن أن يكون الأثر على مستوى البروتوكول بأكمله، لأن الوظائف المتميزة غالبًا ما تتحكم في الأموال المجمعة أو الإعدادات الحساسة للحوكمة. فقدت Poly Network مبلغ 611.0 مليون دولار في 10-08-2021 عبر Ethereum و BSC و Polygon في حادثة مصنفة من قبل DefiLlama على أنها “السيطرة غير الصحيحة على الوصول”. لذلك، فإن الأذن ليست مجرد مسألة واجهة المستخدم أو تفاصيل إدارية، بل هي جزء أساسي من حدود أمن العقد.

ما هو ثغرة السيطرة غير الصحيحة على الوصول في Solidity؟

توجد ثغرة السيطرة غير الصحيحة على الوصول عندما لا يتحقق العقد بشكل موثوق من أن msg.sender مخول لأداء إجراء مميز، أو عندما يمنح نموذج الأذونات الخاص به صلاحيات أكثر مما يتطلبه ذلك الإجراء. في Solidity، غالبًا ما تظهر الإخفاقات في الوظائف القابلة للاستدعاء من الخارج التي تعدل التخزين الحساس، أو تنقل الأصول، أو ترقية الشيفرة، أو تغير الأطراف الثقة.

يمكن أن يكون العقد معرضًا للخطر حتى عندما يحتوي على متغير owner. السؤال الحرج هو ما إذا كانت كل مسارات التنفيذ المميزة تتحقق من الصلاحية بشكل صحيح، بما في ذلك الوظائف المساعدة، والمبادرات، ووظائف إدارة الأدوار، وخطافات الترقية.

تتبع هجمة السيطرة غير الصحيحة على الوصول النموذجية تسلسل الخطوات التالي:

  1. يحدد المهاجم وظيفة قابلة للاستدعاء من الخارج ذات نتائج مميزة.
  2. الوظيفة لا تتضمن فحص للمتصل، أو تتحقق من عنوان خاطئ، أو تعتمد على حالة يتحكم فيها المهاجم.
  3. يتصل المهاجم مباشرة بالوظيفة، متجاوزًا أية قيود على الواجهة الأمامية.
  4. يمنح الاتصال دورًا، أو يغير عنواناً موثوقًا، أو ينفذ عملية نقل أصول.
  5. يستخدم المهاجم القدرة الجديدة لاستخراج الأموال أو تغيير سلوك البروتوكول.
  6. يقبل العقد المعاملة لأن التحقق يتم فقط على السلسلة.

أزرار الواجهة، والوثائق، والإجراءات التشغيلية المقصودة لا تقيد مكالمات العقد المباشرة. يمكن لأي حساب إرسال calldata إلى وظيفة عامة أو خارجية. لذلك، يجب على كود Solidity فرض السياسة عند النقطة الدقيقة التي يحدث فيها الانتقال الحساس في الحالة.

ما هي الأنماط الشائعة في Solidity التي تتسبب في هجمات السيطرة على الوصول وتصعيد الامتيازات؟

تظهر ثغرات السيطرة غير الصحيحة على الوصول غالبًا عندما يقوم المطورون بكشف أدوات الإعداد، أو مساعدي الإدارات، أو مسارات التهيئة بدون النظر إلى أن كل وظيفة عامة تمثل واجهة برمجة تطبيقات موجهة للمهاجم. النمط الخطير لا يقتصر على غياب معدل onlyOwner بشكل كامل، بل يشمل أيضًا الأذونات التي يمكن للمُتصِل العشوائي تعديلها، أو توريثها بشكل غير صحيح، أو تطبيقها بشكل غير متسق، أو كونها واسعة جدًا بالنسبة للعملية.

أنماط الفشل الشائعة تشمل:

  • أدوات ضبط عامة تتيح لأي شخص استبدال الخزينة، أو المصدق، أو التنفيذ، أو المشغل، أو المستفيد.
  • وظيفة إدارة الأدوار تفتقد إلى تحقق من مسؤول.
  • مبادرات يمكن استدعاؤها بعد النشر أو بواسطة حساب غير مقصود.
  • وظائف تتحقق من tx.origin بدلاً من msg.sender.
  • وظيفة داخلية مميزة يمكن الوصول إليها عبر غلاف خارجي غير محمي.
  • دور مسؤول واحد يمكنه تنفيذ جميع الإجراءات التشغيلية، مما يزيد من نطاق الاختراق.
  • اعتماد الأذونات على عنوان يُقدمه المستخدم بدلاً من خريطة الأدوار الثابتة أو المُتحكم فيها.
  • وظائف الترقية التي تتحقق من المتصل لكن لا تتحقق من التنفيذ الجديد أو مسار الترقية.

هذا النمط المعوج يصبح أكثر دقة عندما يكون على النحو التالي:

function setOperator(address newOperator) external {
    operator = newOperator;
}

قد تتطلب وظيفة الدفع بشكل صحيح أن يكون msg.sender == operator، لكن حدود التحكم في الوصول قد فشلت بالفعل لأن أي شخص يمكن أن يعين نفسه كعامل. على المدققين تتبع كل وظيفة حساسة لمعرفة من يمكنه اكتساب الدور المطلوب.

تعتبر منهجية Soken تعيين إدارة الأدوار وتكوين العناوين الموثوقة عمليات مميزة بحد ذاتها. تصميم السيطرة على الوصول الآمن يحمي كل من التحويل النهائي للأصول وكل مسار يمكن أن يفتح الباب له.

كيف يظهر هجوم السيطرة غير الصحيحة على الوصول في كود Solidity؟

يحقق هذا الخزنة الضعيفة من المتصل قبل صرف المدفوعات، ولكن وظيفة setPayoutOperator الخاصة به عامة. يمكن للمهاجم تحديد نفسه كعامل ثم استدعاء pay، مما يجعل فحص التفويض لاحقًا بلا معنى.

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

contract VulnerablePayoutVault {
    address public payoutOperator;

    constructor(address initialOperator) {
        payoutOperator = initialOperator;
    }

    receive() external payable {}

    function setPayoutOperator(address newOperator) external {
        // قابل للثغرة: أي متصل يمكن أن يمنح نفسه صلاحية المدفوعات.
        payoutOperator = newOperator;
    }

    function pay(address payable recipient, uint256 amount) external {
        require(msg.sender == payoutOperator, "not operator");
        require(address(this).balance >= amount, "insufficient balance");

        (bool sent, ) = recipient.call{value: amount}("");
        require(sent, "payment failed");
    }
}

الحل يفصل بين الإدارة والعمليات. فقط المالك يمكنه منح أو سحب دور المدفوعات الضيق، بينما لا يمكن لعامل المدفوعات إدارة الأدوار أو تغيير الملكية.

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

contract FixedPayoutVault {
    address public owner;
    mapping(address => bool) public payoutOperators;

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    modifier onlyPayoutOperator() {
        require(payoutOperators[msg.sender], "not payout operator");
        _;
    }

    constructor(address initialOwner, address initialOperator) {
        require(initialOwner != address(0), "zero owner");
        require(initialOperator != address(0), "zero operator");
        owner = initialOwner;
        payoutOperators[initialOperator] = true;
    }

    receive() external payable {}

    function setPayoutOperator(address operator, bool allowed) external onlyOwner {
        require(operator != address(0), "zero operator");
        // تصحيح: يمكن فقط للمالك تعديل عضوية الأدوار المميزة.
        payoutOperators[operator] = allowed;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "zero owner");
        owner = newOwner;
    }

    function pay(address payable recipient, uint256 amount)
        external
        onlyPayoutOperator
    {
        require(address(this).balance >= amount, "insufficient balance");
        (bool sent, ) = recipient.call{value: amount}("");
        require(sent, "payment failed");
    }
}

إذا كانت هناك تغييرات في الأدوار، أو أذونات الترقية، أو سحب الخزينة، فإن تدقيق العقود الذكيّة والتحليل الأمني المركّز على السيطرة على الوصول يمكن أن يتعقب ما إذا كانت كل الطرق إلى تلك القدرات مقيدة بشكل صحيح.

كيف يمكن لمطوري Solidity منع ثغرات السيطرة غير الصحيحة على الوصول؟

الامتناع عن هجمات السيطرة غير الصحيحة على الوصول يتطلب نموذج إذن واضح، وليس مجرد عبارات require متناثرة تُضاف بعد إكمال الوظائف. يجب تحديد من يمكنه أداء كل عملية حساسة، ولماذا توجد تلك الصلاحية، وأي دور أصغر يمكنه أداء المهام الروتينية بأمان.

التقنية ما توقفه تكلفة الغاز أو التعقيد القيد
معدل onlyOwner إدارة غير مصرّح بها للوظائف التي تقتصر على المالك قراءة تخزين واحدة لكل استدعاء محمي لا يزال المالك المُمَثَّل لديه كامل الصلاحية إذا تم اختراقه
خرائط الأدوار أو AccessControl من OpenZeppelin استخدام غير مصرّح به لأدوار تشغيلية منفصلة قراءات التخزين ومنطق إدارة الأدوار تصميم الأدوار السيء قد لا يزال يخلق امتيازات مفرطة
الأدوار ذات أقل قدر من الامتياز حارس أو مشغل يستخدم صلاحيات إدارية غير ذات صلة مزيد من الوظائف وتعريف الأدوار للحفظ يتطلب حدود تشغيل واضحة
توقيتات لإجراء تغييرات حساسة ترقية فورية خبيثة أو تغييرات في المعاملات تنفيذ مؤجل وعقود إضافية لا يمنع إجراء خبيث بعد المهلة إلا إذا كان بإمكان المستخدمين الرد
إدارة متعددة التوقيعات مفتاح واحد يمكنه تغير الحالة المميزة أحادي الجانب تنسيق الموقعين والنفقات التشغيلية الموقعون المشتركين أو السياسات الضعيفة لا تزال تشكل خطرًا
حراسة التهيئة مرة واحدة تهيئة غير مصرّح بها أو متكررة علم تخزين ومنطق تهيئة لا تزال إجراءات النشر يجب أن تحدد السلطة المقصودة

استخدام أدوات إدارة الملكية والأدوار ذات التقييم الجيد مناسب، لكن لا تعتمد على مكتبة كنهية كنموذج أمني كامل. على سبيل المثال، Ownable من OpenZeppelin يحدد آليات الملكية، لكن فريق البروتوكول لا يزال عليه أن يقرر هل يجب أن يتحكم الملكية في التوقف، التحديثات، جمع الرسوم، استبدال المرجع، أو الاسترداد في الحالات الطارئة.

تجنب استخدام tx.origin للمصادقة، لأنه يمكن التأثير عليه من خلال عقود وسيطة ولا يحدد المتصل المباشر. استخدم msg.sender ثم تحقق منه مقابل مالك معرف، أو دور، أو توقيع متعدد، أو قاعدة تفويض خاصة بالعقد.

أي حوادث حقيقية تُظهر تأثير هجمات السيطرة على الوصول؟

يسجل DefiLlama 130 حادثة لعقود ذكية مصنفة على أنها سيطرة غير صحيحة على الوصول من 2020 حتى 2026، باستثناء الاختراقات المتعلقة بالتبادلات أو المحافظ، مع خسائر مجمعة بلغت 909.7 مليون دولار. تظهر هذه الحوادث الموثقة أن فشل السيطرة على الوصول يؤثر على السلاسل والجسور، وبروتوكولات DeFi، والروبوتات، والتطبيقات.

البروتوكول التاريخ المبلغ السلسلة
Poly Network 2021-08-10 611.0 مليون دولار Ethereum، BSC، Polygon
Mirror 2021-10-08 90.0 مليون دولار Terra
Wintermute 2022-06-05 17.6 مليون دولار Optimism
ValueDefi 2021-05-05 10.0 مليون دولار BSC
Cork V1 2025-05-28 12.0 مليون دولار Ethereum
SUPERFORTUNE AI 2026-05-27 15.2 مليون دولار Ethereum
The Sandbox 2026-08-22 675 ألف دولار Base، BSC، Ethereum
Radix 2026-08-31 1.2 مليون دولار Radix

تحتوي هذه الحوادث على درس أمني مشترك: يمكن أن يُعرّض قيمة بروتوكول اقتصادية عندما لا تتطابق حدود سلطة العقد مع النموذج الموثوق به المقصود. قد تختلف الوظيفة المتأثرة تحديدًا، لكن فئة الفشل هي ذاتها: يُحصل یا يمارس جهة خارجية سلطة كان من المفترض أن يتنازل عنها التصميم.

كيف تكشف عن ثغرات السيطرة على الوصول غير الصحيحة قبل النشر؟

الكشف عن ثغرات السيطرة على الوصول يتطلب ربط التغييرات الحاسمة في الحالة بشروط التفويض الخاصة بها، ثم إثبات أنه لا يمكن لمسار استدعاء بديل تجاوز تلك الشروط. تساعد الأدوات الآلية على تحديد الأنماط المشبوهة، ولكن المراجعة اليدوية ضرورية لفهم صلاحية الأعمال، وافتراضات الثقة، وسلاسل تصعيد الامتياز.

استخدم قائمة مراجعة:

  1. أدرج كل وظيفة تنقل الأصول، أو تغيّر الأدوار، أو ترقى الشيفرة، أو تتوقف، أو تغير العناوين الموثوقة.
  2. حدد المتصل المقصود وقاعدة الصلاحية لكل وظيفة مدرجة.
  3. تتبع كل وظيفة يمكنها أن تمنح، أو تسحب، أو تنقل، أو تبدأ تلك الصلاحية.
  4. راقب كل الغليفات الخارجية التي تصل إلى الوظائف الداخلية المميزة.
  5. تحقق أن التهيئة يمكن أن تحدث مرة واحدة فقط وتحت عملية النشر المقصودة.
  6. تأكد أن الأدوار الطارئة لا يمكنها تنفيذ إجراءات روتينية أو حوكمة غير ذات صلة.
  7. اختبر سحب الأدوار، ونقل الملكية، ومعالجة العنوان الصفري.
  8. راجع البروكسيات، والعقود الموروثة، ومسارات delegatecall كجزء من نفس حدود الصلاحية.

يمكن لأداة Slither أن ترصد أنماط غياب التفويض، استخدام tx.origin الخطر، وعدم التناسق في المعدلات، لكن نتائجها تحتاج إلى تصديق يدوي. استخدامـ Foundry مع تقنية الـ fuzzing مفيد لتوليد استدعاءات من عناوين عشوائية، والتحقق من أن المتصلين غير المخولين لا يمكنهم تغيير الحالة المميزة.

الاختبارات الثابتة (Invariant Tests) فعالة بشكل خاص، على سبيل المثال: يمكن أن ينص قيد الخزنة على أن عنوانًا بدون دور المدفوعات لا يجب أن يسبب انخفاض رصيد الخزنة عبر pay. أو أن المالك فقط هو من يمكنه تغيير عضوية الدور. تختبر هذه الخواص مباشرة قاعدة الأمان بدلاً من اختبار تدفقات المستخدم المتوقعة فقط.

رؤية أمنية، منهجية Soken: راجع تفويض الصلاحية كنموذج رسم بياني للقدرة، وليس كقائمة للمعدلات. يجب أن يكون لكل نتيجة مميزة مسار متعمد وقابل لله تدقيقه نحو السلطة، وكل مسار غير مقصود هو طريق محتمل للتصعيد.

تُمنع هجمات السيطرة غير الصحيحة على الوصول من خلال جعل السلطة صريحة، وتقليل صلاحيات كل دور، وحماية الوظائف التي تمنح تلك الصلاحيات بنفس العناية التي تُتبع في وظائف نقل الأموال. وظيفة سحب الأموال المقيدة بشكل صحيح ليست آمنة إذا كان بإمكان أي شخص تعيين الساحب.

ابدأ ببناء خريطة سلطة لعقودك المنشورة والمخطط لها، بما في ذلك البروكسيات والحسابات التشغيلية، ثم حول كل قاعدة إلى معدل والفحص الثابت. لاستكشاف أعمق لمخاطر أمن Solidity، والأنماط القريبة من الثغرات، استكشف Soken Hub.

بقلم

الأسئلة الشائعة

ما هو ثغرة Broken Access Control في Solidity؟

ثغرة Broken Access Control تحدث عندما يفشل العقد في تأكيد أن msg.sender مخول لإجراء إجراء مميز. تسمح للغير موثوق به بالوصول إلى قدرات تغيير الحالة، مما قد يؤدي إلى تصعيد صلاحيات فعال دون انتهاك التشفير.

كيف تخلق هجمات Broken Access Control تصعيد الصلاحيات؟

تحدث هجمات Broken Access Control عندما يمنح عقد Solidity للغير موثوق به إمكانية الوصول إلى قدرة مخصصة لطرف مخول، مما يمكّنه من أداء إجراءات تغيّر الحالة مثل نقل الأصول أو تغيير التنفيذ أو تعيين الأدوار.

أي إجراءات في Solidity يجب أن يخضها التحكم في الوصول؟

يجب أن يقيّد تحكم الوصول في Solidity كل قدرة تغير حالة غير مخصصة للمتصلين بشكل عشوائي، مثل نقل الأصول المجمعة، تغيير التنفيذ، تعيين الأدوار، وتكوين عناوين موثوقة، مع ربط ذلك بقرارات تفويض موثوقة.

ماذا حدث في حادث التحكم في الوصول بشبكة Poly Network؟

خسرت Poly Network 611 مليون دولار في 10 أغسطس 2021 عبر Ethereum وBSC وPolygon، والتصنيف من قبل DefiLlama كان لثغرة _Improper Access Control_. يوضح الحدث أهمية التحقق من التفويض لحماية الأموال والإدارة.

لماذا يعتبر التفويض حد أمان أساسي في Solidity؟

التفويض هو حد أمان أساسي في Solidity لأنه يمنع وصول المستخدمين غير الموثوق بهم إلى قدرات التحكم التي تؤثر على الأموال والتهيئة الحساسة للحكم على النظام.ّ غياب أو خطأ أو اتساع صلاحية التفويض يهدد أمان العقد ككل.

دردشة