CORE · مفاهیم مجوزدهی

OAuth — چارچوب تفویض دسترسی

OAuth 2.0

OAuth 2.0 یک چارچوب تفویض دسترسی است و OIDC لایه‌ی هویتی روی آن؛ تقریباً همه‌ی یافته‌های جدی در این حوزه نه از خود پروتکل، بلکه از پیاده‌سازی سهل‌گیرانه‌ی اعتبارسنجی redirect_uri، حذف state یا نبود PKCE می‌آیند.

تیم فنی پی‌هانتر

OAuth و OIDC چه می‌کنند؟

OAuth 2.0 یک چارچوب تفویض دسترسی است: به برنامه‌ی «الف» اجازه می‌دهد بدون دانستن رمز عبور کاربر، به بخشی از داده‌ی او در سرویس «ب» دسترسی پیدا کند. OpenID Connect (OIDC) لایه‌ای است که روی OAuth 2.0 ساخته شده و آن را از تفویض دسترسی به احراز هویت گسترش می‌دهد؛ خروجی مشخصه‌ی آن id_token است، یک JWT که می‌گوید کاربر کیست.

تفکیک این دو مهم است: «ورود با گوگل» یک عملیات OIDC است، نه صرفاً OAuth. استفاده از Access Token به‌جای ID Token برای تشخیص هویت کاربر، یک الگوی ناامن شناخته‌شده است.

جریان اصلی و توصیه‌شده، Authorization Code است:

  1. کلاینت کاربر را به سرور مجوزدهی می‌فرستد، با client_id، redirect_uri، scope، state و code_challenge.
  2. کاربر احراز هویت می‌شود و رضایت می‌دهد.
  3. سرور مجوزدهی کاربر را به redirect_uri بازمی‌گرداند، همراه با یک کد مجوز کوتاه‌عمر.
  4. کلاینت در یک درخواست پشت‌صحنه (Back-channel) کد را به همراه code_verifier با توکن دسترسی مبادله می‌کند.

ارزش این طراحی در گام چهارم است: توکن هرگز از مرورگر عبور نمی‌کند. هر انحرافی از این الگو — مثل جریان Implicit که توکن را در قطعه‌ی URL برمی‌گرداند — این ویژگی را از بین می‌برد.

وضعیت واقعی OAuth 2.1

باور غلط رایج

«OAuth 2.1 استاندارد منتشرشده است و باید به آن مهاجرت کنیم.» OAuth 2.1 هنوز RFC نیست. این سند یک Internet-Draft فعال گروه کاری IETF است؛ نسخه‌ی جاری draft-ietf-oauth-v2-1-15 با تاریخ ۲ مارس ۲۰۲۶ است و هدف‌گذاری ارسال به IESG برای دسامبر ۲۰۲۶ اعلام شده. سایت رسمی oauth.net هم آن را «تلاشی در جریان» توصیف می‌کند. در گزارش تست نفوذ می‌توان به آن به‌عنوان راهنمای عملی جاری ارجاع داد، اما نه به‌عنوان استاندارد منتشرشده.

مهم‌ترین تغییرات پیشنهادی OAuth 2.1 نسبت به 2.0 که در عمل باید رعایت شوند:

  • PKCE برای همه‌ی کلاینت‌هایی که از جریان Authorization Code استفاده می‌کنند الزامی است — نه فقط کلاینت‌های عمومی و موبایل.
  • مقایسه‌ی redirect_uri باید تطبیق رشته‌ای دقیق باشد؛ درخواست‌هایی با آدرسی که دقیقاً با آدرس ثبت‌شده یکی نیست باید رد شوند (با استثنائات محدود برای Loopback).
  • جریان Implicit حذف شده است — RFC 9700 (سند Security BCP برای OAuth 2.0) آن را ناامن می‌داند.
  • جریان Resource Owner Password Credentials حذف شده است.
  • توکن Bearer نباید در Query String آدرس منتقل شود.
  • Refresh Token برای کلاینت‌های عمومی باید یا مقید به فرستنده باشد یا چرخشی.

PKCE و state: دو کنترل متفاوت که جای هم را نمی‌گیرند

این یکی از پرتکرارترین سوءتفاهم‌ها در بررسی معماری است:

کنترلچه چیزی را متوقف می‌کند
PKCE (RFC 7636)سرقت و تزریق کد مجوز. کلاینت یک code_verifier تصادفی می‌سازد و هش آن را در درخواست اولیه می‌فرستد؛ در گام تبادل، اصل مقدار را ارائه می‌دهد. کدِ دزدیده‌شده بدون code_verifier بی‌ارزش است.
stateCSRF روی خود جریان مجوزدهی. بدون آن، مهاجم می‌تواند پاسخ مجوزدهی حساب خودش را به مرورگر قربانی تحمیل کند و حساب قربانی را به حساب مهاجم پیوند بزند (Account Linking Takeover).

یعنی PKCE جایگزین state نیست و هر دو لازم‌اند. در OIDC یک کنترل سوم هم اضافه می‌شود: nonce، که جلوی بازپخش id_token را می‌گیرد و باید در سمت کلاینت با مقدار ارسالی مقایسه شود.

حالت‌های تنزل هم باید آزموده شوند: آیا سرور درخواستی بدون code_challenge را می‌پذیرد؟ آیا code_challenge_method=plain را قبول می‌کند؟ آیا state را واقعاً بررسی می‌کند یا فقط بازتاب می‌دهد؟

خطاهای پیکربندی رایج

  • اعتبارسنجی سهل‌گیرانه‌ی redirect_uri. کلاسیک‌ترین مسیر به سرقت کد مجوز. الگوهای معیوب: تطبیق پیشوندی، پذیرش زیردامنه با Wildcard، اجازه‌ی افزودن Query یا Fragment دلخواه، و پذیرش /../ در مسیر.
  • زنجیره‌ی Open Redirect. اگر یکی از آدرس‌های مجاز خودش یک تغییر مسیر باز داشته باشد، مهاجم کد را از طریق همان آدرس مجاز به دامنه‌ی خودش منتقل می‌کند. این تنها دلیلی است که برای رد کردن «Open Redirect کم‌اهمیت است» کافی است.
  • نشت کد از طریق Referer. اگر صفحه‌ی مقصد اسکریپت ثالث بارگذاری کند، کد مجوز در هدر Referer به بیرون می‌رود. همچنین آدرس کامل در تاریخچه‌ی مرورگر و لاگ‌های پروکسی می‌ماند.
  • نبود اعتبارسنجی id_token در OIDC: بررسی نشدن امضا، نبود بررسی iss/aud/exp/nonce، و پذیرش alg: none.
  • سردرگمی مخاطب توکن: توکنی که برای یک منبع صادر شده، توسط منبع دیگری پذیرفته می‌شود.
  • ارتقای دامنه‌ی دسترسی (Scope): سرور scope درخواستی را نادیده می‌گیرد، یا کلاینت دامنه‌ای به‌مراتب گسترده‌تر از نیازش می‌خواهد.
  • استفاده از جریان Implicit — در ۲۰۲۶ یک یافته‌ی خودکار است.
  • ثبت‌نام پویای کلاینت (Dynamic Client Registration) که باز رها شده و به مهاجم اجازه می‌دهد کلاینت خودش را با redirect_uri دلخواه ثبت کند.

سناریوی واقعی

یک سرویس با «ورود با حساب سازمانی» که به‌عنوان کلاینت محرمانه در سرور مجوزدهی ثبت شده است. تیم توسعه چون کلاینت را محرمانه می‌داند، PKCE را پیاده نکرده و اعتبارسنجی redirect_uri را با تطبیق پیشوندی انجام داده تا محیط‌های Staging هم کار کنند.

در تست، مقدار redirect_uri=https://app.example.com/cb/../../../redirect?to= پذیرفته می‌شود، چون رشته با پیشوند ثبت‌شده شروع می‌شود. مسیر /redirect روی همان دامنه یک تغییر مسیر باز دارد. نتیجه: کد مجوز به دامنه‌ی خارج از کنترل کلاینت منتقل می‌شود و چون PKCE وجود ندارد، مبادله‌ی کد با توکن دسترسی بدون هیچ راز اضافه‌ای ممکن است.

هر سه ضعف به‌تنهایی «متوسط» به‌نظر می‌رسیدند: تطبیق پیشوندی، تغییر مسیر باز، و نبود PKCE. زنجیره‌ی آن‌ها تصاحب حساب است. این دقیقاً دلیلی است که یافته‌های OAuth باید به‌صورت زنجیره‌ای ارزیابی شوند، نه تک‌تک.

چگونه در تست نفوذ کشف می‌شود

  1. ثبت کامل جریان در پروکسی: از کلیک روی «ورود با …» تا بازگشت نهایی. هر پارامتر درخواست مجوزدهی باید مستند شود.
  2. خواندن سند کشف: مسیر /.well-known/openid-configuration فهرست اندپوینت‌ها، الگوریتم‌های پشتیبانی‌شده و قابلیت‌های سرور را می‌دهد — از جمله اینکه آیا plain برای PKCE مجاز است.
  3. آزمون redirect_uri: افزودن مسیر، افزودن Query، تغییر زیردامنه، تغییر پورت، تغییر طرح به http، افزودن @ و کاراکترهای کدشده. هر پذیرشی که به دامنه‌ی خارج از کنترل کلاینت ختم شود، یافته است.
  4. حذف کنترل‌ها: state را حذف کنید و ببینید جریان کامل می‌شود یا نه؛ همین را برای code_challenge و nonce تکرار کنید.
  5. بازاستفاده و عمر کد: یک کد مجوز را دو بار مبادله کنید. کد باید یک‌بارمصرف باشد و بازاستفاده باید کل توکن‌های صادرشده را باطل کند.
  6. آزمون id_token: همه‌ی حملات JWT روی آن قابل اجراست.

این آزمون‌ها بخش استانداردی از تست نفوذ برنامه‌ی وب هستند و با اسکنر خودکار پوشش داده نمی‌شوند، چون ابزار نمی‌داند کدام redirect_uri مجاز است.

نمونه‌ی فنی و نگاشت به استانداردها

یک درخواست مجوزدهی درست، همه‌ی کنترل‌ها را با هم دارد:

GET /authorize?response_type=code
  &client_id=app123
  &redirect_uri=https://app.example.com/cb
  &scope=openid%20profile
  &state=<random>&nonce=<random>
  &code_challenge=<S256-hash>
  &code_challenge_method=S256

نبود هر یک از state، nonce یا code_challenge — و نیز مقدار plain برای روش چالش — در گزارش به‌عنوان یافته ثبت می‌شود.

نگاشت به استانداردها

  • A07:2025 — Authentication Failures برای نقص‌های احراز هویت و نشست؛ A01:2025 — Broken Access Control وقتی نتیجه، دسترسی به داده‌ی کاربر دیگر باشد؛ A02:2025 — Security Misconfiguration برای پیکربندی سهل‌گیرانه‌ی سرور مجوزدهی.
  • اسناد مرجع: RFC 6749 (OAuth 2.0)، RFC 7636 (PKCE)، RFC 9700 (Security BCP)، و پیش‌نویس draft-ietf-oauth-v2-1. برای برنامه‌های تک‌صفحه‌ای، پیش‌نویس draft-ietf-oauth-browser-based-apps مرجع اختصاصی است.

پرسش‌های متداول

آیا OAuth 2.1 منتشر شده است؟

خیر. OAuth 2.1 هنوز یک Internet-Draft فعال گروه کاری IETF است؛ نسخه‌ی جاری draft-ietf-oauth-v2-1-15 با تاریخ ۲ مارس ۲۰۲۶ است و هنوز وارد فرایند IESG نشده. توصیه‌های آن ارزش عملی دارند، اما نباید آن را «استاندارد منتشرشده» خطاب کرد.

آیا PKCE فقط برای اپلیکیشن موبایل لازم است؟

خیر. این یک باور قدیمی است. در OAuth 2.1 استفاده از PKCE برای همه‌ی کلاینت‌هایی که از جریان Authorization Code استفاده می‌کنند الزامی است — از جمله کلاینت‌های محرمانه‌ی سمت سرور. دلیلش این است که PKCE مسیر تزریق و سرقت کد مجوز را می‌بندد، و این مسیر مختص موبایل نیست.

اگر PKCE داریم، آیا هنوز به state نیاز است؟

بله. این دو مسئله‌ی متفاوتی را حل می‌کنند: PKCE از تزریق و سرقت کد جلوگیری می‌کند، و state از CSRF روی خود جریان مجوزدهی — یعنی حمله‌ای که حساب قربانی را به حساب مهاجم پیوند می‌زند. هر دو لازم‌اند و هیچ‌کدام جای دیگری را نمی‌گیرد.

چرا جریان Implicit دیگر توصیه نمی‌شود؟

چون توکن دسترسی را در قطعه‌ی URL به مرورگر برمی‌گرداند؛ در نتیجه توکن در تاریخچه‌ی مرورگر، لاگ‌ها و هدر Referer قابل نشت است و امکان مقیدسازی آن به کلاینت وجود ندارد. RFC 9700 آن را ناامن می‌داند و پیش‌نویس OAuth 2.1 حذفش کرده است. جایگزین درست، Authorization Code به‌همراه PKCE است.

پیشگیری / رفع

به ترتیب اثربخشی:

  1. تطبیق رشته‌ای دقیق برای redirect_uri. هیچ تطبیق پیشوندی، هیچ Wildcard، هیچ افزودن Query یا Fragment. این تنها کنترلی است که کل خانواده‌ی سرقت کد را می‌بندد.
  2. PKCE با روش S256 برای همه‌ی کلاینت‌ها، و رد کردن درخواست‌های بدون code_challenge یا با روش plain.
  3. state تصادفی و مقید به نشست که در بازگشت راستی‌آزمایی شود؛ در OIDC، nonce را هم به همین شکل بررسی کنید. این‌ها مکمل PKCE‌اند، نه جایگزین آن.
  4. جریان Authorization Code و حذف Implicit و Password Grant.
  5. اعتبارسنجی کامل id_token: امضا، iss، aud، exp، nonce — و فهرست سفید الگوریتم در سمت کلاینت.
  6. کد مجوز یک‌بارمصرف و کوتاه‌عمر؛ بازاستفاده باید به ابطال توکن‌های مرتبط منجر شود.
  7. حذف هر تغییر مسیر باز روی دامنه‌های ثبت‌شده به‌عنوان redirect_uri، و بستن ثبت‌نام پویای کلاینت اگر لازم نیست.
  8. اصل کمترین دسترسی در scope و رد درخواست‌های خارج از دامنه‌ی ثبت‌شده در سمت سرور.

بازبینی این جریان پیش از انتشار به‌مراتب ارزان‌تر از رفع پس از حادثه است؛ مشاوره‌ی امنیتی و تست نفوذ وب هر دو این مسیر را پوشش می‌دهند.

→ بازگشت به دانشنامه
PUT IT TO THE TEST

امنیت سامانه‌ی شما را
به مهاجمان واگذار نکنید

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