نگاره‌هایی پیرامون امنیت، شبکه و رمزنگاری

20 سپتامبر 2024

من مهدی مرادلو هستم و  در این گزارش قصد دارم به بررسی یک آسیب‌پذیری در بخش تأیید رسید پرداخت اسنپ‌باکس بپردازم. این آسیب‌پذیری به مهاجم اجازه می‌داد تا از یک رسید دیجیتال بیش از یک‌بار استفاده کند. ابتدا نیاز هست فرآیند پرداخت در PSP (سرویس دهنده ای پرداخت) رو باهم بررسی کنیم.

ایجاد فاکتور

پس از اینکه شما روی گزینه “پرداخت” برای یک سفارش کلیک می‌کنید، سامانه ابتدا یک فاکتور برای پرداخت شما ایجاد می‌کند. (بسته به سامانه ممکنه متفاوت باشه ولی عموما به همین شکل پیاده سازی میشه)

ارسال اطلاعات به بانک: سامانه سپس اطلاعات مربوط به سفارش شما را به بانک ارسال می‌کند.

هدایت به درگاه بانکی: پس از دریافت توکن از بانک، کاربر به درگاه بانکی هدایت می‌شود.

نمایش اطلاعات پرداخت:

درگاه بانکی توکن دریافت شده را بررسی کرده و بر اساس آن، اطلاعات پرداخت را نمایش می‌دهد.

ورود اطلاعات کارت بانکی:

در این مرحله، شما اطلاعات کارت بانکی خود را وارد می‌کنید.

انجام پرداخت:

پس از وارد کردن اطلاعات کارت بانکی و تایید آن، پرداخت از طریق درگاه بانکی انجام می‌شود.

ارسال اطلاعات پرداخت به پذیرنده:

پس از تکمیل پرداخت، درگاه بانکی اطلاعات مربوط به پرداخت را از طریق مرورگر شما به پذیرنده ارسال می‌کند.

تایید پرداخت و ارائه خدمات:

در نهایت، پذیرنده پرداخت شما را تایید می‌کند(بعد از استعلام اطلاعات پرداخت شما از بانک) و خدمات مربوطه را به شما ارائه می‌دهد.

در صورتی که پس از هدایت به درگاه بانکی گزینه انصراف از خرید را انتخاب کنید، همچنان اطلاعاتی به سمت پذیرنده ارسال می‌شود تا وضعیت فاکتور مشخص شود.

قبل از ورود به مباحث اصلی ابتدا دو نمونه درخواستی که پس از پرداخت و انصراف از پرداخت به سمت پذیرنده ارسال میشه رو بررسی میکنیم.

نمونه درخواست ارسالی توسط پذیرنده به سمت PSP برای دیافت توکن و هدایت کاربر به درگاه بانکی

POST /onlinepg/onlinepg HTTP/2
Host: smaple.com
User-Agent: Mozilla/5.0 
Accept: accept application/json

{ "action":"token", "TerminalId":"0000", "Amount":12000, "ResNum":"1qaz@WSX", "RedirectUrl":"http://mysite.com/receipt", "CellNumber":"9120000000" }

در پاسخ این درخواست بانک یک توکن باز می گرداند

{ 
  "status":1, 
  "token":"2c3c1fefac5a48geb9f9be7e445dd9b2" 
}

حالا پذیرنده کاربر را همراه با توکن به درگاه هدایت می کند برای مثال کاربر به آدرس زیر هدایت می شود:

https://sample.com/pay?token=2c3c1fefac5a48geb9f9be7e445dd9b2

وقتی کاربر وارد درگاه بانکی می‌شود، چند حالت ممکن است رخ دهد:

کاربر پرداخت را با موفقیت انجام می‌دهد

کاربر به هر دلیلی، از جمله تغییر نظر یا مواجهه با مشکل، گزینه انصراف از خرید را انتخاب می‌کند

نمونه یک درخواست که پس از پرداخت موفقیت آمیز به سمت پذیرنده ارسال میشه

POST /v1/transaction/verify HTTP/2
Host: wallet.snapp-box.com
User-Agent: Mozilla/5.0 
Accept: text/html,applicationxhtml+xml

MID=10920870&TerminalId=10920870&RefNum=GDASGDDSADFDASGDSAGASDGASDGS&ResNum=545512&State=success&Amount=100000

بسته به PSP ممکنه این درخواست و اسم پارامتر ها متفاوت باشه ولی در کل ساختار و منطق یکسانی دارند در این درخواست پارامتر ResNum همان شماره فاکتوری است که در سمت پذیرنده معتبر است و Refnum رسید دیجیتال بانکی است که   PSP تولید می کند تا پذیرنده با دریافت و استعلامش بتونه  اطلاعات پرداخت رو دریافت و درصورت صحت تائید کنه .

در سیستم‌های پرداخت ، TerminalId شناسه ترمینال است که به هر درگاه پرداخت اختصاص داده می‌شود و MID شناسه پذیرنده (Merchant ID) است که به پذیرنده مربوط می‌شود.

تایید پرداخت توسط پذیرنده

پذیرنده پس از دریافت درخواست بالا وضعیت تراکنش را تشخیص می دهد و آن را چک می نماید:

 از روی فیلد وضعیت تراکنشSTATE، فروشنده می تواند متوجه شود که آیا پرداخت موفقیت آمیز بوده است یا خیر اگر خرید موفقیت آمیز نبود فروشنده موظف است خطای به وجود آمده را با توجه به فیلد وضعیت تراکنش برای خریدار  علت خطا را  شرح دهد.  اگر وضعیت تراکنش  OK  بود ،فروشنده رسید دیجیتالی RefNum را در پایگا ه داده خود جستجو می کند. این کار به منظور جلوگیری از  Double Spending  یا دوبار مصرف شدن یک رسید دیجیتالی است پس از طی این مراحل پذیرنده اطلاعات ارسال کاربر را به سمت PSP ارسال می کند و سپس با دریافت پاسخ و تطابق اطلاعات پرداخت با سفارش کاربر ،پرداخت را تائید یا رد می کند.

نمونه درخواستی که پذیرنده برای استعلام تراکنش به PSP ارسال می کند

POST /ipg/VerifyTransaction HTTP/2
Host: smaple.com
User-Agent: Mozilla/5.0 
Accept: accept application/json

{ "RefNum": "jJnBmy/IojtTemplUH5ke9ULCGtDtb", "TerminalNumber": 2015 }

نمونه پاسخی که PSP برای درخواست بالا بازمی گرداند

{
  "RRN": "14226761817" 
  "RefNum": "50" 
  "MaskedPan": "621986****8080" 
  "TerminalNumber": 2001 
  "OrginalAmount": 1000 
  "Success": true
  "StraceNo": "100428"
} 

حالا پذیرنده این اطلاعات رو دریافت می کنه عموما سامانه ها به دو روش پس از دریافت این اطلاعات پرداخت رو تایید می کنند

روش اول: اگر مقدار پارامتر Success برابر با true باشد، سامانه رسید دیجیتال را در پایگاه داده ذخیره کرده و پرداخت را تأیید می‌کند.

روش دوم: در این روش، سامانه تنها به مقدار Success اکتفا نمی‌کند. پیش از تأیید پرداخت، سامانه بررسی می‌کند که اطلاعات فاکتور کاربر با داده‌های پرداخت بازگشتی از بانک (مانند شماره فاکتور، مبلغ و سایر پارامترهای کلیدی) تطابق داشته باشد. این روش ایمن‌تر است و از احتمال خطا یا سوءاستفاده جلوگیری می‌کند.

نکته‌ای که در اینجا وجود دارد این است که بیشتر PSPها با اینکه هنگام ایجاد توکن، شماره فاکتور را دریافت می‌کنند، اما هنگام پاسخ به استعلام پذیرنده، این مقدار را از پذیرنده  دریافت نمی کنند . در این فرآیند، فقط شماره پذیرنده و رسید دیجیتال پرداخت دریافت و پاسخ به همین اطلاعات بازگردانده می‌شود. این موضوع به خودی خود مشکل خاصی ایجاد نمی‌کند، اما اگر PSPها علاوه بر رسید دیجیتال، شماره فاکتور را هم دریافت و بررسی می‌کردند، خطاهای توسعه‌دهندگان به‌طور چشم‌گیری کاهش می‌یافت. از بین PSPها، تنها دو مورد (سداد و به‌پرداخت) این نکته را رعایت کرده‌اند که جای تشکر دارد.

نمونه یک درخواست انصراف از خرید

POST /v1/transaction/verify HTTP/2
Host: test.com

TOKEN=sF2rDSF#fp#FSD$kfdsGDFA#RDSFSDFSDF&MID=10920870&TerminalId=10920870&RefNum=&ResNum=4212412&State=CanceledByUser

زمانی که کاربر پس از هدایت به درگاه بانکی گزینه انصراف از خرید را انتخاب می‌کند، درخواستی مشابه به این برای پذیرنده ارسال می‌شود. هر سامانه ممکن است به شیوه‌ای متفاوت با این درخواست برخورد کند و تصمیم‌گیری در مورد وضعیت فاکتور یا تراکنش را انجام دهد. نحوه برخورد سامانه با این درخواست می‌تواند تأثیر مستقیمی بر امنیت و صحت پردازش تراکنش‌ها داشته باشد.

ایجاد اختلال در سرویس پرداخت  و جلوگیری از انجام تراکنش ها

این آسیب‌پذیری اولین موردی بود که در قسمت تایید پرداخت اسنپ‌باکس تست کردم. یکی از اشتباهات رایج در پیاده‌سازی سرویس‌های پرداخت این است که بدون انجام اعتبارسنجی صحیح، وضعیت یک فاکتور را ثبت می‌کنند. این موضوع در برخی موارد می‌تواند سامانه را با بحران مواجه کند. یکی از بحران‌های مهم، جلوگیری از انجام تراکنش‌های موفق در یک سامانه است. برای مثال، تصور کنید هیچ‌کس نتواند در اسنپ تراکنش موفقی داشته باشد یا پرداخت خود را تکمیل کند. اما چگونه چنین مشکلی ممکن است رخ دهد؟

مشکلی که در پیاده‌سازی برخی از سامانه‌ها وجود دارد این است که هنگامی که درخواست انصراف از خرید برای یک فاکتور دریافت می‌شود، سایر پارامترها نادیده گرفته می‌شوند و هیچ اعتبارسنجی روی پارامترهای دریافتی انجام نمی‌شود. در واقع، تنها شماره فاکتور دریافت شده و وضعیت انصراف از خرید برای آن ثبت می‌شود. حالا مشکل کجاست؟

مشکل در نوع برخورد سامانه با درخواست انصراف از خرید است ، برخی از سامانه ها پس از دریافت درخواست انصراف از خرید اعتبارسنجی صحیحی بروی اطلاعات دریافتی انجام نمی دهند و این مسئله باعث ایجاد مشکلات بحرانی می شود سامانه را در نظر بگیرید که وقتی درخواست انصراف از پرداخت را دریافت می کند بدون اعتبارسنجی فاکتور پرداخت را لغو می کند و بعد از ثبت این وضعیت نیز امکان تغییر وضیعت آن وجود ندارد .

مشکل این است که این رویکرد می‌تواند به مهاجمان این امکان را بدهد که با ارسال درخواست برای  یک شماره فاکتور (که کاربر آن در درگاه بانکی در حال انحام عملیات پرداخت است) سبب لغو آن فاکتور می شوند و وضعیت آن نیز تغییر نمی کند حالا کاربر پرداخت را انجام داده وقتی اطلاعات به پذیرنده ارسال می شود چون قبلا برای این فاکتور ثبت وضعیت شده پذیرنده پرداخت را رد کرده و پرداخت ثبت نمی شود.عموماً شناسه فاکتورها از نوع اینتجر (Integer) هستند و با هر فاکتور جدید یک واحد افزایش پیدا می‌کنند.در واقع از ویژگی، Auto Increment ID  استفاده می شود)،در اینصورت مهاجم براحتی می تواند شناسه فاکتور سایر کاربران را داشته باشد و  کلیه پرداخت‌های بعدی را لغو کنند.

برای تست این مورد، ابتدا روی خرید یک محصول یا شارژ کیف پول کلیک کنید و تا مرحله پرداخت در درگاه بانکی پیش بروید. سپس کافی است روی گزینه انصراف از خرید کلیک کنید. زیرا مقدار پارامتر State برای انصراف از خرید در PSPهای مختلف متفاوت است، این روش بهتری است. حال که درخواست انصراف از خرید را به دست آورده‌اید، آن را به Repeater ارسال کنید.

در مرحله دوم، باید یک پرداخت موفق داشته باشید، اما قبل از اینکه اطلاعات به اندپوینت تأیید پرداخت ارسال شود، باید مقدار ResNum یا OrderID و … (بسته به PSP) را از درخواست موفق کپی کرده و در درخواست انصراف از خرید جایگذاری کنید. ابتدا درخواست انصراف را ارسال کنید، سپس درخواست پرداخت موفق را بدون تغییر ارسال کنید.

اگر پرداخت موفق ثبت شد، به این معناست که سامانه مشکلی ندارد؛ اما اگر پرداخت شما به‌صورت موفقیت‌آمیز ثبت نشد، سامانه آسیب‌پذیر است و می‌توان به‌صورت کلی عملکرد آن را مختل کرد. این مورد در اسنپ باکس وجود نداشت، اما در ادامه به یک آسیب‌پذیری رسیدیم که مربوط به پارامتر State بود.

آسیب‌پذیری تصاحب حساب کاربری

یکی از مواردی که نادیده گرفته می‌شود و به‌ندرت در تست کیس‌های درگاه پرداخت به آن پرداخته می‌شود، تست تصاحب حساب کاربری است. بسته به شرایط پیاده‌سازی، می‌توان سناریوهای مختلفی را اجرا کرد. در اینجا یکی از موارد خاصی که روی یکی از برنامه‌های باگ بانتی بررسی کردم و گزارش دادم را توضیح می‌دهم (مابقی سناریوها نیاز به یک رایت اپ جداگانه دارند).

موردی که می‌خواهم به آن اشاره کنم کمی غیرمتعارف است و در شرایط خاصی اتفاق می‌افتد. سناریو به این شکل بود که پس از پرداخت موفق فاکتور توسط کاربر، زمانی که اطلاعات پرداخت به callback پذیرنده ارسال می‌شد، اگر cookie کاربر در درخواست وجود نداشت یا منقضی شده بود، سامانه اقدام به بازگرداندن cookieهای حساب کاربری مرتبط با شماره فاکتور (Resnum) می‌کرد. (نکته‌ای که وجود داشت این است که باید پرداخت حتماً موفق می‌بود.)

تا اینجای کار مشکلی نبود، اما سامانه روی پارامتر Resnum اعتبارسنجی صحیحی انجام نداده بود.وبرای بهره بردای کافی بود یک پرداخت موفق انجام می‌دادید و سپس شماره فاکتور یک کاربر دیگر را در آن قرار می‌دادید تا به cookieهای حساب قربانی به‌صورت کور دسترسی پیدا کنید. البته مشکلات خاص خود را نیز داشت؛ برای هر تصاحب حساب کاربری باید یک پرداخت انجام می‌دادید، که خوش‌شانس بودم و قیمت کمترین محصولش 2000 تومان بود. 😊

برای اثبات آسیب‌پذیری، من یک حساب کاربری دیگر ایجاد کردم و این‌طور عمل کردم: یک فاکتور در حساب کاربری اول ایجاد کردم و پس از هدایت به درگاه بانکی و پرداخت آن، وقتی اطلاعات به callback پذیرنده ارسال می‌شد، از ارسال درخواست جلوگیری کردم و درخواست را به Repeater ارسال کردم.

سپس وارد حساب کاربری دوم شدم و دقیقاً مانند مرحله قبل روی خرید یک محصول کلیک کردم تا به درگاه بانکی برسم. سپس روی انصراف از خرید زدم. وقتی اطلاعات به callback پذیرنده ارسال می‌شد، مقدار Resnum را کپی کرده و درخواست را حذف کردم. حالا به درخواست ارسالی به Repeater مراجعه کرده و cookieها را از هدر درخواست پاک کردم و مقدار Resnum که کپی کرده بودم را جایگزین مقدار اصلی کردم و درخواست را ارسال کردم. در هدرهای پاسخ، مقدار cookieها بازگردانده شد که مربوط به حساب کاربری دوم بود. اما بعد از ارسال گزارش، این پیام را دریافت کردم که چالش جدیدی ایجاد شد.

البته همه موارد ذکر شده صحیح نبود و تنها کافی بود که مقدار مبلغ Resnum و رسید دیجیتال پرداخت یکسان باشند و مهم نبود شماره فاکتور برای پرداخت به کدام درگاه هدایت شده است (مثلاً شماره فاکتوری که برای پرداخت به بانک ملت هدایت شده، می‌توانست با اندپوینت مربوط به تأیید پرداخت بانک سامان تأیید شود، به شرطی که رسید دیجیتال پرداختی از بانک سامان که مصرف نشده باشد، موجود باشد). از طرفی، اگر رسید دیجیتال با مبلغ فاکتور تطابق نداشت، پرداخت انجام نمی‌شد و cookieها بازگردانده نمی‌شدند. نکته‌ای که کار را دشوارتر می‌کرد این بود که حتی اگر پرداخت موفق هم نبود، رسید دیجیتال در پایگاه داده ذخیره می‌شد و امکان استفاده مجدد از آن وجود نداشت. همچنین، بازه قیمتی محصولات بسیار متفاوت بود و از دو هزار تومان تا چند میلیون تومان متغیر بود که کار را سخت‌تر می‌کرد.

همین‌طور که به حل چالش فکر می‌کردم، به راه حل مناسبی رسیدم. به سامانه رفتم و پرفروش‌ترین محصولش را انتخاب کردم و به‌طور جداگانه پنج بار خرید انجام دادم، اما بعد از هر خرید از ارسال درخواست به سمت سامانه جلوگیری می‌کردم. حالا پنج رسید دیجیتال داشتم که مبلغ آن‌ها با بیشتر فاکتورها تطابق داشت. دلیل ایجاد پنج رسید دیجیتال این بود که اگر اولی روی یک شماره فاکتور موفق نبود، شماره فاکتور را افزایش می‌دادم و رسید بعدی را جایگزین می‌کردم. با توجه به پرفروش بودن محصول، حدس می‌زدم از هر سه فاکتور، یکی از آن‌ها با این مبلغ تطابق داشته باشد. در نهایت، روی چهارمین درخواست، فاکتور یک کاربر دیگر پرداخت شد و cookieهای او را به‌دست آوردم و تصاحب حساب کاربری انجام شد.

با توجه به اینکه این مورد هزینه‌بر بود و نیاز به ایجاد شرایط خاصی داشت، شدت آسیب‌پذیری (Severity) گزارش پایین آمد و گزارش به حالت Medium بسته شد. اما ممکن است شما با سامانه‌ای روبه‌رو شوید که در پرداخت‌های ناموفق هم cookieها را بازگرداند. در این حالت، می‌توانید به تصاحب حساب کاربری گسترده دست بزنید که قطعاً بانتی خوبی هم به همراه خواهد داشت. لازم به ذکر است که این مورد هم روی اسنپ باکس وجود نداشت.

آسیب‌پذیری Double-spending

یکی از مواردی که خیلی کم پیش می‌آید، این است که سامانه رسید دیجیتال را در پایگاه داده ذخیره نکند. برای تست این آسیب‌پذیری، کافی است که یک پرداخت موفق داشته باشید و وضعیت آن ثبت شود. سپس پس از تأیید و تکمیل پرداخت، درخواست ارسالی به اندپوینت تأیید پرداخت را به Repeater ارسال کنید و یک ResNum که مبلغ آن با رسید دیجیتال برابر است را جایگزین کنید و درخواست را ارسال کنید. اگر سامانه رسید دیجیتال را ذخیره نکند، فاکتور دوم هم پرداخت می‌شود و… این مورد را هم سامانه اسنپ باکس رعایت کرده بود.

اما امکان ارسال درخواست انصراف خرید برای یک فاکتور پرداخت شده در سامانه وجود داشت که در نهایت سامانه را مجبور می‌کرد Refnum را از پایگاه داده حذف کند. این عمل باعث می‌شد که یک رسید دیجیتال بیشتر از یک‌بار مصرف شود. مراحلی که باید طی می‌شد به این ترتیب  بود:

1-ابتدا به قسمت کیف پول می‌رفتم و روی گزینه شارژ کیف پول کلیک می‌کردم. سپس مبلغ مورد نظر را وارد کرده و به درگاه بانکی هدایت می‌شدم تا پرداخت را انجام دهم و ترافیک را در Burp دریافت می‌کردم. بعد از اینکه درخواست به callback پذیرنده (اسنپ باکس) ارسال می‌شد و پرداخت تأیید می‌شد، درخواست را به Repeater ارسال می‌کردم.

POST /v1/transaction/verify HTTP/2
Host: wallet.snapp-box.com
User-Agent: Mozilla/5.0 
Accept: text/html,applicationxhtml+xml
MID=10920870&TerminalId=10920870&RefNum=GDASGDDSADFDASGDSAGASDGASDGS&ResNum=545512&State=success&Amount=100000

2-حالا مقدار RefNum رو پاک میکردم و State رو هم برابر با CanceledByUser قرار میدادم و درخواست رو ارسال میکردم

POST /v1/transaction/verify HTTP/2
Host: wallet.snapp-box.com
User-Agent: Mozilla/5.0 
Accept: text/html,application/xhtml+xml
MID=10920870&TerminalId=10920870&RefNum=&ResNum=545512&State= CanceledByUser&Amount=100000

وقتی این درخواست ارسال می‌شد، چون وضعیت انصراف از خرید بود، سامانه دیگر اعتبارسنجی نمی‌کرد که آیا وضعیت این فاکتور قبلاً ثبت شده یا نه. در نتیجه، رکورد مربوط به این فاکتور را در پایگاه داده آپدیت می‌کرد. از آنجایی که پارامتر RefNum که که من در این درخواست ارسال کردم، NULL بود، RefNum از پایگاه داده حذف می‌شد.

3 – حالا در مرحله سوم، من دقیقاً همان درخواست اولی را بدون تغییر ارسال می‌کردم و اتفاقی که می‌افتاد این بود که پرداخت مجدداً تأیید می‌شد و پول به کیف پول اضافه می‌شد. این فرآیند را می‌توانستم تا نیم ساعت انجام دهم و هر چند بار که می‌خواستم تکرار کنم. (محدودیت نیم‌ساعته نیز به خاطر محدودیت بانک بود که فقط تا نیم ساعت بعد از پرداخت، استعلام رسید دیجیتال را می‌داد تا پذیرنده پرداخت را تأیید کند.)

امیدوارم که از خوندن این رایت‌آپ لذت برده باشید.و نکته اخر اینکه برای ارسال گزارش آسیب‌پذیری بروی مجموعه اسنپ‌می توانید به این لینک زیر مراجعه کنید.

1 پست نوشته شده
  • به اشتراک بگذارید:
  1. عباس گفت:

    عالی بود دمت گرم♥️

  2. aghayeoji گفت:

    ساغول قارداش 🙂

  3. Golzari گفت:

    Great ❤️

  4. debug گفت:

    عالیییی