پیوند — ویپیان معکوس
یک سرویس تجاری فعال که به ایرانیان خارج از کشور آیپی ایران میدهد، تا بتوانند به بانکها و سامانههای دولتی که اتصال از بیرون را رد میکنند دسترسی داشته باشند

پیوند نه سایت آکادمیک است، نه فروشگاه، نه سایت شرکت تولیدی — و عمداً اینجاست. آن سه چیزی که میفروشم روی کاری بنا شدهاند که کارفرما هیچوقت نمیبیندش: احراز هویت، پرداخت، و دادهای که نباید غلط باشد. این پروژه جایی است که آن کار دیده میشود. اگر میخواهید بدانید کسی که سایت شما را میسازد به وبهوک پرداخت یا مهاجرت پایگاه داده قابلاعتماد است یا نه، همین یکی را بخوانید.
درباره کارفرما
بیشتر ویپیانها به آدمها کمک میکنند از یک شبکه محدودشده بیرون بروند. پیوند کار برعکس را میکند. بانکهای ایرانی، سامانه ثنا، پورتالهای دانشگاهی و سامانههای بیمه و مالیات، اتصال از آیپی خارجی را رد میکنند — پس یک ایرانی ساکن خارج نمیتواند وارد حساب بانکی خودش شود. پیوند به او آیپی ایران میدهد تا بتواند. این یک سرویس تجاری واقعی با مشتری پرداختکننده است، نه یک نمونه آزمایشی.
مسئله
وسط ساختن لایه تجاری فهمیدم سرویسهای شخصسومی که داشتم به آنها وصل میشدم، از روی سرور در دسترس نیستند. ایران api.telegram.org را در سطح کشور مسدود میکند — این را حدس نزدم، بررسی کردم: DNS روی سرور، تلگرام را به 10.10.34.36 ترجمه میکرد، همان آدرسی که سامانه فیلترینگ بهجای آدرس واقعی برمیگرداند، در حالی که بقیه اینترنت درست جواب میداد. پیپال از سمت خودش آیپی ایران را مسدود میکند. و زرینپال از بیرون ایران بالا نمیآید — که یک حلقه میسازد: مشتری برای خریدن ویپیان به ویپیان نیاز دارد. سه محدودیت جدا، همه بعد از نوشتن کد کشف شدند.
دامنه پروژه و نقش من
همهاش را خودم ساختم: ocserv و FreeRADIUS روی سرور ایران، یک API تخصیص اشتراک با Node و Express، فرانتاند و پنل کاربری دوزبانه، یک ربات فروش تلگرام با گردش کار تأیید ادمین، و لایه پرداخت که بعد از فهمیدن اینکه هر ماشین به چه چیزی دسترسی دارد، از صفر بازطراحی شد.
قابلیتهای اصلی
چالشهای اصلی
امضای درخواست، تمام مرز امنیتی است
سرور ایران نمیتواند هیچچیز را مستقل تأیید کند — نمیتواند از پیپال بپرسد آیا پرداخت انجام شد، چون پیپال مسدود است. باید حرف لایه لبه را باور کند. پس آن یک اندپوینت چهار لایه دارد، نه یکی: HMAC-SHA256 روی زمان، یک nonce و بایتهای خام دقیق درخواست؛ تازگی زمان، تا درخواست ضبطشده بعد از پنج دقیقه منقضی شود؛ nonce یکبارمصرف که با کلید یکتای دیتابیس تضمین میشود نه با بررسی در کد، تا دو تکرار همزمان نتوانند هر دو موفق شوند؛ و مقایسه در زمان ثابت، با بررسی طول بهصورت جداگانه و پیش از آن، چون تابع مقایسه در صورت اختلاف طول خطا میدهد و همان خطا طول را لو میدهد.
پرداختی که دو بار خرج نمیشود
امضای webhook تأیید میشود، چون آدرس آن عمومی است و بدون تأیید هرکسی میتواند «پرداخت انجام شد» بفرستد. مبلغ در سمت سرور با جدول قیمت خودِ سرور مقایسه میشود، نه از بدنه درخواست خوانده شود. و تخصیص اشتراک بر اساس شناسه تراکنش درگاه idempotent است، تا تلاش مجدد و webhook تکراری نتوانند یک اشتراک را دو بار تحویل بدهند.
کد خودم را بازبینی کردم و هفت مشکل پیدا کردم، دو تا جدی
یک webhook اگر یک متغیر محیطی جا افتاده بود، کاملاً بدون احراز هویت اجرا میشد — کتابخانه در آن حالت بیصدا بررسی را رد میکند، نه اینکه خطا بدهد. مشکل دوم اجازه میداد هرکسی حساب تلگرام خودش را بدون اثبات مالکیت به حساب ویپیان یک غریبه وصل کند. هر دو رفع شدند؛ اتصال حالا رمز میخواهد، در زمان ثابت مقایسه میشود، و پیام خطا عامدانه یکسان است تا نتوان از آن برای فهمیدن اینکه چه نامکاربریهایی وجود دارند استفاده کرد.
توابع serverless هیچچیز را در حافظه نگه نمیدارند
تمام وضعیت گفتوگو باید به دیتابیس منتقل میشد. یک متغیر ساده که جای کاربر در گفتوگو را نگه دارد، در تست محلی بیعیب کار میکند و در محیط واقعی بیصدا شکست میخورد، چون پیام بعدی روی نمونه دیگری مینشیند.
فیلتر fail2ban که با لاگ واقعی اعتبارسنجی شد
نه با آنچه فکر میکردم لاگ مینویسد. همین باعث شد حالتی پیدا شود که در آن، نامکاربری حاوی متن «auth failed» میتوانست یک ورود موفق را بهعنوان ورود ناموفق ثبت کند.
نتیجه
یک سرویس تجاری فعال با مشتری پرداختکننده، که از دو کشور اداره میشود. حدود ۱۰۰ تست خودکار، با وزن بیشتر روی حملهها تا مسیرهای موفق: درخواست تکرارشده، داده دستکاریشده، زمان منقضی و زمان آینده، امضای جعلی، انتقال بدون رمزنگاری با امضای درست، سرقت حساب، و حمله جستوجوی فراگیر روی مسیر ورود. راهاندازی زرینپال، پرداخت با USDT، مسیریابی split-tunnel تا فقط ترافیک بانکی ایران از سرویس بگذرد، و سرور دوم برای پشتیبانگیری، در برنامه هستند.
تصاویر پروژه
تصاویر این پروژه در حال آمادهسازی است.