وقتی str.lower() تو پایتون یه آسیبپذیری میشه
خلاصهٔ کاملتر
توی مقالهی ست لارسون اومده که ایراد امنیتی تابع map_table_b3 در ماژول stringprep پایتون فقط یه فراخوانی سادهی str.lower() بوده:
def map_table_b3(code):
r = b3_exceptions.get(ord(code))
if r is not None: return r
return code.lower()StringPrep و پروفایلش NamePrep (یعنی همون قواعدی که اسم دامنهی یونیکدی رو به شکل استاندارد ASCII درمیارن) پایهی IDNA 2003 هستن. StringPrep تو RFC 3454 تعریف شده و جدولهای B.2 و B.3 توش چیزی نیستن جز قواعد case-folding (یعنی همون قانون بزرگ و کوچیک کردن حروف) یونیکد ۳.۲.۰ که به شکل جدول نوشته شدن. پس پیادهسازی هم باید دقیقا همون نسخه رو مبنا بذاره، نه نسخهی روز.
مشکل اینجاست که str.lower() از دیتای یونیکدی استفاده میکنه که خود مفسر باهاش عرضه شده؛ با unicodedata.unidata_version میشه دیدش که مثلا روی ۱۷.۰.۰ هست. برای همین خروجی str.encode('idna') برای بعضی کاراکترها با چیزی که RFC میگه یکی نبود و همین فاصلهی بین پیادهسازی و استاندارد، آسیبپذیری حساب میشه. جالب اینکه پایتون از قبل unicodedata.ucd_3_2_0 رو دقیقا برای همین کار داره و ماژول stringprep هم همون رو import میکنه.
راهحل این بود که تکتک code pointها بررسی بشن و هرجا رفتار str.lower() با یونیکد ۳.۲.۰ فرق داشت، به عنوان استثنا ثبت بشه؛ با این کار IDNA 2003 دوباره با استاندارد همخوان شده. نویسنده یادآوری میکنه که بهطور کلی بهتره بهجای str.encode('idna') از پکیج idna روی PyPI استفاده کنید که IDNA 2008 رو پیاده کرده، مگر اینکه واقعا به رفتار قدیمی نیاز داشته باشید.
نکات کلیدی:
- شناسهی این آسیبپذیری CVE-2026-17084 هست و به ماژول stringprep و کدک idna پایتون مربوط میشه.
- طبق RFC 3454، الگوریتم StringPrep باید قواعد case-folding یونیکد ۳.۲.۰ رو اجرا کنه، نه نسخهی جدید.
- پایتون برای همین کار unicodedata.ucd_3_2_0 رو داره؛ ایراد از فراخوانی مستقیم str.lower() بود.
- وصله با ثبت استثنا برای هر code point که رفتارش با یونیکد ۳.۲.۰ فرق داشت اعمال شد.
- برای کار جدید بهتره پکیج idna روی PyPI (یعنی IDNA 2008) استفاده بشه، نه کدک idna داخل خود پایتون.




