6.0 KiB
6.0 KiB
گزارش باگ — ممیزی ۱۴۰۵/۰۳/۳۰ (2026-06-20)
ممیزی رفتاریِ کل APIها (۲۴۳ endpoint) با توکن واقعیِ هر ۶ نقش (admin/clinic/doctor/secretary/representation/user) روی https://clinic-pro.ddev.site. مقایسه با docs/api/*.
خلاصه
| سطح | تعداد |
|---|---|
| CRITICAL | 0 |
| HIGH | 1 |
| MEDIUM | 2 |
| LOW | 0 |
| رفعشده در همین جلسه | 1 (BUG-1) |
نتیجهی کلیِ امنیتی مثبت: لایهی authorization محکم است — همهی /api/v1/admin/* برای نقشهای غیرادمین 403 دادند؛ scope نماینده درست محدود است؛ IDOR روی پروفایل کاربر و ویرایش کلینیکِ غیرمالک 403/404 میگیرد؛ ورودیهای مرزیِ لیستها (page/limit بزرگ/منفی/غیرعددی، تاریخ نامعتبر) همگی graceful بودند (بدون ۵۰۰).
باگها
[HIGH] BUG-3 — ساخت نماینده با شماره موبایلِ نامعتبر (بدون اعتبارسنجی فرمت)
- دامنه: representation
- endpoint:
POST /api/v1/representation(ROLE_ADMIN) - بازتولید:
curl -sk -X POST .../api/v1/representation -H "Authorization: Bearer <ADMIN>" \ -H "Content-Type: application/json" -d '{"full_name":"qa","mobile_number":"not-a-mobile"}' - انتظار:
422(شماره موبایل نامعتبر). - واقعیت:
201 Created— یک نماینده ساخته شد و یکUser("not-a-mobile")ایجاد شد (در پاسخmobile_number: null). - اثر: رکورد آشغال + کاربرِ بدون شمارهی لاگین معتبر؛ شماره موبایل کلیدِ ورود است، پس نمایندهای ساخته میشود که نمیتواند وارد شود/پیامک بگیرد.
- ریشه:
src/Representation/Controller/RepresentationController.php:86,89— فقطempty($mobile)چک میشود، فرمت ایران (^09\d{9}$) چک نمیشود. - رفع پیشنهادی: بعد از
emptyچک،if (!preg_match('/^09\d{9}$/', $mobile)) return $this->error(ERR_VALIDATION_001, 'شماره موبایل نامعتبر', 422);(همان regex فرانت). - وضعیت: گزارششده (رفع نشده — نیاز به تأیید).
[MEDIUM] BUG-2 — کد ملی نامعتبر بدون اعتبارسنجی سمت سرور ذخیره میشود
- دامنه: user-profile
- endpoint:
PATCH /api/v1/user-profile/{uuid} - بازتولید:
curl -sk -X PATCH .../api/v1/user-profile/<own-uuid> -H "Authorization: Bearer <USER>" \ -H "Content-Type: application/json" -d '{"national_code":"1111111111"}' - انتظار:
422(رقم کنترلیِ کد ملی نامعتبر است). - واقعیت:
200وnational_code: "1111111111"ذخیره شد. - اثر: اعتبارسنجی کد ملی فقط در فرانت (
isValidIranNationalCode) است؛ هر کلاینت/فرانتِ دورزدهشده میتواند کد ملی نامعتبر ثبت کند. - ریشه:
src/UserProfile/Controller/UserProfileController.php→hydrate()فقطsetNationalCode($data['national_code'])میکند، بدون validation. - رفع پیشنهادی: اعتبارسنجی الگوریتم رقم کنترلیِ ایران سمت سرور قبل از
setNationalCode(پورت همان منطق فرانت). - وضعیت: گزارششده.
[MEDIUM] BUG-1 — ۵۰۰ روی ساخت بلاگ با تایپ اشتباهِ body ✅ رفع شد
- دامنه: blog
- endpoint:
POST /api/v1/blog(ROLE_ADMIN) - بازتولید:
-d '{"title":"valid","body":["x"]}' - انتظار:
422. - واقعیت (قبل رفع):
HTTP 500/ERR_INTERNAL_001. - ریشه:
src/Blog/Controller/BlogController.php:178—trim($data['body'] ?? '')وقتیbodyآرایه است →TypeErrorدر PHP 8. - رفع انجامشده: چک
is_stringروی title/body (۴۲۲ صریح) + cast(string). تست شد: body آرایه →422، بلاگ معتبر همچنان201. - یادداشت: اسکن سراسری نشان داد بقیهی controllerها از
(string)cast استفاده میکنند؛ این تنها نقطهی بدون cast بود.
مواردی که تست شدند و سالم بودند (شواهد مثبت)
| تست | نتیجه |
|---|---|
/api/v1/admin/* (users, payments, settlements, doctors, clinics, sms/logs) با ۵ نقش غیرادمین |
همه 403 ✓ (admin 200) |
representation/{me,doctors,clinics,appointments} |
rep 200، بقیه 403 ✓ |
| IDOR: user → PATCH پروفایلِ کاربر دیگر | 404 (canAccess ownership) ✓ |
| IDOR: doctor → PATCH کلینیکِ غیرمالک | 403 (ERR_AUTH_006) ✓ |
لیستهای paginated: page=99999, limit=-1/0, page=abc |
همه 200 graceful ✓ |
| detail بلاگها (کلاس باگ proxy author) | همه 200 ✓ (قبلاً رفع شده بود) |
POST با بدنهی خالی {} (doctor/clinic/representation/blog/send-code) |
همه 422 ✓ |
tsc --noEmit + yarn dev (admin build) |
بدون خطا ✓ |
توصیهها
- یک Trait/سرویس اعتبارسنجی مشترک سمت سرور برای موبایل و کد ملی بساز (الان منطق فقط در فرانت تکرار شده). BUG-2 و BUG-3 هر دو از همین خلأ میآیند.
- الگوی
trim((string) ($data[...] ?? ''))را بهعنوان قاعده در همهی هندلرها رعایت کن (BUG-1). - این ممیزی روی محورهای پرریسک متمرکز بود؛ برای پوشش کامل ۲۴۳ endpoint، یک suite
phpunitرفتاری (functional) ارزش سرمایهگذاری دارد (الان فقطtests/bootstrap.phpهست).