دولت تصمیم میگیره ارز رو برای مدتی نامعلوم با قیمتی ثابت عرضه کنه؛ احتمالاً به این امید که قیمت بازار رو کنترل کنه. قیمت بازار، روز به روز بالا میره؛ و یک جایی مثل امروز، در حالی که دلار در بازار بالاتر از ۵۸ هزار تومنه، دلار دولتی حدود ۴۵ هزار تومن (با احتساب کارمزد) برای شما آب میخوره. با ضرب اختلاف این دو عدد در ۲۱۰۰ دلار که به هر فرد داده میشه، متوجه میشین میتونین [تقریباً] در یک چشم به هم زدن ۲۷ میلیون تومن کاسب بشین! مشخصه که همچین عددی هر آدم عاقلی رو وسوسه میکنه که هر طور شده ۹۵ میلیون تومن جور کنه تا از این حاتمبخشی دولت بینصیب نمونه.
حالا که این میزان تقاضا شکل گرفته، دولت یادش میافته که انقدر منابع ارزی نداره که به هر کسی درخواست کرد، اسکناس بده. پس با اعطای سهمیه به [برخی؟] صرافیها، ورودی رو کنترل میکنه و اینطوری یه رقابت شکل میگیره: رقابت برای دسترسی به سهمیه محدود صرافیها. به احتمال خیلی زیاد، دست من و شما که به این سهمیه نمیرسه (مگر اینکه شما آشنا داشته باشی 😉). امّا فکر اینجا رو هم کردن: با ارائه نوبت اینترنتی، عدالت [در دسترسی] برقرار میشه. کافیه به «سامانه نوبتدهی صرافی ملی» سر بزنید و نوبت بگیرید. برای افزایش عدالت دسترسی (یا شاید هم هیجان!) این بند هم اضافه میشه که: ساعت ۱۶ هر روز (و پنجشنبهها ۱۲)، نوبتهای روز کاری بعد فعال میشه.
شما و بقیه شهروندهای متمدن چند دقیقه قبل از ساعت اعلام شده وارد سایت میشین، شرایط و قوانین رو میپذیرید و به مرحله بعد میرید و منتظر وقت موعود میمونین تا روی دکمه «جستجوی نوبت» بزنین و...حالا مسابقه تبدیل میشه به ثبت نوبت توی سایت: چند صدهزار (شاید هم چند میلیون) درخواست رأس یک ساعت برای دستیابی به ۸۰-۹۰ نوبت. فرآیند به این شکل طراحی شده:
تمام این اطلاعاتی که از شما گرفته میشه رو وقتی تشریف میبرید صرافی باید تو یه فرم کاغذی پر کنین! نه فقط اطلاعات، که صرافی حتی به ساعتی که رزرو کردین هم کاری نداره و همونجا مثل بانک نوبت میده بهتون! با این تفاسیر، به نظر چرا این اطلاعات رو اینجا باید وارد کنین؟
امّا این حالت ایدهآل ماجراست. در واقعیت، یکی از این چند حالت اتفاق میافته:
در قدم ورود اطلاعات مشکلات دیگهای هم وجود داره و اون اینه که قواعد درستی برای ورودی تعریف نشده. برای نمونه، آدرس فقط میتونه شامل حرف باشه و مثلاً شما نمیتونی بنویسی «خیابان فردوسی، پلاک ۲۷۰» (چون ویرگول و عدد داره!). البته این دربرابر افتضاح کل سیستم که در ادامه مفصل بهش میپردازم، هیچ به حساب میاد.
چند روز به همین منوال میگذره و بعد، شما که آدم اهل فکر و حسابوکتابی هستی، با خودت میگی: بعیده آدمی سریعتر از این بتونه فرآیند رو طی کنه؛ پس چطوری نوبت گیرم نیومد؟ حتماً اینا رو دارن به آشناهاشون میدن... ولی دادن به آشنا که این همه فرآیند و سیستم و هزینه پیامک و... نیاز نداره! پس نکنه... بله حدستون درسته: شما با آدمهای دیگه رقابت نمیکنین. با کامپیوترهای دیگه (یا همون رباتها) دارین رقابت میکنین. امّا چطور ممکنه؟ مگه کپچا و «ربات نیستم» برای جلوگیری از همین نیست؟
بعد یه چرخ توی توییتر و تلگرام میزنین و میبینین بله، ربات هست براتون نوبت میگیره، ۳-۵ میلیون تومن. (یاد «ربات درست کردن با انسان مناظره میکنه» شاهین نجفی افتادم 😀) ممکنه قیمت زیادی به نظر بیاد، امّا وقتی یادتون بیاد که قراره ۲۷ میلیون تومن کاسب بشین، با خودتون میگین ۳ تومن بدم و ۲۴ تومن گیرم بیاد بهتر از اینه که هیچی ندم و هیچی گیرم نیاد. امّا چون مهندس خوبی هستین، با خودتون میگین: اگه یکی دیگه تونسته، حتماً من هم میتونم!
توجه! همین ابتدا بگم که الآن دیگه نوبتدهی از طریق پیامرسان بله انجام میشه و دیگه چیزی رو وبسایت وجود نداره. مواردی که در ادامه میبینین، کاربرد آموزش مفاهیم مهندسی نرمافزار و امنیت دارن. مسئولیت هرنوع استفاده از این موارد، متوجه خود شماست.
اولین موضوعی که باید ازش سر در بیارین، اینه که چطوری رباتها از سد CAPTCHA میگذرن؟ پس روز بعدی که میخواین پروسه رو طی کنین، Developer Tools مرورگر رو باز میکنین و درخواستهایی که به سرور فرستاده میشه رو نگاه میکنین. در کمال تعجب و ناباوری، پاسخ reCAPTCHA به سرور فرستاده نمیشه و صرفاً فعال/غیرفعال شدن دکمه فرم توی UI رو کنترل میکنه. پس شما میتونین بدون هیچ محدودیتی APIها رو فراخوانی کنین.
اصلاً شروع ماجراجویی من وقتی بود که دیدم امید توی توییتی به این موضوع اشاره کرده که ریکپچاها دکوره.
از اینجا به بعد دیگه به نسبت سرراسته: باید درخواستهایی که طی فرآیند به سرور فرستاده میشه رو پیدا کنین و بعد یه برنامه بنویسین که سر ساعت مورد نظر، فرآیند رو اجرا کنه. این کار رو دو جور میتونین انجام بدین:
درخواستها به این شرح هستن:
{'ID': 8704, 'TURNTIME': '14:00', 'CUSTOMER_ID': 9108, 'ISACTIVE': 'Y'}
با توجه به اینکه یک قسمت از فرآیند از موبایل میگذره، باید به یه نحوی بتونین پیامکها رو بخونین و مرحله بعد رو اجرا کنین (حالا یا همون سمت موبایل، یا با رسوندن پیام به کدی که دارین). این کار هم به نسبت سادهست:
در ابتدا با کمی دقت متوجه میشین شناسه نوبتها پشت سر هم هستن. اگر آخرین نوبت امروز (که روز قبل داده شده) n باشه، نوبتهای فردا از n+1 شروع میشه تا n+85. یعنی شما میتونین از دریافت لیست نوبتها بگذرین و یکراست یه عدد تصادفی در این بازه انتخاب کنین و براش درخواست کد تایید بدین. چون بدون شک تو اون زمانی که شما دارین برای بار اول لیست نوبتها رو میگیرین، تمام نوبتها آزاد هستن. امّا این API میتونه یه نقش دیگه رو برای ما ایفا کنه: از اونجایی که ساعت ما و سرور مورد نظر کاملاً منطبق نیست و سرور هم احتمالاً نوبتگیری رو با یه Cron Job شروع میکنه که اون هم لزوماً رأس ساعت (= ثانیه صفر) کار رو انجام نمیده، شما با رصد (monitor) کردن این API توی یه حلقه، میتونین متوجه بشین نوبتدهی دقیقاً کی شروع میشه (این برای مرحله بعد، حیاتیه)
مرحله بعد، درخواست و ثبت OTPـه. اولین ایدهای که برای دور زدن این دو درخواست به ذهن میاد، امتحان کردن همه حالتهاست (brute force). خوشبختانه (؟) تا حد خوبی جلوی این موضوع با Rate Limiting و محدودیت «یک درخواست در دقیقه» گرفته شده. امّا این محدودیت، نه روی کلاینت که روی شماره موبایله. اگر قبل از شروع نوبتدهی درخواست بدین، اگرچه که نوبتی وجود نداره و خطا دریافت میکنین، ولی تا یک دقیقه بعد نمیتونین درخواست بدین. به همین خاطر فهمیدن زمان دقیق شروع نوبتدهی حیایته.
به عنوان یه سناریوی آسیب، شما میتونین با بینهایت شماره درخواست بدین: این کار از یه طرف باعث ایجاد صف و منع سرویسدهی به دیگران میشه و از طرف دیگه، ضرر مالی به صاحب سرویس میزده (هزینه پیامک).
با کمی آزمون و خطا متوجه میشین که این «یک دقیقه» معادل ۶۰ ثانیه نیست، بلکه ۷۰ ثانیهست 🤭 و از آخرین درخواست شما هم حساب میشه. یعنی اگر درخواست دوم رو ۶۹ ثانیه بعد از درخواست اول بدین، نه تنها خطای محدودیت تعداد درخواست میگیرین، بلکه دوباره از نو ۷۰ ثانیه رو میشمره! حتی اگر خطا از سمت سرور باشه هم (مثل محدودیتی که در بند بعد دربارهش میکنم) باز شما از درخواست مجدد منع میشین.
علاوهبر این، متوجه میشین طراح سیستم (که یادش رفته در رو روی رباتها ببنده) یه مانع خیالی دیگه گذاشته: تا ۱۰ ثانیه بعد از شروع نوبتدهی، شما نمیتونین OTP درخواست کنین. نمیدونم دقیقاً چه تصوری داشته، ولی این کار صرفاً باعث میشه یه خط sleep(10) به کد اضافه بشه. یا حتی اگر زمانش رو ندونیم، با توجه به اینکه با شمارههای دیگه میشه درخواست دارد، با یه حلقه شمارههای مختلف رو انقدر تکرار میکنین تا دیگه خطای «مجاز به فراخوانی سرویس در این بازه زمانی نمی باشید.» نگیرین و بعد با شماره خودتون درخواست میدین.
با توجه به این مشاهده که در انتهای فرآیند خطای «نوبت وجود ندارد» داده میشه، میتونیم نتیجه بگیریم که یا رزروی وجود نداره یا زمان رزرو کوتاهتر از طی کردن کل فرآیند (حتی توسط ربات) تعیین شده. علاوه بر این، با دیدن نوبتهای روزهای قبل میبینیم که برای برخی زمانها بیش از یک نوبت وجود داره (مثلاً ۲تا ۰۸:۴۵ داریم). این یعنی کنترلی روی یکتا بودن ساعت و روز وجود نداره. مجموع این موارد من رو نگران میکنه که ممکنه جلوی Lost Update رو هم نگیرن.
به هر صورت، تو مرحله دریافت کد خیلی کار خاصی نمیتونیم بکنیم، جز اینکه در سریعترین زمان ممکن کد رو دریافت و ثبت کنیم. مرحله ثبت فرم از این هم سختتر به نظر میاد: اگر اینجا با یه کد عددی ۶ رقمی روبهرو هستیم، اونجا با یه رشته طولانی روبهرو هستیم (یه hash). ممکنه اگر زمان بگذارین ببینین چیزی هست که قابل بازیابیه ¯\_(ツ)_/¯
حالا که یک دور فرآیند رو مرور کردیم، میتونیم به این هم فکر کنیم که: کدوم نوبتها دیرتر پر میشن؟ چون شانس برنده شدن رابطه مستقیم با زمان در دسترس بودن داره. با توجه به اینکه نه تنها شماره نوبتها، بلکه شماره مشتریها هم متوالیه، با مرتب کردن نوبتهای روزهای قبل بر اساس شماره مشتری، میتونین ببینین کدوم نوبتها دیرتر پر شدن و شما شانستون رو به جا یه عدد تصادفی در کل بازه، یه عدد تصادفی تو بازههایی که شانس بیشتری دارن امتحان کنین. حتی میتونین این کار رو دقیقتر انجام بدین و هنگامی که نوبتدهی فعاله، هر ثانیه لیست نوبتهای در دسترسی رو بگیرین. و ثبت کنین که هر نوبتی چه زمانی از دسترس خارج میشه؟
کنجکاوی بیشتر
توی گشتوگذار توی کد، به یک سری endpoint میبینین که نه هیچ وقت فراخوانی شدن، و با فراخوانیشون هم به نظر میاد که چیزی اونجا نیست. از خودتون میپرسین: آیا به خاطر محدودیت دسترسی جوابی نمیگیرم؟ یا کد متروک مربوط به یه نسخه قدیمیه؟ یا ...؟
وقتی کانال یکی از رباتهای دریافت نوبت توی تلگرام اسکرینشات صفحه پیامهای کسایی که براشون نوبت گرفته بود رو گذاشت، نظرم به این جلب شد که قبل از ساعت شروع نوبتدهی، برای افراد پیامک کد ارسال شده از همین سرشماره. با بررسی بیشتر سایت، متوجه میشین که این سرویس پیش از این، به جای زیردامنه nobat.mex.co.ir روی دامنه mex.co.ir بالا بوده و هنوز هم سرویس قبلی APIهاش در دسترسه. از جمله اینکه شما میتونین با فراخوانی APIها توش ثبتنام کنین (که کار بیخاصیتیه) یا حتی درخواست OTP بدین (بدون اینکه نوبتی وجود داشته باشه یا نوبتدهی فعال باشه). این دومی رو کانال مربوطه برای تست برنامهای که برای موبایل به مشتریها داده بود استفاده میکرد: کافیه یه درخواست به این API بدین و ببینین که آیا برنامهای که روی موبایل نصب کردین کد رو به سرور شما میفرسته یا نه.
علاوهبر این، یک API برای دریافت تنظیمات و/یا محتوای dynamic از سرور تعریف شده بود (که کار مرسومیه) ولی یکی از مواردی که توی جواب ارائه میکرد، موجودی پنل پیامکی بود! (که این واقعاً عجیبه!)
یکی از سوالهایی که برای مصاحبهها میپرسیدم، سناریویی نزدیک به همین مسئله بود: یک رقابت [شدید] برای منابع محدود. این سوال رو دوست داشتم به این خاطر که علیرغم ظاهر سادهش، نکات ریزی داشت که رعایت نکردنشون منجر به درست کار نکردن سیستم میشد، و همین موارد فرصتهایی برای پرسش و گفتگو درباره موضوعات مهندسی به وجود میاورد. اینجا علاوه بر اون موارد، با چند مشکل دیگه هم روبهرو هستیم. تازه اینها مواردی از نگاه کسی که تخصص امنیت و نفوذ داره نیست.
چه چیزهایی از مطالعه این سیستم یاد میگیریم؟
شما فکر میکنین دیگه چه نکتهای میشه از این تجربه یاد گرفت؟