بیشتر آسیبپذیریهایی که به یک فروشگاه اینترنتی پول تحمیل میکنند، تزریق SQL یا XSS نیستند. آنها باگهای منطق کسبوکار هستند: هر درخواست بهتنهایی کاملاً قانونی است، اما ترتیب یا مقدارش چیزی را ممکن میکند که هرگز قرار نبوده ممکن باشد — خرید با قیمت صفر، استفادهی چندباره از یک کد تخفیف، یا ثبت سفارش موفق بدون پرداخت واقعی. هیچ اسکنری اینها را پیدا نمیکند و همین است که امنیت سایت فروشگاهی را به یک کار دستی و درکمحور تبدیل میکند. این مقاله الگوهای واقعی را با تمرکز بر بازار ایران — و مخصوصاً مسئلهی تأیید کالبک درگاه پرداخت — توضیح میدهد.
در یک نگاه
- خطرناکترین الگو در بازار ایران: تأیید نادرست کالبک درگاه پرداخت. نتیجهی پرداخت باید سرور به سرور از PSP وریفای شود، هرگز از کلاینت پذیرفته نشود.
- قیمت، تخفیف، تعداد و هزینهی ارسال باید در سمت سرور محاسبه شوند؛ هر مقداری که از فرم میآید یک پیشنهاد است، نه یک واقعیت.
- شرایط رقابتی روی موجودی، کیف پول و کد تخفیف با حملهی تکبستهای (single-packet) روی HTTP/2 عملی است — حتی از فاصلهی جغرافیایی زیاد.
- IDOR روی سفارش و فاکتور رایجترین یافتهی افشای داده در فروشگاههاست و اسکنرها آن را نمیبینند.
- کاهش دامنهی PCI DSS با عدم لمس دادهی کارت، از هر کنترل امنیتی دیگری ارزانتر و مؤثرتر است.
چرا امنیت فروشگاه با امنیت یک سایت معمولی متفاوت است
در یک سایت محتوایی، بدترین اتفاق افشای داده یا دیفیس است. در یک فروشگاه، مهاجم میتواند مستقیماً پول شما را ببرد — و بدون شکستن هیچ کنترل امنیتی کلاسیکی. سه ویژگی این تفاوت را میسازد:
- جریانهای چندمرحلهای با وضعیت. سبد، حملونقل، تخفیف، پرداخت و تأیید یک ماشین حالت است؛ هرجا وضعیت هست، امکان رسیدن به وضعیت غیرمجاز هم هست.
- یکپارچگی با سرویسهای بیرونی. هر یکپارچهسازی یک مرز اعتماد است، و مرز اعتمادِ بدون راستیآزمایی یعنی آسیبپذیری.
OWASP این دسته را در A06:2025 طراحی ناامن قرار میدهد و جملهی کلیدیاش اینجا کاملاً مصداق دارد: «یک طراحی ناامن را هیچ پیادهسازی بینقصی نمیتواند اصلاح کند.» اگر جریان پرداخت شما از پایه به دادهی کلاینت اعتماد کند، هیچ مقدار اعتبارسنجی ورودی آن را نجات نمیدهد.
«اسکن آسیبپذیری زدیم، خروجی پاک بود، پس فروشگاه امن است.» اسکنرها بر پایهی الگوهای شناختهشده کار میکنند و همهی موارد این مقاله را از دست میدهند — چون از دید ابزار، درخواستی که قیمت را 1000 اعلام میکند از درخواستی که 1 اعلام میکند قابل تمییز نیست. هیچکدام «مخرب» به نظر نمیرسند. تفاوت اسکن و تست نفوذ واقعی دقیقاً همینجا خودش را نشان میدهد.
دستکاری قیمت، تعداد و مقادیر محاسباتی
سادهترین و همچنان یکی از رایجترین کلاسها. ریشهی همهی حالتها یک چیز است: مقداری که باید در سرور محاسبه شود، از کلاینت پذیرفته میشود.
الگوهای عملی
- قیمت در فیلد مخفی فرم. اگر
priceدر بدنهی درخواست افزودن به سبد وجود دارد و سرور آن را استفاده میکند، آسیبپذیری قطعی است. - تعداد منفی.
quantity=-1در سبدی که مجموع را با ضرب محاسبه میکند، میتواند مجموع را کاهش دهد یا حتی منفی کند. حالتهای مشابه: عدد اعشاری، عدد بسیار بزرگ که سرریز میشود، و مقدار غیرعددی. - دستکاری مبلغ در مرحلهی انتقال به درگاه. اگر مبلغ نهایی از پارامتر درخواست ساخته شود و در سمت سرور با مجموع سبد مقایسه نشود، مهاجم مبلغ کمتری به درگاه میفرستد.
- گردکردن و اعشار. اختلاف گردکردن بین مراحل محاسبهی تخفیف میتواند به سود مهاجم انباشته شود.
POST /cart/add
{ "product_id": 4471, "quantity": 1, "price": 2450000 }
// هر فیلدی جز شناسه و تعداد باید در سرور بازمحاسبه شوداصلاح
- از کلاینت فقط شناسه و تعداد را بپذیرید. قیمت، تخفیف، مالیات و هزینهی ارسال را همیشه از منبع معتبر سمت سرور بخوانید.
- بررسی معقولبودن در لایهی نهایی: پیش از ثبت سفارش، مجموع را مستقل بازمحاسبه کنید و با مبلغ پرداختشده مقایسه کنید. این «بررسی پایانی» بسیاری از حالتهای ناشناخته را هم میگیرد.
- سفارشهای با مبلغ غیرعادی (خیلی کم، منفی، یا بسیار بزرگ) را ثبت لاگ و هشدار کنید — این همان کاربرد عملی A09:2025 است.
سوءاستفاده از کد تخفیف: انباشتگی و استفادهی مجدد
کدهای تخفیف قواعد کسبوکاری پیچیدهای دارند و هر قاعدهی نانوشته یک آسیبپذیری است. الگوهای واقعی:
- انباشتگی (Stacking): ارسال چند کد تخفیف در یک درخواست، یا ارسال متوالی چند کد که هر بار روی مبلغ کاهشیافتهی قبلی اعمال میشود. اگر منطق «فقط یک کد» را در UI پیاده کردهاید و نه در سرور، این کار میکند.
- استفادهی مجدد از کد یکبارمصرف: با درخواستهای موازی (بخش شرایط رقابتی)، یا با اعمال کد سپس حذف محصول و افزودن مجدد، یا با ثبت سفارشهای موازی از یک حساب.
- حذف نکردن تخفیف پس از تغییر سبد: کد روی سبد پر اعمال میشود، اقلام حذف میشوند و مبلغ تخفیف باقی میماند — گاهی بیشتر از مبلغ سبد.
- حدس زدن کدها. کدهای الگودار (
OFF10) قابل فهرستسازیاند؛ اندپوینت اعتبارسنجی بدون نرخبندی یک اوراکل حدس است.
اصلاح
- قواعد تخفیف را صریح و در سمت سرور مدل کنید: سازگاری کدها، سقف تخفیف، و حداقل مبلغ سبد.
- مصرف کد را با محدودیت یکتایی در پایگاهداده ثبت کنید تا در شرایط موازی هم دو بار ثبت نشود.
- کدها را تصادفی تولید و اندپوینت اعتبارسنجی را نرخبندی کنید.
- سقف تخفیف را در لایهی پرداخت هم بررسی کنید؛ مبلغ نهایی هرگز نباید منفی شود.
پرش از مراحل خرید و دور زدن گردش کار
فرایند خرید یک ماشین حالت است: سبد → آدرس → روش ارسال → پرداخت → تأیید. اگر انتقالهای مجاز در سمت سرور اعمال نشوند، مهاجم میتواند مرحلهای را حذف یا وضعیتی را جعل کند.
الگوهای رایج
- فراخوانی مستقیم اندپوینت تأیید سفارش بدون طی مرحلهی پرداخت؛ اگر
order/completeوضعیت پرداخت را بررسی نکند، سفارش بدون پرداخت ثبت میشود. - تغییر سبد بین اعتبارسنجی پرداخت و ثبت سفارش: مبلغ برای سبد کوچک پرداخت میشود و بعد اقلام گران اضافه میشوند.
- استفادهی مجدد از یک شناسهی پرداخت موفق برای چند سفارش، وقتی شناسهی تراکنش یکبارمصرف نیست و به سفارش خاصی گره نخورده.
اصلاح
- وضعیت را در سرور نگه دارید و انتقالها را صریح اعمال کنید؛ هر اندپوینت باید بررسی کند سفارش در وضعیت مجاز است.
- انتقالهای غیرمجاز را رد و لاگ کنید؛ تلاش برای پرش از مرحله یک سیگنال حمله است، نه خطای کاربر.
- پس از شروع پرداخت، سبد را قفل کنید و شناسهی تراکنش را یکبارمصرف و گرهخورده به همان سفارش نگه دارید.
تأیید کالبک درگاه پرداخت — مخربترین الگو در بازار ایران
اگر از این مقاله فقط یک بخش را بخوانید، همین باشد. الگوی خطا در فروشگاههای ایرانی بهطور نگرانکنندهای تکرار میشود و اثرش مستقیماً مالی است.
جریان درست پرداخت
- سرور شما مبلغ را خودش محاسبه میکند و درخواست ایجاد تراکنش را به درگاه میفرستد؛ درگاه یک شناسه/توکن تراکنش برمیگرداند.
- کاربر به صفحهی درگاه هدایت میشود و پرداخت را انجام میدهد.
- درگاه کاربر را به آدرس بازگشت (callback) شما برمیگرداند، همراه با پارامترهایی دربارهی نتیجه.
- سرور شما یک درخواست مستقل و سرور به سرور به API درگاه میزند و تراکنش را وریفای میکند. فقط پاسخ این درخواست است که «پرداخت موفق» را اثبات میکند.
- سرور بررسی میکند مبلغ تأییدشده با مبلغ سفارش برابر است، تراکنش قبلاً استفاده نشده است، و شناسهی تراکنش به همین سفارش تعلق دارد.
- سپس و فقط سپس، سفارش را پرداختشده علامت میزند.
الگوهای خطا که دیده میشوند
- اعتماد به پارامترهای آدرس بازگشت. اگر کد شما با دیدن
?status=OKیاStatus=successدر URL بازگشت، سفارش را تأیید میکند، مهاجم میتواند خودش همان آدرس را با همان پارامترها باز کند. این کاملترین شکل «اعتماد به دادهی کلاینت» است. - وریفای نکردن مبلغ. تراکنش واقعاً موفق بوده، اما برای مبلغ بسیار کمتر؛ اگر مبلغ تأییدشده با مبلغ سفارش مقایسه نشود، مهاجم با هزار تومان سفارش چند میلیونی ثبت میکند.
- یکتا نبودن تراکنش: یک شناسهی موفق برای چند سفارش، چون رکورد مصرف با محدودیت یکتایی محافظت نشده.
- کالبک بدون احراز اصالت. اندپوینت اعلان پرداخت (IPN/webhook) بدون بررسی امضا و بدون وریفای متقابل، هر بدنهای را میپذیرد.
- شرایط رقابتی روی کالبک: دو درخواست همزمان، در نبود قفل یا محدودیت یکتایی، دو بار کیف پول را شارژ میکند.
هیچ پارامتری که از مرورگر کاربر عبور کرده باشد، هرگز نمیتواند مدرک پرداخت باشد — نه status، نه amount، نه امضایی که کلیدش در سمت کلاینت است. مدرک پرداخت فقط پاسخ درخواست وریفای سرور شما به سرور PSP است، و آن پاسخ باید هم موفقیت، هم مبلغ، هم یکتایی و هم تعلق به سفارش را تأیید کند. این چهار بررسی جدا از هم هستند و هر چهار لازماند.
در گزارشهای تست نفوذ، این کلاس یافته معمولاً بالاترین شدت را میگیرد چون بهرهبرداریاش ساده، تکرارپذیر و مستقیماً مالی است.
شرایط رقابتی روی موجودی، کیف پول و کد تخفیف
شرایط رقابتی در فروشگاهها یک مسئلهی واقعی و پرتکرار است، نه یک سناریوی آکادمیک. الگوی پایه: برنامه ابتدا شرطی را بررسی میکند و بعد عمل میکند، و بین این دو یک پنجرهی زمانی وجود دارد.
نقاط پرخطر
- موجودی انبار: ده درخواست موازی برای آخرین کالا؛ همه «۱ عدد موجود است» را میبینند و همه ثبت میشوند.
- کیف پول و اعتبار: دو برداشت موازی از موجودی ۱۰۰ هزار تومان، هر دو کامل انجام میشوند. همین الگو در بازگشت وجه هم هست.
- هر شمارندهای با الگوی بررسیسپسافزایش: امتیاز وفاداری، محدودیت خرید، شمارندهی تلاش.
چرا «از راه اینترنت عملی نیست» درست نیست
باور قدیمی این بود که برای بهرهبرداری از شرایط رقابتی باید به سرور نزدیک بود، چون نوسان شبکه پنجره را از بین میبرد. حملهی تکبستهای (single-packet attack) این فرض را باطل کرده: با مالتیپلکسینگ HTTP/2 میتوان چندین درخواست کامل را در یک بستهی TCP بستهبندی کرد تا همزمان برسند. پژوهش PortSwigger نشان داده با این روش حدود بیست تا سی درخواست در پنجرهای زیر یک میلیثانیه اجرا میشود — حتی در ارتباطات قارهای. در Burp Repeater با گروهبندی درخواستها و «Send group in parallel» انجام میشود.
اصلاح — و اشتباه رایج در اصلاح
- بهروزرسانی اتمی و شرطی در پایگاهداده بهترین راهحل است:
UPDATE wallets SET balance = balance - 50000
WHERE user_id = ? AND balance >= 50000;
-- سپس تعداد سطرهای تغییریافته را بررسی کنید؛ صفر یعنی موجودی کافی نبود- قفل سطری (
SELECT ... FOR UPDATE) یا تراکنش با سطح ایزولهسازی مناسب روی رکورد موجودی. - محدودیت یکتایی بهعنوان تور ایمنی برای مصرف کد تخفیف و شناسهی تراکنش، و کلید ایدمپوتنسی برای پرداخت و ثبت سفارش.
- اشتباه رایج: قفل در سطح برنامه (mutex). روی چند نمونهی افقی سرور کار نمیکند و رایجترین «اصلاحِ ناکارآمد» است؛ اتمیسازی در پایگاهداده یا قفل توزیعشده لازم است.
IDOR روی سفارش و فاکتور، و سوءاستفاده از بازگشت وجه
IDOR روی اندپوینتهای سفارش
IDOR رایجترین یافتهی افشای داده در فروشگاههاست. الگو: اندپوینتی که شناسهی سفارش را میگیرد و بررسی نمیکند این سفارش به کاربر درخواستکننده تعلق دارد.
- مشاهدهی سفارش و فاکتور (
/invoice/download?id=10432). دادهی افشاشده معمولاً کامل است: نام، آدرس، موبایل، اقلام و مبلغ. - API پنل کاربری که در نسخهی وب بررسی مالکیت دارد و در نسخهی موبایل ندارد.
- عملیات نوشتن: لغو یا تغییر آدرس سفارش دیگری.
و یک تذکر مهم: UUID این مشکل را حل نمیکند. شناسهی غیرقابلحدس هزینهی کشف را بالا میبرد اما بررسی مجوزی اضافه نمیکند، و UUIDها از پاسخ اندپوینتهای دیگر و از لینکهای اشتراکی نشت میکنند. اصلاح درست: مالکیت را در همان کوئری خواندن اعمال کنید (WHERE id = ? AND user_id = ?).
سوءاستفاده از بازگشت وجه و مرجوعی
فرایند بازگشت وجه، ترکیبی از منطق کسبوکار و پول است، پس هدف طبیعی است:
- بازگشت وجه برای سفارشی که قبلاً بازگشت وجه شده (نبود بررسی وضعیت).
- بازگشت وجه بیشتر از مبلغ پرداختشده، یا بازگشتهای جزئیِ چندباره که مجموعشان از کل بیشتر میشود.
- شرایط رقابتی روی خودِ بازگشت وجه: دو درخواست موازی، دو بار واریز.
اصلاح: هر عملیات مالی باید در برابر وضعیت فعلی سفارش اعتبارسنجی شود، مجموع بازگشتها سقف داشته باشد، عملیات ایدمپوتنت و در تراکنش پایگاهداده انجام شود، و هر رخداد مالی لاگ تغییرناپذیر داشته باشد.
دادهی کارت، دامنهی PCI DSS و جدول کنترلها
سیاست درست دربارهی دادهی کارت یک جمله است: به آن دست نزنید. هرچه دادهی کارت کمتر از سیستم شما بگذرد، دامنهی الزامات و ریسک کمتر است.
- هرگز شمارهی کارت، CVV یا تاریخ انقضا را ذخیره نکنید. در مدل رایج بازار ایران دادهی کارت فقط در صفحهی درگاه وارد میشود و به سرور شما نمیرسد؛ این مدل را با فرم واسط خودتان جایگزین نکنید.
- دادهی کارت را در لاگ ننویسید. این یکی از رایجترین راههای نشت است و در CWE-532 (درج اطلاعات حساس در فایل لاگ) دستهبندی میشود. لاگ کامل بدنهی درخواست در جریان پرداخت را غیرفعال کنید.
- PCI DSS نسخهی جاری v4.0.1 است (ژوئن ۲۰۲۴) و الزامات آیندهدار v4.x از ۳۱ مارس ۲۰۲۵ اجباری شدهاند. بند 11.4 تست نفوذ دورهای (حداکثر هر ۱۲ ماه و پس از تغییر مهم) و آزمون مجدد پس از رفع را الزامی میکند.
| ریسک | چرا اسکنر پیدا نمیکند | کنترل مؤثر |
|---|---|---|
| دستکاری قیمت و تعداد | درخواست از نظر ساختاری کاملاً معتبر است | محاسبهی سمت سرور + بازمحاسبهی نهایی پیش از ثبت |
| انباشتگی و استفادهی مجدد کد تخفیف | نیازمند درک قواعد کسبوکار است | مدلسازی صریح قواعد + محدودیت یکتایی مصرف |
| پرش از مراحل خرید | هر درخواست بهتنهایی مجاز است | ماشین حالت سمت سرور + قفل سبد پس از پرداخت |
| تأیید نادرست کالبک پرداخت | ابزار نمیداند کدام پارامتر «مدرک پرداخت» است | وریفای سرور به سرور + بررسی مبلغ + یکتایی تراکنش |
| شرایط رقابتی | نیازمند ارسال همزمان و مشاهدهی اثر انباشته | بهروزرسانی اتمی شرطی + قفل سطری + کلید ایدمپوتنسی |
| IDOR روی سفارش و فاکتور | ابزار نمیداند کدام سفارش به کدام کاربر تعلق دارد | اعمال مالکیت در کوئری داده + آزمون با دو حساب |
| سوءاستفاده از بازگشت وجه | جریان چندمرحلهای و وابسته به وضعیت است | اعتبارسنجی وضعیت + سقف مجموع + عملیات ایدمپوتنت |
| نشت دادهی کارت در لاگ | در لاگ سرور است، نه در پاسخ HTTP | پالایش لاگ + عدم لمس دادهی کارت (CWE-532) |
برنامهی آزمون: چه چیزی را چطور بررسی کنیم
همهی موارد این مقاله یک ویژگی مشترک دارند: آزمون دستی لازم است. برنامهی عملی:
- جریانها را نقشهبرداری کنید: یک خرید کامل انجام دهید و همهی درخواستها را با Burp Suite یا Caido ثبت کنید.
- هر پارامتر محاسباتی را دستکاری کنید و ترتیب مراحل را بشکنید: مرحلهی بعد را بدون مرحلهی قبل صدا بزنید و به مرحلهی قبل برگردید و داده را تغییر دهید.
- جریان پرداخت را با دقت مضاعف بررسی کنید: وریفای سرور به سرور، مقایسهی مبلغ، و یکبارمصرف بودن تراکنش.
- درخواستهای موازی بفرستید روی موجودی، کیف پول، کد تخفیف و بازگشت وجه.
- آزمون کنترل دسترسی با دو حساب: درخواستهای حساب اول را با نشست حساب دوم بازپخش کنید.
- مدلسازی تهدید پیش از توسعهی قابلیتهای مالی جدید — ارزانترین نقطهی مداخله.
تقریباً هیچ. فایروال برنامهی وب نمیداند مبلغ ۱۰۰۰ تومان برای این سفارش درست است یا نه، و نمیداند این کاربر حق دیدن این فاکتور را دارد یا نه. WAF در برابر اسکنهای خودکار و برای وصلهی موقت یک CVE مفید است؛ در برابر باگ منطق کسبوکار ساختاراً نابیناست. جزئیات در دانشنامهی WAF.
برای فروشگاههایی با گردش مالی واقعی، ارزیابی این کلاس آسیبپذیریها فقط با آزمون دستی ممکن است؛ در تست نفوذ وب پیهانتر منطق کسبوکار و جریان پرداخت اختصاصی پوشش داده میشود. برای شکل خروجی گزارش تست نفوذ و برای بودجه هزینهی تست نفوذ را ببینید.
پرسشهای متداول
امنیت فروشگاه اینترنتی از کجا باید شروع شود؟
از جریان پرداخت. سه سؤال را جواب دهید: آیا مبلغ در سمت سرور محاسبه میشود؟ آیا نتیجهی پرداخت با یک درخواست مستقل سرور به سرور از درگاه وریفای میشود؟ آیا مبلغ تأییدشده با مبلغ سفارش مقایسه و تراکنش یکبارمصرف میشود؟ اگر پاسخ هر سه بله نیست، این بالاترین اولویت شماست.
چرا اسکنر امنیتی باگهای فروشگاه من را پیدا نمیکند؟
چون این باگها الگوی حمله ندارند. درخواستی که price=1 میفرستد از نظر ساختاری با درخواست معتبر تفاوتی ندارد، و ابزار نمیداند کاربر شمارهی ۵ حق دیدن سفارش شمارهی ۷ را دارد یا نه. تشخیص این موارد نیازمند درک قواعد کسبوکار است و فقط با آزمون دستی بهدست میآید.
آیا استفاده از درگاه پرداخت معتبر کافی است؟
نه. درگاه معتبر دادهی کارت را از سیستم شما بیرون نگه میدارد که خیلی خوب است، اما مسئولیت وریفای تراکنش در سمت شما باقی میماند. اکثر آسیبپذیریهای پرداخت در بازار ایران در همان کد سمت فروشگاه است: پذیرش نتیجه از پارامتر URL، وریفای نکردن مبلغ، یا یکبارمصرف نبودن شناسهی تراکنش.
شرایط رقابتی در فروشگاه چطور اتفاق میافتد و چطور جلویش را بگیریم؟
وقتی برنامه ابتدا شرطی را بررسی میکند و بعد عمل میکند، بین این دو یک پنجره وجود دارد که با درخواستهای همزمان قابل بهرهبرداری است. راهحل، بهروزرسانی اتمی شرطی در پایگاهداده (UPDATE ... WHERE balance >= ?)، قفل سطری، محدودیت یکتایی و کلید ایدمپوتنسی است. قفل در سطح برنامه روی چند نمونهی سرور کار نمیکند و رایجترین اصلاح ناکارآمد است.
آیا باید اطلاعات کارت مشتریان را برای خرید بعدی ذخیره کنیم؟
خیر. ذخیرهی شمارهی کارت و مخصوصاً CVV نه لازم است و نه قابل دفاع؛ شما را وارد دامنهی الزامات سنگین PCI DSS میکند و در صورت رخنه، بدترین نوع داده را در معرض قرار میدهد. اجازه دهید دادهی کارت فقط در صفحهی درگاه وارد شود و هرگز از سیستم شما عبور نکند.
هر چند وقت یکبار فروشگاه اینترنتی باید تست نفوذ شود؟
قاعدهی عملی: حداقل سالی یکبار و علاوه بر آن پس از هر تغییر مهم در جریان پرداخت، سبد خرید، سیستم تخفیف یا احراز هویت. اگر در دامنهی PCI DSS هستید، بند 11.4 آزمون دورهای حداکثر هر ۱۲ ماه، آزمون پس از تغییر مهم، و آزمون مجدد پس از رفع یافتهها را الزامی میکند.
