تكتب ميزة جديدة في تطبيقك، صفحة تعرض فاتورة العميل، وتختبرها بحسابك فتعمل، ثم تختبرها بحساب آخر فتعمل أيضاً، فتدمج الكود وتنشره. وبعد شهرين يرسل لك أحد العملاء رسالة يقول فيها إنه غير الرقم في رابط الفاتورة من 1001 إلى 1002 فظهرت له فاتورة شركة أخرى. لم يخترق أحد السيرفر، ولم يكسر أحد التشفير، وإنما سطر واحد في الكود نسي أن يسأل «هل هذه الفاتورة لك؟». وهذا هو حال أغلب الثغرات في تطبيقات الويب، فهي لا تأتي من هجمات معقدة، وإنما من أخطاء متكررة يقع فيها المطورون في كل لغة وفي كل إطار عمل، ولذلك جمعتها مؤسسة OWASP في قائمة واحدة قصيرة يعرفها كل من يعمل في أمن التطبيقات، وهي OWASP Top 10.
وهذا المقال هو المقال الرئيسي لمرحلة أمن التطبيقات AppSec في الموقع، فهو يعطيك الخريطة الكاملة للمخاطر العشر في نسختها الحالية لعام 2025، وفي كل خطر يشرح الفكرة ويعطيك مثالاً صغيراً من كود حقيقي بالشكل الخاطئ ثم بالشكل الصحيح، ثم يرسلك إلى المقال الذي يتعمق فيه. والهدف هنا دفاعي تعليمي، أي أن تعرف كيف تمنع كل خطر وأنت تكتب الكود وكيف تكتشفه إذا فاتك، وليس كيف تهاجم تطبيقات غيرك.
وسوف نناقش في هذا المقال ما يلي:
- ما هي قائمة OWASP Top 10، وكيف تبنى من البيانات Data والاستبيان Survey، وما الذي تغير في نسخة 2025.
- علاقة القائمة بأقاربها: قائمة أمن واجهات API، ومعيار التحقق ASVS، وقاموس نقاط الضعف CWE، وقائمة مخاطر نماذج اللغة LLM Top 10.
- المخاطر العشر واحداً واحداً على تطبيق فواتير صغير، ولكل خطر مثال ضعيف Vulnerable ومثال مصلح Fixed بلغات C# وJava وPython وJavaScript، وطرق المنع Prevention والاكتشاف Detection.
- تجربة عملية Lab صغيرة نرى فيها فحص صلاحيات ناقصاً ثم إصلاحه بأوامر curl ومخرجاتها الحقيقية.
- أين يكتشف كل خطر: في التصميم أم الكود أم البناء أم أثناء التشغيل، ثم مسار القراءة في بقية المرحلة، والأخطاء الشائعة، وقائمة تحقق Checklist.
ما هي قائمة OWASP Top 10، وما الذي ليست هي؟
مؤسسة OWASP (اختصاراً لـ Open Worldwide Application Security Project) مؤسسة غير ربحية تنشر مشاريع مفتوحة في أمن البرمجيات، وأشهر مشاريعها OWASP Top 10، وتصفه صفحة المشروع بأنه «a standard awareness document for developers and web application security»، أي وثيقة توعية Awareness Document للمطورين ولأمن تطبيقات الويب، تمثل اتفاقاً واسعاً على أخطر المخاطر الأمنية في تطبيقات الويب. والنسخة الحالية بحسب صفحة المشروع هي OWASP Top 10:2025، وهي الإصدار الثامن من القائمة كما تقول مقدمتها، وقبلها نسختا 2021 و2017.
ولاحظ كلمة «توعية»، فالقائمة تقول عن نفسها في قسم بناء برنامج أمن التطبيقات إن قوائم Top 10 وثائق توعية، وإنها ليست قائمة كاملة وإنما نقطة بداية. وكل بند فيها ليس ثغرة واحدة، وإنما فئة Category تجمع عدداً من نقاط الضعف المعروفة في قاموس CWE، ففي نسخة 2025 تحتوي الفئات العشر على 248 نقطة ضعف CWE. وبالتالي فالتطبيق الذي لا يقع في أي من الفئات العشر ليس تطبيقاً آمناً بالضرورة، وإنما تطبيق تجنب أشهر الأخطاء، وهذا في حد ذاته إنجاز جيد لأغلب الفرق.
وقد يتساءل البعض: هل أستطيع أن أقول إن تطبيقي «متوافق مع OWASP Top 10»؟ والإجابة أن القائمة لم تكتب لتكون معياراً للتحقق، فهي تصف المخاطر ولا تعطيك متطلبات قابلة للاختبار، وإذا أردت قائمة متطلبات تتحقق منها في المراجعة أو تطلبها من مورد فالمعيار المناسب هو ASVS، وسوف نرى الفرق بعد قليل.
كيف تبنى القائمة: البيانات Data والاستبيان Survey
تشرح مقدمة نسخة 2025 طريقة البناء بالتفصيل، وأهم ما فيها ما يلي:
- البيانات المساهمة Contributed Data: جمعت المؤسسة نتائج اختبار لأكثر من 2.8 مليون تطبيق من شركات فحص وأدوات أمنية ومتبرعين لم تذكر أسماؤهم، وحللت 589 نقطة ضعف CWE في هذه البيانات، بعد أن كانت حوالي 30 في نسخة 2017 وقرابة 400 في نسخة 2021.
- الانتشار Prevalence وليس التكرار Frequency: السؤال هو كم تطبيقاً فيه نقطة الضعف مرة واحدة على الأقل، وليس كم مرة ظهرت، والسبب أن الأداة الآلية تعد كل تكرار نتيجة مستقلة بينما يكتب الفاحص البشري الثغرة مرة واحدة، فلو حسبنا التكرار لفازت الفئات التي تحبها الأدوات.
- قابلية الاستغلال Exploitability والأثر Impact: أخذت المؤسسة درجات CVSS من حوالي 220 ألف ثغرة منشورة CVE مرتبطة بنقاط الضعف، وحسبت لكل CWE متوسط قابلية الاستغلال ومتوسط الأثر.
- الاستبيان المجتمعي Community Survey: البيانات تنظر إلى الماضي، لأنها محدودة بما تستطيع الأدوات أن تختبره آلياً، والثغرة الجديدة تحتاج سنوات حتى تدخل في الأدوات، لذلك تختار القائمة ثماني فئات من البيانات، وتترك فئتين يرفعهما المختصون في الاستبيان لأنهم يرونها في عملهم وإن لم تظهر في الأرقام.
وفي نسخة 2025 جاءت فئة سلسلة التوريد A03 وفئة السجلات والتنبيه A09 بأصوات الاستبيان، فالأولى صوت لها نصف المشاركين بالضبط في المرتبة الأولى، والثانية تقول عنها OWASP إنها سوف تبقى دائماً أقل ظهوراً في البيانات مما تستحق، لأن غياب السجل لا تجده أداة فحص.
ويجدر الإشارة هنا إلى أن القائمة تركز على السبب الجذري Root Cause وليس العرض Symptom كلما أمكن، فبدل فئة مثل «كشف البيانات الحساسة» وهي نتيجة، تجد «أخطاء التشفير» وهي السبب، والفائدة لك كمطور أن اسم الفئة يدلك على ما يجب إصلاحه.
ما الذي تغير عن نسخة 2021؟
بحسب المقدمة هناك فئتان جديدتان ودمج واحد، والتفاصيل كما يلي:
- التحكم في الوصول المكسور Broken Access Control بقي في المرتبة الأولى، ودخلت فيه ثغرة طلب السيرفر المزور Server-Side Request Forgery واختصاراً SSRF التي كانت فئة مستقلة في 2021.
- أخطاء الإعداد الأمني Security Misconfiguration صعدت من المرتبة الخامسة إلى الثانية، والسبب كما تقول OWASP أن سلوك التطبيقات أصبح يعتمد على الإعدادات أكثر من قبل.
- أخطاء سلسلة التوريد البرمجية Software Supply Chain Failures جديدة في المرتبة الثالثة، وهي توسيع لفئة المكونات الضعيفة والقديمة Vulnerable and Outdated Components من 2021 لتشمل خط البناء والتوزيع كله.
- سوء التعامل مع الحالات الاستثنائية Mishandling of Exceptional Conditions فئة جديدة في المرتبة العاشرة، تجمع أخطاء التعامل مع الأخطاء والفشل المفتوح Failing Open.
- تغير اسم فئتين قليلاً: «Identification and Authentication Failures» أصبحت Authentication Failures، و«Security Logging and Monitoring Failures» أصبحت Security Logging and Alerting Failures لأن السجل الذي لا يطلق تنبيهاً قليل الفائدة.
وتذكر OWASP في صفحة الخطوات التالية مخاطر كانت قريبة من الدخول ولم تدخل، أولها ضعف قدرة التطبيق على الصمود Lack of Application Resilience، وهي إعادة تسمية لفئة حجب الخدمة Denial of Service، وكانت درجتها قريبة جداً من الفئة العاشرة.
والصورة التالية تبين الفئات العشر مجمعة حسب المكان الذي تبدأ منه في التطبيق، وهذا التجميع من عندنا لتسهيل الفهم:

القائمة وأقاربها: API Security Top 10 وASVS وCWE وLLM Top 10
ستسمع أسماء أخرى قريبة من هذه القائمة، وكل منها له وظيفة مختلفة، والجدول التالي يختصر الفرق:
| الاسم | ما هو | متى تستخدمه |
|---|---|---|
| OWASP Top 10:2025 | وثيقة توعية بأخطر عشر فئات في تطبيقات الويب | تدريب الفريق، ونقطة بداية لبرنامج أمن التطبيقات |
| OWASP API Security Top 10:2023 | قائمة منفصلة لمخاطر واجهات API، أولها تفويض مستوى الكائن المكسور Broken Object Level Authorization واختصاراً BOLA | عندما يكون تطبيقك API يستهلكها تطبيق جوال أو واجهة SPA أو أنظمة أخرى |
| OWASP ASVS | معيار التحقق من أمن التطبيقات Application Security Verification Standard، وفيه متطلبات مرقمة قابلة للاختبار، وآخر إصدار مستقر 5.0.0 | متطلبات المشروع، ومراجعة الكود، والتعاقد مع مورد أو فاحص |
| CWE من MITRE | قاموس نقاط الضعف Common Weakness Enumeration، ولكل نقطة رقم مثل CWE-89 لحقن SQL | لغة مشتركة بين الأدوات والتقارير، فكل أداة SAST تذكر رقم CWE لكل نتيجة |
| OWASP Top 10 for LLM Applications | قائمة مخاطر التطبيقات المبنية على نماذج اللغة، أولها حقن التعليمات Prompt Injection | عندما يستقبل تطبيقك نصاً من المستخدم ويمرره إلى نموذج لغة أو أدوات Agent |
وصفحة فئة الحقن A05:2025 نفسها تشير إلى أن نوعاً قريباً من الحقن أصبح شائعاً في نماذج اللغة، وتحيل إلى LLM01:2025 Prompt Injection، وقد شرحنا هذا الخطر في قسم الجودة والأمان في مقال مصطلحات الذكاء الاصطناعي، ثم مع الأدوات في قسم الأمان في مقال بروتوكول MCP. أما قائمة API فلها مقال مستقل في هذه المرحلة، وكذلك معيار ASVS ومراجعة الكود.
التطبيق الذي سنمشي عليه في كل خطر
حتى لا تبقى المخاطر أسماء مجردة، سوف نأخذ تطبيقاً واحداً صغيراً ونرى كل خطر فيه، وهو تطبيق فواتير لشركات صغيرة فيه ما يلي:
- تسجيل دخول بالبريد وكلمة المرور، وجلسة Session في Cookie.
- صفحة
/invoices/{id}تعرض فاتورة، وبحث عن العملاء بالاسم، وتصدير PDF. - لوحة إدارة Admin Panel لموظفي الشركة، وحجز مقاعد في دورة تدريبية بخصم للمجموعات.
- مكتبات من npm وNuGet وMaven وPyPI، وخط CI يبني Docker Image وينشرها.
ولاحظ أن الأمثلة في كل قسم مختصرة عمداً، فهي تبين الفكرة في سطور قليلة بالشكل الضعيف ثم بالشكل المصلح، أما التطبيق الكامل الذي نكسره ونصلحه خطوة خطوة فهو موضوع مقال ثغرات الويب بالتجربة.
A01:2025 التحكم في الوصول المكسور Broken Access Control
ما هو بلغة بسيطة؟
التحكم في الوصول Access Control هو القاعدة التي تقول من يحق له أن يفعل ماذا، وتقول صفحة A01 إنه يفرض سياسة تمنع المستخدم من أن يتصرف خارج صلاحياته، وإن فشله يؤدي إلى كشف بيانات أو تعديلها أو حذفها أو تنفيذ عملية خارج حدود المستخدم. ومن صوره المذكورة في الصفحة: تغيير المعرف في الرابط للوصول إلى سجل غيرك، وهو ما يسمى الإشارة المباشرة غير الآمنة إلى الكائن Insecure Direct Object Reference واختصاراً IDOR، وواجهة API تفحص الصلاحية في GET وتنساها في POST وPUT وDELETE، والعبث بتوكن JWT أو Cookie لرفع الصلاحية، وإعداد CORS خاطئ، والوصول إلى صفحات الإدارة بتخمين الرابط. وفي نسخة 2025 دخلت فيه ثغرة SSRF أيضاً، وهي أن يجعل المستخدم السيرفر يطلب رابطاً داخلياً لم يكن من حقه الوصول إليه.
وهذه الفئة في المرتبة الأولى للمرة الثانية، وتقول صفحتها إن كل التطبيقات التي اختبرت في البيانات وجد فيها شكل ما من هذه المشكلة، وإنها أكثر الفئات ظهوراً في البيانات.
المثال على تطبيق الفواتير
هذا بالضبط ما رأيناه في بداية المقال، فالمطور تأكد من أن المستخدم سجل دخوله، ثم افترض أن أي مستخدم مسجل يطلب فاتورته هو فقط. والمثال التالي بلغة C# مع ASP.NET Core وEntity Framework Core يبين الشكل الضعيف:
// Vulnerable: any logged-in user can read any invoice by changing the id
[Authorize]
[HttpGet("/invoices/{id:int}")]
public async Task<IActionResult> Get(int id)
{
var invoice = await _db.Invoices.FindAsync(id);
return invoice is null ? NotFound() : Ok(invoice);
}والشكل المصلح يضع شرط الملكية في الاستعلام نفسه، فلا يمكن أن تخرج فاتورة لا تخص المستخدم الحالي:
// Fixed: ownership is part of the query, checked on every request
[Authorize]
[HttpGet("/invoices/{id:int}")]
public async Task<IActionResult> Get(int id)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var invoice = await _db.Invoices
.SingleOrDefaultAsync(i => i.Id == id && i.OwnerId == userId);
return invoice is null ? NotFound() : Ok(invoice); // same answer for "not yours" and "not found"
}في الكود أعلاه لاحظ التالي:
- الخاصية
[Authorize]تجيب عن سؤال المصادقة Authentication، أي «هل أنت مسجل؟»، ولا تجيب عن سؤال التفويض Authorization، أي «هل هذه الفاتورة لك؟»، والخلط بين السؤالين هو أصل هذه الفئة. - معرف المستخدم يؤخذ من التوكن أو الجلسة التي تحقق منها السيرفر، وليس من حقل في الطلب يستطيع المستخدم أن يغيره.
- الرد واحد في الحالتين، 404 للفاتورة غير الموجودة وللفاتورة التي ليست لك، والسبب أن الرد المختلف يخبر المستخدم أن الرقم 1002 موجود.
والصورة التالية تبين مكان هذا الفحص في مسار الطلب، ولماذا لا يحسب إخفاء الزر في الواجهة فحصاً:

تجربة عملية Lab: فحص صلاحيات Authorization Check ناقص ثم إصلاحه
لنرى الفرق بأعيننا، كتبنا خدمة صغيرة جداً بمكتبة Python القياسية وحدها، دون أي مكتبة خارجية، فيها مستخدمان alice وbob ولكل منهما فاتورة، وتعمل بوضعين يحددهما المتغير MODE: الوضع الضعيف vuln الذي يكتفي بالتأكد من أن المستخدم مسجل، والوضع المصلح fixed الذي يفحص الملكية في كل طلب. والملف app.py كاملاً كما يلي:
import json, os, re, sys
from http.server import BaseHTTPRequestHandler, HTTPServer
MODE = os.environ.get("MODE", "vuln") # vuln | fixed
# Two users and their tokens (in a real app: a session or a validated JWT)
TOKENS = {"tok-alice": "alice", "tok-bob": "bob"}
INVOICES = {
1001: {"id": 1001, "owner": "alice", "amount": 420},
1002: {"id": 1002, "owner": "bob", "amount": 9800},
}
class Handler(BaseHTTPRequestHandler):
def send(self, status, body):
data = json.dumps(body).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def current_user(self):
auth = self.headers.get("Authorization", "")
return TOKENS.get(auth.removeprefix("Bearer "))
def do_GET(self):
m = re.fullmatch(r"/invoices/(\d+)", self.path)
if not m:
return self.send(404, {"error": "not found"})
user = self.current_user()
if user is None:
return self.send(401, {"error": "login required"})
invoice = INVOICES.get(int(m.group(1)))
if MODE == "vuln":
# Authenticated, so the code trusts the id in the URL
if invoice is None:
return self.send(404, {"error": "not found"})
return self.send(200, invoice)
# fixed: the server checks ownership on every request, deny by default
if invoice is None or invoice["owner"] != user:
print(f"access_denied user={user} invoice={m.group(1)}", file=sys.stderr, flush=True)
return self.send(404, {"error": "not found"})
return self.send(200, invoice)
def log_message(self, fmt, *args):
pass
HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()ثم شغلنا نسختين من الملف نفسه في Docker Image الرسمية python:3.12-slim، الأولى بالوضع الضعيف على المنفذ 20680 والثانية بالوضع المصلح على المنفذ 20681، وكلاهما على 127.0.0.1 فقط، وذلك من المجلد الذي فيه app.py:
docker run -d --name lab-owasp-vuln -e MODE=vuln -p 127.0.0.1:20680:8000 \
-v "$PWD:/app:ro" python:3.12-slim python /app/app.py
docker run -d --name lab-owasp-fixed -e MODE=fixed -p 127.0.0.1:20681:8000 \
-v "$PWD:/app:ro" python:3.12-slim python /app/app.pyالآن نرسل الطلبات نفسها إلى النسخة الضعيفة، فتطلب alice فاتورتها، ثم فاتورة bob، ثم نطلب دون توكن:
curl -s -w " HTTP %{http_code}\n" -H "Authorization: Bearer tok-alice" http://127.0.0.1:20680/invoices/1001
curl -s -w " HTTP %{http_code}\n" -H "Authorization: Bearer tok-alice" http://127.0.0.1:20680/invoices/1002
curl -s -w " HTTP %{http_code}\n" http://127.0.0.1:20680/invoices/1002والمخرج سوف يكون كما يلي:
{"id": 1001, "owner": "alice", "amount": 420} HTTP 200
{"id": 1002, "owner": "bob", "amount": 9800} HTTP 200
{"error": "login required"} HTTP 401لاحظ أن الطلب الثالث رفض، فالمصادقة تعمل، ومن يختبر التطبيق دون توكن سوف يظن أن كل شيء سليم، ولكن الطلب الثاني أعاد فاتورة bob إلى alice. والآن الطلبات نفسها على النسخة المصلحة، ومعها طلب لفاتورة غير موجودة أصلاً:
curl -s -w " HTTP %{http_code}\n" -H "Authorization: Bearer tok-alice" http://127.0.0.1:20681/invoices/1001
curl -s -w " HTTP %{http_code}\n" -H "Authorization: Bearer tok-alice" http://127.0.0.1:20681/invoices/1002
curl -s -w " HTTP %{http_code}\n" http://127.0.0.1:20681/invoices/1002
curl -s -w " HTTP %{http_code}\n" -H "Authorization: Bearer tok-alice" http://127.0.0.1:20681/invoices/1003
docker logs lab-owasp-fixedوالمخرج سوف يكون كما يلي:
{"id": 1001, "owner": "alice", "amount": 420} HTTP 200
{"error": "not found"} HTTP 404
{"error": "login required"} HTTP 401
{"error": "not found"} HTTP 404
access_denied user=alice invoice=1002
access_denied user=alice invoice=1003في المخرج أعلاه لاحظ التالي:
- فاتورة bob وفاتورة 1003 غير الموجودة أعادتا الرد نفسه تماماً، فلا تستطيع alice أن تعرف أي الأرقام موجود.
- كل رفض كتب سطراً في السجل فيه المستخدم والرقم، وهذا ما تطلبه صفحة A01 نفسها، وهو الرابط مع الفئة A09، فمستخدم يجرب عشرين رقماً في دقيقة يجب أن يطلق تنبيهاً.
- الفرق بين النسختين سطر شرط واحد، وهذا هو سبب انتشار هذه الفئة، فلا توجد أداة تعرف أن الفاتورة يجب أن تكون لصاحبها إلا إذا أخبرتها أنت، ولذلك فالحماية الحقيقية اختبار آلي يرسل طلب مستخدم آخر ويتوقع الرفض.
وبعد التجربة حذفنا ال Containers التي أنشأناها بالاسم فقط:
docker rm -f lab-owasp-vuln lab-owasp-fixedكيف تمنعه؟
- الرفض هو الأصل Deny by Default: كل ما ليس عاماً مرفوض حتى توجد قاعدة تسمح به، والسبب أن نسيان قاعدة يجب أن يغلق الباب لا أن يفتحه.
- الفحص على السيرفر فقط: تقول OWASP إن التحكم في الوصول لا يكون فعالاً إلا في كود يثق به السيرفر ولا يستطيع المستخدم تغييره، فإخفاء زر في JavaScript ليس فحصاً، لأن أي مستخدم يستطيع إرسال الطلب بأداة curl.
- اكتبه مرة واحدة وأعد استخدامه: مكتبة أو Middleware أو محرك سياسات مركزي، بدل شرط مكتوب يدوياً في كل Endpoint، وقد شرحنا النماذج والمحركات في دليل الصلاحيات المركزية Authorization مع RBAC وReBAC.
- ملكية السجل في الاستعلام: وإذا كان التطبيق متعدد المستأجرين Multi-Tenant فاجعل قاعدة البيانات نفسها تمنع الخطأ بسياسات الصفوف، كما في قسم سياسات الصفوف Row-Level Security.
- الجلسات والتوكنات: ألغ الجلسة على السيرفر عند تسجيل الخروج، واجعل توكن JWT قصير العمر، وقد شرحنا ذلك في دليل الجلسات وملفات تعريف الارتباط وJWT.
- CORS بقائمة محددة: لا تفتح CORS لكل المصادر لأن الواجهة «لا تعمل»، وقد شرحنا السبب في دليل CORS وسياسة المصدر الواحد.
- لا تعتمد على المعرف الصعب التخمين: المعرف العشوائي مثل UUID يصعب التخمين، لكنه ليس فحص صلاحية، كما ذكرنا في الملاحظات الأمنية في مقال المعرفات الفريدة.
- حدود المعدل Rate Limiting وتسجيل الرفض: كما في التجربة أعلاه، فكل رفض يسجل، والتكرار يطلق تنبيهاً.
كيف تكتشفه؟
هذه الفئة من أصعب ما تجده الأدوات، لأن أداة الفحص الثابت SAST لا تعرف من يملك ماذا، وأداة الفحص أثناء التشغيل DAST تحتاج حسابين مختلفين وسيناريو مكتوباً حتى تلاحظ أن الأول رأى بيانات الثاني. لذلك فأقوى أداة هنا هي اختبارات التكامل Integration Tests التي تكتبها أنت، اختبار لكل Endpoint يرسل طلباً بحساب مستخدم آخر ويتوقع 403 أو 404، ثم مراجعة الكود ونمذجة التهديدات واختبار الاختراق.
A02:2025 أخطاء الإعداد الأمني Security Misconfiguration
ما هو بلغة بسيطة؟
تعرف صفحة A02 هذه الفئة بأنها نظام أو تطبيق أو خدمة سحابية معدة بشكل خاطئ من الناحية الأمنية، والكود هنا قد يكون سليماً تماماً، ولكن ما حوله مفتوح: حساب افتراضي Default Account بكلمة مرور لم تتغير، ووضع التطوير Debug Mode يعرض تفاصيل الخطأ Stack Trace للزوار، وميزات وخدمات ومنافذ لا يحتاجها أحد، وترويسات الأمان Security Headers غير موجودة، وتخزين سحابي مفتوح للعامة، وعرض محتوى المجلدات Directory Listing. ويدخل فيها أيضاً حقن الكيانات الخارجية في XML واختصاراً XXE، لأن سببه إعداد خاطئ في محلل XML. وقد صعدت هذه الفئة إلى المرتبة الثانية، وتقول OWASP إن ذلك متوقع لأن سلوك البرمجيات أصبح يعتمد على الإعدادات أكثر من أي وقت.
المثال على تطبيق الفواتير
نسخة الإنتاج Production من التطبيق تعمل بإعداد التطوير، فعندما يحدث خطأ في تصدير PDF يرى العميل صفحة فيها مسارات الملفات على السيرفر وأسماء المكتبات وإصداراتها، وهذه بالضبط المعلومات التي يبحث عنها من يريد أن يعرف أي ثغرة منشورة تناسب تطبيقك. والمثال التالي من ملف Program.cs في ASP.NET Core:
// Vulnerable: detailed exception pages for everyone, in every environment
var app = builder.Build();
app.UseDeveloperExceptionPage();والشكل المصلح هو ما يولده قالب المشروع نفسه، فصفحة المطور تعمل في بيئة التطوير فقط، وفي غيرها صفحة خطأ عامة والتفاصيل تذهب إلى السجل:
// Fixed: generic error page outside Development, details only in the logs
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}ولاحظ أن توثيق Microsoft يقول إن الوسيط UseExceptionHandler يلتقط الاستثناءات التي لم تعالج ويسجلها ثم يعيد تنفيذ الطلب على مسار صفحة الخطأ، ومثله في Spring Boot الخاصية server.error.include-stacktrace التي يجب ألا تكون always في الإنتاج.
والصورة التالية تبين أن هذه الفئة لا تسكن في ملف واحد، وإنما في كل طبقة حول التطبيق:

كيف تمنعه؟
- تقوية مكررة Repeatable Hardening: تطلب OWASP أن تكون بيئات التطوير والاختبار والإنتاج معدة بالطريقة نفسها مع أسرار مختلفة لكل بيئة، وأن تكون العملية آلية، أي إعداد ككود Infrastructure as Code وليس خطوات يدوية يتذكرها شخص واحد.
- منصة صغيرة Minimal Platform: احذف ما لا تحتاجه من ميزات وأمثلة وصفحات توثيق وأدوات اختبار، والسبب أن كل ما يعمل ولا تستخدمه سطح لا تراقبه.
- ترويسات الأمان: أرسل HSTS وCSP وغيرها من مكان واحد، وقد شرحنا المجموعة الأساسية في دليل ترويسات الأمان HSTS وCSP، وإخفاء معلومات الإصدار في قسم لا تعط المهاجم معلومات مجانية.
- معالج أخطاء مركزي: صفحة خطأ عامة للمستخدم، والتفاصيل في السجل.
- لا تضع مفاتيح ثابتة في الإعدادات: تنصح OWASP باستخدام هوية المنصة وأسرار قصيرة العمر بدل مفاتيح ثابتة في الكود أو ملفات الإعداد أو خط CI.
كيف تكتشفه؟
هنا تعمل الأدوات جيداً، ففحص ال Images والبنية ككود Infrastructure as Code مع Trivy يقرأ ملفات Dockerfile وKubernetes وTerraform قبل النشر، كما في مقال فحص ال Images والبنية ككود مع Trivy، وأداة DAST مثل ZAP تلاحظ الترويسات الناقصة وصفحات الخطأ التفصيلية من الخارج، كما في مقال الفحص أثناء التشغيل DAST. والقاعدة أن تعيد الفحص بعد كل نشر وليس مرة واحدة، لأن الإعداد يتغير أكثر من الكود.
A03:2025 أخطاء سلسلة التوريد البرمجية Software Supply Chain Failures
ما هو بلغة بسيطة؟
تعرف صفحة A03 هذه الفئة بأنها أعطال أو اختراقات في عملية بناء البرمجيات أو توزيعها أو تحديثها، وغالباً سببها ثغرات أو تعديلات خبيثة في كود طرف ثالث أو أدوات أو اعتماديات Dependencies يعتمد عليها نظامك. أي أن السؤال لم يعد فقط «هل في مكتبتي ثغرة معروفة؟»، وهو ما كانت تسأله فئة المكونات القديمة في 2021، وإنما أيضاً «هل المكتبة نفسها من مصدرها الحقيقي؟ وهل خط CI الذي يبني تطبيقي أقل أماناً من التطبيق؟». ومن العلامات التي تذكرها الصفحة: ألا تعرف إصدارات كل مكوناتك بما فيها غير المباشرة Transitive، وأن تؤجل التحديث إلى نافذة شهرية أو ربع سنوية، وأن يستطيع شخص واحد أن يكتب الكود وينشره إلى الإنتاج دون مراجعة من شخص آخر.
وهذه الفئة قليلة الظهور في البيانات، ولكنها بحسب المقدمة صاحبة أعلى متوسط لقابلية الاستغلال والأثر في درجات CVE، وجاءت في المرتبة الأولى في الاستبيان.
المثال على تطبيق الفواتير
واجهة التطبيق مكتوبة بـ JavaScript، وملف package.json يقبل أي إصدار، وخط CI يشغل npm install فيأخذ في كل بناء ما هو موجود في المستودع العام ذلك اليوم:
{
"dependencies": {
"express": "*"
}
}والشكل المصلح نطاق إصدار محدد، وملف القفل package-lock.json محفوظ في Git، وخط CI يثبت ما في ملف القفل فقط ويفحصه:
# package.json: "express": "^5.1.0", and package-lock.json is committed
npm ci --ignore-scripts # exactly the lock file, fail if it does not match package.json
osv-scanner scan source -L package-lock.json # known vulnerabilities in direct and transitive packagesفي الإعداد أعلاه لاحظ التالي:
- بحسب توثيق npm ci فإن الأمر يحتاج ملف قفل موجوداً، ويتوقف بخطأ إذا لم يطابق
package.jsonبدل أن يعدله، أي أن ما اختبرته هو ما ينشر. - الخيار
--ignore-scriptsيمنع تشغيل سكربتات التثبيت في الحزم، وهي الطريقة التي انتشرت بها أكثر من حملة على npm، فإذا احتاجت حزمة معينة سكربتها فاسمح لها وحدها. - الفحص يقرأ ملف القفل كاملاً، أي المكتبات التي اخترتها والمكتبات التي جاءت معها.
والصورة التالية تبين حلقات السلسلة من جهاز المطور إلى الإنتاج، ومكان A03 وA08 فيها:

كيف تمنعه؟
- قائمة مكونات البرنامج SBOM: اعرف كل ما في تطبيقك، المباشر وغير المباشر، وراقب قواعد الثغرات مثل CVE وOSV باستمرار، وليس عند البناء فقط.
- مصادر موثوقة وإصدارات مختارة: تنصح OWASP بأن تختار الإصدار عمداً ولا تحدث إلا عند الحاجة، وأن تأخذ الحزم من مصادرها الرسمية، وأن تفضل الحزم الموقعة.
- حماية خط CI والمستودع: المصادقة متعددة العوامل MFA، وحماية الفروع، وفصل المهام Separation of Duties، وأسرار محصورة في البيئة، وسجلات لا يمكن العبث بها.
- النشر على مراحل Staged Rollout: لا تحدث كل الأنظمة في وقت واحد، حتى إذا اخترق مورد موثوق كان الضرر محدوداً.
وقد شرحنا التهديدات والدفاعات على كل حلقة مع حوادثها الموثقة في دليل أمن سلسلة التوريد البرمجية.
كيف تكتشفه؟
فحص الاعتماديات SCA هو الأداة الرئيسية هنا، وقد شرحناه مع OSV-Scanner وDependency-Track في مقال فحص الاعتماديات SCA، ومعه فحص ال Images في مقال فحص ال Images والبنية ككود مع Trivy، وفحص الأسرار المسربة من جهاز المطور وخط CI في مقال اكتشاف الأسرار المسربة.
A04:2025 أخطاء التشفير Cryptographic Failures
ما هو بلغة بسيطة؟
تقول صفحة A04 إن هذه الفئة تشمل غياب التشفير، أو التشفير الضعيف، أو تسريب المفاتيح، وما يتصل بذلك، وإن ثلاثاً من أكثر نقاط الضعف شيوعاً فيها تتعلق بمولد أرقام عشوائية ضعيف. ومن أسئلتها: هل ترسل بيانات دون TLS؟ هل تستخدم خوارزميات قديمة مثل MD5 وSHA1؟ هل المفاتيح في مستودع الكود؟ هل تستخدم كلمة مرور كمفتاح تشفير دون دالة اشتقاق مفتاح؟ هل العشوائية Randomness التي تستخدمها مصممة للتشفير أصلاً؟
المثال على تطبيق الفواتير
التطبيق ينشئ رمز إعادة تعيين كلمة المرور Password Reset Token بمولد أرقام عادي، ويحفظ كلمات المرور بهاش MD5، والمثال التالي بلغة Java:
// Vulnerable: predictable token, fast unsalted hash
Random rnd = new Random();
String resetToken = Long.toHexString(rnd.nextLong());
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] stored = md5.digest(password.getBytes(StandardCharsets.UTF_8));والشكل المصلح يستخدم مولداً عشوائياً مصمماً للتشفير، ودالة تخزين كلمات مرور بطيئة مع ملح Salt من مكتبة موثوقة، وهنا من Spring Security:
// Fixed: CSPRNG for tokens, a password hashing function with salt and work factor
SecureRandom rnd = new SecureRandom();
byte[] bytes = new byte[32];
rnd.nextBytes(bytes);
String resetToken = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String stored = encoder.encode(password);في الكود أعلاه لاحظ التالي:
- الكلاس
java.util.Randomمصمم للمحاكاة والألعاب، وناتجه يمكن توقعه إذا عرفت بعض قيمه، أماSecureRandomفمولد مخصص للتشفير، ومثله في Python الوحدة secrets وفي .NET الكلاسRandomNumberGenerator. - تذكر OWASP في الصفحة نفسها دوال Argon2 وyescrypt وscrypt وPBKDF2-HMAC-SHA-512 لتخزين كلمات المرور، والسبب أنها بطيئة عمداً ومع ملح لكل كلمة، فلا تفيد الجداول المحسوبة مسبقاً، ويصبح التخمين مكلفاً حتى على كروت الشاشة GPU.
- الدالة
Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()مذكورة في توثيق Spring Security لتخزين كلمات المرور، وتحتاج مكتبة Bouncy Castle في المشروع.
كيف تمنعه؟
- صنف البيانات Data Classification أولاً: اعرف أي البيانات حساسة، ولا تخزن ما لا تحتاجه، فالبيانات غير المحفوظة لا يمكن سرقتها كما تقول OWASP.
- TLS 1.2 فما فوق مع HSTS: وقد شرحنا الشهادات وسلسلة الثقة في دليل الشهادات الرقمية TLS وX.509.
- تشفير موثق Authenticated Encryption: مثل AES-GCM، بدل التشفير وحده، ولا تكرر ال IV مع المفتاح نفسه أبداً.
- لا تخترع خوارزميتك: استخدم مكتبات معروفة، واحفظ المفاتيح في خزنة أسرار Secrets Vault أو وحدة HSM وليس في الكود.
- استعد للتشفير ما بعد الكم Post-Quantum Cryptography: تذكر الصفحة أن الأنظمة عالية الخطورة يجب أن تكون آمنة قبل نهاية 2030 بحسب خارطة ENISA.
وتفاصيل الهاش وتخزين كلمات المرور والتشفير المتماثل وغير المتماثل وإدارة المفاتيح هي موضوع مقال التشفير للمطورين في هذه المرحلة.
كيف تكتشفه؟
أدوات SAST تجد جيداً استدعاء MD5 وSHA1 وjava.util.Random في سياق أمني، وفحص الأسرار يجد المفاتيح في المستودع، أما إعداد TLS فيفحص من الخارج بأدوات فحص الشهادات وDAST. والجزء الذي لا تجده الأدوات هو تصنيف البيانات، أي هل كان هذا الحقل يستحق التشفير أصلاً، وهذا مكانه التصميم.
A05:2025 الحقن Injection
ما هو بلغة بسيطة؟
تعرف صفحة A05 الحقن بأنه خلل يسمح بإرسال مدخلات غير موثوقة من المستخدم إلى مفسر Interpreter مثل المتصفح أو قاعدة البيانات أو سطر الأوامر، فيقوم المفسر بتنفيذ أجزاء من هذه المدخلات كأوامر. والفكرة واحدة في كل أنواعه: حقن SQL، وحقن NoSQL، وحقن أوامر النظام OS Command Injection، وحقن LDAP، وحقن لغات التعبير Expression Language، وكذلك البرمجة عبر المواقع Cross-site Scripting واختصاراً XSS التي تدخل في هذه الفئة، وتقول الصفحة إن XSS كثيرة الحدوث قليلة الأثر بأكثر من 30 ألف CVE، بينما حقن SQL قليل الحدوث كبير الأثر بأكثر من 14 ألف CVE.
والسبب في كل الحالات أن الكود يبني أمراً بدمج نص ثابت مع نص من المستخدم في سلسلة واحدة، فلا يعرف المفسر أين انتهى كودك وأين بدأت البيانات. والصورة التالية تبين ذلك باسم حقيقي فيه علامة اقتباس، وهو كاف ليكسر الاستعلام المبني بالدمج:

المثال على تطبيق الفواتير
صفحة البحث عن العملاء بالاسم مكتوبة بلغة Java مع JDBC، والشكل الضعيف يبني الاستعلام بالدمج:
// Vulnerable: user input becomes part of the SQL text
String sql = "SELECT id, name FROM customers WHERE name = '" + name + "'";
ResultSet rs = conn.createStatement().executeQuery(sql);والشكل المصلح استعلام ذو معاملات Parameterized Query، فنص الاستعلام ثابت والقيمة ترسل منفصلة:
// Fixed: the SQL text is fixed, the value is bound as data
PreparedStatement ps = conn.prepareStatement(
"SELECT id, name FROM customers WHERE name = ?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();والفكرة نفسها في المتصفح مع XSS، فصفحة نتائج البحث تكتب كلمة البحث في الصفحة، والشكل الضعيف بلغة JavaScript يجعل المتصفح يفسر النص كـ HTML:
// Vulnerable: the browser parses the user's text as HTML
results.innerHTML = "<p>Results for " + query + "</p>";// Fixed: the text is set as text, never parsed as HTML
const p = document.createElement("p");
p.textContent = "Results for " + query;
results.replaceChildren(p);في الكود أعلاه لاحظ التالي:
- الحل في الحالتين ليس «تنظيف» المدخل، وإنما فصل البيانات عن الأوامر، وهذا ما تقوله OWASP بالنص: أفضل طريقة لمنع الحقن هي إبقاء البيانات منفصلة عن الأوامر والاستعلامات.
- ال ORM يحميك ما دمت تستخدمه بالمعاملات، وتذكر الصفحة مثالاً بلغة HQL في Hibernate يبنى بالدمج فيبقى ضعيفاً، وكذلك الإجراءات المخزنة Stored Procedures إذا دمجت النصوص داخلها.
- أسماء الجداول والأعمدة لا يمكن تمريرها كمعاملات، فإذا كان المستخدم يختار عمود الترتيب مثلاً فاقبل فقط أسماء من قائمة مسموحة Allowlist في الكود.
- في XSS تعتمد أغلب أطر الواجهات مثل React وAngular الترميز التلقائي Output Encoding، والخطر يعود عندما تستخدم الطرق التي تتجاوزه، ومعها سياسة CSP كطبقة ثانية.
كيف تمنعه؟
- واجهة آمنة Safe API: الاستعلامات ذات المعاملات أو ORM، وللأوامر استدعاء البرنامج بقائمة معاملات بدل سطر أوامر يمر عبر ال Shell، والتفاصيل في OWASP Query Parameterization Cheat Sheet.
- التحقق من المدخلات Input Validation على السيرفر: بقائمة مسموحة لما يجب أن يكون عليه المدخل، وتقول OWASP إن هذا ليس دفاعاً كاملاً لأن بعض الحقول تحتاج حروفاً خاصة، فهو طبقة ثانية وليس الحل.
- ترميز المخرجات Output Encoding حسب السياق: HTML أو JavaScript أو URL، كما في OWASP XSS Prevention Cheat Sheet.
وكل هذه الأنواع مع تطبيق نكسره ونصلحه في مقال ثغرات الويب بالتجربة، والتحقق من المدخلات وترميز المخرجات بأمثلة في كل لغة في مقال البرمجة الآمنة Secure Coding.
كيف تكتشفه؟
هذه أكثر فئة تجيدها الأدوات، فأداة SAST تتبع المدخل من مصدره Source إلى الدالة الخطرة Sink، كما رأينا في مقال الفحص الثابت SAST، وأداة DAST ترسل مدخلات إلى كل حقل وتراقب الرد. وتقول صفحة A05 إن الاكتشاف الأفضل هو الجمع بين مراجعة الكود والاختبار الآلي لكل المعاملات والترويسات والروابط وملفات تعريف الارتباط وبيانات JSON وXML.
A06:2025 التصميم غير الآمن Insecure Design
ما هو بلغة بسيطة؟
هذه الفئة مختلفة عن غيرها، فهي لا تتكلم عن خطأ في سطر، وإنما عن ضابط أمني Security Control لم يصمم أصلاً، وتقول صفحة A06 إن التصميم الآمن قد يحتوي على أخطاء في التنفيذ، أما التصميم غير الآمن فلا يصلحه تنفيذ مثالي، لأن الضوابط المطلوبة لم تصنع للدفاع ضد هجمات محددة. ويدخل فيها منطق العمل Business Logic، مثل أن تسمح المنظومة بحالة لم يفكر فيها أحد، وأسئلة استعادة الحساب السرية Security Questions التي تقول الصفحة إن NIST 800-63b وASVS وOWASP Top 10 تمنعها، لأن أكثر من شخص قد يعرف إجاباتها.
المثال على تطبيق الفواتير
تذكر الصفحة مثال سلسلة سينما تسمح بخصم الحجز الجماعي حتى خمسة عشر شخصاً قبل أن تطلب عربوناً، ثم يحجز أحدهم ستمئة مقعد في كل الصالات بطلبات قليلة. وفي تطبيقنا حجز مقاعد الدورة التدريبية بالخصم الجماعي، والحد مكتوب فقط في نموذج الصفحة max="15"، والسعر يحسب كما يرسله الطلب، والمثال التالي بلغة Python مع Flask:
# Vulnerable: the limit exists only in the HTML form, nothing on the server
@app.post("/bookings")
def book():
seats = int(request.json["seats"])
price = seats * SEAT_PRICE * (0.8 if seats >= 5 else 1)
return create_booking(current_user(), seats, price)# Fixed: the business rules from the design are enforced on the server
MAX_SEATS_WITHOUT_DEPOSIT = 15
MAX_BOOKINGS_PER_DAY = 3
@app.post("/bookings")
def book():
seats = int(request.json["seats"])
if not 1 <= seats <= MAX_SEATS_WITHOUT_DEPOSIT:
abort(422, "group bookings above 15 seats need a deposit")
if bookings_today(current_user()) >= MAX_BOOKINGS_PER_DAY:
abort(429, "too many bookings today")
price = seats * SEAT_PRICE * (0.8 if seats >= 5 else 1) # computed on the server only
return create_booking(current_user(), seats, price)ولاحظ أن الإصلاح هنا ليس سطراً تقنياً ذكياً، وإنما قاعدتان من قواعد العمل كتبهما أحد في المتطلبات، أي أن أحداً سأل في مرحلة التصميم «ماذا لو حجز أحدهم ستمئة مقعد؟»، ولو لم يسأل هذا السؤال لما وجدته أي أداة، لأن الكود الأول لا يحتوي على أي دالة خطرة.
كيف تمنعه؟
- نمذجة التهديدات Threat Modeling: تطلبها OWASP للأجزاء الحرجة مثل المصادقة والتحكم في الوصول ومنطق العمل والمسارات الأساسية، وقد شرحناها مع STRIDE في دليل نمذجة التهديدات Threat Modeling.
- حالات سوء الاستخدام Misuse Cases: بجانب كل قصة مستخدم User Story، اكتب ما يجب ألا يحدث، ثم اختباراً يتأكد من ذلك.
- مكتبة أنماط آمنة Secure Design Patterns: مكونات جاهزة مجربة للمصادقة والصلاحيات والملفات، بدل أن يخترع كل فريق طريقته.
- فصل المستأجرين Tenants بالتصميم: في كل الطبقات، وليس بشرط في الاستعلام فقط.
كيف تكتشفه؟
الأدوات الآلية ضعيفة هنا لأن الخطأ ليس في الكود، والاكتشاف يكون في مراجعة التصميم ونمذجة التهديدات قبل الكود، ثم في اختبار الاختراق الذي يجرب منطق العمل، ثم في المراقبة التي تلاحظ سلوكاً غير طبيعي مثل مئات الحجوزات من حساب واحد.
A07:2025 أخطاء المصادقة Authentication Failures
ما هو بلغة بسيطة؟
تقول صفحة A07 إن هذه الثغرة موجودة عندما يستطيع المهاجم أن يجعل النظام يقبل مستخدماً غير صحيح على أنه صحيح. ومن صورها: السماح بهجمات حشو بيانات الدخول Credential Stuffing التي تجرب قوائم أسماء وكلمات مرور مسربة من مواقع أخرى، وعدم إيقاف التخمين الآلي Brute Force، وقبول كلمات مرور ضعيفة أو مسربة، واستعادة الحساب بأسئلة سرية، وغياب المصادقة متعددة العوامل MFA، ووضع معرف الجلسة Session ID في الرابط، وعدم تغييره بعد الدخول، وعدم إلغائه عند الخروج.
المثال على تطبيق الفواتير
صفحة الدخول تخبر الزائر أي الحقلين خطأ، فتقول له «لا يوجد حساب بهذا البريد» أو «كلمة المرور خاطئة»، ولا تقفل الحساب بعد المحاولات الفاشلة، والمثال التالي بلغة C# مع ASP.NET Core Identity:
// Vulnerable: tells which emails exist, never slows down guessing
var user = await _users.FindByEmailAsync(model.Email);
if (user is null) return BadRequest("No account with this email");
var result = await _signIn.PasswordSignInAsync(user, model.Password,
isPersistent: false, lockoutOnFailure: false);
if (!result.Succeeded) return BadRequest("Wrong password");// Fixed: one message for every failure, lockout after repeated failures
var result = await _signIn.PasswordSignInAsync(model.Email, model.Password,
isPersistent: false, lockoutOnFailure: true);
if (!result.Succeeded) return BadRequest("Invalid email or password.");في الكود أعلاه لاحظ التالي:
- الرسالة الواحدة لكل الحالات هي ما تطلبه OWASP نصاً، لأن الرسالتين المختلفتين تعطيان المهاجم قائمة البريد المسجل في تطبيقك، وهذا يشمل صفحة التسجيل واستعادة كلمة المرور أيضاً.
- المعامل
lockoutOnFailureفي الدالة PasswordSignInAsync يفعل القفل المؤقت بعد عدد من المحاولات الفاشلة حسب إعدادات Identity، وانتبه إلى ما تحذر منه OWASP، أي ألا يتحول القفل إلى طريقة لحجب الخدمة عن المستخدمين الحقيقيين، لذلك اجعله مؤقتاً، ومعه حد للمعدل على مستوى ال Reverse Proxy. - الكود الأول يفترض أن اسم المستخدم هو البريد، والثاني كذلك، فإذا كان التطبيق يفصل بينهما فابحث عن المستخدم أولاً ولكن أعد الرسالة نفسها في كل الحالات.
كيف تمنعه؟
- المصادقة متعددة العوامل MFA: وأفضل منها الدخول دون كلمة مرور، كما في دليل Passkeys وFIDO2، لأن المفتاح لا يعطى للموقع المزيف أصلاً.
- سياسة كلمات مرور حديثة: تحيل OWASP إلى NIST SP 800-63B، أي طول كاف، وفحص كلمة المرور الجديدة مقابل قوائم المسرب، وعدم إجبار الناس على تغييرها دورياً إلا عند الشك في تسريب.
- لا تنشر التطبيق بكلمات مرور افتراضية: وخصوصاً لحساب المدير، فاجعل الإعداد الأول يطلب كلمة مرور جديدة.
- جلسة يديرها السيرفر: معرف عشوائي جديد بعد الدخول، في Cookie آمنة، يلغى عند الخروج وبعد مدة الخمول، كما في دليل الجلسات وJWT، ومع JWT تحقق من الحقلين
audوissوالصلاحيات. - لا تكتب المصادقة بنفسك: تنصح OWASP باستخدام نظام جاهز موثوق للهوية والجلسات، وقد شرحنا البروتوكولات في دليل OAuth وOpenID Connect.
كيف تكتشفه؟
أداة SAST تجد كلمات المرور المكتوبة في الكود، وفحص الأسرار يجد بيانات الدخول المسربة في المستودع، وأداة DAST تفحص خصائص Cookie الجلسة ورسائل الدخول، أما محاولات التخمين الفعلية فتظهر في السجلات، وقد شرحنا حد المعدل والحظر التلقائي في قسم الحد من معدل الطلبات Rate Limiting.
A08:2025 أخطاء سلامة البرمجيات والبيانات Software or Data Integrity Failures
ما هو بلغة بسيطة؟
تقول صفحة A08 إن هذه الفئة تتعلق بكود وبنية تحتية لا تحمي من أن يعامل كود أو بيانات غير موثوقة على أنها موثوقة وصحيحة، وإنها تعمل على مستوى أدنى من فئة سلسلة التوريد. ومن أمثلتها: تحميل إضافات أو مكتبات من مصادر وشبكات توزيع محتوى CDN غير موثوقة، وتحديث تلقائي Auto-update يثبت دون التحقق من التوقيع، وخط CI يسحب ملفات دون أن يتحقق منها، وفك التسلسل غير الآمن Insecure Deserialization، أي أن يستقبل التطبيق كائناً مسلسلاً Serialized Object من المستخدم فيعيد بناءه كما هو.
المثال على تطبيق الفواتير
سلة الخدمات في التطبيق محفوظة في Cookie، والكود يقرؤها بالوحدة pickle في Python، وتوثيق pickle يقول بوضوح إن الوحدة غير آمنة، وإن بيانات pickle المعدة بطريقة معينة تستطيع تنفيذ كود عند فكها، فلا تفك إلا بيانات تثق بها:
# Vulnerable: rebuilds whatever object the client sends
import base64, pickle
cart = pickle.loads(base64.b64decode(request.cookies["cart"]))والشكل المصلح يستخدم صيغة بيانات فقط مثل JSON، ويوقع القيمة بمفتاح على السيرفر حتى يكتشف أي تعديل، وهو ما يقترحه توثيق pickle نفسه حين يذكر التوقيع بـ hmac:
# Fixed: data-only format, signed by the server, checked before use
import base64, hashlib, hmac, json, os
KEY = os.environ["CART_SIGNING_KEY"].encode()
def load_cart(cookie: str) -> dict:
payload, _, signature = cookie.partition(".")
expected = hmac.new(KEY, payload.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
raise ValueError("cart cookie was modified")
return json.loads(base64.urlsafe_b64decode(payload))في الكود أعلاه لاحظ التالي:
- JSON يعيد قيماً وقوائم وقواميس فقط، ولا يستطيع أن يبني كائناً من أي كلاس، وهذا هو الفرق الجوهري مع pickle، ومثله في Java تجنب
ObjectInputStreamعلى بيانات من المستخدم، وفي .NET تجنبBinaryFormatterالذي تقول Microsoft في دليلها الأمني إنه غير آمن ولا يمكن جعله آمناً، وإن تنفيذه المدمج يرمي استثناء عند استخدامه بدءاً من .NET 9. - الدالة
hmac.compare_digestتقارن في زمن ثابت، والسبب ألا يعرف المهاجم من زمن الرد كم حرفاً صحيحاً في التوقيع. - الحل الأبسط في أغلب التطبيقات ألا تخزن السلة في المتصفح أصلاً، وإنما على السيرفر مربوطة بالجلسة.
والفكرة نفسها في ملفات JavaScript التي تحملها من CDN، فالخاصية integrity تجعل المتصفح يرفض الملف إذا تغير محتواه عن الهاش الذي حددته، كما في توثيق Subresource Integrity في MDN:
<script src="https://cdn.example.com/chart.min.js"
integrity="sha384-<hash of the exact file you reviewed>"
crossorigin="anonymous"></script>كيف تمنعه؟
- التوقيع الرقمي Digital Signature: تحقق من توقيع البرمجيات والتحديثات وال Images قبل تشغيلها.
- مستودعات موثوقة: أن تسحب npm وMaven من مستودعات موثوقة، ومع الخطورة الأعلى مستودع داخلي مفحوص.
- مراجعة التغييرات: على الكود والإعدادات وخط CI، حتى لا يدخل كود خبيث دون أن يراه أحد.
- لا تقبل بيانات مسلسلة غير موقعة: من عميل غير موثوق، كما في المثال أعلاه، والتفاصيل في OWASP Deserialization Cheat Sheet.
كيف تكتشفه؟
أداة SAST تجد استدعاء pickle.loads وObjectInputStream على بيانات من الطلب، وفحص خط البناء يتأكد من التوقيع ومصدر البناء Provenance، كما في قسم الدفاعات من دليل أمن سلسلة التوريد ومقال خط CI/CD آمن من البداية للنهاية.
A09:2025 أخطاء السجلات والتنبيه Security Logging and Alerting Failures
ما هو بلغة بسيطة؟
تقول صفحة A09 إنه دون سجلات ومراقبة لا يمكن اكتشاف الهجمات والاختراقات، ودون تنبيه يصعب جداً أن تستجيب بسرعة. ومن صورها: أحداث مهمة لا تسجل، مثل الدخول الفاشل والعمليات المالية الكبيرة، أو تسجيل الدخول الناجح فقط دون الفاشل، وسجلات لا يراقبها أحد، وسجلات محفوظة على السيرفر نفسه فقط، وفحص DAST أو اختبار اختراق كامل يمر دون أن يطلق تنبيهاً واحداً، وتسجيل بيانات حساسة لا يجب أن تسجل، وسجلات غير مرمزة تسمح بحقن السجلات Log Injection، وكثرة التنبيهات الخاطئة حتى يتجاهلها الفريق.
المثال على تطبيق الفواتير
كود الدخول يكتب سطراً عند النجاح فقط، وفيه كلمة المرور، ولا يكتب شيئاً عند الفشل، والمثال التالي بلغة Java مع SLF4J:
// Vulnerable: secret in the log, failures are invisible
if (authenticated) {
log.info("login " + email + " password=" + password);
}// Fixed: both outcomes, no secrets, placeholders and a structured (JSON) appender
if (authenticated) {
log.info("login_succeeded user={} ip={}", userId, clientIp);
} else {
log.warn("login_failed email={} ip={}", email, clientIp); // counted by an alert rule
}في الكود أعلاه لاحظ التالي:
- كلمة المرور والتوكنات وأرقام البطاقات لا تدخل السجل أبداً، لأن السجل يقرؤه أشخاص وأنظمة أكثر بكثير ممن يقرأ قاعدة البيانات، وقد شرحنا القائمة في قسم ما لا يدخل السجل أبداً.
- القيمة
emailجاءت من المستخدم، وإذا كتبت بالدمج في سطر نصي فقد تحتوي على سطر جديد يزور سطراً كاملاً في السجل، أما مع مخرج منظم JSON فتصبح القيمة حقلاً مرمزاً، كما في قسم حقن السجلات Log Injection. - السطر الذي لا يقرؤه أحد لا يحمي أحداً، لذلك فالمهم هو قاعدة التنبيه التي تقول مثلاً: أكثر من خمسين فشلاً في الدقيقة من عنوان واحد يرسل إشعاراً.
كيف تمنعه؟
- سجل كل ضابط أمني: تطلب OWASP تسجيل فشل الدخول والتحكم في الوصول والتحقق من المدخلات مع سياق كاف لمعرفة الحساب، وحفظه مدة كافية للتحقيق لاحقاً.
- سجل تدقيق Audit Trail لا يقبل التعديل: للعمليات المهمة، مثل جدول يقبل الإضافة فقط Append-only، كما في دليل سجل التدقيق Audit Trail.
- جمع مركزي وتنبيه: السجل يخرج من السيرفر إلى نظام مركزي له لوحات وتنبيهات، كما في دليل السجلات Logging للمطورين ومديري السيرفرات.
- الطعم Honeytokens: تقترح OWASP حسابات أو سجلات وهمية لا يستخدمها أحد في العمل العادي، فأي وصول إليها يطلق تنبيهاً شبه خال من الإنذارات الخاطئة.
- خطة استجابة Incident Response: مكتوبة قبل الحادثة، ومعروف فيها من يستلم التنبيه.
كيف تكتشفه؟
هذه الفئة لا تجدها أداة فحص لأنك تبحث عن شيء غائب، والاختبار العملي الذي تقترحه الصفحة نفسها بسيط: شغل فحص DAST على بيئة المعاينة، ثم اسأل هل وصل تنبيه؟ فإذا مر فحص كامل دون أن يلاحظه أحد، فالهجوم الحقيقي سوف يمر كذلك.
A10:2025 سوء التعامل مع الحالات الاستثنائية Mishandling of Exceptional Conditions
ما هو بلغة بسيطة؟
هذه الفئة جديدة في 2025، وتقول صفحة A10 إنها تحدث عندما يفشل البرنامج في منع حالة غير عادية أو في اكتشافها أو في الاستجابة لها، فيؤدي ذلك إلى توقف أو سلوك غير متوقع أو ثغرة. ومن نقاط الضعف التي تذكرها: رسالة خطأ فيها معلومات حساسة، وعدم التعامل مع معامل ناقص، والفشل المفتوح Failing Open أي أن يسمح النظام بالعملية عندما يفشل الفحص، ومعاملة مالية تتوقف في منتصفها دون أن ترجع خطواتها.
المثال على تطبيق الفواتير
التطبيق يسأل خدمة صلاحيات منفصلة قبل فتح المستند، والمطور لا يريد أن يتوقف العمل إذا تعطلت الخدمة، فيكتب الكود التالي بلغة C#:
// Vulnerable: fails open, an outage of the policy service grants access to everyone
bool allowed;
try
{
allowed = await _policy.CanReadAsync(userId, documentId);
}
catch (Exception)
{
allowed = true; // "do not block users when the policy service is down"
}// Fixed: fails closed, logs the error, and the user gets a clear answer
bool allowed;
try
{
allowed = await _policy.CanReadAsync(userId, documentId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Policy check failed for document {DocumentId}", documentId);
return StatusCode(503); // no answer from the policy service means no access
}
if (!allowed) return NotFound();في الكود أعلاه لاحظ التالي:
- الكود الأول يبدو لطيفاً مع المستخدم، لكن من يستطيع أن يجعل خدمة الصلاحيات تتعطل أو تبطئ حتى تنتهي مهلتها Timeout قد حصل على صلاحية الجميع، ولهذا تقول صفحة A09 «Always fail closed».
- الخطأ يسجل مرة واحدة مع تفاصيله، والمستخدم يرى رداً عاماً دون Stack Trace، وهذا الرابط بين A10 وA02 وA09.
- وفي العمليات متعددة الخطوات، مثل خصم من حساب وإضافة إلى آخر وتسجيل العملية، تطلب OWASP أن ترجع المعاملة Transaction كلها عند أي خطأ بدل محاولة إكمالها من منتصفها، وفي EF Core مثلاً المعاملة التي لم ينفذ عليها
CommitAsyncترجع عند التخلص منها.
كيف تمنعه؟
- توقع الأسوأ: التقط الخطأ في المكان الذي يحدث فيه وتعامل معه هناك، ومع ذلك ضع معالجاً عاماً Global Exception Handler لما فاتك، كما في OWASP Error Handling Cheat Sheet.
- الفشل المغلق Fail Closed: في كل فحص أمني، وكذلك رجوع المعاملة كاملة.
- حدود في كل مكان: تقول الصفحة إنه لا يجب أن يكون شيء في تقنية المعلومات بلا حدود، فضع حداً للمعدل Rate Limit وحصصاً Quotas ومهلاً Timeouts، ومنها حدود حجم الملفات التي شرحناها في دليل حدود حجم رفع الملفات.
- طريقة واحدة في المؤسسة كلها: معالجة الأخطاء في مكان واحد وبالطريقة نفسها، لأن ذلك يسهل مراجعتها.
كيف تكتشفه؟
أداة SAST تجد كتل catch الفارغة والاستثناءات التي تبتلع، ومراجعة الكود تجد الفشل المفتوح لأنه قرار وليس خطأ في الكتابة، واختبارات الضغط Stress Tests واختبار الاختراق تكشف ما يحدث تحت الحمل وعند انقطاع الشبكة، وقد شرحنا اختبار الحمل في دليل اختبار الأحمال والضغط Load وStress Testing.
أين تكتشف كل خطر؟ في التصميم Design والكود Code والبناء Build والتشغيل Runtime
بعد أن مررنا على الفئات العشر، لاحظ أن كل فئة لها مكان تظهر فيه بسهولة وأماكن لا تظهر فيها أبداً، والصورة التالية تجمع ذلك في جدول واحد، وهي تقدير تحريري منا مبني على أقسام الوقاية في صفحات OWASP وليس بيانات منشورة:

وفي الصورة أعلاه لاحظ التالي:
- لا توجد أداة واحدة تغطي العمود كله، ولا يوجد عمود يغطي القائمة كلها، وهذا ما رأيناه عملياً في تجربة مقال DevSecOps للمبتدئين حين فحصت أربع أدوات التطبيق نفسه فرأت كل منها جزءاً منه.
- الفئتان A01 وA06، أي الأولى والسادسة، تحتاجان إنساناً يعرف ما يجب أن يحدث، لأن الأداة لا تعرف قواعد عملك.
- الفئات التي تجيدها الأدوات، مثل الحقن والمكتبات والإعدادات، هي التي يجب أن تعمل آلياً في كل طلب دمج Pull Request، حتى يتفرغ الناس للفئات التي لا تجيدها الأدوات.
كيف تستخدم القائمة في فريقك، وكيف لا تستخدمها؟
الاستخدام الصحيح للقائمة أن تكون لغة مشتركة في الفريق، فعندما يقول أحدهم في مراجعة الكود «هذا A01» يفهم الجميع أن المشكلة صلاحية ناقصة، وأن تكون منهج تدريب أول للمطورين الجدد، وأن تسأل بها بعد نمذجة التهديدات «هل نسينا فئة مشهورة؟». أما الاستخدام الخاطئ فهو أن تتحول إلى قائمة تحقق للامتثال Compliance، فيكتب الفريق «فحصنا العشرة» ويعتبر التطبيق آمناً، والسبب أن القائمة كما تقول OWASP نقطة بداية وليست قائمة كاملة، وأن كل فئة فيها عشرات نقاط الضعف التي لا تختبر بسؤال واحد.
وإذا أردت متطلبات تتحقق منها فعلاً، فالخطوة التالية هي معيار ASVS، وفيه متطلبات مرقمة لكل مستوى، وقد خصصنا له مقال متطلبات الأمان ومراجعة الكود مع OWASP ASVS، ومعه قائمة مراجعة لكل Pull Request.
مسار القراءة Reading Path في مرحلة أمن التطبيقات AppSec
هذا المقال يعطيك الخريطة، والمقالات التالية في المرحلة نفسها تتعمق في كل جزء، بهذا الترتيب: الثغرات بالتجربة، ثم واجهات API، ثم البرمجة الآمنة بأمثلة في كل لغة، ثم التشفير للمطورين، ثم المتطلبات ومراجعة الكود:
دليلثغرات الويب بالتجربة: تطبيق نكسره ثم نصلحهنرى أثر أشهر ثغرات الويب على متجر صغير على جهازك، ثم السطر المسؤول عن كل ثغرة، ثم الإصلاح والاختبار الذي يثبته.
دليلأمن ال API بالأمثلة مع OWASP API Security Top 10أشهر عشرة مخاطر في واجهات API كما ترتبها OWASP، ولكل خطر مثال لكود ضعيف وكيف تصلحه وتختبره.
دليلالبرمجة الآمنة Secure Coding بأمثلة من أربع لغاتكيف تكتب الكود الذي يتعامل مع مدخلات المستخدم والملفات والأخطاء بحيث لا يحتاج إصلاحاً أمنياً لاحقاً، بأمثلة في أربع لغات.
دليلالتشفير للمطورين: ماذا تستخدم ولماذا لا تخترع خوارزميتكأي هاش تستخدم لكلمات المرور، ومتى تحتاج التشفير المتماثل أو غير المتماثل، وأين تحفظ المفاتيح بأمان في تطبيقك.
دليلOWASP ASVS ومراجعة الكود الأمنية لكل Pull Requestكيف تكتب متطلبات أمان يمكن اختبارها مع OWASP ASVS، وتراجع كل طلب دمج أمنياً في عشرين دقيقة بقائمة جاهزة.وقبلها أو بجانبها، مقالان يكملان الصورة، الأول عن التصميم قبل الكود، والثاني عن الفحوص الآلية في خط CI/CD:
دليلنمذجة التهديدات Threat Modeling للمبتدئينكيف تسأل ما الذي قد يسوء في تصميم تطبيقك قبل أن تكتب الكود، وتقرر لكل تهديد هل تصلحه أو تقبله، مع قالب جاهز للنسخ.
دليلDevSecOps للمبتدئين: الأمن داخل خط CI/CD ومتى يعمل كل فحصما الذي يجده كل فحص أمني في خط CI/CD وما الذي يفوته، وكيف ترتب نتائجه حتى تصل إلى قرار بدل قائمة طويلة.وهذه المقالات التي تملك كل فئة من الفئات وتشرحها بالتفصيل، مرتبة حسب الفئة:
دليلAuthorization: كيف تجمع قرارات الصلاحيات في مكان واحدلماذا تتحول شروط الصلاحيات المتفرقة في الكود إلى ثغرات، وكيف تنقل القرار إلى محرك سياسات واحد.
دليلتطبيق SaaS متعدد المستأجرين عملياً مع Row-Level Securityنبني على PostgreSQL تطبيق SaaS يعزل كل مستأجر Tenant عن غيره بسياسات Row-Level Security، ونرى أين تتسرب البيانات رغم ذلك.
دليلHSTS وCSP: ترويسات الأمان التي يحتاجها كل تطبيقما الذي تطلبه كل ترويسة أمان من المتصفح، ولماذا يصعب التراجع عن HSTS، وكيف تبني سياسة CSP لا تكسر تطبيقك.
دليلأمن سلسلة التوريد البرمجية Software Supply Chain للمبتدئينكل ما بين كود غيرك وسيرفرك قد يحمل كود المهاجم، وهنا تعرف التهديدات الحقيقية بحوادث موثقة والدفاع المناسب لكل حلقة.
دليلكيف يتذكرك الموقع بعد تسجيل الدخول؟ الجلسات وJWTبروتوكول HTTP لا يتذكر أحداً، فكيف يبقى المستخدم مسجلاً بين الصفحات، ومتى تختار الجلسة على السيرفر ومتى تختار JWT.
دليلالدخول دون كلمة مرور: شرح Passkeys وWebAuthnلماذا لا تحميك رموز SMS وTOTP من التصيد الحديث، وكيف تحل Passkeys المشكلة بمفتاح خاص لا يغادر جهازك.
دليلالسجلات Logging للمطورين: ماذا تسجل وماذا لا تسجلكيف تكتب سجلات Logs تساعدك على فهم ما حدث في الإنتاج، وما الذي لا يدخل السجل أبداً، وكيف تجمعها في مكان واحد.
دليلAudit Trail: كيف تبني سجل تدقيق لا يقبل العبثكيف تصمم سجلاً يجيب عن سؤال من غير هذه الصلاحية ومتى، وتحميه من الحذف والتعديل بعد كتابته.أخطاء شائعة Common Mistakes
- الخلط بين المصادقة والتفويض:
[Authorize]أو@login_requiredتعني أن المستخدم مسجل، ولا تعني أن السجل الذي يطلبه له، وهذا أصل الفئة الأولى. - الفحص في الواجهة فقط: إخفاء الزر أو تعطيل الحقل أو وضع حد في نموذج HTML، والطلب يمكن إرساله دون الواجهة.
- تنظيف المدخلات بدل فصلها: قائمة حروف ممنوعة أو استبدال علامات الاقتباس، والحل الصحيح الاستعلام ذو المعاملات وترميز المخرجات حسب السياق.
- إعداد التطوير في الإنتاج: صفحات الخطأ التفصيلية، والحسابات التجريبية، والمنافذ المفتوحة لأنها «مؤقتة».
- الفشل المفتوح باسم تجربة المستخدم: السماح بالعملية عندما يتعطل الفحص، فيتحول كل عطل إلى ثغرة.
- سجلات لا يقرؤها أحد: أو سجلات فيها كلمات المرور والتوكنات.
- اعتبار القائمة معياراً للامتثال: «فحصنا OWASP Top 10» ليست شهادة أمان، فاستخدم ASVS للمتطلبات واختبار الاختراق للتحقق.
- الاكتفاء بأداة واحدة: كل أداة ترى طبقة، والفئتان الأولى والسادسة لا تراهما أداة تقريباً.
قائمة تحقق Checklist لكل ميزة جديدة
- هل يفحص السيرفر ملكية كل سجل في كل Endpoint، مع اختبار آلي يرسل طلب مستخدم آخر ويتوقع الرفض؟ (A01)
- هل يعمل الإنتاج بإعدادات الإنتاج، فلا توجد صفحات خطأ تفصيلية ولا حسابات افتراضية، وترويسات الأمان موجودة، والمنافذ الداخلية مربوطة على 127.0.0.1؟ (A02)
- هل ملف القفل محفوظ في Git، والتثبيت في CI من ملف القفل فقط، وفحص SCA يعمل في كل طلب دمج؟ (A03)
- هل البيانات الحساسة مصنفة ومشفرة، وكلمات المرور مخزنة بدالة مثل Argon2id، والتوكنات من مولد عشوائي للتشفير؟ (A04)
- هل كل استعلام بالمعاملات، وكل مخرج مرمز حسب السياق، ولا يوجد استدعاء Shell بنص من المستخدم؟ (A05)
- هل سأل أحد «ماذا لو…؟» على هذه الميزة، وهل قواعد العمل مكتوبة على السيرفر؟ (A06)
- هل رسالة الدخول واحدة في كل الحالات، ومع قفل مؤقت أو حد للمعدل، وMFA متاح؟ (A07)
- هل البيانات التي تأتي من العميل بصيغة بيانات فقط وموقعة إن احتجت أن تعود إليك، وال Images والتحديثات موقعة؟ (A08)
- هل يسجل الفشل كما يسجل النجاح، دون أسرار، مع تنبيه يصل إلى شخص معروف؟ (A09)
- هل كل فحص أمني يفشل مغلقاً، وكل معاملة ترجع كاملة عند الخطأ، ولكل مورد حد؟ (A10)
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- OWASP Top 10 وثيقة توعية بأخطر عشر فئات في تطبيقات الويب، ونسختها الحالية 2025 هي الثامنة، وكل فئة فيها مجموعة من نقاط الضعف CWE، وليست معياراً للتحقق ولا قائمة كاملة.
- تبنى القائمة من بيانات اختبار أكثر من 2.8 مليون تطبيق مع درجات الثغرات المنشورة، ثم يضيف الاستبيان فئتين لا تظهران جيداً في البيانات، وهما في 2025 سلسلة التوريد والسجلات والتنبيه.
- الفئات بالترتيب: Broken Access Control، وSecurity Misconfiguration، وSoftware Supply Chain Failures، وCryptographic Failures، وInjection، وInsecure Design، وAuthentication Failures، وSoftware or Data Integrity Failures، وSecurity Logging and Alerting Failures، وMishandling of Exceptional Conditions.
- في التجربة رأينا أن الفرق بين فحص صلاحيات ناقص ومصلح سطر واحد، وأن الرد الواحد في حالتي «ليس لك» و«غير موجود» مع سطر في السجل يجمع الإصلاح من A01 وA09 معاً.
- الأدوات تجيد الحقن والمكتبات والإعدادات، أما الصلاحيات والتصميم فتحتاج اختبارات تكتبها أنت ونمذجة تهديدات ومراجعة من إنسان.
- استخدم القائمة لغة مشتركة ومنهج تدريب، وASVS للمتطلبات، والفحوص الآلية في CI لما تجيده الأدوات.
وفي المقالات القادمة بإذن الله سوف نبني تطبيقاً فيه أشهر هذه الثغرات، ونكسره ثم نصلحه واحدة واحدة، بدءاً من حقن SQL وXSS.
سجل التحديثات
- أكتوبر 2026: كتابة المقال على نسخة OWASP Top 10:2025 من موقعها الرسمي، والتحقق من أسماء الفئات وترتيبها وطريقة بنائها من مقدمة القائمة وصفحات الفئات، وتنفيذ تجربة فحص الصلاحيات بمكتبة Python القياسية على python:3.12-slim (Python 3.12.15) داخل Docker Engine 29.8.0.