اگر ابزاری ساختهاید که به عاملهای هوش مصنوعی اجازه میدهد در وب جستوجو کنند، احتمالاً همین حالا یک درِ پشتی باز برای مهاجمان گذاشتهاید. باید بدانید که تکیه بر بررسیهای متنی ساده (String Checks) برای مسدود کردن آدرسهای IP داخلی مانند 169.254.169.254، در برابر روشهای دور زدن مدرن کاملاً بیاثر است. وقتی ابزار fetch یک عامل هوش مصنوعی تنها بر اساس لیستی از «رشتههای بد» عمل میکند، مهاجم میتواند به سادگی آدرس هدف را به صورت هگزادسیمال یا دهدهی بازنویسی کند تا از سد محافظ عبور کرده و اعتبارنامههای متادیتای ابری را سرقت کند.
این آسیبپذیری بهویژه برای عاملهای هوش مصنوعی (AI Agents) که در محیطهای ابری مانند AWS EC2 اجرا میشوند، بحرانی است. در این ساختار، نقطه اتصال متادیتای ابری (169.254.169.254) میتواند اعتبارنامههای موقت IAM (نقشهای دسترسی) را به هر کسی که موفق شود درخواستی به آن ارسال کند، تحویل دهد. هرچند استاندارد IMDSv2 سطح امنیت را بالا برده است، اما بسیاری از نمونههای فعال (Instances) هنوز اجازه دسترسی به IMDSv1 را میدهند و در صورت باز بودن لایه شبکه، بهشدت در معرض خطر هستند.
بسیاری از توسعهدهندگان بهطور غریزی به سراغ «فهرست سیاه» یا Denylist میروند؛ لیستی از میزبانهای ممنوعه. اما یک آدرس IP داخلی میتواند به چندین شکل مختلف نوشته شود که مقایسههای متنی ساده آنها را تشخیص نمیدهند. برای مثال، IP مورد اشاره (169.254.169.254) را میتوان به صورت عدد صحیح ۳۲ بیتی ۲۸۵۲۰۳۹۱۶۶، مقدار هگز 0xA9FEA9FE یا فرمت IPv6-mapped literal به شکل [::ffff:169.254.169.254] نوشت.
طبق یک تحلیل فنی منتشرشده در dev.to در ۲۷ ژوئن ۲۰۲۶، یک فهرست سیاه متنی ساده توانست تنها ۱ مورد از ۸ URL آزمایشی را مسدود کند؛ یعنی ۷ مورد از ۸ URL عبور کردند و ۶ مورد از آنها بهراحتی به اهداف داخلی ممنوعه دسترسی پیدا کردند. این نشان میدهد که مهاجمان نیازی به اکسپلویتهای پیچیده ندارند؛ آنها فقط باید «املای» IP را تغییر دهند. مستندات OWASP در «برگه تقلب پیشگیری از SSRF» بهصراحت این موارد را به عنوان روشهای دور زدن برای تست لیست میکند: «رمزگذاریهای هگز، اکتال، Dword، URL و رمزگذاریهای ترکیبی».

زمینه: کالبدشکافی یک شکست حفاظتی
نویسنده این گزارش به مورد خاصی اشاره میکند که در آن یک ابزار web_fetch در پروتکل MCP با ۶۰ خط کد منتشر شده بود که دارای یک حفاظ داخلی در برابر SSRF بود. این حفاظ سعی میکرد از تطبیق متنی ساده اجتناب کند و ابتدا میزبان را از طریق Resolver سیستمعامل تحلیل میکرد. با استفاده از تابع socket.gethostbyname(host) و ارسال نتیجه به ipaddress.ip_address()، این حفاظ میتوانست فرمتهای هگز مانند 0xA9FEA9FE را دوباره به 169.254.169.254 تبدیل کند و سپس درباره آنها قضاوت کند.
با این حال، علیرغم این بهبود، حفاظ دارای یک حفره حیاتی بود: این ابزار درخواستهای بازنشانی (Redirect) را دنبال میکرد بدون اینکه مقصد نهایی را مجدداً بررسی کند. منطق مورد استفاده این بود: if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved: raise ValueError. در حالی که این منطق IP اولیه را بهدرستی طبقهبندی میکرد، اما پس از یک بازنشانی 302، URL جدید را دوباره بررسی نمیکرد.
این یعنی یک میزبان عمومی که در لیست سفید قرار داشت، میتوانست عامل هوش مصنوعی را به محدوده داخلی بازگرداند و کل بررسیهای پیش از fetch را دور بزند. نویسنده در پست اصلی خود اشاره کرده بود که «در هر سیستم جدی، باید گام نهایی (Final Hop) را مجدداً بررسی کرد»، اما سپس کد را بدون پیادهسازی این مورد منتشر کرد. این جمله در نهایت به یک گزارش باگ تبدیل شد که علیه خود نویسنده ثبت شد.
زمینه: خطرات رویکرد «اجازه پیشفرض»
نویسنده تأکید میکند که حفاظ منتشرشده اساساً یک Denylist بود. این سیستم آدرسهایی را رد میکرد که توسعهدهنده به یادش آمده بود در لیست قرار دهد. این رویکرد ذاتاً شکننده است. به محض اینکه کسی از طریق محدودهای مسیربندی کند که توسعهدهنده فراموش کرده است، یا یک بلوک رزرو شده جدید معرفی شود، پاسخ پیشفرض سیستم همچنان «اجازه» (Allow) خواهد بود.
به همین دلیل است که OWASP صریحاً درباره این خطر هشدار میدهد: فهرستهای سیاه مستعد دور زده شدن هستند. تنها درمان قابل اعتماد این است که فقط یک آدرس IP معتبر یا نام دامنه را بپذیرید که از پیش مورد اعتماد (Trusted) باشد. تغییر از «اجازه پیشفرض» به «ممنوعیت پیشفرض» (Default-Deny)، حالت شکست سیستم را از یک نقض امنیتی احتمالی به یک رد عملکردی ساده تغییر میدهد.
یک لیست سفید (Allowlist) پاسخ میدهد: «آیا این یکی از موارد خوبی است که من نام بردهام؟»، در حالی که یک لیست سیاه میپرسد: «آیا این یکی از موارد بدی است که لیست کردهام؟». برای عاملی که دستورات خود را تا حدی از وب دریافت میکند و در وب جستوجو میکند، لیست سفید تنها پیشفرض امن است. شما میتوانید درباره لیست کوتاهی از چیزهایی که به آنها اعتماد دارید استدلال کنید، اما نمیتوانید درباره لیست بیپایانی از چیزهایی که به آنها اعتماد ندارید، استدلال کنید.
جزئیات: تست خط پایه در مقابل حفاظ
برای نمایش شکست دفاعهای مبتنی بر متن، در این گزارش از یک مجموعه آزمایشی مصنوعی شامل ۸ URL استفاده شده است که نتایجی کاملاً یکسان در اجراهای متعدد تولید کردند (MD5 خروجی: 94a8382cc19daf3134693340491070b2). این تست برای اینکه قطعی (Deterministic) باقی بماند، از هیچ سوکتی، DNS، ساعت یا تصادفیسازی استفاده نمیکند و تنها بر دو کتابخانه استاندارد ipaddress و re تکیه دارد (استفاده از urllib برای به حداقل رساندن وابستگیها عمداً حذف شده است).
فهرست سیاه متنی ساده (خط پایه - Baseline):
- میزبان قانونی:
http://api.example.com/data$
ightarrow$ اجازه داده شد. - IP مستقیم متادیتا:
http://169.254.169.254/latest/meta-data/$
ightarrow$ مسدود شد (به دلیل وجود صریح در لیست سیاه). - فرمت دهدهی (Decimal Dword):
http://2852039166/latest/meta-data/$
ightarrow$ اجازه داده شد (به هدف ممنوعه رسید). - فرمت هگز:
http://0xA9FEA9FE/$
ightarrow$ اجازه داده شد (به هدف ممنوعه رسید). - فرمت IPv6-Mapped:
http://[::ffff:169.254.169.254]/$
ightarrow$ اجازه داده شد (به هدف ممنوعه رسید). - میزبان خصوصی:
http://10.0.0.5/admin$
ightarrow$ اجازه داده شد (آدرس RFC 1918؛ به هدف ممنوعه رسید). - طرح غیر HTTP:
file:///etc/passwd$
ightarrow$ اجازه داده شد (چون میزبان شبکهای برای تطبیق وجود نداشت؛ به هدف ممنوعه رسید). - بازنشانی (Redirect):
http://reviews.example.com/page$
ightarrow$ اجازه داده شد (میزبان اولیه امن بود، اما با 30x به 169.254.169.254 بازگرداند).
حفاظ لیست سفید نرمالسازیشده:
این نسخه از رویکرد «ممنوعیت پیشفرض» استفاده میکند. این ابزار میزبان را با استفاده از یک Regex تحلیل میکند که بهطور صریح کروشههای IPv6 (\[[^\]]+\]) را مدیریت میکند تا از تخریب میزبان جلوگیری کند. بدون این کار، [::ffff:169.254.169.254] در اولین دو-نقطه (Colon) برش میخورد و باعث میشود حفاظ آن را به دلیل «میزبان خراب» مسدود کند، نه به دلیل «IP محلی-لینک»؛ مسدود شدن به دلیل دلیل اشتباه، یک حالت شکست خطرناک است که باعث میشود حفاظها در تستها پاس شوند اما در محیط عملیاتی شکست بخورند.
- IP مستقیم/دهدهی/هگز/IPv6-Mapped: همگی به عنوان
private-ipشناسایی و از طریق نرمالسازیas_ipمسدود شدند. - میزبان RFC 1918: به عنوان
private-ip:10.0.0.5مسدود شد. - طرح غیر HTTP: به عنوان
scheme:fileمسدود شد (هر چیزی غیر از http یا https رد میشود). - بازنشانی (Redirect): به عنوان
redirect-to:private-ip:169.254.169.254مسدود شد، زیرا حفاظ مقصد را پس از گام بازنشانی مجدداً بررسی میکند. - نتیجه: دقیقاً ۱ مورد از ۸ URL اجازه دسترسی یافت.
جزئیات: سازوکار نرمالسازی
نرمالسازی کلید غلبه بر مشکل «املای» آدرسها است. این حفاظ توالی خاصی را برای پردازش هر میزبان به کار میبرد:
- تحلیل با Regex: طرح (Scheme) و میزبان را استخراج کرده و کروشههای IPv6 را با استفاده از
_URL_REحذف میکند. - منطق
as_ip: بررسی میکند که آیا میزبان یک لیترال هگز (شروع با0x)، یک Dword دهدهی (تماماً عدد) یا یک رشته IP استاندارد است. برای مثال، ازint(host, 16)برای فرمتهای هگز استفاده میشود. - تبدیل به
ipaddress: تمام این فرمتها از طریقipaddress.ip_address()به یک شیءipaddressتبدیل میشوند. - طبقهبندی داخلی: تابع
ip_is_internalویژگیهایis_private،is_loopback،is_link_local،is_reserved،is_multicastوis_unspecifiedرا بررسی میکند. - نگاشت IPv4: بهطور خاص برای لیترالهای IPv6-mapped، حفاظ به ویژگی
ipv4_mappedدسترسی پیدا میکند تا آدرس را بر اساس چهره IPv4 آن قضاوت کند.
پیادهسازی یک حفاظ مستحکم
برای عبور از «نمایش امنیتی» (Security Theater)، این گزارش یک استراتژی دفاعی چهار لایهای را پیشنهاد میکند که محوریت آن مدل ممنوعیت پیشفرض است. حالت شکست یک لیست سفید ناقص، صرفاً رد کردن یک سایت قانونی است؛ اما حالت شکست یک لیست سیاه ناقص، استخراج اعتبارنامههای داخلی سازمان است.
- نرمالسازی پیش از قضاوت: هر میزبان را از طریق کتابخانه
ipaddressپایتون عبور دهید. این کار تضمین میکند که فرمهای هگز، دهدهی و IPv6-mapped — که در واقع املایهای مختلفی از همان چهار بایت هستند — به یک حکم واحد تبدیل شوند. باید ویژگیهایis_private،is_loopback،is_link_local،is_reserved،is_multicastوis_unspecifiedبررسی شوند. - لیست سفید با ممنوعیت پیشفرض: سوال را از «آیا این یک سایت بد است؟» به «آیا این سایتی است که من صریحاً به آن اعتماد دارم؟» تغییر دهید. تمام آدرسهای IP خام را بهطور کامل رد کنید، حتی IPهای عمومی را، و فقط نام میزبانهای خاصی را که در یک
frozensetلیست سفید هستند، بپذیرید. - بررسی مجدد بازنشانیها: بررسی پیش از fetch که بازنشانیها را نادیده میگیرد، بیفایده است. یا
follow_redirects=Trueرا غیرفعال کرده و هر گام را دستی طی کنید، یا حفاظ امنیتی کامل را روی URL مقصد نهایی اجرا کنید (مثلاً بررسی تا ۵ گام). - تثبیت در سطح سوکت (Socket-Level Pinning): برای متوقف کردن DNS Rebinding — جایی که یک نام در زمان بررسی به IP امن اشاره میکند اما در لحظه اتصال به IP خصوصی تغییر میکند (حمله TOCTOU) — باید IP را در زمان اتصال Resolve کنید، آن IP را تثبیت (Pin) کنید و پس از باز شدن سوکت، مجدداً آن را بررسی کنید.
محدودیتهای فیلترهای URL
بسیار مهم است که بدانید فیلترهای URL نمیتوانند همه چیز را بگیرند و این گزارش صریحاً «سقف» تواناییهای خود را چاپ میکند تا ادعای بیش از حد نکند. سه شکاف نامگذاری شده باقی میماند:
۱. DNS Rebinding/TOCTOU: چون فیلتر دمو DNS Resolve نمیکند (از لیترالهای مصنوعی استفاده میکند)، نمیتواند میزبانانی را ببیند که مقدار Resolution خود را بین زمان بررسی و زمان fetch تغییر میدهند. راه حل واقعی، Resolve در زمان اتصال، Pin کردن IP و بررسی مجدد پس از ایجاد سوکت است.
۲. میزبانهای پروکسی (Proxying Hosts): اگر یک میزبان مورد اعتماد در لیست سفید شما باشد که برای فوروارد کردن درخواستها به یک سرویس داخلی پیکربندی شده است، URL تمیز باقی میماند اما مقصد نهایی مخاطرهآمیز است. این مورد خارج از محدوده فیلترهای URL است.
۳. محدودیتهای تجزیکننده (Parser): عبارتهای منظم ساده برای آموزش هستند. سیستمهای عملیاتی باید از تجزیکنندههای URL سختگیرانه برای مدیریت ترفندهای userinfo@host، نقاط انتهایی (Trailing Dots) و IDN/Punycode استفاده کنند.
این آسیبپذیری لایه شبکه است و با حملات تزریق پرامپت (Prompt Injection) متفاوت است. وقتی یک صفحه استخراجشده به عامل شما میگوید چه کاری انجام دهد، حمله در متنی است که مدل روی آن استدلال میکند. SSRF در لایهای پایینتر قرار دارد؛ موضوع این است که سوکت به کدام IP متصل میشود، فارغ از اینکه مدل چه فکر میکند. یک دیوار آتش بینقص برای پرامپتها همچنان اجازه میدهد یک عامل IP هگز 0xA9FEA9FE را fetch کند اگر لایه شبکه محافظت نشده باشد.
برای توسعهدهندگانی که ناوگانهای استخراج داده (Scraping) را در مقیاس وسیع مدیریت میکنند — مانند کسانی که هزاران اجرای تولیدی را در Actors متنوع اجرا میکنند — امنترین مکان برای تصمیمگیری «به آنجا متصل نشو»، در کدی است که پیش از درخواست اجرا میشود. در یک محیط تولیدی با ۲,۱۹۰ اجرا در ۳۲ Actor منتشر شده (از جمله یک scraper Trustpilot با بیش از ۹۶۲ اجرا)، عاملها مدام لیستهای URL ارسالی کاربران را دریافت میکنند. تکیه بر این موضوع که یک مدل از دستور «اجتناب از IPهای داخلی» در برابر ورودیهای متخاصم پیروی کند، یک ریسک غیرقابل قبول است.
اگر در حال حاضر ابزارهای هوش مصنوعی با قابلیت web-fetch میسازید، فوراً باید در کد خود عبارت follow_redirects=True را جستوجو کرده و ارزیابی کنید که آیا حفاظ شما در برابر بازنشانی به یک محدوده خصوصی دوام میآورد یا خیر. نتیجه نهایی از OWASP روشن است: یک لیست سفید از مقاصد مجاز را ترجیح دهید، زیرا شما میتوانید درباره لیست کوتاهی از چیزهایی که به آنها اعتماد دارید استدلال کنید، اما نمیتوانید درباره لیست بیپایانی از چیزهایی که به آنها اعتماد ندارید، استدلال کنید.
گام بعدی شما
- فوراً در کد خود عبارت
follow_redirects=Trueرا جستوجو کرده و بررسی کنید که آیا پس از بازنشانی، مقصد نهایی باز هم فیلتر میشود یا خیر. - به جای لیست سیاه، سیستمی را پیاده کنید که هرگونه IP خام را رد کرده و فقط دامنه های مورد اعتماد را بپذیرد.
- برای محیطهای ابری، از IMDSv2 استفاده کنید تا ریسک سرقت توکنهای IAM کاهش یابد.
اما داستان سختافزاری این تحول در لایه شبکه حتی پیچیدهتر است — به تحلیل ما دربارهی معماریهای توزیعشده در مراکز داده مراجعه کنید.




گفتگو