
تحقق بشكل متقاطع من عروض سطح الهجوم الخاص بك واعثر على نقاط النهاية التي لا يمكنها تأكيد بعضها البعض.
تحقّق من تطابق وجهات النظر المختلفة لسطح الهجوم لديك واعثر على نقاط النهاية التي لا يمكنها أن تتوافق مع بعضها البعض.
ينبغي أن تكون نقطة النهاية قادرة على أن تُثبت وجودها بنفسها. فهي موجودة في الشيفرة، لذا ينبغي أن يصفها عقد. وهي موجودة في العقد، لذا ينبغي أن يُنفّذها شيء ما. وهي تستقبل حركة مرور حقيقية، لذا من الأفضل أن تكون موجودة في مكان ما. وعندما تعرف إحدى وجهات النظر بوجود نقطة نهاية بينما لا تعرفها الأخريات، فإن تلك الفجوة هي النتيجة.
يشغّل alibi أداة OWASP noir، ويقرأ مخرجاتها بصيغة JSON، ويقارن وجهات النظر ببعضها البعض.
تقرأ Noir بالفعل خمس وجهات نظر مستقلة عن السطح نفسه:
| وجهة النظر | تُقرأ من |
|---|---|
| code | أكثر من 200 محلّل عبر 33 لغة |
| doc | OpenAPI، RAML، WSDL، GraphQL SDL، AsyncAPI، gRPC، Smithy، TypeSpec، OData، OpenRPC |
| traffic | HAR، mitmproxy، Burp، Caido، ZAP، Postman، Insomnia، Bruno، .http |
| gateway | nginx، Apache، Envoy، Kong، Traefik، APISIX، Caddy، Istio، Kubernetes Ingress و Gateway API |
| infra | Terraform، CloudFormation، CDK، Serverless، Vercel، Netlify، Wrangler، Azure Functions، Kamal |
ما لا تفعله هو مقارنتها. وهذا هو جوهر العمل هنا، ولا يتطلب أي تغيير في noir — يشغّله alibi مرة واحدة لكل وجهة نظر ثم يدمج النتائج.
جزء "لكل وجهة نظر" مهم. تُزيل Noir التكرار حسب (method, url) عبر كل المحلّلات، لذا فإن مسار Flask ومسار OpenAPI المكتوبين بالطريقة نفسها يندمجان في نقطة نهاية واحدة تحمل تقنية واحدة. هذا صحيح بالنسبة لأداة استكشاف — فهي نقطة نهاية واحدة — لكنه يمحو التوافق الذي بُنيت هذه الأداة لقياسه، ويمحوه في أسوأ اتجاه ممكن: فكلما اتفقت وجهتا نظر بشكل أفضل، اختفى المزيد منهما. تُفحص Casdoor على أنها 372 نقطة نهاية في الشيفرة و9 موثّقة؛ افحص دليل swagger/ الخاص بها وحده فسيكون لدى المواصفة 235.
يقيّد --only-techs مجموعة الكواشف، لذا فإن فحصًا واحدًا لكل وجهة نظر يُبقي كل واحدة منها كاملة. أي تقنية تتحدث عن أي وجهة نظر يُحدَّد في views.yml؛ وأي التقنيات موجودة هو ما يُبلّغ عنه noir list techs.
لا يحلّل alibi أي صيغ API بنفسه. مدخله الوحيد هو مخرجات noir بصيغة JSON.
يتطلب noir الإصدار 1.0.0 أو أحدث على PATH -- وهو الإصدار الذي أصبح فيه noir list techs أمرًا فرعيًا، وهذا الكتالوج هو ما يسنِد كل تقنية إلى وجهة نظر. تتبع التطويرات إصدار noir الحالي. يُرفض أي ملف تنفيذي أقدم بالاسم بدلًا من تركه يفشل عند أول قراءة للكتالوج.
$ uv tool install noir-alibi # or: pipx install noir-alibi
$ alibi scan ./my-service
$ alibi scan # the working directory
$ alibi scan ./service ./contracts ./prod.har # or wherever the views live
كل مسار هو مصدر، يُفحص مرة واحدة لكل عرض. وجّهه إلى أي شيء لديك — شجرة مصدر، دليل مواصفات، ملف التقاط واحد — والعروض التي تفتقدها تُعطّل قواعدها بدلًا من إغراق التقرير.
alibi · 1 source · 377 endpoints
code 372 doc 235
230 corroborated -- vouched for by more than one view
19 endpoints nearly matched another view -- these may be matching failures, not real gaps
SHADOW Shadow API -- Implemented, but no contract describes it
134 findings · 4 critical, 57 high, 62 medium, 11 low
critical POST /api/upload-groups router.go:87
upload paths carry more consequence than reads
critical POST /api/upload-permissions router.go:208
...
... and 122 more (SHADOW in full: -f json)
TWO SURFACES?
The doc view is 97% under /api, and 37 of these findings are outside it.
If that is a separate surface the contract never covered, narrow the scan:
alibi scan <paths> --ignore '^/(?!api(/|$))'
If it is the same surface left undocumented, they are the findings that matter most.
تتوقف المجموعات عند اثنتي عشرة — الترتيب من الأسوأ إلى الأفضل، لذا فإن الذيل هو الجزء الأقل إفادة، و-f json يحتوي عليه كاملاً.
تأتي أعلام noir بعد -- مجردة أو عبر --noir-arg. المرشحات مثل --exclude-path مقبولة؛ أما الأعلام التي من شأنها استبدال عقد JSON أو إسقاط عمليات الفحص لكل عرض في alibi (--format، --diff-*، --only-techs، …) فتُرفض مع حالة خروج 2.
- run: alibi scan . ./contracts -f sarif > alibi.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: alibi.sarif }
يقول التقرير إن العروض تختلف؛ ويوضح --endpoints ما احتواه كل منها.
$ alibi scan ./repo -f json --endpoints
كل عرض يحصل على قائمة: المفتاح، والعروض التي وثقت به، والتقنيات التي تقف خلفه، والملفات، والتهجئة قبل التطبيع — وهنا يكون الفرق دائمًا عندما كان ينبغي أن يتطابق صفان ولم يتطابقا. إنها ثلاثة إلى أربعة أضعاف بقية الحمولة، لذا فهي علامة اختيارية وليست الافتراضي.
أو البوابة مباشرةً: alibi scan . ./contracts --fail-on high يخرج بقيمة غير صفرية عندما
يصل اكتشاف إلى تلك الخطورة. الفحص الذي لم يتمكن noir من قراءته بالكامل يُبلّغ
executionSuccessful: false، لذا فإن التشغيل المتدهور لا يمر على أنه نظيف.
الرابط الرمزي أو الملف الثنائي أو الصورة التي تجاوزها noir عن قصد لا تُحتسب.
يحتفظ Noir بصيغة المسار الخاصة بكل إطار عمل بدلًا من اختراع صيغة مشتركة، لذا تصل نقطة النهاية نفسها مكتوبة بعدة طرق:
python_flask /api/users/<int:user_id>
aiohttp /users/{id}
java_spring /api/catalog/{id}
oas3 /v1/pets/{petId}
rails /posts/:id
nginx /admin/.*
القاعدة التي تجعل هذه قابلة للمقارنة: اسم معامل المسار ليس جزءًا من
هويته. {petId} و<int:user_id> يصفان الفتحة نفسها؛ فقط موضعها وما إذا كانت
تمتد عبر / هو ما يهم. تُحفظ الأسماء كدليل ويُبلَّغ عنها، لكنها لا تصل أبدًا إلى
المفتاح.
تُبيّن النتائج كيف تمت المطابقة:
| الدرجة | المعنى |
|---|---|
G1 | التهجئتان اتفقتا بالفعل |
G2 | تتفقان بمجرد تطبيع صياغة المعامل |
G0 | عرض واحد فقط يحتوي عليه — لم تتم مطابقة شيء |
أداة كهذه تموت بالإبلاغ عن مئات النتائج في أول تشغيل لها، أو بالإبلاغ عن تقدم لم يحرزه أحد. ستة أمور تدفع في الاتجاه المعاكس:
القواعد لا تُفعَّل دون وجود العرضين. افحص قاعدة شيفرة بلا عقود في أي مكان وسيكون كل نقطة نهاية مؤهلة تقنيًا كواجهة برمجية ظل غير موثقة. تلك النتائج لا تقول شيئًا سوى أنك لم توفّر أي توثيق، لذا لا تُشغَّل القاعدة إلا عندما يكون كل عرض تستدل به قد كان فعليًا ضمن الفحص. ويسمّي التقرير القواعد التي لم تُشغَّل.
الاقترابات الفائتة تُبلَّغ عنها كشك، لا كنتائج. "في الشيفرة، لا في التوثيق" لا يمكن تمييزه عن "في كليهما، لكن alibi فشل في محاذاتهما." لذا تُفحص نقطة النهاية التي تقع في عرض واحد مقابل العروض الأخرى بحثًا عن اقتراب فائت — المسار نفسه بفعل مختلف، أو على مسافة مقطع واحد حيث يكون لأحد الجانبين معامل وللآخر قيمة حرفية. النتائج التي تحمل اقترابًا فائتًا تُخفَّض درجتها وتُعلَّم للمراجعة. ذلك العدد يجلس بجوار الإجماليات، لأن كل نتيجة موثوقة بقدر ما هي صغيرة فقط.
العروض التي لم تلتقِ قط هي تشخيص واحد، لا مئات النتائج. يسجّل Argo CD
المسار /api في Go ويوثّق 198 مسارًا تحته، لذا لا تشترك شيفرته ومواصفته في نقطة
نهاية واحدة. بالقراءة الحرفية، هذا 58 واجهة برمجية ظل و198 عقدًا وهميًا، ولا شيء
منها حقيقي. صفر تأكيد متبادل بين عرضين مأهولين يعني أن المقارنة لم تنجح — نقطة
تركيب تقف مكان المسارات تحتها، أو حزمة تعذّر على noir قراءتها — لذا تُكفّ القواعد
وتُطبع السبب بدلًا من ذلك. المسارات التي يتبيّن أن تحتها العديد من نقاط النهاية من
عروض أخرى تُوسم كنقاط تركيب محتملة.
عندما تتراصف العرضان بمجرد إزالة بادئة ثابتة من أحدهما، يذكر التشخيص ذلك ويسمّي
البادئة. تُعلن مواصفة Gitea المولّدة basePath: /GITEA-API-APP-SUBURL/api/v1
بينما يركّب موجّه Go الخاص بها /api/v1؛ لا تشترك العرضان في شيء، لكن 154 من
المسارات الموثقة البالغ عددها 535 تطابق مسارًا في الشيفرة بمجرد إزالة تلك المقاطع
الثلاثة. ذلك إما basePath في المواصفة، أو servers[].url، أو نقطة تركيب أسقطها
قارئ الشيفرة — ويُبلَّغ عنه، ولا يُطبَّق أبدًا، لأن إعادة محاذاة المسارات ستخفي
الخلل الذي وجده.
الفيضان الذي هو في الحقيقة شجرة فرعية واحدة مفقودة يُسمّى واحدًا: 207 من أصل 354
عقدًا وهميًا في NodeBB تقع تحت /api/v3، حيث لا يحتوي عرض الشيفرة على شيء على
الإطلاق.